1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Player data at runtime */
19 
20 #include "C4Include.h"
21 #include "player/C4Player.h"
22 
23 #include "control/C4GameControl.h"
24 #include "game/C4Application.h"
25 #include "game/C4FullScreen.h"
26 #include "game/C4GraphicsSystem.h"
27 #include "game/C4Viewport.h"
28 #include "graphics/C4GraphicsResource.h"
29 #include "gui/C4GameMessage.h"
30 #include "gui/C4GameOverDlg.h"
31 #include "gui/C4MessageInput.h"
32 #include "gui/C4MouseControl.h"
33 #include "landscape/C4Landscape.h"
34 #include "lib/C4Random.h"
35 #include "network/C4League.h"
36 #include "network/C4Network2Stats.h"
37 #include "object/C4Command.h"
38 #include "object/C4Def.h"
39 #include "object/C4DefList.h"
40 #include "object/C4GameObjects.h"
41 #include "object/C4Object.h"
42 #include "object/C4ObjectInfo.h"
43 #include "object/C4ObjectMenu.h"
44 #include "platform/C4GamePadCon.h"
45 #include "player/C4PlayerList.h"
46 
C4Player()47 C4Player::C4Player() : C4PlayerInfoCore()
48 {
49 	Filename[0] = 0;
50 	Number = C4P_Number_None;
51 	ID = 0;
52 	Team = 0;
53 	DefaultRuntimeData();
54 	Menu.Default();
55 	Crew.Default();
56 	CrewInfoList.Default();
57 	LocalControl = false;
58 	BigIcon.Default();
59 	Next = nullptr;
60 	fFogOfWar = true;
61 	LeagueEvaluated = false;
62 	GameJoinTime = 0; // overwritten in Init
63 	pstatControls = pstatActions = nullptr;
64 	ControlCount = ActionCount = 0;
65 	LastControlType = PCID_None;
66 	LastControlID = 0;
67 	pMsgBoardQuery = nullptr;
68 	NoEliminationCheck = false;
69 	Evaluated = false;
70 	ZoomLimitMinWdt = ZoomLimitMinHgt = ZoomLimitMaxWdt = ZoomLimitMaxHgt = ZoomWdt = ZoomHgt = 0;
71 	ZoomLimitMinVal = ZoomLimitMaxVal = ZoomVal = Fix0;
72 	ViewLock = true;
73 	SoundModifier.Set0();
74 }
75 
~C4Player()76 C4Player::~C4Player()
77 {
78 	ClearGraphs();
79 	Menu.Clear();
80 	SetSoundModifier(nullptr);
81 	while (pMsgBoardQuery)
82 	{
83 		C4MessageBoardQuery *pNext = pMsgBoardQuery->pNext;
84 		delete pMsgBoardQuery;
85 		pMsgBoardQuery = pNext;
86 	}
87 	ClearControl();
88 }
89 
ObjectInCrew(C4Object * tobj)90 bool C4Player::ObjectInCrew(C4Object *tobj)
91 {
92 	if (!tobj) return false;
93 	for (C4Object *cobj : Crew)
94 		if (cobj==tobj) return true;
95 	return false;
96 }
97 
ClearPointers(C4Object * pObj,bool fDeath)98 void C4Player::ClearPointers(C4Object *pObj, bool fDeath)
99 {
100 	// Crew
101 	while (Crew.Remove(pObj)) {}
102 	// View-Cursor
103 	if (ViewCursor==pObj) ViewCursor = nullptr;
104 	// View
105 	if (ViewTarget==pObj) ViewTarget=nullptr;
106 	// Cursor
107 	if (Cursor == pObj)
108 	{
109 		// object is to be deleted; do NOT do script calls (like in Cursor->UnSelect(true))
110 		Cursor = nullptr; AdjustCursorCommand(); // also selects and eventually does a script call!
111 	}
112 	// Menu
113 	Menu.ClearPointers(pObj);
114 	// messageboard-queries
115 	RemoveMessageBoardQuery(pObj);
116 }
117 
ScenarioAndTeamInit(int32_t idTeam)118 bool C4Player::ScenarioAndTeamInit(int32_t idTeam)
119 {
120 	C4PlayerInfo *pInfo = GetInfo();
121 	if (!pInfo) return false;
122 	C4Team *pTeam;
123 	if (idTeam == TEAMID_New)
124 	{
125 		// creation of a new team only if allowed by scenario
126 		if (!Game.Teams.IsAutoGenerateTeams())
127 			pTeam = nullptr;
128 		else
129 		{
130 			if ((pTeam = Game.Teams.GetGenerateTeamByID(idTeam))) idTeam = pTeam->GetID();
131 		}
132 	}
133 	else
134 	{
135 		// uage of an existing team
136 		pTeam = Game.Teams.GetTeamByID(idTeam);
137 	}
138 	C4Team *pPrevTeam = Game.Teams.GetTeamByID(Team);
139 	// check if join to team is possible; e.g. not too many players
140 	if (pPrevTeam != pTeam && idTeam)
141 	{
142 		if (!Game.Teams.IsJoin2TeamAllowed(idTeam, pInfo->GetType()))
143 		{
144 			pTeam = nullptr;
145 		}
146 	}
147 	if (!pTeam && idTeam)
148 	{
149 		OnTeamSelectionFailed();
150 		return false;
151 	}
152 	// team selection OK; execute it!
153 	if (pPrevTeam) pPrevTeam->RemovePlayerByID(pInfo->GetID());
154 	if (pTeam) pTeam->AddPlayer(*pInfo, true);
155 	if (!ScenarioInit()) return false;
156 	if (!FinalInit(false)) return false;
157 	// perform any pending InitializePlayers() callback
158 	::Game.OnPlayerJoinFinished();
159 	return true;
160 }
161 
Execute()162 void C4Player::Execute()
163 {
164 	if (!Status) return;
165 
166 	// Open/refresh team menu if desired
167 	if (Status==PS_TeamSelection)
168 	{
169 		int32_t idSelectedTeam;
170 		if ((idSelectedTeam = Game.Teams.GetForcedTeamSelection(ID)))
171 		{
172 			// There's only one team left to join? Join there immediately.
173 			if (Menu.IsActive() && Menu.GetIdentification() == C4MN_TeamSelection) Menu.TryClose(false, false);
174 			if (LocalControl && !::Control.isReplay())
175 			{
176 				// team selection done through queue because TeamSelection-status may not be in sync (may be TeamSelectionPending!)
177 				DoTeamSelection(idSelectedTeam);
178 			}
179 		}
180 		else if (!Menu.IsActive()) ActivateMenuTeamSelection(false);
181 		else
182 		{
183 			// during team selection: Update view to selected team, if it has a position assigned
184 			C4MenuItem *pSelectedTeamItem;
185 			if ((pSelectedTeamItem = Menu.GetSelectedItem()))
186 			{
187 				int32_t idSelectedTeam = atoi(pSelectedTeamItem->GetCommand()+8);
188 				if (idSelectedTeam)
189 				{
190 					C4Team *pSelectedTeam;
191 					if ((pSelectedTeam = Game.Teams.GetTeamByID(idSelectedTeam)))
192 					{
193 						int32_t iPlrStartIndex = pSelectedTeam->GetPlrStartIndex();
194 						if (iPlrStartIndex && Inside<int32_t>(iPlrStartIndex, 1, C4S_MaxPlayer))
195 						{
196 							if (Game.C4S.PlrStart[iPlrStartIndex-1].Position[0] > -1)
197 							{
198 								// player has selected a team that has a valid start position assigned
199 								// set view to this position!
200 								ViewX = Game.C4S.PlrStart[iPlrStartIndex-1].Position[0] * ::Landscape.GetMapZoom();
201 								ViewY = Game.C4S.PlrStart[iPlrStartIndex-1].Position[1] * ::Landscape.GetMapZoom();
202 							}
203 						}
204 					}
205 				}
206 			}
207 		}
208 	}
209 	else if (Menu.IsActive() && Menu.GetIdentification() == C4MN_TeamSelection)
210 	{
211 		Menu.TryClose(false, false);
212 	}
213 
214 	// Do we have a gamepad?
215 	if (pGamepad)
216 	{
217 		// Check whether it's still connected.
218 		if (!pGamepad->IsAttached())
219 		{
220 			// Allow the player to plug the gamepad back in. This allows
221 			// battery replacement or plugging the controller back
222 			// in after someone tripped over the wire.
223 			if (!FindGamepad())
224 			{
225 				LogF("%s: No gamepad available.", Name.getData());
226 				::Game.Pause();
227 			}
228 		}
229 	}
230 	// Should we have one? The player may have started the game
231 	// without turning their controller on, only noticing this
232 	// after the game started.
233 	else if (LocalControl && ControlSet && ControlSet->HasGamepad())
234 	{
235 		FindGamepad();
236 	}
237 
238 	// Tick1
239 	UpdateView();
240 	ExecuteControl();
241 	Menu.Execute();
242 
243 	// ::Game.iTick35
244 	if (!::Game.iTick35 && Status==PS_Normal)
245 	{
246 		ExecBaseProduction();
247 		CheckElimination();
248 		if (pMsgBoardQuery && LocalControl) ExecMsgBoardQueries();
249 	}
250 
251 	// Delays
252 	if (MessageStatus>0) MessageStatus--;
253 	if (RetireDelay>0) RetireDelay--;
254 	if (CursorFlash>0) CursorFlash--;
255 }
256 
Init(int32_t iNumber,int32_t iAtClient,const char * szAtClientName,const char * szFilename,bool fScenarioInit,class C4PlayerInfo * pInfo,C4ValueNumbers * numbers)257 bool C4Player::Init(int32_t iNumber, int32_t iAtClient, const char *szAtClientName,
258                     const char *szFilename, bool fScenarioInit, class C4PlayerInfo *pInfo, C4ValueNumbers * numbers)
259 {
260 	// safety
261 	if (!pInfo)
262 	{
263 		LogF("ERROR: Init player %s failed: No info!", szFilename);
264 		assert(false);
265 		return false;
266 	}
267 	// Status init
268 	Status=PS_Normal;
269 	Number = iNumber;
270 	ID = pInfo->GetID();
271 	Team = pInfo->GetTeam();
272 	NoEliminationCheck = pInfo->IsNoEliminationCheck();
273 
274 	// At client
275 	AtClient=iAtClient; SCopy(szAtClientName,AtClientName,C4MaxTitle);
276 
277 	if (szFilename)
278 	{
279 		// Load core & crew info list
280 		if (!Load(szFilename, !fScenarioInit)) return false;
281 	}
282 	else
283 	{
284 		// no core file present: Keep defaults
285 		// This can happen for script players only
286 		assert(pInfo->GetType() == C4PT_Script);
287 	}
288 
289 	// Take player name from player info; forcing overloads by the league or because of doubled player names
290 	Name.Copy(pInfo->GetName());
291 
292 	// view pos init: Start at center pos
293 	ViewX = ::Landscape.GetWidth()/2; ViewY = ::Landscape.GetHeight()/2;
294 
295 	// Scenario init
296 	if (fScenarioInit)
297 	{
298 		// mark player join in player info list
299 		// for non-scenarioinit, player should already be marked as joined
300 		pInfo->SetJoined(iNumber);
301 
302 		// Number might have changed: Recheck list sorting before scenarioinit, which will do script calls
303 		::Players.RecheckPlayerSort(this);
304 
305 		// check for a postponed scenario init, if no team is specified (post-lobby-join in network, or simply non-network)
306 		C4Team *pTeam = nullptr;
307 		if (Team)
308 		{
309 			if (Game.Teams.IsAutoGenerateTeams())
310 				pTeam = Game.Teams.GetGenerateTeamByID(Team);
311 			else
312 				pTeam = Game.Teams.GetTeamByID(Team);
313 		}
314 		if (!pTeam && Game.Teams.IsRuntimeJoinTeamChoice())
315 		{
316 			if (pInfo->GetType() == C4PT_Script)
317 			{
318 				// script player without team: This can usually not happen, because RecheckPlayerInfoTeams should have been executed
319 				// just leave this player without the team
320 				assert(false);
321 			}
322 			else
323 			{
324 				// postponed init: Chose team first
325 				Status = PS_TeamSelection;
326 			}
327 		}
328 
329 		// Init control method before scenario init, because script callbacks may need to know it!
330 		ClearControl();
331 		InitControl();
332 
333 		// defaultdisabled controls
334 		Control.Init();
335 
336 		// Special: Script players may skip scenario initialization altogether, and just desire a single callback to all objects
337 		// of a given ID
338 		if (!pInfo->IsScenarioInitDesired())
339 		{
340 			// only initialization that's done anyway is team hostility
341 			if (Team) SetTeamHostility();
342 			// callback definition passed?
343 			C4ID idCallback = pInfo->GetScriptPlayerExtraID();
344 			C4Def *pDefCallback;
345 			if (idCallback && (pDefCallback = C4Id2Def(idCallback)))
346 			{
347 				pDefCallback->Call(PSF_InitializeScriptPlayer, &C4AulParSet(Number, Team));
348 			}
349 		}
350 		else
351 		{
352 			// player preinit: In case a team needs to be chosen first, no InitializePlayer-broadcast is done
353 			// this callback shall give scripters a chance to do stuff like starting an intro or enabling FoW, which might need to be done
354 			::Game.GRBroadcast(PSF_PreInitializePlayer, &C4AulParSet(Number));
355 			// direct init
356 			if (Status != PS_TeamSelection) if (!ScenarioInit()) return false;
357 		}
358 	}
359 
360 	// Load runtime data
361 	else
362 	{
363 		assert(pInfo->IsJoined());
364 		assert(numbers);
365 		// (compile using DefaultRuntimeData) - also check if compilation returned sane results, i.e. ID assigned
366 		if (!LoadRuntimeData(Game.ScenarioFile, numbers) || !ID)
367 		{
368 			// for script players in non-savegames, this is OK - it means they get restored using default values
369 			// this happens when the users saves a scenario using the "Save scenario"-option while a script player
370 			// was joined
371 			if (!Game.C4S.Head.SaveGame && pInfo->GetType() == C4PT_Script)
372 			{
373 				Number = pInfo->GetInGameNumber();
374 				ColorDw = pInfo->GetColor();
375 				ID = pInfo->GetID();
376 				Team = pInfo->GetTeam();
377 			}
378 			else
379 				return false;
380 		}
381 		// Reset values default-overriden by old runtime data load (safety?)
382 		if (Number==C4P_Number_None) Number=iNumber;
383 		if (szFilename) SCopy(Config.AtUserDataPath(szFilename),Filename); else *Filename='\0';
384 		// NET2: Direct joins always send accurate client IDs and names in params
385 		// do not overwrite them with savegame data, because players might as well
386 		// change clients
387 		// (only call should be savegame recreation by C4PlayerInfoList::RecreatePlayers)
388 		AtClient = iAtClient;
389 		SCopy(szAtClientName,AtClientName,C4MaxTitle);
390 		// Number might have changed: Recheck list sorting
391 		::Players.RecheckPlayerSort(this);
392 		// Init control after loading runtime data, because control init will overwrite some of the values
393 		InitControl();
394 	}
395 
396 	// store game joining time
397 	GameJoinTime = Game.Time;
398 
399 	// Init FoW-viewobjects: NO_OWNER-FoW-repellers might need to be added
400 	for (C4Object *pObj : Objects)
401 	{
402 		if ((pObj->lightRange || pObj->lightFadeoutRange) && pObj->Owner == NO_OWNER)
403 			pObj->UpdateLight();
404 	}
405 
406 	// init graphs
407 	if (Game.pNetworkStatistics) CreateGraphs();
408 
409 	// init sound mod
410 	SetSoundModifier(SoundModifier.getPropList());
411 
412 	return true;
413 }
414 
Save()415 bool C4Player::Save()
416 {
417 	C4Group hGroup;
418 	// Regular player saving need not be done for script players
419 	if (GetType() == C4PT_Script) return false;
420 	// Log
421 	LogF(LoadResStr("IDS_PRC_SAVEPLR"), Config.AtRelativePath(Filename));
422 	::GraphicsSystem.MessageBoard->EnsureLastMessage();
423 	// copy player to save somewhere else
424 	char szPath[_MAX_PATH + 1];
425 	SCopy(Config.AtTempPath(C4CFN_TempPlayer), szPath, _MAX_PATH);
426 	MakeTempFilename(szPath);
427 	// For local players, we save over the old player file, as there might
428 	// be all kinds of non-essential stuff in it. For non-local players, we
429 	// just re-create it every time (it's temporary anyway).
430 	if (LocalControl)
431 	{
432 		// But make sure to copy it first so full hard (flgr stupid) disks
433 		// won't corrupt any player files...
434 		C4Group_CopyItem(Filename, szPath);
435 	}
436 	else
437 	{
438 		// For non-local players, we can actually use the loaded definition
439 		// list to strip out all non-existant definitions. This is only valid
440 		// because we know the file to be temporary.
441 		CrewInfoList.Strip(::Definitions);
442 	}
443 	// Open group
444 	if (!hGroup.Open(szPath,true))
445 		return false;
446 	// Save
447 	if (!Save(hGroup, false, !LocalControl))
448 		{ hGroup.Close(); return false; }
449 	// Close group
450 	if (!hGroup.Close()) return false;
451 	// resource
452 	C4Network2Res::Ref pRes = ::Network.ResList.getRefRes(Filename),
453 	                          pDRes = nullptr;
454 	bool fOfficial = pRes && ::Control.isCtrlHost();
455 	if (pRes) pDRes = pRes->Derive();
456 	// move back
457 	if (ItemExists(Filename)) EraseItem(Filename);
458 	if (!C4Group_MoveItem(szPath, Filename)) return false;
459 	// finish update
460 	if (pDRes && fOfficial) pDRes->FinishDerive();
461 	// Success
462 	return true;
463 }
464 
Save(C4Group & hGroup,bool fSavegame,bool fStoreTiny)465 bool C4Player::Save(C4Group &hGroup, bool fSavegame, bool fStoreTiny)
466 {
467 	// Save core
468 	if (!C4PlayerInfoCore::Save(hGroup))
469 		return false;
470 	// Save crew
471 	C4DefList *pDefs = &::Definitions;
472 	if (!CrewInfoList.Save(hGroup, fSavegame, fStoreTiny, pDefs))
473 		{ hGroup.Close(); return false; }
474 	// Sort
475 	hGroup.Sort(C4FLS_Player);
476 	return true;
477 }
478 
PlaceReadyCrew(int32_t tx1,int32_t tx2,int32_t ty,C4Object * FirstBase)479 void C4Player::PlaceReadyCrew(int32_t tx1, int32_t tx2, int32_t ty, C4Object *FirstBase)
480 {
481 	int32_t cnt,ctx,cty;
482 	C4Object *nobj;
483 	C4ObjectInfo *pInfo;
484 	C4Def *pDef;
485 
486 	// Place crew
487 	int32_t iCount;
488 	C4ID id;
489 	for (cnt=0; (id=Game.C4S.PlrStart[PlrStartIndex].ReadyCrew.GetID(cnt,&iCount)); cnt++)
490 	{
491 		// Minimum one clonk if empty id
492 		iCount = std::max<int32_t>(iCount,1);
493 
494 		for (int32_t cnt2=0; cnt2<iCount; cnt2++)
495 		{
496 			// Select member from home crew, add new if necessary
497 			while (!(pInfo=CrewInfoList.GetIdle(id,::Definitions)))
498 				if (!CrewInfoList.New(id,&::Definitions))
499 					break;
500 			// Safety
501 			if (!pInfo || !(pDef=C4Id2Def(pInfo->id))) continue;
502 			// Crew placement location
503 			ctx=tx1+Random(tx2-tx1); cty=ty;
504 			if (!Game.C4S.PlrStart[PlrStartIndex].EnforcePosition)
505 				FindSolidGround(ctx,cty,pDef->Shape.Wdt*3);
506 			// Create object
507 			if ((nobj=Game.CreateInfoObject(pInfo,Number,ctx,cty)))
508 			{
509 				// Add object to crew
510 				Crew.Add(nobj, C4ObjectList::stNone);
511 				// add visibility range
512 				nobj->SetLightRange(C4FOW_DefLightRangeX, C4FOW_DefLightFadeoutRangeX);
513 				// If base is present, enter base
514 				if (FirstBase) { nobj->Enter(FirstBase); nobj->SetCommand(C4CMD_Exit); }
515 				// OnJoinCrew callback
516 				{
517 					C4DebugRecOff DbgRecOff{ !DEBUGREC_RECRUITMENT };
518 					C4AulParSet parset(Number);
519 					nobj->Call(PSF_OnJoinCrew, &parset);
520 				}
521 			}
522 		}
523 	}
524 
525 }
526 
PlaceReadyBase(int32_t & tx,int32_t & ty,C4Object ** pFirstBase)527 void C4Player::PlaceReadyBase(int32_t &tx, int32_t &ty, C4Object **pFirstBase)
528 {
529 	int32_t cnt,cnt2,ctx,cty;
530 	C4Def *def;
531 	C4ID cid;
532 	C4Object *cbase;
533 	// Create ready base structures
534 	for (cnt=0; (cid=Game.C4S.PlrStart[PlrStartIndex].ReadyBase.GetID(cnt)); cnt++)
535 	{
536 		if ((def=C4Id2Def(cid)))
537 			for (cnt2=0; cnt2<Game.C4S.PlrStart[PlrStartIndex].ReadyBase.GetCount(cnt); cnt2++)
538 			{
539 				ctx=tx; cty=ty;
540 				if (Game.C4S.PlrStart[PlrStartIndex].EnforcePosition
541 				    || FindConSiteSpot(ctx,cty,def->Shape.Wdt,def->Shape.Hgt,20))
542 					if ((cbase=Game.CreateObjectConstruction(C4Id2Def(cid),nullptr,Number,ctx,cty,FullCon,true)))
543 					{
544 						// FirstBase
545 						if (!(*pFirstBase)) if ((cbase->Def->Entrance.Wdt>0) && (cbase->Def->Entrance.Hgt>0))
546 								{ *pFirstBase=cbase; tx=(*pFirstBase)->GetX(); ty=(*pFirstBase)->GetY(); }
547 					}
548 			}
549 	}
550 }
551 
PlaceReadyVehic(int32_t tx1,int32_t tx2,int32_t ty,C4Object * FirstBase)552 void C4Player::PlaceReadyVehic(int32_t tx1, int32_t tx2, int32_t ty, C4Object *FirstBase)
553 {
554 	int32_t cnt,cnt2,ctx,cty;
555 	C4Def *def; C4ID cid; C4Object *cobj;
556 	for (cnt=0; (cid=Game.C4S.PlrStart[PlrStartIndex].ReadyVehic.GetID(cnt)); cnt++)
557 	{
558 		if ((def=C4Id2Def(cid)))
559 			for (cnt2=0; cnt2<Game.C4S.PlrStart[PlrStartIndex].ReadyVehic.GetCount(cnt); cnt2++)
560 			{
561 				ctx=tx1+Random(tx2-tx1); cty=ty;
562 				if (!Game.C4S.PlrStart[PlrStartIndex].EnforcePosition)
563 					FindLevelGround(ctx,cty,def->Shape.Wdt,6);
564 				if ((cobj=Game.CreateObject(cid,nullptr,Number,ctx,cty)))
565 				{
566 					if (FirstBase) // First base overrides target location
567 						{ cobj->Enter(FirstBase); cobj->SetCommand(C4CMD_Exit); }
568 				}
569 			}
570 	}
571 }
572 
PlaceReadyMaterial(int32_t tx1,int32_t tx2,int32_t ty,C4Object * FirstBase)573 void C4Player::PlaceReadyMaterial(int32_t tx1, int32_t tx2, int32_t ty, C4Object *FirstBase)
574 {
575 	int32_t cnt,cnt2,ctx,cty;
576 	C4Def *def; C4ID cid;
577 
578 	// In base
579 	if (FirstBase)
580 	{
581 		FirstBase->CreateContentsByList(Game.C4S.PlrStart[PlrStartIndex].ReadyMaterial);
582 	}
583 
584 	// Outside
585 	else
586 	{
587 		for (cnt=0; (cid=Game.C4S.PlrStart[PlrStartIndex].ReadyMaterial.GetID(cnt)); cnt++)
588 		{
589 			if ((def=C4Id2Def(cid)))
590 				for (cnt2=0; cnt2<Game.C4S.PlrStart[PlrStartIndex].ReadyMaterial.GetCount(cnt); cnt2++)
591 				{
592 					ctx=tx1+Random(tx2-tx1); cty=ty;
593 					if (!Game.C4S.PlrStart[PlrStartIndex].EnforcePosition)
594 						FindSolidGround(ctx,cty,def->Shape.Wdt);
595 					Game.CreateObject(cid,nullptr,Number,ctx,cty);
596 				}
597 		}
598 	}
599 }
600 
ScenarioInit()601 bool C4Player::ScenarioInit()
602 {
603 	int32_t ptx,pty;
604 
605 	// player start index by team, if specified. Otherwise by player number
606 	PlrStartIndex = Number % C4S_MaxPlayer;
607 	C4Team *pTeam; int32_t i;
608 	if (Team && (pTeam = Game.Teams.GetTeamByID(Team))) if ((i=pTeam->GetPlrStartIndex())) PlrStartIndex=i-1;
609 
610 	C4PlayerInfo *pInfo = GetInfo();
611 	if (!pInfo) { assert(false); LogF("Internal error: ScenarioInit for ghost player %s!", GetName()); return false; }
612 
613 	// set color by player info class
614 	// re-setting, because runtime team choice may have altered color
615 	ColorDw = pInfo->GetColor();
616 
617 	// any team selection is over now
618 	Status = PS_Normal;
619 
620 	// Wealth, home base materials, abilities
621 	Wealth=Game.C4S.PlrStart[PlrStartIndex].Wealth.Evaluate();
622 	BaseMaterial=Game.C4S.PlrStart[PlrStartIndex].BaseMaterial;
623 	BaseMaterial.ConsolidateValids(::Definitions);
624 	BaseProduction=Game.C4S.PlrStart[PlrStartIndex].BaseProduction;
625 	BaseProduction.ConsolidateValids(::Definitions);
626 	Knowledge=Game.C4S.PlrStart[PlrStartIndex].BuildKnowledge;
627 	Knowledge.ConsolidateValids(::Definitions);
628 
629 	// Starting position
630 	ptx = Game.C4S.PlrStart[PlrStartIndex].Position[0];
631 	pty = Game.C4S.PlrStart[PlrStartIndex].Position[1];
632 
633 	// Zoomed position
634 	if (ptx>-1) ptx = Clamp<int32_t>( ptx * Game.C4S.Landscape.MapZoom.Evaluate(), 0, ::Landscape.GetWidth()-1 );
635 	if (pty>-1) pty = Clamp<int32_t>( pty * Game.C4S.Landscape.MapZoom.Evaluate(), 0, ::Landscape.GetHeight()-1 );
636 
637 	// Standard position (PrefPosition)
638 	if (ptx<0)
639 		if (Game.StartupPlayerCount>=2)
640 		{
641 			int32_t iMaxPos=Game.StartupPlayerCount;
642 			// Try to initialize PrefPosition using teams. This should put players of a team next to each other.
643 			int PrefPosition = 0;
644 			C4PlayerInfo *plr;
645 			for (int i = 0; (plr = Game.PlayerInfos.GetPlayerInfoByIndex(i)) != nullptr; i++)
646 			{
647 				if (plr->GetTeam() < Team)
648 					PrefPosition++;
649 			}
650 			// Map preferred position to available positions
651 			int32_t iStartPos=Clamp(PrefPosition*iMaxPos/C4P_MaxPosition,0,iMaxPos-1);
652 			int32_t iPosition=iStartPos;
653 			// Distribute according to availability
654 			while (::Players.PositionTaken(iPosition))
655 			{
656 				++iPosition;
657 				iPosition %= iMaxPos;
658 				if (iPosition == iStartPos)
659 					break;
660 			}
661 			Position=iPosition;
662 			// Set x position
663 			ptx=Clamp(16+Position*(::Landscape.GetWidth()-32)/(iMaxPos-1),0,::Landscape.GetWidth()-16);
664 		}
665 
666 	// All-random position
667 	if (ptx<0) ptx=16+Random(::Landscape.GetWidth()-32);
668 	if (pty<0) pty=16+Random(::Landscape.GetHeight()-32);
669 
670 	// Place to solid ground
671 	if (!Game.C4S.PlrStart[PlrStartIndex].EnforcePosition)
672 	{
673 		// Use nearest above-ground...
674 		FindSolidGround(ptx,pty,30);
675 		// Might have hit a small lake, or similar: Seach a real site spot from here
676 		FindConSiteSpot(ptx, pty, 30, 50, 400);
677 	}
678 
679 	// Place Readies
680 	C4Object *FirstBase = nullptr;
681 	PlaceReadyBase(ptx,pty,&FirstBase);
682 	PlaceReadyMaterial(ptx-10,ptx+10,pty,FirstBase);
683 	PlaceReadyVehic(ptx-30,ptx+30,pty,FirstBase);
684 	PlaceReadyCrew(ptx-30,ptx+30,pty,FirstBase);
685 
686 	// set initial hostility by team info
687 	if (Team) SetTeamHostility();
688 
689 	// Scenario script initialization
690 	::Game.GRBroadcast(PSF_InitializePlayer, &C4AulParSet(Number,
691 	                        ptx,
692 	                        pty,
693 	                        FirstBase,
694 	                        Team,
695 	                        C4Id2Def(GetInfo()->GetScriptPlayerExtraID())));
696 	return true;
697 }
698 
FinalInit(bool fInitialScore)699 bool C4Player::FinalInit(bool fInitialScore)
700 {
701 	if (!Status) return true;
702 
703 	// Init player's mouse control
704 	if (LocalControl)
705 		if (MouseControl)
706 			::MouseControl.Init(Number);
707 
708 	// Set initial score
709 	if (fInitialScore)
710 	{
711 		InitialScore=CurrentScore;
712 	}
713 
714 	// Cursor
715 	if (!Cursor) AdjustCursorCommand();
716 
717 	// Update counts, pointers, views
718 	Execute();
719 
720 	return true;
721 }
722 
SetFoW(bool fEnable)723 void C4Player::SetFoW(bool fEnable)
724 {
725 	// set flag
726 	fFogOfWar = fEnable;
727 }
728 
DoWealth(int32_t iChange)729 bool C4Player::DoWealth(int32_t iChange)
730 {
731 	if (LocalControl)
732 	{
733 		if (iChange>0) StartSoundEffect("UI::Cash");
734 		if (iChange<0) StartSoundEffect("UI::UnCash");
735 	}
736 	SetWealth(Wealth+iChange);
737 
738 	return true;
739 }
740 
SetWealth(int32_t iVal)741 bool C4Player::SetWealth(int32_t iVal)
742 {
743 	if (iVal == Wealth) return true;
744 
745 	Wealth=Clamp<int32_t>(iVal,0,1000000000);
746 
747 	::Game.GRBroadcast(PSF_OnWealthChanged,&C4AulParSet(Number));
748 
749 	return true;
750 }
751 
SetKnowledge(C4ID id,bool fRemove)752 bool C4Player::SetKnowledge(C4ID id, bool fRemove)
753 {
754 	if (fRemove)
755 	{
756 		long iIndex = Knowledge.GetIndex(id);
757 		if (iIndex<0) return false;
758 		return Knowledge.DeleteItem(iIndex);
759 	}
760 	else
761 	{
762 		if (!C4Id2Def(id)) return false;
763 		return Knowledge.SetIDCount(id, 1, true);
764 	}
765 }
766 
SetViewMode(int32_t iMode,C4Object * pTarget,bool immediate_position)767 void C4Player::SetViewMode(int32_t iMode, C4Object *pTarget, bool immediate_position)
768 {
769 	// safe back
770 	ViewMode=iMode; ViewTarget=pTarget;
771 	// immediate position set desired?
772 	if (immediate_position)
773 	{
774 		UpdateView();
775 		C4Viewport *vp = ::Viewports.GetViewport(this->Number);
776 		if (vp) vp->AdjustPosition(true);
777 	}
778 }
779 
ResetCursorView(bool immediate_position)780 void C4Player::ResetCursorView(bool immediate_position)
781 {
782 	// reset view to cursor if any cursor exists
783 	if (!ViewCursor && !Cursor) return;
784 	SetViewMode(C4PVM_Cursor, nullptr, immediate_position);
785 }
786 
Evaluate()787 void C4Player::Evaluate()
788 {
789 	// do not evaluate twice
790 	if (Evaluated) return;
791 
792 	const int32_t SuccessBonus=100;
793 
794 	// Set last round
795 	LastRound.Title = Game.ScenarioTitle;
796 	time(reinterpret_cast<time_t *>(&LastRound.Date));
797 	LastRound.Duration = Game.Time;
798 	LastRound.Won = !Eliminated;
799 	// Melee: personal value gain score ...check ::Objects(C4D_Goal)
800 	if (Game.C4S.Game.IsMelee()) LastRound.Score = std::max<int32_t>(CurrentScore-InitialScore,0);
801 	// Cooperative: shared score
802 	else LastRound.Score = std::max(::Players.AverageScoreGain(),0);
803 	LastRound.Level = 0; // unknown...
804 	LastRound.Bonus = SuccessBonus * LastRound.Won;
805 	LastRound.FinalScore = LastRound.Score + LastRound.Bonus;
806 	LastRound.TotalScore = TotalScore + LastRound.FinalScore;
807 
808 	// Update player
809 	Rounds++;
810 	if (LastRound.Won) RoundsWon++; else RoundsLost++;
811 	TotalScore=LastRound.TotalScore;
812 	TotalPlayingTime+=Game.Time-GameJoinTime;
813 
814 	// Crew
815 	CrewInfoList.Evaluate();
816 
817 	// league
818 	if (Game.Parameters.isLeague())
819 		EvaluateLeague(false, Game.GameOver && !Eliminated);
820 
821 	// Player is now evaluated
822 	Evaluated = true;
823 
824 	// round results
825 	Game.RoundResults.EvaluatePlayer(this);
826 }
827 
Surrender()828 void C4Player::Surrender()
829 {
830 	if (Surrendered) return;
831 	Surrendered=true;
832 	Eliminated=true;
833 	RetireDelay=C4RetireDelay;
834 	StartSoundEffect("UI::Eliminated");
835 	Log(FormatString(LoadResStr("IDS_PRC_PLRSURRENDERED"),GetName()).getData());
836 }
837 
SetHostility(int32_t iOpponent,int32_t hostile,bool fSilent)838 bool C4Player::SetHostility(int32_t iOpponent, int32_t hostile, bool fSilent)
839 {
840 	assert(hostile == 0 || hostile == 1);
841 	// Check opponent valid
842 	C4Player *opponent = ::Players.Get(iOpponent);
843 	if (!opponent || opponent == this)
844 		return false;
845 	// Set hostility
846 	if (hostile)
847 		Hostility.insert(opponent);
848 	else
849 		Hostility.erase(opponent);
850 	// no announce in first frame, or if specified
851 	if (!Game.FrameCounter || fSilent) return true;
852 	// Announce
853 	StartSoundEffect("UI::Trumpet");
854 	Log(FormatString(LoadResStr(hostile ? "IDS_PLR_HOSTILITY" : "IDS_PLR_NOHOSTILITY"),
855 	                 GetName(),opponent->GetName()).getData());
856 	// Success
857 	return true;
858 }
859 
IsHostileTowards(const C4Player * plr) const860 bool C4Player::IsHostileTowards(const C4Player *plr) const
861 {
862 	assert(plr);
863 	if (!plr) return false;
864 	return Hostility.find(plr) != Hostility.end();
865 }
866 
GetHiExpActiveCrew()867 C4Object* C4Player::GetHiExpActiveCrew()
868 {
869 	C4Object *hiexp=nullptr;
870 	int32_t iHighestExp=-2, iExp;
871 	for (C4Object *cobj : Crew)
872 	{
873 		if (!cobj->CrewDisabled)
874 		{
875 			if (cobj->Info) iExp = cobj->Info->Experience; else iExp=-1;
876 			if (!hiexp || (iExp>iHighestExp))
877 			{
878 				hiexp=cobj;
879 				iHighestExp=iExp;
880 			}
881 		}
882 	}
883 	return hiexp;
884 }
885 
GetHiRankActiveCrew()886 C4Object* C4Player::GetHiRankActiveCrew()
887 {
888 	C4Object *hirank=nullptr;
889 	int32_t iHighestRank=-2, iRank;
890 	for (C4Object *cobj : Crew)
891 	{
892 		if (!cobj->CrewDisabled)
893 		{
894 			if (cobj->Info) iRank = cobj->Info->Rank; else iRank=-1;
895 			if (!hirank || (iRank>iHighestRank))
896 			{
897 				hirank=cobj;
898 				iHighestRank=iRank;
899 			}
900 		}
901 	}
902 	return hirank;
903 }
904 
CheckCrewExPromotion()905 void C4Player::CheckCrewExPromotion()
906 {
907 	C4Object *hirank;
908 	if ((hirank=GetHiRankActiveCrew()))
909 		if (hirank->Info)
910 			if (hirank->Info->Rank<1) // No Fähnrich -> except. promo.
911 				if ((hirank=GetHiExpActiveCrew()))
912 					hirank->Promote(1,true,false);
913 }
914 
SetTeamHostility()915 void C4Player::SetTeamHostility()
916 {
917 	// team only
918 	if (!Team) return;
919 	// set hostilities
920 	for (C4Player *pPlr = ::Players.First; pPlr; pPlr = pPlr->Next)
921 		if (pPlr != this)
922 		{
923 			bool fHostile = (pPlr->Team != Team);
924 			SetHostility(pPlr->Number, fHostile, true);
925 			pPlr->SetHostility(Number, fHostile, true);
926 		}
927 }
928 
Message(const char * szMsg)929 bool C4Player::Message(const char *szMsg)
930 {
931 	if (!szMsg) return false;
932 	SCopy(szMsg,MessageBuf,256);
933 	MessageStatus=SLen(szMsg)*2;
934 	return true;
935 }
936 
Load(const char * szFilename,bool fSavegame)937 bool C4Player::Load(const char *szFilename, bool fSavegame)
938 {
939 	C4Group hGroup;
940 	// Open group
941 	if (!Reloc.Open(hGroup, szFilename)) return false;
942 	// Remember filename
943 	SCopy(hGroup.GetFullName().getData(), Filename, _MAX_PATH);
944 	// Load core
945 	if (!C4PlayerInfoCore::Load(hGroup))
946 		{ hGroup.Close(); return false; }
947 	// Load BigIcon
948 	if (hGroup.FindEntry(C4CFN_BigIcon)) BigIcon.Load(hGroup, C4CFN_BigIcon, C4FCT_Full, C4FCT_Full, false, 0);
949 	// Load crew info list
950 	CrewInfoList.Load(hGroup);
951 	// Close group
952 	hGroup.Close();
953 	// Success
954 	return true;
955 }
956 
Strip(const char * szFilename,bool fAggressive)957 bool C4Player::Strip(const char *szFilename, bool fAggressive)
958 {
959 	// Open group
960 	C4Group Grp;
961 	if (!Grp.Open(szFilename))
962 		return false;
963 	// Which type of stripping?
964 	if (!fAggressive)
965 	{
966 		// remove bigicon, if the file size is too large
967 		size_t iBigIconSize=0;
968 		if (Grp.FindEntry(C4CFN_BigIcon, nullptr, &iBigIconSize))
969 			if (iBigIconSize > C4NetResMaxBigicon*1024)
970 				Grp.Delete(C4CFN_BigIcon);
971 		Grp.Close();
972 	}
973 	else
974 	{
975 		// Load info core and crew info list
976 		C4PlayerInfoCore PlrInfoCore;
977 		C4ObjectInfoList CrewInfoList;
978 		if (!PlrInfoCore.Load(Grp) || !CrewInfoList.Load(Grp))
979 			return false;
980 		// Strip crew info list (remove object infos that are invalid for this scenario)
981 		CrewInfoList.Strip(::Definitions);
982 		// Create a new group that receives the bare essentials
983 		Grp.Close();
984 		if (!EraseItem(szFilename) ||
985 		    !Grp.Open(szFilename, true))
986 			return false;
987 		// Save info core & crew info list to newly-created file
988 		if (!PlrInfoCore.Save(Grp) || !CrewInfoList.Save(Grp, true, true, &::Definitions))
989 			return false;
990 		Grp.Close();
991 	}
992 	return true;
993 }
994 
DrawHostility(C4Facet & cgo,int32_t iIndex)995 void C4Player::DrawHostility(C4Facet &cgo, int32_t iIndex)
996 {
997 	C4Player *pPlr;
998 	if ((pPlr=::Players.GetByIndex(iIndex)))
999 	{
1000 		::GraphicsResource.fctCrewClr.DrawClr(cgo, true, pPlr->ColorDw);
1001 		// Other player and hostile
1002 		if (pPlr != this)
1003 			if (Hostility.find(pPlr) != Hostility.end())
1004 				::GraphicsResource.fctMenu.GetPhase(7).Draw(cgo);
1005 	}
1006 }
1007 
MakeCrewMember(C4Object * pObj,bool fForceInfo,bool fDoCalls)1008 bool C4Player::MakeCrewMember(C4Object *pObj, bool fForceInfo, bool fDoCalls)
1009 {
1010 	C4ObjectInfo *cInf = nullptr;
1011 	if (!pObj || !pObj->Def->CrewMember || !pObj->Status) return false;
1012 
1013 	// only if info is not yet assigned
1014 	if (!pObj->Info && fForceInfo)
1015 	{
1016 		// Find crew info by name
1017 		if (pObj->nInfo)
1018 			cInf = CrewInfoList.GetIdle(pObj->nInfo.getData());
1019 
1020 		// Find crew info by id
1021 		if (!cInf)
1022 			while (!( cInf = CrewInfoList.GetIdle(pObj->id,::Definitions) ))
1023 				if (!CrewInfoList.New(pObj->id,&::Definitions))
1024 					return false;
1025 
1026 		// Set object info
1027 		pObj->Info = cInf;
1028 		pObj->SetName(cInf->Name);
1029 	}
1030 
1031 	// Add to crew
1032 	if (!Crew.GetLink(pObj))
1033 		Crew.Add(pObj, C4ObjectList::stNone);
1034 
1035 	// add light
1036 	if (!pObj->lightRange)
1037 		pObj->SetLightRange(C4FOW_DefLightRangeX, C4FOW_DefLightFadeoutRangeX);
1038 	else
1039 		pObj->UpdateLight();
1040 
1041 	// controlled by the player
1042 	pObj->Controller = Number;
1043 
1044 	// OnJoinCrew callback
1045 	if (fDoCalls)
1046 	{
1047 		C4AulParSet parset(Number);
1048 		pObj->Call(PSF_OnJoinCrew, &parset);
1049 	}
1050 
1051 	return true;
1052 }
1053 
ExecuteControl()1054 void C4Player::ExecuteControl()
1055 {
1056 	Control.Execute();
1057 }
1058 
AdjustCursorCommand()1059 void C4Player::AdjustCursorCommand()
1060 {
1061 	// Reset view
1062 	ResetCursorView();
1063 	// Default cursor to hirank clonk
1064 	if (!Cursor || Cursor->CrewDisabled)
1065 	{
1066 		C4Object *pHiRank = GetHiRankActiveCrew();
1067 		if (!pHiRank)
1068 			return;
1069 		SetCursor(pHiRank,true);
1070 		UpdateView();
1071 	}
1072 }
1073 
CompileFunc(StdCompiler * pComp,C4ValueNumbers * numbers)1074 void C4Player::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers)
1075 {
1076 	assert(ID);
1077 
1078 	pComp->Value(mkNamingAdapt(Status,              "Status",               0));
1079 	pComp->Value(mkNamingAdapt(AtClient,            "AtClient",             C4ClientIDUnknown));
1080 	pComp->Value(mkNamingAdapt(toC4CStr(AtClientName),"AtClientName",        "Local"));
1081 	pComp->Value(mkNamingAdapt(Number,              "Index",                C4P_Number_None));
1082 	pComp->Value(mkNamingAdapt(ID,                  "ID",                   0));
1083 	pComp->Value(mkNamingAdapt(Eliminated,          "Eliminated",           0));
1084 	pComp->Value(mkNamingAdapt(Surrendered,         "Surrendered",          0));
1085 	pComp->Value(mkNamingAdapt(Evaluated,            "Evaluated",            false));
1086 	pComp->Value(mkNamingAdapt(ColorDw,             "ColorDw",              0u));
1087 	pComp->Value(mkNamingAdapt(Position,            "Position",             0));
1088 	pComp->Value(mkNamingAdapt(ViewMode,            "ViewMode",             C4PVM_Cursor));
1089 	pComp->Value(mkNamingAdapt(ViewX,               "ViewX",                0));
1090 	pComp->Value(mkNamingAdapt(ViewY,               "ViewY",                0));
1091 	pComp->Value(mkNamingAdapt(ViewLock,            "ViewLock",             true));
1092 	pComp->Value(mkNamingAdapt(ZoomLimitMinWdt,     "ZoomLimitMinWdt",      0));
1093 	pComp->Value(mkNamingAdapt(ZoomLimitMinHgt,     "ZoomLimitMinHgt",      0));
1094 	pComp->Value(mkNamingAdapt(ZoomLimitMaxWdt,     "ZoomLimitMaxWdt",      0));
1095 	pComp->Value(mkNamingAdapt(ZoomLimitMaxHgt,     "ZoomLimitMaxHgt",      0));
1096 	pComp->Value(mkNamingAdapt(ZoomWdt,             "ZoomWdt",              0));
1097 	pComp->Value(mkNamingAdapt(ZoomHgt,             "ZoomHgt",              0));
1098 	pComp->Value(mkNamingAdapt(ZoomLimitMinVal,     "ZoomLimitMinVal",      Fix0));
1099 	pComp->Value(mkNamingAdapt(ZoomLimitMaxVal,     "ZoomLimitMaxVal",      Fix0));
1100 	pComp->Value(mkNamingAdapt(ZoomVal,             "ZoomVal",              Fix0));
1101 	pComp->Value(mkNamingAdapt(fFogOfWar,           "FogOfWar",             false));
1102 	pComp->Value(mkNamingAdapt(ShowStartup,         "ShowStartup",          false));
1103 	pComp->Value(mkNamingAdapt(Wealth,              "Wealth",               0));
1104 	pComp->Value(mkNamingAdapt(CurrentScore,        "Score",                0));
1105 	pComp->Value(mkNamingAdapt(InitialScore,        "InitialScore",         0));
1106 	pComp->Value(mkNamingAdapt(ObjectsOwned,        "ObjectsOwned",         0));
1107 	pComp->Value(mkNamingAdapt(Hostility,           "Hostile"               ));
1108 	pComp->Value(mkNamingAdapt(ProductionDelay,     "ProductionDelay",      0));
1109 	pComp->Value(mkNamingAdapt(ProductionUnit,      "ProductionUnit",       0));
1110 	pComp->Value(mkNamingAdapt(CursorFlash,         "CursorFlash",          0));
1111 	pComp->Value(mkNamingAdapt(Cursor,              "Cursor",               C4ObjectPtr::Null));
1112 	pComp->Value(mkNamingAdapt(ViewCursor,          "ViewCursor",           C4ObjectPtr::Null));
1113 	pComp->Value(mkNamingAdapt(MessageStatus,       "MessageStatus",        0));
1114 	pComp->Value(mkNamingAdapt(toC4CStr(MessageBuf),"MessageBuf",           ""));
1115 	pComp->Value(mkNamingAdapt(BaseMaterial,        "BaseMaterial"          ));
1116 	pComp->Value(mkNamingAdapt(BaseProduction,      "BaseProduction"        ));
1117 	pComp->Value(mkNamingAdapt(Knowledge,           "Knowledge"             ));
1118 	pComp->Value(mkNamingAdapt(mkParAdapt(Crew, numbers), "Crew"            ));
1119 	pComp->Value(mkNamingAdapt(CrewInfoList.iNumCreated, "CrewCreated",     0));
1120 	pComp->Value(mkNamingPtrAdapt( pMsgBoardQuery,  "MsgBoardQueries"        ));
1121 	pComp->Value(mkNamingAdapt(mkParAdapt(SoundModifier, numbers), "SoundModifier", C4Value()));
1122 
1123 	if (pComp->isDeserializer())
1124 	{
1125 		SoundModifier.Denumerate(numbers);
1126 	}
1127 
1128 	// Keys held down
1129 	pComp->Value(Control);
1130 }
1131 
LoadRuntimeData(C4Group & hGroup,C4ValueNumbers * numbers)1132 bool C4Player::LoadRuntimeData(C4Group &hGroup, C4ValueNumbers * numbers)
1133 {
1134 	const char *pSource;
1135 	// Use loaded game text component
1136 	if (!(pSource = Game.GameText.GetData())) return false;
1137 	// safety: Do nothing if player section is not even present (could kill initialized values)
1138 	if (!SSearch(pSource, FormatString("[Player%i]", ID).getData())) return false;
1139 	// Compile (Search player section - runtime data is stored by unique player ID)
1140 	// Always compile exact. Exact data will not be present for savegame load, so it does not matter
1141 	assert(ID);
1142 	if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(
1143 	      mkNamingAdapt(mkParAdapt(*this, numbers), FormatString("Player%i", ID).getData()),
1144 	      StdStrBuf(pSource),
1145 	      Game.GameText.GetFilePath()))
1146 		return false;
1147 	// Denumerate pointers
1148 	DenumeratePointers();
1149 	// Success
1150 	return true;
1151 }
1152 
ExecBaseProduction()1153 void C4Player::ExecBaseProduction()
1154 {
1155 	const int32_t MaxBaseProduction = 25;
1156 	ProductionDelay++;
1157 	if (ProductionDelay>=60) // Minute Production Unit
1158 	{
1159 		ProductionDelay=0; ProductionUnit++;
1160 		for (int32_t cnt=0; BaseProduction.GetID(cnt); cnt++)
1161 			if (BaseProduction.GetCount(cnt)>0)
1162 				if (ProductionUnit % Clamp<int32_t>(11-BaseProduction.GetCount(cnt),1,10) ==0)
1163 					if (BaseMaterial.GetIDCount(BaseProduction.GetID(cnt)) < MaxBaseProduction)
1164 						BaseMaterial.IncreaseIDCount(BaseProduction.GetID(cnt));
1165 	}
1166 }
1167 
CheckElimination()1168 void C4Player::CheckElimination()
1169 {
1170 	// Standard elimination: no crew
1171 	if (!Crew.GetFirstObject())
1172 		// Already eliminated safety
1173 		if (!Eliminated)
1174 			// No automatic elimination desired?
1175 			if (!NoEliminationCheck)
1176 				// Do elimination!
1177 				Eliminate();
1178 }
1179 
UpdateView()1180 void C4Player::UpdateView()
1181 {
1182 	// view target/cursor
1183 	switch (ViewMode)
1184 	{
1185 	case C4PVM_Cursor:
1186 	{
1187 		C4Object *pViewObj;
1188 		if (!(pViewObj=ViewCursor)) pViewObj=Cursor;
1189 		if (pViewObj)
1190 		{
1191 			ViewX=pViewObj->GetX(); ViewY=pViewObj->GetY();
1192 		}
1193 		break;
1194 	}
1195 	case C4PVM_Target:
1196 		if (ViewTarget)
1197 		{
1198 			ViewX=ViewTarget->GetX(); ViewY=ViewTarget->GetY();
1199 		}
1200 		break;
1201 	case C4PVM_Scrolling:
1202 		break;
1203 	}
1204 }
1205 
DefaultRuntimeData()1206 void C4Player::DefaultRuntimeData()
1207 {
1208 	Status=0;
1209 	Eliminated=0;
1210 	Surrendered=0;
1211 	AtClient=C4ClientIDUnknown;
1212 	SCopy("Local",AtClientName);
1213 	ControlSet = nullptr;
1214 	ControlSetName.Clear();
1215 	MouseControl=false;
1216 	Position=-1;
1217 	PlrStartIndex=0;
1218 	RetireDelay=0;
1219 	ViewMode=C4PVM_Cursor;
1220 	ViewX=ViewY=0;
1221 	ViewTarget=nullptr;
1222 	ShowStartup=true;
1223 	Wealth=0;
1224 	CurrentScore=InitialScore=0;
1225 	ObjectsOwned=0;
1226 	ProductionDelay=ProductionUnit=0;
1227 	Cursor=ViewCursor=nullptr;
1228 	CursorFlash=30;
1229 	MessageStatus=0;
1230 	MessageBuf[0]=0;
1231 	Hostility.clear();
1232 	BaseMaterial.Default();
1233 	BaseProduction.Default();
1234 	Knowledge.Default();
1235 	FlashCom=0;
1236 }
1237 
ActivateMenuTeamSelection(bool fFromMain)1238 bool C4Player::ActivateMenuTeamSelection(bool fFromMain)
1239 {
1240 	// Menu symbol/init
1241 	bool fSwitch = !(Status==PS_TeamSelection);
1242 	Menu.InitRefSym(C4GUI::Icon::GetIconFacet(C4GUI::Ico_Team),LoadResStr("IDS_MSG_SELTEAM"),Number, C4MN_Extra_None, 0, fSwitch ? C4MN_TeamSwitch : C4MN_TeamSelection);
1243 	Menu.SetAlignment(fSwitch ? C4MN_Align_Left | C4MN_Align_Bottom : 0);
1244 	Menu.Refill();
1245 	// Go back to options menu on close
1246 	if (fFromMain) Menu.SetCloseCommand("ActivateMenu:Main");
1247 	return true;
1248 }
1249 
DoTeamSelection(int32_t idTeam)1250 void C4Player::DoTeamSelection(int32_t idTeam)
1251 {
1252 	// stop team selection. This might close the menu forever if the control gets lost
1253 	// let's hope it doesn't!
1254 	Status = PS_TeamSelectionPending;
1255 	::Control.DoInput(CID_PlrAction, C4ControlPlayerAction::InitScenarioPlayer(this, idTeam), CDT_Queue);
1256 }
1257 
DenumeratePointers()1258 void C4Player::DenumeratePointers()
1259 {
1260 	// Crew
1261 	Crew.DenumeratePointers();
1262 	// Cursor
1263 	Cursor.DenumeratePointers();
1264 	// ViewCursor
1265 	ViewCursor.DenumeratePointers();
1266 	// messageboard-queries
1267 	for (C4MessageBoardQuery *pCheck = pMsgBoardQuery; pCheck; pCheck = pCheck->pNext)
1268 		pCheck->CallbackObj.DenumeratePointers();
1269 }
1270 
RemoveCrewObjects()1271 void C4Player::RemoveCrewObjects()
1272 {
1273 	C4Object *pCrew;
1274 
1275 	// Remove all crew objects
1276 	while ((pCrew = Crew.GetObject())) pCrew->AssignRemoval(true);
1277 }
1278 
FindNewOwner() const1279 int32_t C4Player::FindNewOwner() const
1280 {
1281 	int32_t iNewOwner = NO_OWNER;
1282 	C4Team *pTeam;
1283 	if (Team) if ((pTeam = Game.Teams.GetTeamByID(Team)))
1284 	{
1285 		for (int32_t i=0; i<pTeam->GetPlayerCount(); ++i)
1286 		{
1287 			int32_t iPlrID = pTeam->GetIndexedPlayer(i);
1288 			if (iPlrID && iPlrID != ID)
1289 			{
1290 				C4PlayerInfo *pPlrInfo = Game.PlayerInfos.GetPlayerInfoByID(iPlrID);
1291 				if (pPlrInfo) if (pPlrInfo->IsJoined())
1292 				{
1293 					// this looks like a good new owner
1294 					iNewOwner = pPlrInfo->GetInGameNumber();
1295 					break;
1296 				}
1297 			}
1298 		}
1299 	}
1300 	// if noone from the same team was found, try to find another non-hostile player
1301 	// (necessary for cooperative rounds without teams)
1302 	if (iNewOwner == NO_OWNER)
1303 		for (C4Player *pOtherPlr = ::Players.First; pOtherPlr; pOtherPlr = pOtherPlr->Next)
1304 			if (pOtherPlr != this) if (!pOtherPlr->Eliminated)
1305 					if (!::Players.Hostile(pOtherPlr->Number, Number))
1306 						iNewOwner = pOtherPlr->Number;
1307 
1308 	return iNewOwner;
1309 }
1310 
NotifyOwnedObjects()1311 void C4Player::NotifyOwnedObjects()
1312 {
1313 	int32_t iNewOwner = FindNewOwner();
1314 	// notify objects in all object lists
1315 	for (C4ObjectList *pList = &::Objects; pList; pList = ((pList == &::Objects) ? &::Objects.InactiveObjects : nullptr))
1316 	{
1317 		for (C4Object *cobj : *pList)
1318 		{
1319 			if (cobj->Status && cobj->Owner == Number)
1320 			{
1321 				C4AulFunc *pFn = cobj->GetFunc(PSF_OnOwnerRemoved);
1322 				if (pFn)
1323 				{
1324 					C4AulParSet pars(iNewOwner);
1325 					pFn->Exec(cobj, &pars);
1326 				}
1327 				else
1328 				{
1329 					// crew members: Those are removed later (AFTER the player has been removed, for backwards compatiblity with relaunch scripting)
1330 					if (Crew.IsContained(cobj))
1331 						continue;
1332 					// Regular objects: Try to find a new, suitable owner from the same team
1333 					// Ignore StaticBack, because this would not be compatible with many internal objects such as team account
1334 					if ((cobj->Category & C4D_StaticBack) == 0)
1335 						cobj->SetOwner(iNewOwner);
1336 				}
1337 			}
1338 		}
1339 	}
1340 }
1341 
DoScore(int32_t iChange)1342 bool C4Player::DoScore(int32_t iChange)
1343 {
1344 	CurrentScore = Clamp<int32_t>( CurrentScore+iChange, -100000, 100000 );
1345 	return true;
1346 }
1347 
SetCursor(C4Object * pObj,bool fSelectArrow)1348 void C4Player::SetCursor(C4Object *pObj, bool fSelectArrow)
1349 {
1350 	// check disabled
1351 	if (pObj) if (pObj->CrewDisabled) return;
1352 	bool fChanged = pObj != Cursor;
1353 	C4Object *pPrev = Cursor;
1354 	// Set cursor
1355 	Cursor=pObj;
1356 	// unselect previous
1357 	if (pPrev && fChanged) pPrev->UnSelect();
1358 	// Select object
1359 	if (fChanged && Cursor) { Cursor->DoSelect(); }
1360 	// View flash
1361 	if (fSelectArrow) CursorFlash=30;
1362 }
1363 
ScrollView(float iX,float iY,float ViewWdt,float ViewHgt)1364 void C4Player::ScrollView(float iX, float iY, float ViewWdt, float ViewHgt)
1365 {
1366 	if (ViewLock) return;
1367 	SetViewMode(C4PVM_Scrolling);
1368 	float ViewportScrollBorder = Application.isEditor ? 0 : C4ViewportScrollBorder;
1369 	ViewX = Clamp<C4Real>( ViewX+ftofix(iX), ftofix(ViewWdt/2.0f-ViewportScrollBorder), ftofix(::Landscape.GetWidth()+ViewportScrollBorder-ViewWdt/2.0f) );
1370 	ViewY = Clamp<C4Real>( ViewY+ftofix(iY), ftofix(ViewHgt/2.0f-ViewportScrollBorder), ftofix(::Landscape.GetHeight()+ViewportScrollBorder-ViewHgt/2.0f) );
1371 }
1372 
ClearControl()1373 void C4Player::ClearControl()
1374 {
1375 	// Mark any control set as unused
1376 	Control.Clear();
1377 	// Reset control
1378 	LocalControl = false;
1379 	ControlSetName.Clear();
1380 	ControlSet=nullptr;
1381 	MouseControl = false;
1382 	if (pGamepad)
1383 	{
1384 		pGamepad->SetPlayer(NO_OWNER);
1385 		pGamepad.reset();
1386 	}
1387 	// no controls issued yet
1388 	ControlCount = ActionCount = 0;
1389 	LastControlType = PCID_None;
1390 	LastControlID = 0;
1391 }
1392 
InitControl()1393 void C4Player::InitControl()
1394 {
1395 	// Check local control
1396 	if (AtClient == ::Control.ClientID())
1397 		if (!GetInfo() || GetInfo()->GetType() == C4PT_User)
1398 			if (!::Control.isReplay())
1399 				LocalControl=true;
1400 	// needs to init control for local players only
1401 	if (LocalControl)
1402 	{
1403 		// Preferred control
1404 		ControlSetName = PrefControl;
1405 		ControlSet = Game.PlayerControlUserAssignmentSets.GetSetByName(ControlSetName.getData());
1406 		// control set unassigned/not known? fallback to some default then (=first defined control set)
1407 		if (!ControlSet) ControlSet = Game.PlayerControlUserAssignmentSets.GetDefaultSet();
1408 		// gamepad control safety (assuming the default control set is not using gamepad)
1409 		if (ControlSet && ControlSet->HasGamepad() && !Config.General.GamepadEnabled)
1410 		{
1411 			ControlSet = Game.PlayerControlUserAssignmentSets.GetDefaultSet();
1412 		}
1413 		// Choose next while control taken
1414 		// TODO
1415 		// init gamepad
1416 		if (ControlSet && ControlSet->HasGamepad())
1417 		{
1418 			if (!FindGamepad())
1419 			{
1420 				LogF("No gamepad available for %s, please plug one in!", Name.getData());
1421 				::Game.Pause();
1422 			}
1423 		}
1424 		// Mouse
1425 		if (ControlSet && ControlSet->HasMouse() && PrefMouse)
1426 			if (!::Players.MouseControlTaken())
1427 				MouseControl=true;
1428 		// Some controls such as gamepad control need special synced GUI elements
1429 		// Do a script callback for selected control
1430 		::Control.DoInput(CID_PlrAction, C4ControlPlayerAction::InitPlayerControl(this, ControlSet), CDT_Queue);
1431 	}
1432 	// clear old control method and register new
1433 	Control.RegisterKeyset(Number, ControlSet);
1434 }
1435 
FindGamepad()1436 bool C4Player::FindGamepad()
1437 {
1438 	auto newPad = Application.pGamePadControl->GetAvailableGamePad();
1439 	if (!newPad) return false;
1440 	newPad->SetPlayer(ID);
1441 	// Release the old gamepad.
1442 	if (pGamepad) pGamepad->SetPlayer(NO_OWNER);
1443 	pGamepad = newPad;
1444 	LogF("%s: Using gamepad #%d.", Name.getData(), pGamepad->GetID());
1445 	return true;
1446 }
1447 
1448 int igOffX, igOffY;
1449 
VisibilityCheck(int iVis,int sx,int sy,int cx,int cy)1450 int VisibilityCheck(int iVis, int sx, int sy, int cx, int cy)
1451 {
1452 	sx -= igOffX; sy -= igOffY; cx -= igOffX; cy -= igOffY;
1453 	int st = std::max(1, std::max(Abs(sx - cx), Abs(sy - cy)));
1454 	for (int i = 0; i <= st; i++)
1455 	{
1456 		int x = (sx * (st - i) + cx * i) / st, y = (sy * (st - i) + cy * i) / st;
1457 		if (GBackSolid(x, y))
1458 		{
1459 			if ((iVis -= 2) <= 0)
1460 				return 0;
1461 		}
1462 	}
1463 	return iVis;
1464 }
1465 
CloseMenu()1466 void C4Player::CloseMenu()
1467 {
1468 	// cancel all player menus
1469 	Menu.Close(false);
1470 }
1471 
Eliminate()1472 void C4Player::Eliminate()
1473 {
1474 	if (Eliminated) return;
1475 	Eliminated=true;
1476 	RetireDelay=C4RetireDelay;
1477 	StartSoundEffect("UI::Eliminated");
1478 	Log(FormatString(LoadResStr("IDS_PRC_PLRELIMINATED"),GetName()).getData());
1479 
1480 	// Early client deactivation check
1481 	if (::Control.isCtrlHost() && AtClient > C4ClientIDHost && !::Application.isEditor)
1482 	{
1483 		// Check: Any player left at this client?
1484 		C4Player *pPlr = nullptr;
1485 		for (int i = 0; (pPlr = ::Players.GetAtClient(AtClient, i)); i++)
1486 			if (!pPlr->Eliminated)
1487 				break;
1488 		// If not, deactivate the client
1489 		if (!pPlr)
1490 			::Control.DoInput(CID_ClientUpdate,
1491 			                  new C4ControlClientUpdate(AtClient, CUT_Activate, false),
1492 			                  CDT_Sync);
1493 	}
1494 }
1495 
ActiveCrewCount()1496 int32_t C4Player::ActiveCrewCount()
1497 {
1498 	// get number of objects in crew that is not disabled
1499 	int32_t iNum=0;
1500 	for (C4Object *cObj : Crew)
1501 		if (cObj)
1502 			if (!cObj->CrewDisabled)
1503 				++iNum;
1504 	// return it
1505 	return iNum;
1506 }
1507 
GetSelectedCrewCount()1508 int32_t C4Player::GetSelectedCrewCount()
1509 {
1510 	if (Cursor && !Cursor->CrewDisabled)
1511 		return 1;
1512 	return 0;
1513 }
1514 
EvaluateLeague(bool fDisconnected,bool fWon)1515 void C4Player::EvaluateLeague(bool fDisconnected, bool fWon)
1516 {
1517 	// already evaluated?
1518 	if (LeagueEvaluated) return; LeagueEvaluated=true;
1519 	// set fate
1520 	C4PlayerInfo *pInfo = GetInfo();
1521 	if (pInfo)
1522 	{
1523 		if (fDisconnected)
1524 			pInfo->SetDisconnected();
1525 		if (fWon)
1526 			pInfo->SetWinner();
1527 	}
1528 }
1529 
LocalSync()1530 bool C4Player::LocalSync()
1531 {
1532 	// local sync not necessary for script players
1533 	if (GetType() == C4PT_Script) return true;
1534 	// evaluate total playing time
1535 	TotalPlayingTime+=Game.Time-GameJoinTime;
1536 	GameJoinTime = Game.Time;
1537 	// evaluate total playing time of all the crew
1538 	for (C4ObjectInfo *pInf = CrewInfoList.GetFirst(); pInf; pInf=pInf->Next)
1539 		if (pInf->InAction)
1540 		{
1541 			pInf->TotalPlayingTime+=(Game.Time-pInf->InActionTime);
1542 			pInf->InActionTime = Game.Time;
1543 		}
1544 	// save player
1545 	if (!Save())
1546 		return false;
1547 	// done, success
1548 	return true;
1549 }
1550 
GetInfo()1551 C4PlayerInfo *C4Player::GetInfo()
1552 {
1553 	return Game.PlayerInfos.GetPlayerInfoByID(ID);
1554 }
1555 
SetObjectCrewStatus(C4Object * pCrew,bool fNewStatus)1556 bool C4Player::SetObjectCrewStatus(C4Object *pCrew, bool fNewStatus)
1557 {
1558 	// either add...
1559 	if (fNewStatus)
1560 	{
1561 		// is in crew already?
1562 		if (Crew.IsContained(pCrew)) return true;
1563 		return MakeCrewMember(pCrew, false);
1564 	}
1565 	else
1566 	{
1567 		// already outside?
1568 		if (!Crew.IsContained(pCrew)) return true;
1569 		// ...or remove
1570 		Crew.Remove(pCrew);
1571 		C4AulParSet parset(Number);
1572 		pCrew->Call(PSF_OnRemoveCrew, &parset);
1573 		// remove info, if assigned to this player
1574 		// theoretically, info objects could remain when the player is deleted
1575 		// but then they would be reassigned to the player crew when loaded in a savegame
1576 		//  by the crew-assignment code kept for backwards compatibility with pre-4.95.2-savegames
1577 		if (pCrew->Info && CrewInfoList.IsElement(pCrew->Info))
1578 		{
1579 			pCrew->Info->Retire();
1580 			pCrew->Info = nullptr;
1581 		}
1582 	}
1583 	// done, success
1584 	return true;
1585 }
1586 
CreateGraphs()1587 void C4Player::CreateGraphs()
1588 {
1589 	// del prev
1590 	ClearGraphs();
1591 	// create graphs
1592 	if (Game.pNetworkStatistics)
1593 	{
1594 		DWORD dwGraphClr = ColorDw;
1595 		C4PlayerInfo *pInfo;
1596 		if (ID && (pInfo = Game.PlayerInfos.GetPlayerInfoByID(ID)))
1597 		{
1598 			// set color by player info class
1599 			dwGraphClr = pInfo->GetColor();
1600 		}
1601 		C4GUI::MakeColorReadableOnBlack(dwGraphClr); dwGraphClr &= 0xffffff;
1602 		pstatControls = new C4TableGraph(C4TableGraph::DefaultBlockLength * 20, Game.pNetworkStatistics->ControlCounter);
1603 		pstatControls->SetColorDw(dwGraphClr);
1604 		pstatControls->SetTitle(GetName());
1605 		pstatActions = new C4TableGraph(C4TableGraph::DefaultBlockLength * 20, Game.pNetworkStatistics->ControlCounter);
1606 		pstatActions->SetColorDw(dwGraphClr);
1607 		pstatActions->SetTitle(GetName());
1608 		// register into
1609 		Game.pNetworkStatistics->statControls.AddGraph(pstatControls);
1610 		Game.pNetworkStatistics->statActions.AddGraph(pstatActions);
1611 	}
1612 }
1613 
ClearGraphs()1614 void C4Player::ClearGraphs()
1615 {
1616 	// del all assigned graphs
1617 	if (pstatControls)
1618 	{
1619 		if (Game.pNetworkStatistics) Game.pNetworkStatistics->statControls.RemoveGraph(pstatControls);
1620 		delete pstatControls;
1621 		pstatControls = nullptr;
1622 	}
1623 	if (pstatActions)
1624 	{
1625 		if (Game.pNetworkStatistics) Game.pNetworkStatistics->statActions.RemoveGraph(pstatActions);
1626 		delete pstatActions;
1627 		pstatActions = nullptr;
1628 	}
1629 }
1630 
CountControl(ControlType eType,int32_t iID,int32_t iCntAdd)1631 void C4Player::CountControl(ControlType eType, int32_t iID, int32_t iCntAdd)
1632 {
1633 	// count it
1634 	ControlCount += iCntAdd;
1635 	// catch doubles
1636 	if (eType == LastControlType && iID == LastControlID) return;
1637 	// no double: count as action
1638 	LastControlType = eType;
1639 	LastControlID = iID;
1640 	ActionCount += iCntAdd;
1641 	// and give experience
1642 	if (Cursor && Cursor->Info)
1643 	{
1644 		if (Cursor->Info)
1645 		{
1646 			Cursor->Info->ControlCount++; if ((Cursor->Info->ControlCount%5) == 0) Cursor->DoExperience(+1);
1647 		}
1648 	}
1649 }
1650 
ExecMsgBoardQueries()1651 void C4Player::ExecMsgBoardQueries()
1652 {
1653 	// already active?
1654 	if (::MessageInput.IsTypeIn()) return;
1655 	// find an un-evaluated query
1656 	C4MessageBoardQuery *pCheck = pMsgBoardQuery;
1657 		while (pCheck) if (!pCheck->fAnswered) break; else pCheck = pCheck->pNext;
1658 	if (!pCheck) return;
1659 	// open it
1660 	::MessageInput.StartTypeIn(true, pCheck->CallbackObj, pCheck->fIsUppercase, false, Number, pCheck->sInputQuery);
1661 }
1662 
CallMessageBoard(C4Object * pForObj,const StdStrBuf & sQueryString,bool fIsUppercase)1663 void C4Player::CallMessageBoard(C4Object *pForObj, const StdStrBuf &sQueryString, bool fIsUppercase)
1664 {
1665 	// remove any previous query for the same object
1666 	RemoveMessageBoardQuery(pForObj);
1667 	// sort new query to end of list
1668 	C4MessageBoardQuery **ppTarget = &pMsgBoardQuery;
1669 	while (*ppTarget) ppTarget = &((*ppTarget)->pNext);
1670 	*ppTarget = new C4MessageBoardQuery(pForObj, sQueryString, fIsUppercase);
1671 }
1672 
RemoveMessageBoardQuery(C4Object * pForObj)1673 bool C4Player::RemoveMessageBoardQuery(C4Object *pForObj)
1674 {
1675 	// get matching query
1676 	C4MessageBoardQuery **ppCheck = &pMsgBoardQuery, *pFound;
1677 		while (*ppCheck) if ((*ppCheck)->CallbackObj == pForObj) break; else ppCheck = &((*ppCheck)->pNext);
1678 	pFound = *ppCheck;
1679 	if (!pFound) return false;
1680 	// remove it
1681 	*ppCheck = (*ppCheck)->pNext;
1682 	delete pFound;
1683 	return true;
1684 }
1685 
MarkMessageBoardQueryAnswered(C4Object * pForObj)1686 bool C4Player::MarkMessageBoardQueryAnswered(C4Object *pForObj)
1687 {
1688 	// get matching query
1689 	C4MessageBoardQuery *pCheck = pMsgBoardQuery;
1690 		while (pCheck) if (pCheck->CallbackObj == pForObj && !pCheck->fAnswered) break; else pCheck = pCheck->pNext;
1691 	if (!pCheck) return false;
1692 	// mark it
1693 	pCheck->fAnswered = true;
1694 	return true;
1695 }
1696 
HasMessageBoardQuery()1697 bool C4Player::HasMessageBoardQuery()
1698 {
1699 	// return whether any object has a messageboard-query
1700 	return !!pMsgBoardQuery;
1701 }
1702 
OnTeamSelectionFailed()1703 void C4Player::OnTeamSelectionFailed()
1704 {
1705 	// looks like a selected team was not available: Go back to team selection if this is not a mislead call
1706 	if (Status == PS_TeamSelectionPending)
1707 		Status = PS_TeamSelection;
1708 }
1709 
SetPlayerColor(uint32_t dwNewClr)1710 void C4Player::SetPlayerColor(uint32_t dwNewClr)
1711 {
1712 	// no change?
1713 	if (dwNewClr == ColorDw) return;
1714 	// reflect change in all active, player-owned objects
1715 	// this can never catch everything (thinking of overlays, etc.); scenarios that allow team changes should take care of the rest
1716 	uint32_t dwOldClr = ColorDw;
1717 	ColorDw = dwNewClr;
1718 	for (C4Object *pObj : Objects)
1719 		if (pObj && pObj->Status && pObj->Owner == Number)
1720 		{
1721 			if ((pObj->Color & 0xffffff) == (dwOldClr & 0xffffff))
1722 				pObj->Color = (pObj->Color & 0xff000000u) | (dwNewClr & 0xffffff);
1723 		}
1724 }
1725 
GetType() const1726 C4PlayerType C4Player::GetType() const
1727 {
1728 	// type by info
1729 	C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(ID);
1730 	if (pInfo) return pInfo->GetType(); else { assert(false); return C4PT_User; }
1731 }
1732 
IsInvisible() const1733 bool C4Player::IsInvisible() const
1734 {
1735 	// invisible by info
1736 	C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(ID);
1737 	if (pInfo) return pInfo->IsInvisible(); else { assert(false); return false; }
1738 }
1739 
ToggleMouseControl()1740 void C4Player::ToggleMouseControl()
1741 {
1742 	// Activate mouse control if it's available
1743 	if (!MouseControl && !::Players.MouseControlTaken())
1744 	{
1745 		::MouseControl.Init(Number);
1746 		MouseControl=true;
1747 	}
1748 	// Deactivate mouse control
1749 	else if (MouseControl)
1750 	{
1751 		::MouseControl.Clear();
1752 		::MouseControl.Default();
1753 		MouseControl = 0;
1754 		// Scrolling isn't possible any more
1755 		if (ViewMode == C4PVM_Scrolling)
1756 			SetViewMode(C4PVM_Cursor);
1757 	}
1758 }
1759 
ActivateMenuMain()1760 bool C4Player::ActivateMenuMain()
1761 {
1762 	// Not during game over dialog
1763 	if (C4GameOverDlg::IsShown()) return false;
1764 	// Open menu
1765 	return !!Menu.ActivateMain(Number);
1766 }
1767 
CompileFunc(StdCompiler * pComp)1768 void C4Player::HostilitySet::CompileFunc(StdCompiler *pComp)
1769 {
1770 	int entries = size();
1771 	if (pComp->isDeserializer())
1772 	{
1773 		clear();
1774 		pComp->Value(entries);
1775 		while (entries--)
1776 		{
1777 			int number;
1778 			pComp->Value(number);
1779 			assert(::Players.Valid(number));
1780 			C4Player *plr = ::Players.Get(number);
1781 			if (plr)
1782 				insert(plr);
1783 		}
1784 	}
1785 	else
1786 	{
1787 		pComp->Value(entries);
1788 		for (auto it : *this)
1789 		{
1790 			int32_t num = it->Number;
1791 			pComp->Value(num); // Can't use (*it)->Number directly because StdCompiler is dumb about constness
1792 		}
1793 	}
1794 }
1795 
SetZoomByViewRange(int32_t range_wdt,int32_t range_hgt,bool direct,bool no_increase,bool no_decrease)1796 void C4Player::SetZoomByViewRange(int32_t range_wdt, int32_t range_hgt, bool direct, bool no_increase, bool no_decrease)
1797 {
1798 	AdjustZoomParameter(&ZoomWdt, range_wdt, no_increase, no_decrease);
1799 	AdjustZoomParameter(&ZoomHgt, range_hgt, no_increase, no_decrease);
1800 	ZoomToViewports(direct, no_decrease, no_increase); // inc/dec swapped for zoom, because it's inversely proportional to range
1801 }
1802 
SetMinZoomByViewRange(int32_t range_wdt,int32_t range_hgt,bool no_increase,bool no_decrease)1803 void C4Player::SetMinZoomByViewRange(int32_t range_wdt, int32_t range_hgt, bool no_increase, bool no_decrease)
1804 {
1805 	AdjustZoomParameter(&ZoomLimitMinWdt, range_wdt, no_increase, no_decrease);
1806 	AdjustZoomParameter(&ZoomLimitMinHgt, range_hgt, no_increase, no_decrease);
1807 	ZoomLimitsToViewports();
1808 }
1809 
SetMaxZoomByViewRange(int32_t range_wdt,int32_t range_hgt,bool no_increase,bool no_decrease)1810 void C4Player::SetMaxZoomByViewRange(int32_t range_wdt, int32_t range_hgt, bool no_increase, bool no_decrease)
1811 {
1812 	AdjustZoomParameter(&ZoomLimitMaxWdt, range_wdt, no_increase, no_decrease);
1813 	AdjustZoomParameter(&ZoomLimitMaxHgt, range_hgt, no_increase, no_decrease);
1814 	ZoomLimitsToViewports();
1815 }
1816 
SetZoom(C4Real zoom,bool direct,bool no_increase,bool no_decrease)1817 void C4Player::SetZoom(C4Real zoom, bool direct, bool no_increase, bool no_decrease)
1818 {
1819 	AdjustZoomParameter(&ZoomVal, zoom, no_increase, no_decrease);
1820 	ZoomToViewports(direct, no_increase, no_decrease);
1821 }
1822 
SetMinZoom(C4Real zoom,bool no_increase,bool no_decrease)1823 void C4Player::SetMinZoom(C4Real zoom, bool no_increase, bool no_decrease)
1824 {
1825 	AdjustZoomParameter(&ZoomLimitMinVal, zoom, no_increase, no_decrease);
1826 	ZoomLimitsToViewports();
1827 }
1828 
SetMaxZoom(C4Real zoom,bool no_increase,bool no_decrease)1829 void C4Player::SetMaxZoom(C4Real zoom, bool no_increase, bool no_decrease)
1830 {
1831 	AdjustZoomParameter(&ZoomLimitMaxVal, zoom, no_increase, no_decrease);
1832 	ZoomLimitsToViewports();
1833 }
1834 
ZoomToViewports(bool direct,bool no_increase,bool no_decrease)1835 void C4Player::ZoomToViewports(bool direct, bool no_increase, bool no_decrease)
1836 {
1837 	C4Viewport *vp = nullptr;
1838 	while((vp = ::Viewports.GetViewport(Number, vp)) != nullptr)
1839 		ZoomToViewport(vp, direct, no_increase, no_decrease);
1840 }
1841 
ZoomToViewport(C4Viewport * vp,bool direct,bool no_increase,bool no_decrease)1842 void C4Player::ZoomToViewport(C4Viewport* vp, bool direct, bool no_increase, bool no_decrease)
1843 {
1844 	float new_zoom = ZoomVal ? fixtof(ZoomVal) : vp->GetZoomByViewRange((ZoomWdt || ZoomHgt) ? ZoomWdt : C4VP_DefViewRangeX,ZoomHgt);
1845 	float old_zoom = vp->GetZoomTarget();
1846 	if (new_zoom > old_zoom && no_increase) return;
1847 	if (new_zoom < old_zoom && no_decrease) return;
1848 	vp->SetZoom(new_zoom, direct);
1849 }
1850 
ZoomLimitsToViewports()1851 void C4Player::ZoomLimitsToViewports()
1852 {
1853 	C4Viewport *vp = nullptr;
1854 	while((vp = ::Viewports.GetViewport(Number, vp)) != nullptr)
1855 		ZoomLimitsToViewport(vp);
1856 }
1857 
ZoomLimitsToViewport(C4Viewport * vp)1858 void C4Player::ZoomLimitsToViewport(C4Viewport* vp)
1859 {
1860 	float zoom_max = ZoomLimitMaxVal ? fixtof(ZoomLimitMaxVal) : vp->GetZoomByViewRange((ZoomLimitMinWdt || ZoomLimitMinHgt) ? ZoomLimitMinWdt : C4VP_DefMinViewRangeX,ZoomLimitMinHgt);
1861 	float zoom_min = ZoomLimitMinVal ? fixtof(ZoomLimitMinVal) : vp->GetZoomByViewRange((ZoomLimitMaxWdt || ZoomLimitMaxHgt) ? ZoomLimitMaxWdt : C4VP_DefMaxViewRangeX,ZoomLimitMaxHgt);
1862 	vp->SetZoomLimits(zoom_min, zoom_max);
1863 }
1864 
AdjustZoomParameter(int32_t * range_par,int32_t new_val,bool no_increase,bool no_decrease)1865 bool C4Player::AdjustZoomParameter(int32_t *range_par, int32_t new_val, bool no_increase, bool no_decrease)
1866 {
1867 	// helper function: Adjust *range_par to new_val if increase/decrease not forbidden
1868 	if (new_val < *range_par)
1869 	{
1870 		if (!no_decrease) *range_par = new_val;
1871 		return !no_decrease;
1872 	}
1873 	else if(new_val > *range_par)
1874 	{
1875 		if (!no_increase) *range_par = new_val;
1876 		return !no_increase;
1877 	}
1878 	return true;
1879 }
1880 
AdjustZoomParameter(C4Real * zoom_par,C4Real new_val,bool no_increase,bool no_decrease)1881 bool C4Player::AdjustZoomParameter(C4Real *zoom_par, C4Real new_val, bool no_increase, bool no_decrease)
1882 {
1883 	// helper function: Adjust *zoom_par to new_val if increase/decrease not forbidden
1884 	if (new_val < *zoom_par)
1885 	{
1886 		if (!no_decrease) *zoom_par = new_val;
1887 		return !no_decrease;
1888 	}
1889 	else if(new_val > *zoom_par)
1890 	{
1891 		if (!no_increase) *zoom_par = new_val;
1892 		return !no_increase;
1893 	}
1894 	return true;
1895 }
1896 
SetViewLocked(bool to_val)1897 void C4Player::SetViewLocked(bool to_val)
1898 {
1899 	if ((ViewLock = to_val))
1900 	{
1901 		// view was locked - cancel any scrolling
1902 		if (ViewMode == C4PVM_Scrolling) SetViewMode(C4PVM_Cursor);
1903 	}
1904 }
1905 
GainScenarioAchievement(const char * achievement_id,int32_t value,const char * scen_name_override)1906 bool C4Player::GainScenarioAchievement(const char *achievement_id, int32_t value, const char *scen_name_override)
1907 {
1908 	// Determine full ID of achievement
1909 	if (!scen_name_override)
1910 	{
1911 		if (::Game.C4S.Head.Origin.getLength())
1912 			scen_name_override = ::Game.C4S.Head.Origin.getData();
1913 		else
1914 			scen_name_override = ::Game.ScenarioFilename;
1915 	}
1916 	StdStrBuf sAchvID = C4ScenarioParameters::AddFilename2ID(scen_name_override, achievement_id);
1917 	// Gain achievement iff it's an improvement
1918 	Achievements.SetValue(sAchvID.getData(), value, true);
1919 	return true;
1920 }
1921 
SetSoundModifier(C4PropList * new_modifier)1922 void C4Player::SetSoundModifier(C4PropList *new_modifier)
1923 {
1924 	// set modifier to be applied to all new sounds being played in a player's viewport
1925 	// update prop list parameter
1926 	C4SoundModifier *mod;
1927 	if (new_modifier)
1928 	{
1929 		SoundModifier.SetPropList(new_modifier);
1930 		mod = ::Application.SoundSystem.Modifiers.Get(new_modifier, true);
1931 	}
1932 	else
1933 	{
1934 		SoundModifier.Set0();
1935 		mod = nullptr;
1936 	}
1937 	// update in sound system
1938 	::Application.SoundSystem.Modifiers.SetGlobalModifier(mod, Number);
1939 }
1940