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