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 /* Control packets contain all player input in the message queue */
19
20 #include "C4Include.h"
21 #include "control/C4Control.h"
22
23 #include "control/C4GameControl.h"
24 #include "control/C4GameSave.h"
25 #include "control/C4RoundResults.h"
26 #include "editor/C4Console.h"
27 #include "game/C4GameScript.h"
28 #include "game/C4GraphicsSystem.h"
29 #include "gui/C4GameLobby.h"
30 #include "gui/C4GameMessage.h"
31 #include "gui/C4MessageInput.h"
32 #include "gui/C4ScriptGuiWindow.h"
33 #include "landscape/C4Landscape.h"
34 #include "landscape/C4MassMover.h"
35 #include "landscape/C4PXS.h"
36 #include "lib/C4Random.h"
37 #include "network/C4Network2Dialogs.h"
38 #include "object/C4Def.h"
39 #include "object/C4DefList.h"
40 #include "object/C4GameObjects.h"
41 #include "object/C4Object.h"
42 #include "player/C4Player.h"
43 #include "player/C4PlayerList.h"
44 #include "player/C4RankSystem.h"
45 #include "script/C4AulExec.h"
46
47 #ifndef NOAULDEBUG
48 #include "script/C4AulDebug.h"
49 #endif
50
51 // *** C4ControlPacket
C4ControlPacket()52 C4ControlPacket::C4ControlPacket()
53 : iByClient(::Control.ClientID())
54 {
55
56 }
57
58 C4ControlPacket::~C4ControlPacket() = default;
59
LocalControl() const60 bool C4ControlPacket::LocalControl() const
61 {
62 return iByClient == ::Control.ClientID();
63 }
64
SetByClient(int32_t inByClient)65 void C4ControlPacket::SetByClient(int32_t inByClient)
66 {
67 iByClient = inByClient;
68 }
69
CompileFunc(StdCompiler * pComp)70 void C4ControlPacket::CompileFunc(StdCompiler *pComp)
71 {
72 // Section must be set by caller
73 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iByClient), "ByClient", -1));
74 }
75
76 // *** C4Control
77
78 C4Control::C4Control() = default;
79
~C4Control()80 C4Control::~C4Control()
81 {
82 Clear();
83 }
84
Clear()85 void C4Control::Clear()
86 {
87 Pkts.Clear();
88 }
89
PreExecute() const90 bool C4Control::PreExecute() const
91 {
92 bool fReady = true;
93 for (C4IDPacket *pPkt = firstPkt(); pPkt; pPkt = nextPkt(pPkt))
94 {
95 // recheck packet type: Must be control
96 if (pPkt->getPktType() & CID_First)
97 {
98 C4ControlPacket *pCtrlPkt = static_cast<C4ControlPacket *>(pPkt->getPkt());
99 if (pCtrlPkt)
100 fReady &= pCtrlPkt->PreExecute();
101 }
102 else
103 {
104 LogF("C4Control::PreExecute: WARNING: Ignoring packet type %2x (not control.)", pPkt->getPktType());
105 }
106 }
107 return fReady;
108 }
109
Execute() const110 void C4Control::Execute() const
111 {
112 for (C4IDPacket *pPkt = firstPkt(); pPkt; pPkt = nextPkt(pPkt))
113 {
114 // recheck packet type: Must be control
115 if (pPkt->getPktType() & CID_First)
116 {
117 C4ControlPacket *pCtrlPkt = static_cast<C4ControlPacket *>(pPkt->getPkt());
118 if (pCtrlPkt)
119 pCtrlPkt->Execute();
120 }
121 else
122 {
123 LogF("C4Control::Execute: WARNING: Ignoring packet type %2x (not control.)", pPkt->getPktType());
124 }
125 }
126 }
127
PreRec(C4Record * pRecord) const128 void C4Control::PreRec(C4Record *pRecord) const
129 {
130 for (C4IDPacket *pPkt = firstPkt(); pPkt; pPkt = nextPkt(pPkt))
131 {
132 C4ControlPacket *pCtrlPkt = static_cast<C4ControlPacket *>(pPkt->getPkt());
133 if (pCtrlPkt)
134 pCtrlPkt->PreRec(pRecord);
135 }
136 }
137
CompileFunc(StdCompiler * pComp)138 void C4Control::CompileFunc(StdCompiler *pComp)
139 {
140 pComp->Value(Pkts);
141 }
142
143 // *** C4ControlSet
144
Execute() const145 void C4ControlSet::Execute() const
146 {
147 switch (eValType)
148 {
149 case C4CVT_None: break;
150
151 case C4CVT_ControlRate: // adjust control rate
152 // host only
153 if (iByClient != C4ClientIDHost) break;
154 // adjust control rate
155 ::Control.ControlRate += iData;
156 ::Control.ControlRate = Clamp<int32_t>(::Control.ControlRate, 1, C4MaxControlRate);
157 Game.Parameters.ControlRate = ::Control.ControlRate;
158 // write back adjusted control rate to network settings
159 if (::Control.isCtrlHost() && !::Control.isReplay() && ::Control.isNetwork())
160 Config.Network.ControlRate = ::Control.ControlRate;
161 // always show msg
162 ::GraphicsSystem.FlashMessage(FormatString(LoadResStr("IDS_NET_CONTROLRATE"),::Control.ControlRate,Game.FrameCounter).getData());
163 break;
164
165 case C4CVT_DisableDebug: // force debug mode disabled
166 {
167 if (Game.DebugMode)
168 {
169 Game.DebugMode=false;
170 ::GraphicsSystem.DeactivateDebugOutput();
171 }
172 // save flag, log
173 Game.Parameters.AllowDebug = false;
174 C4Client *client = ::Game.Clients.getClientByID(iByClient);
175 LogF("Debug mode forced disabled by %s", client ? client->getName() : "<unknown client>");
176 break;
177 }
178 break;
179
180 case C4CVT_MaxPlayer:
181 // host only
182 if (iByClient != C4ClientIDHost) break;
183 // not in league
184 if (Game.Parameters.isLeague())
185 {
186 Log("/set maxplayer disabled in league!");
187 C4GUI::GUISound("UI::Error");
188 break;
189 }
190 // set it
191 Game.Parameters.MaxPlayers = iData;
192 LogF("MaxPlayer = %d", (int)Game.Parameters.MaxPlayers);
193 break;
194
195 case C4CVT_TeamDistribution:
196 // host only
197 if (iByClient != C4ClientIDHost) break;
198 // set new value
199 Game.Teams.SetTeamDistribution(static_cast<C4TeamList::TeamDist>(iData));
200 break;
201
202 case C4CVT_TeamColors:
203 // host only
204 if (!HostControl()) break;
205 // set new value
206 Game.Teams.SetTeamColors(!!iData);
207 break;
208 }
209 }
210
CompileFunc(StdCompiler * pComp)211 void C4ControlSet::CompileFunc(StdCompiler *pComp)
212 {
213 pComp->Value(mkNamingAdapt(mkIntAdapt(eValType), "Type", C4CVT_None));
214 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iData), "Data", 0));
215 C4ControlPacket::CompileFunc(pComp);
216 }
217
218 // *** C4ControlScript
219
Execute() const220 void C4ControlScript::Execute() const
221 {
222 const char *szScript = Script.getData();
223 // user script: from host only
224 if ((iByClient != C4ClientIDHost) && !Console.Active) return;
225 // only allow scripts when debug mode is not forbidden
226 if (!Game.Parameters.AllowDebug) return;
227
228 // execute
229 C4PropList *pPropList = nullptr;
230 if (iTargetObj == SCOPE_Console)
231 pPropList = ::GameScript.ScenPropList.getPropList();
232 else if (iTargetObj == SCOPE_Global)
233 pPropList = ::ScriptEngine.GetPropList();
234 else if (!(pPropList = ::Objects.SafeObjectPointer(iTargetObj)))
235 // default: Fallback to global context
236 pPropList = ::ScriptEngine.GetPropList();
237 C4Value rVal(AulExec.DirectExec(pPropList, szScript, "console script", false, fUseVarsFromCallerContext ? AulExec.GetContext(AulExec.GetContextDepth()-1) : nullptr));
238 #ifndef NOAULDEBUG
239 C4AulDebug* pDebug;
240 if ( (pDebug = C4AulDebug::GetDebugger()) )
241 {
242 pDebug->ControlScriptEvaluated(szScript, rVal.GetDataString().getData());
243 }
244 #endif
245 // show messages
246 // print script
247 LogF("-> %s::%s", pPropList->GetName(), szScript);
248 // print result
249 bool is_local_script = true;
250 if (!LocalControl())
251 {
252 C4Network2Client *pClient = nullptr;
253 if (::Network.isEnabled())
254 {
255 pClient = ::Network.Clients.GetClientByID(iByClient);
256 if (pClient != ::Network.Clients.GetLocal())
257 {
258 is_local_script = false;
259 }
260 }
261 if (pClient)
262 LogF(" = %s (by %s)", rVal.GetDataString().getData(), pClient->getName());
263 else
264 LogF(" = %s (by client %d)", rVal.GetDataString().getData(), iByClient);
265 }
266 else
267 LogF(" = %s", rVal.GetDataString().getData());
268 // Editor update
269 if (::Console.Active)
270 {
271 C4Object *returned_object = rVal.getObj();
272 if (editor_select_result && is_local_script && returned_object)
273 {
274 ::Console.EditCursor.ClearSelection(returned_object);
275 ::Console.EditCursor.AddToSelection(returned_object);
276 ::Console.EditCursor.OnSelectionChanged();
277 }
278 // Always: refresh property view after script command
279 ::Console.EditCursor.InvalidateSelection();
280 }
281 }
282
CompileFunc(StdCompiler * pComp)283 void C4ControlScript::CompileFunc(StdCompiler *pComp)
284 {
285 pComp->Value(mkNamingAdapt(iTargetObj, "TargetObj", -1));
286 pComp->Value(mkNamingAdapt(fUseVarsFromCallerContext, "UseVarsFromCallerContext", false));
287 pComp->Value(mkNamingAdapt(editor_select_result, "EditorSelectResult", false));
288 pComp->Value(mkNamingAdapt(Script, "Script", ""));
289 C4ControlPacket::CompileFunc(pComp);
290 }
291
292 // *** C4ControlMsgBoardReply
Execute() const293 void C4ControlMsgBoardReply::Execute() const
294 {
295 C4Object *target_object = ::Objects.SafeObjectPointer(target);
296 C4Player *target_player = ::Players.Get(player);
297
298 // remove query
299 if (!target_player) return;
300 if (!target_player->RemoveMessageBoardQuery(target_object)) return;
301
302 // execute callback if answer present
303 if (!reply) return;
304 C4AulParSet pars(C4VString(reply), player);
305 if (target_object)
306 target_object->Call(PSF_InputCallback, &pars);
307 else
308 ::GameScript.Call(PSF_InputCallback, &pars);
309 }
310
CompileFunc(StdCompiler * pComp)311 void C4ControlMsgBoardReply::CompileFunc(StdCompiler *pComp)
312 {
313 pComp->Value(mkNamingAdapt(target, "TargetObj", -1));
314 pComp->Value(mkNamingAdapt(player, "Player", NO_OWNER));
315 pComp->Value(mkNamingAdapt(reply, "Reply", nullptr));
316 C4ControlPacket::CompileFunc(pComp);
317 }
318
319 // *** C4ControlMsgBoardCmd
Execute() const320 void C4ControlMsgBoardCmd::Execute() const
321 {
322 // don't handle this if the game isn't actually running
323 if (!::Game.IsRunning) return;
324
325 // fetch command script
326 C4MessageBoardCommand *cmd = ::MessageInput.GetCommand(command.getData());
327 if (!cmd) return;
328 StdCopyStrBuf script(cmd->Script);
329
330 // interpolate parameters as required
331 script.Replace("%player%", FormatString("%d", player).getData());
332 if (parameter)
333 {
334 script.Replace("%d", FormatString("%d", std::atoi(parameter.getData())).getData());
335 StdCopyStrBuf escaped_param(parameter);
336 escaped_param.EscapeString();
337 script.Replace("%s", escaped_param.getData());
338 }
339
340 // Run script
341 C4Value rv(::AulExec.DirectExec(::ScriptEngine.GetPropList(), script.getData(), "message board command"));
342 #ifndef NOAULDEBUG
343 C4AulDebug* pDebug = C4AulDebug::GetDebugger();
344 if (pDebug)
345 pDebug->ControlScriptEvaluated(script.getData(), rv.GetDataString().getData());
346 #endif
347 }
348
CompileFunc(StdCompiler * pComp)349 void C4ControlMsgBoardCmd::CompileFunc(StdCompiler *pComp)
350 {
351 pComp->Value(mkNamingAdapt(player, "Player", NO_OWNER));
352 pComp->Value(mkNamingAdapt(command, "Command"));
353 pComp->Value(mkNamingAdapt(parameter, "Parameter"));
354 C4ControlPacket::CompileFunc(pComp);
355 }
356 // *** C4ControlPlayerSelect
357
C4ControlPlayerSelect(int32_t iPlr,const C4ObjectList & Objs,bool fIsAlt)358 C4ControlPlayerSelect::C4ControlPlayerSelect(int32_t iPlr, const C4ObjectList &Objs, bool fIsAlt)
359 : iPlr(iPlr), fIsAlt(fIsAlt), iObjCnt(Objs.ObjectCount())
360 {
361 pObjNrs = new int32_t[iObjCnt];
362 int32_t i = 0;
363 for (C4Object *obj : Objs)
364 pObjNrs[i++] = obj->Number;
365 assert(i == iObjCnt);
366 }
367
Execute() const368 void C4ControlPlayerSelect::Execute() const
369 {
370 // get player
371 C4Player *pPlr = ::Players.Get(iPlr);
372 if (!pPlr) return;
373
374 // Check object list
375 C4Object *pObj;
376 int32_t iControlChecksum = 0;
377 for (int32_t i = 0; i < iObjCnt; i++)
378 if ((pObj = ::Objects.SafeObjectPointer(pObjNrs[i])))
379 {
380 iControlChecksum += pObj->Number * (iControlChecksum+4787821);
381 // user defined object selection: callback to object
382 if (pObj->Category & C4D_MouseSelect)
383 {
384 if (fIsAlt)
385 pObj->Call(PSF_MouseSelectionAlt, &C4AulParSet(iPlr));
386 else
387 pObj->Call(PSF_MouseSelection, &C4AulParSet(iPlr));
388 }
389 }
390 // count
391 pPlr->CountControl(C4Player::PCID_Command, iControlChecksum);
392 }
393
CompileFunc(StdCompiler * pComp)394 void C4ControlPlayerSelect::CompileFunc(StdCompiler *pComp)
395 {
396 pComp->Value(mkNamingAdapt(iPlr, "Player", -1));
397 pComp->Value(mkNamingAdapt(fIsAlt, "IsAlt", false));
398 pComp->Value(mkNamingAdapt(iObjCnt, "ObjCnt", 0));
399 // Compile array
400 if (pComp->isDeserializer())
401 { delete[] pObjNrs; pObjNrs = new int32_t [iObjCnt]; }
402 pComp->Value(mkNamingAdapt(mkArrayAdapt(pObjNrs, iObjCnt), "Objs", 0));
403
404 C4ControlPacket::CompileFunc(pComp);
405 }
406
407
408 // *** C4ControlPlayerControl
409
Execute() const410 void C4ControlPlayerControl::Execute() const
411 {
412 C4PlayerControl *pTargetCtrl = nullptr;
413 if (iPlr == -1)
414 {
415 // neutral control packet: Execute in global control
416 }
417 else
418 {
419 // player-based control: Execute on control owned by player
420 C4Player *pPlr=::Players.Get(iPlr);
421 if (pPlr)
422 {
423 pTargetCtrl = &(pPlr->Control);
424 }
425 }
426 if (pTargetCtrl) pTargetCtrl->ExecuteControlPacket(this);
427 }
428
CompileFunc(StdCompiler * pComp)429 void C4ControlPlayerControl::ControlItem::CompileFunc(StdCompiler *pComp)
430 {
431 pComp->Value(iControl);
432 pComp->Separator();
433 pComp->Value(iTriggerMode);
434 }
435
CompileFunc(StdCompiler * pComp)436 void C4ControlPlayerControl::CompileFunc(StdCompiler *pComp)
437 {
438 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iPlr), "Player", -1));
439 pComp->Value(mkNamingAdapt(mkIntAdapt(state), "State", 0));
440 pComp->Value(mkNamingAdapt(ExtraData, "ExtraData", C4KeyEventData()));
441 pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(ControlItems), "Controls", ControlItemVec()));
442 C4ControlPacket::CompileFunc(pComp);
443 }
444
445 // *** C4ControlPlayerMouse
Hover(const C4Player * player,const C4Object * target,const C4Object * old_target,const C4Object * drag)446 C4ControlPlayerMouse *C4ControlPlayerMouse::Hover(const C4Player *player, const C4Object *target, const C4Object *old_target, const C4Object *drag)
447 {
448 assert(player != nullptr);
449 if (!player) return nullptr;
450
451 auto control = new C4ControlPlayerMouse();
452 control->action = CPM_Hover;
453 control->player = player->Number;
454 control->drag_obj = drag ? drag->Number : 0;
455 control->target_obj = target ? target->Number : 0;
456 control->old_obj = old_target ? old_target->Number : 0;
457 return control;
458 }
DragDrop(const C4Player * player,const C4Object * target,const C4Object * drag)459 C4ControlPlayerMouse *C4ControlPlayerMouse::DragDrop(const C4Player *player, const C4Object *target, const C4Object *drag)
460 {
461 assert(player != nullptr);
462 if (!player) return nullptr;
463
464 auto control = new C4ControlPlayerMouse();
465 control->action = CPM_Drop;
466 control->player = player->Number;
467 control->drag_obj = drag ? drag->Number : 0;
468 control->target_obj = target ? target->Number : 0;
469 return control;
470 }
471
Execute() const472 void C4ControlPlayerMouse::Execute() const
473 {
474 const char *callback_name = nullptr;
475 C4AulParSet pars(player);
476
477 switch (action)
478 {
479 case CPM_NoAction:
480 return;
481
482 case CPM_Hover:
483 // Mouse movement, object hover state changed
484 callback_name = PSF_MouseHover;
485 pars[1] = C4VObj(::Objects.SafeObjectPointer(old_obj));
486 pars[2] = C4VObj(::Objects.SafeObjectPointer(target_obj));
487 pars[3] = C4VObj(::Objects.SafeObjectPointer(drag_obj));
488 break;
489
490 case CPM_Drop:
491 // Drag/Drop operation
492 callback_name = PSF_MouseDragDrop;
493 pars[1] = C4VObj(::Objects.SafeObjectPointer(drag_obj));
494 pars[2] = C4VObj(::Objects.SafeObjectPointer(target_obj));
495 break;
496 }
497
498 // Do call
499 if (!callback_name) return;
500 ::ScriptEngine.Call(callback_name, &pars);
501 }
502
CompileFunc(StdCompiler * pComp)503 void C4ControlPlayerMouse::CompileFunc(StdCompiler *pComp)
504 {
505 pComp->Value(mkNamingAdapt(action, "Action"));
506 pComp->Value(mkNamingAdapt(player, "Player", NO_OWNER));
507 pComp->Value(mkNamingAdapt(target_obj, "TargetObj"));
508 pComp->Value(mkNamingAdapt(drag_obj, "DragObj"));
509 pComp->Value(mkNamingAdapt(old_obj, "OldObj"));
510 }
511
512 // *** C4ControlMenuCommand
513
C4ControlMenuCommand(int32_t actionID,int32_t player,int32_t menuID,int32_t subwindowID,C4Object * target,int32_t actionType)514 C4ControlMenuCommand::C4ControlMenuCommand(int32_t actionID, int32_t player, int32_t menuID, int32_t subwindowID, C4Object *target, int32_t actionType)
515 : actionID(actionID), player(player), menuID(menuID), subwindowID(subwindowID), target(target ? target->Number : 0), actionType(actionType)
516 {
517
518 }
519
Execute() const520 void C4ControlMenuCommand::Execute() const
521 {
522 // invalid action? The action needs to be in bounds!
523 if (actionType < 0 || actionType >= C4ScriptGuiWindowPropertyName::_lastProp)
524 {
525 // this could only come from a malicious attempt to crash the engine!
526 Log("Warning: invalid action type for C4ControlMenuCommand!");
527 return;
528 }
529 C4ScriptGuiWindow *menu = ::Game.ScriptGuiRoot->GetChildByID(menuID);
530 // menu was closed?
531 if (!menu) return;
532
533 C4Object *obj = target ? ::Objects.ObjectPointer(target) : nullptr;
534 // target has been removed in the meantime? abort now
535 if (target && !obj) return;
536
537 menu->ExecuteCommand(actionID, player, subwindowID, actionType, obj);
538 }
539
CompileFunc(StdCompiler * pComp)540 void C4ControlMenuCommand::CompileFunc(StdCompiler *pComp)
541 {
542 pComp->Value(mkNamingAdapt(mkIntPackAdapt(actionID), "ID", -1));
543 pComp->Value(mkNamingAdapt(mkIntPackAdapt(player), "Player", -1));
544 pComp->Value(mkNamingAdapt(mkIntPackAdapt(menuID), "Menu", 0));
545 pComp->Value(mkNamingAdapt(mkIntPackAdapt(subwindowID), "Window", 0));
546 pComp->Value(mkNamingAdapt(mkIntPackAdapt(actionType), "Action", 0));
547 pComp->Value(mkNamingAdapt(target, "Target", 0));
548 C4ControlPacket::CompileFunc(pComp);
549 }
550
551 // *** C4ControlPlayerAction
C4ControlPlayerAction(const C4Player * source)552 C4ControlPlayerAction::C4ControlPlayerAction(const C4Player *source)
553 : source(source ? source->Number : NO_OWNER), target(NO_OWNER)
554 {
555 }
556
Surrender(const C4Player * source)557 C4ControlPlayerAction *C4ControlPlayerAction::Surrender(const C4Player *source)
558 {
559 assert(source);
560 C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
561 control->action = CPA_Surrender;
562 return control;
563 }
Eliminate(const C4Player * source)564 C4ControlPlayerAction *C4ControlPlayerAction::Eliminate(const C4Player *source)
565 {
566 assert(source);
567 C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
568 control->action = CPA_Eliminate;
569 return control;
570 }
ActivateGoal(const C4Player * source,const C4Object * goal)571 C4ControlPlayerAction *C4ControlPlayerAction::ActivateGoal(const C4Player *source, const C4Object *goal)
572 {
573 assert(source);
574 assert(goal);
575 C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
576 control->action = CPA_ActivateGoal;
577 control->target = goal->Number;
578 return control;
579 }
ActivateGoalMenu(const C4Player * source)580 C4ControlPlayerAction *C4ControlPlayerAction::ActivateGoalMenu(const C4Player *source)
581 {
582 assert(source);
583 C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
584 control->action = CPA_ActivateGoalMenu;
585 return control;
586 }
SetHostility(const C4Player * source,const C4Player * target,bool hostile)587 C4ControlPlayerAction *C4ControlPlayerAction::SetHostility(const C4Player *source, const C4Player *target, bool hostile)
588 {
589 assert(source);
590 assert(target);
591 C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
592 control->action = CPA_SetHostility;
593 control->target = target ? target->Number : NO_OWNER;
594 control->param_int = hostile;
595 return control;
596 }
SetTeam(const C4Player * source,int32_t team)597 C4ControlPlayerAction *C4ControlPlayerAction::SetTeam(const C4Player *source, int32_t team)
598 {
599 assert(source);
600 C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
601 control->action = CPA_SetTeam;
602 control->target = team;
603 return control;
604 }
InitScenarioPlayer(const C4Player * source,int32_t team)605 C4ControlPlayerAction *C4ControlPlayerAction::InitScenarioPlayer(const C4Player *source, int32_t team)
606 {
607 assert(source);
608 C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
609 control->action = CPA_InitScenarioPlayer;
610 control->target = team;
611 return control;
612 }
InitPlayerControl(const C4Player * source,const C4PlayerControlAssignmentSet * ctrl_set)613 C4ControlPlayerAction *C4ControlPlayerAction::InitPlayerControl(const C4Player *source, const C4PlayerControlAssignmentSet *ctrl_set)
614 {
615 assert(source);
616 C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
617 control->action = CPA_InitPlayerControl;
618 if (ctrl_set)
619 {
620 control->param_str = ctrl_set->GetName();
621 if (ctrl_set->HasKeyboard())
622 control->param_int |= CPA_IPC_HasKeyboard;
623 if (ctrl_set->HasMouse())
624 control->param_int |= CPA_IPC_HasMouse;
625 if (ctrl_set->HasGamepad())
626 control->param_int |= CPA_IPC_HasGamepad;
627 }
628 return control;
629 }
630
Execute() const631 void C4ControlPlayerAction::Execute() const
632 {
633 // The originating player must exist
634 C4Player *source_player = ::Players.Get(source);
635 if (!source_player) return;
636
637 switch (action)
638 {
639 case CPA_Surrender:
640 source_player->Surrender();
641 break;
642
643 case CPA_Eliminate:
644 source_player->Eliminate();
645 break;
646
647 case CPA_ActivateGoal:
648 {
649 // Make sure the object actually exists
650 C4Object *goal = ::Objects.SafeObjectPointer(target);
651 if (!goal) return;
652 // Call it
653 C4AulParSet pars(source_player->Number);
654 goal->Call("Activate", &pars);
655 break;
656 }
657
658 case CPA_ActivateGoalMenu:
659 // open menu
660 source_player->Menu.ActivateGoals(source_player->Number, source_player->LocalControl && !::Control.isReplay());
661 break;
662
663 case CPA_SetHostility:
664 {
665 // Can only set hostility towards a player that exists
666 C4Player *target_player = ::Players.Get(target);
667 if (!target_player) return;
668
669 // Proxy the hostility change through C4Aul, in case a script wants to capture it
670 C4AulParSet pars(source_player->Number, target_player->Number, param_int != 0);
671 ::ScriptEngine.Call("SetHostility", &pars);
672 break;
673 }
674
675 case CPA_SetTeam:
676 {
677 // Make sure team switching is allowed in the first place
678 if (!::Game.Teams.IsTeamSwitchAllowed()) return;
679
680 // We can't change teams to one that doesn't exist
681 C4Team *team = ::Game.Teams.GetTeamByID(target);
682 if (!team && target != TEAMID_New) return;
683
684 // Proxy the team switch through C4Aul, in case a script wants to capture it
685 C4AulParSet pars(source_player->Number, target);
686 ::ScriptEngine.Call("SetPlayerTeam", &pars);
687 break;
688 }
689
690 case CPA_InitScenarioPlayer:
691 {
692 // Proxy the call through C4Aul, in case a script wants to capture it
693 C4AulParSet pars(source_player->Number, target);
694 ::ScriptEngine.Call("InitScenarioPlayer", &pars);
695 break;
696 }
697
698 case CPA_InitPlayerControl:
699 {
700 // Notify scripts about player control selection
701 const char *callback_name = PSF_InitializePlayerControl;
702
703 C4AulParSet pars(source_player->Number);
704 // If the player is using a control set, its name is stored in param_str
705 if (param_str)
706 {
707 pars[1] = C4VString(param_str);
708 pars[2] = C4VBool(CPA_IPC_HasKeyboard == (param_int & CPA_IPC_HasKeyboard));
709 pars[3] = C4VBool(CPA_IPC_HasMouse == (param_int & CPA_IPC_HasMouse));
710 pars[4] = C4VBool(CPA_IPC_HasGamepad == (param_int & CPA_IPC_HasGamepad));
711 }
712 ::ScriptEngine.Call(callback_name, &pars);
713 break;
714 }
715
716 case CPA_NoAction: break;
717 }
718 }
719
CompileFunc(StdCompiler * pComp)720 void C4ControlPlayerAction::CompileFunc(StdCompiler *pComp)
721 {
722 pComp->Value(mkNamingAdapt(source, "Player", NO_OWNER));
723 const StdEnumEntry<Action> ActionNames[] =
724 {
725 { "Surrender", CPA_Surrender },
726 { "ActivateGoal", CPA_ActivateGoal },
727 { "ActivateGoalMenu", CPA_ActivateGoalMenu },
728 { "Eliminate", CPA_Eliminate },
729 { "SetHostility", CPA_SetHostility },
730 { "SetTeam", CPA_SetTeam },
731 { "InitScenarioPlayer", CPA_InitScenarioPlayer },
732 { "InitPlayerControl", CPA_InitPlayerControl },
733 { nullptr, CPA_NoAction }
734 };
735 pComp->Value(mkNamingAdapt(mkEnumAdapt<Action, int32_t>(action, ActionNames), "Action", CPA_NoAction));
736 pComp->Value(mkNamingAdapt(target, "Target", NO_OWNER));
737 pComp->Value(mkNamingAdapt(param_int, "DataI", 0));
738 pComp->Value(mkNamingAdapt(param_str, "DataS", nullptr));
739 C4ControlPacket::CompileFunc(pComp);
740 }
741
742 // *** C4ControlSyncCheck
743
744 C4ControlSyncCheck::C4ControlSyncCheck() = default;
745
Set()746 void C4ControlSyncCheck::Set()
747 {
748 Frame = Game.FrameCounter;
749 ControlTick = ::Control.ControlTick;
750 RandomCount = ::RandomCount;
751 AllCrewPosX = GetAllCrewPosX();
752 PXSCount = ::PXS.Count;
753 MassMoverIndex = ::MassMover.CreatePtr;
754 ObjectCount = ::Objects.ObjectCount();
755 ObjectEnumerationIndex = C4PropListNumbered::GetEnumerationIndex();
756 SectShapeSum = ::Objects.Sectors.getShapeSum();
757 }
758
GetAllCrewPosX()759 int32_t C4ControlSyncCheck::GetAllCrewPosX()
760 {
761 int32_t cpx=0;
762 for (C4Player *pPlr=::Players.First; pPlr; pPlr=pPlr->Next)
763 for (C4Object *member : pPlr->Crew)
764 cpx += fixtoi(member->fix_x, 100);
765 return cpx;
766 }
767
Execute() const768 void C4ControlSyncCheck::Execute() const
769 {
770 // control host?
771 if (::Control.isCtrlHost()) return;
772
773 // get the saved sync check data
774 C4ControlSyncCheck* pSyncCheck = ::Control.GetSyncCheck(Frame), &SyncCheck = *pSyncCheck;
775 if (!pSyncCheck)
776 {
777 ::Control.SyncChecks.Add(CID_SyncCheck, new C4ControlSyncCheck(*this));
778 return;
779 }
780
781 // Not equal
782 if ( Frame != pSyncCheck->Frame
783 ||(ControlTick != pSyncCheck->ControlTick && !::Control.isReplay())
784 || RandomCount != pSyncCheck->RandomCount
785 || AllCrewPosX != pSyncCheck->AllCrewPosX
786 || PXSCount != pSyncCheck->PXSCount
787 || MassMoverIndex != pSyncCheck->MassMoverIndex
788 || ObjectCount != pSyncCheck->ObjectCount
789 || ObjectEnumerationIndex != pSyncCheck->ObjectEnumerationIndex
790 || SectShapeSum != pSyncCheck->SectShapeSum)
791 {
792 const char *szThis = "Client", *szOther = ::Control.isReplay() ? "Rec ":"Host";
793 if (iByClient != ::Control.ClientID())
794 { const char *szTemp = szThis; szThis = szOther; szOther = szTemp; }
795 // Message
796 LogFatal("Network: Synchronization loss!");
797 LogFatal(FormatString("Network: %s Frm %i Ctrl %i Rnc %i Cpx %i PXS %i MMi %i Obc %i Oei %i Sct %i", szThis, Frame,ControlTick,RandomCount,AllCrewPosX,PXSCount,MassMoverIndex,ObjectCount,ObjectEnumerationIndex, SectShapeSum).getData());
798 LogFatal(FormatString("Network: %s Frm %i Ctrl %i Rnc %i Cpx %i PXS %i MMi %i Obc %i Oei %i Sct %i", szOther, SyncCheck.Frame,SyncCheck.ControlTick,SyncCheck.RandomCount,SyncCheck.AllCrewPosX,SyncCheck.PXSCount,SyncCheck.MassMoverIndex,SyncCheck.ObjectCount,SyncCheck.ObjectEnumerationIndex, SyncCheck.SectShapeSum).getData());
799 StartSoundEffect("UI::SyncError");
800 #ifdef _DEBUG
801 // Debug safe
802 C4GameSaveNetwork SaveGame(false);
803 SaveGame.Save(Config.AtExePath("Desync.ocs"));
804 #endif
805 // league: Notify regular client disconnect within the game
806 ::Network.LeagueNotifyDisconnect(C4ClientIDHost, C4LDR_Desync);
807 // Deactivate / end
808 if (::Control.isReplay())
809 Game.DoGameOver();
810 else if (::Control.isNetwork())
811 {
812 Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, "Network: Synchronization loss!");
813 ::Network.Clear();
814 }
815 }
816
817 }
818
CompileFunc(StdCompiler * pComp)819 void C4ControlSyncCheck::CompileFunc(StdCompiler *pComp)
820 {
821 pComp->Value(mkNamingAdapt(mkIntPackAdapt(Frame), "Frame", -1));
822 pComp->Value(mkNamingAdapt(mkIntPackAdapt(ControlTick), "ControlTick", 0));
823 pComp->Value(mkNamingAdapt(RandomCount, "RandomCount", 0));
824 pComp->Value(mkNamingAdapt(mkIntPackAdapt(AllCrewPosX), "AllCrewPosX", 0));
825 pComp->Value(mkNamingAdapt(mkIntPackAdapt(PXSCount), "PXSCount", 0));
826 pComp->Value(mkNamingAdapt(MassMoverIndex, "MassMoverIndex", 0));
827 pComp->Value(mkNamingAdapt(mkIntPackAdapt(ObjectCount), "ObjectCount", 0));
828 pComp->Value(mkNamingAdapt(mkIntPackAdapt(ObjectEnumerationIndex), "ObjectEnumerationIndex", 0));
829 pComp->Value(mkNamingAdapt(mkIntPackAdapt(SectShapeSum), "SectShapeSum", 0));
830 C4ControlPacket::CompileFunc(pComp);
831 }
832
833 // *** C4ControlSynchronize
834
Execute() const835 void C4ControlSynchronize::Execute() const
836 {
837 Game.Synchronize(fSavePlrFiles);
838 if (fSyncClearance) Game.SyncClearance();
839 }
840
CompileFunc(StdCompiler * pComp)841 void C4ControlSynchronize::CompileFunc(StdCompiler *pComp)
842 {
843 pComp->Value(mkNamingAdapt(fSavePlrFiles, "SavePlrs", false));
844 pComp->Value(mkNamingAdapt(fSyncClearance, "SyncClear", false));
845 C4ControlPacket::CompileFunc(pComp);
846 }
847
848 // *** C4ControlClientJoin
849
Execute() const850 void C4ControlClientJoin::Execute() const
851 {
852 // host only
853 if (iByClient != C4ClientIDHost) return;
854 // add client
855 C4Client *pClient = Game.Clients.Add(Core);
856 if (!pClient) return;
857 // log
858 LogF(LoadResStr("IDS_NET_CLIENT_JOIN"), Core.getName());
859 // lobby callback
860 C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
861 if (pLobby) pLobby->OnClientJoin(pClient);
862 // console callback
863 if (Console.Active) Console.UpdateMenus();
864 }
865
CompileFunc(StdCompiler * pComp)866 void C4ControlClientJoin::CompileFunc(StdCompiler *pComp)
867 {
868 pComp->Value(mkNamingAdapt(Core, "ClientCore"));
869 C4ControlPacket::CompileFunc(pComp);
870 }
871
872 // *** C4Control
873
Execute() const874 void C4ControlClientUpdate::Execute() const
875 {
876 // host only
877 if (iByClient != C4ClientIDHost && eType != CUT_SetReady) return;
878 // find client
879 C4Client *pClient = Game.Clients.getClientByID(iID);
880 if (!pClient) return;
881 StdCopyStrBuf strClient(LoadResStr(pClient->isLocal() ? "IDS_NET_LOCAL_CLIENT" : "IDS_NET_CLIENT"));
882 // do whatever specified
883 switch (eType)
884 {
885 case CUT_None: break;
886 case CUT_Activate:
887 // nothing to do?
888 if (pClient->isActivated() == !!iData) break;
889 // log
890 LogF(LoadResStr(iData ? "IDS_NET_CLIENT_ACTIVATED" : "IDS_NET_CLIENT_DEACTIVATED"), strClient.getData(), pClient->getName());
891 // activate/deactivate
892 pClient->SetActivated(!!iData);
893 // local?
894 if (pClient->isLocal())
895 ::Control.SetActivated(!!iData);
896 break;
897 case CUT_SetObserver:
898 // nothing to do?
899 if (pClient->isObserver()) break;
900 // log
901 LogF(LoadResStr("IDS_NET_CLIENT_OBSERVE"), strClient.getData(), pClient->getName());
902 // set observer (will deactivate)
903 pClient->SetObserver();
904 // local?
905 if (pClient->isLocal())
906 ::Control.SetActivated(false);
907 // remove all players ("soft kick")
908 ::Players.RemoveAtClient(iID, true);
909 break;
910 case CUT_SetReady:
911 {
912 // nothing to do?
913 if (pClient->isLobbyReady() == !!iData) break;
914 // ready/unready (while keeping track of time)
915 time_t last_change_time = MinReadyAnnouncementDelay;
916 pClient->SetLobbyReady(!!iData, &last_change_time);
917 // log to others, but don't spam
918 if (last_change_time >= MinReadyAnnouncementDelay)
919 {
920 if (!pClient->isLocal())
921 {
922 LogF(LoadResStr(iData ? "IDS_NET_CLIENT_READY" : "IDS_NET_CLIENT_UNREADY"), strClient.getData(), pClient->getName());
923 }
924 // Also update icons
925 C4GameLobby::MainDlg *lobby = ::Network.GetLobby();
926 if (lobby) lobby->OnClientReadyStateChange();
927 }
928 break;
929 }
930 }
931 // Update console net menu to reflect activation/etc.
932 ::Console.UpdateNetMenu();
933 }
934
CompileFunc(StdCompiler * pComp)935 void C4ControlClientUpdate::CompileFunc(StdCompiler *pComp)
936 {
937 pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eType), "Type", CUT_None));
938 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iID), "ClientID", C4ClientIDUnknown));
939 if (eType == CUT_Activate)
940 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iData), "Data", 0));
941 if (eType == CUT_SetReady)
942 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iData), "Data", 0));
943 C4ControlPacket::CompileFunc(pComp);
944 }
945
946 // *** C4ControlClientRemove
947
Execute() const948 void C4ControlClientRemove::Execute() const
949 {
950 // host only
951 if (iByClient != C4ClientIDHost) return;
952 if (iID == C4ClientIDHost) return;
953 // find client
954 C4Client *pClient = Game.Clients.getClientByID(iID);
955 if (!pClient)
956 {
957 // TODO: in replays, client list is not yet synchronized
958 // remove players anyway
959 if (::Control.isReplay()) ::Players.RemoveAtClient(iID, true);
960 return;
961 }
962 StdCopyStrBuf strClient(LoadResStr(pClient->isLocal() ? "IDS_NET_LOCAL_CLIENT" : "IDS_NET_CLIENT"));
963 // local?
964 if (pClient->isLocal())
965 {
966 StdStrBuf sMsg;
967 sMsg.Format(LoadResStr("IDS_NET_CLIENT_REMOVED"), strClient.getData(), pClient->getName(), strReason.getData());
968 Log(sMsg.getData());
969 Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, sMsg.getData());
970 ::Control.ChangeToLocal();
971 return;
972 }
973 // remove client
974 if (!Game.Clients.Remove(pClient)) return;
975 // log
976 LogF(LoadResStr("IDS_NET_CLIENT_REMOVED"), strClient.getData(), pClient->getName(), strReason.getData());
977 // remove all players
978 ::Players.RemoveAtClient(iID, true);
979 // remove all resources
980 if (::Network.isEnabled())
981 ::Network.ResList.RemoveAtClient(iID);
982 // lobby callback
983 C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
984 if (pLobby && ::pGUI) pLobby->OnClientPart(pClient);
985 // player list callback
986 ::Network.Players.OnClientPart(pClient);
987 // console callback
988 if (Console.Active) Console.UpdateMenus();
989
990 // delete
991 delete pClient;
992 }
993
CompileFunc(StdCompiler * pComp)994 void C4ControlClientRemove::CompileFunc(StdCompiler *pComp)
995 {
996 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iID), "ClientID", CUT_None));
997 pComp->Value(mkNamingAdapt(strReason, "Reason", ""));
998 C4ControlPacket::CompileFunc(pComp);
999 }
1000
1001 // *** C4ControlJoinPlayer
1002
C4ControlJoinPlayer(const char * szFilename,int32_t iAtClient,int32_t iIDInfo,C4Network2ResCore ResCore)1003 C4ControlJoinPlayer::C4ControlJoinPlayer(const char *szFilename, int32_t iAtClient, int32_t iIDInfo, C4Network2ResCore ResCore)
1004 : Filename(szFilename, true), iAtClient(iAtClient),
1005 idInfo(iIDInfo), fByRes(true), ResCore(std::move(ResCore))
1006 {
1007 }
1008
C4ControlJoinPlayer(const char * szFilename,int32_t iAtClient,int32_t iIDInfo)1009 C4ControlJoinPlayer::C4ControlJoinPlayer(const char *szFilename, int32_t iAtClient, int32_t iIDInfo)
1010 : Filename(szFilename, true), iAtClient(iAtClient),
1011 idInfo(iIDInfo)
1012 {
1013 // load from file if filename is given - which may not be the case for script players
1014 StdStrBuf filename;
1015 if (szFilename && Reloc.LocateItem(szFilename, filename))
1016 {
1017 bool file_is_temp = false;
1018 if (DirectoryExists(filename.getData()))
1019 {
1020 // the player file is unpacked - temp pack and read
1021 StdStrBuf filename_buf;
1022 filename_buf.Copy(Config.AtTempPath(GetFilenameOnly(filename.getData())));
1023 MakeTempFilename(&filename_buf);
1024 if (C4Group_PackDirectoryTo(filename.getData(), filename_buf.getData()))
1025 {
1026 filename.Take(filename_buf);
1027 file_is_temp = true;
1028 }
1029 else
1030 {
1031 // pack failed
1032 LogF("[!]Error packing player file %s to %s for join: Pack failed.", filename.getData(), filename_buf.getData());
1033 assert(false);
1034 }
1035 }
1036 bool fSuccess = PlrData.LoadFromFile(filename.getData());
1037 if (!fSuccess)
1038 {
1039 LogF("[!]Error loading player file from %s.", filename.getData());
1040 assert(false);
1041 }
1042 if (file_is_temp) EraseFile(filename.getData());
1043 }
1044 else if(szFilename)
1045 {
1046 LogF("[!]Error loading player file from %s.", szFilename);
1047 assert(false);
1048 }
1049 }
1050
Execute() const1051 void C4ControlJoinPlayer::Execute() const
1052 {
1053 const char *szFilename = Filename.getData();
1054
1055 // get client
1056 C4Client *pClient = Game.Clients.getClientByID(iAtClient);
1057 if (pClient)
1058 {
1059 // get info
1060 C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(idInfo);
1061 if (!pInfo)
1062 {
1063 LogF("ERROR: Ghost player join: No info for %d", idInfo);
1064 assert(false);
1065 }
1066 else if (LocalControl())
1067 {
1068 // Local player: Just join from local file
1069 Game.JoinPlayer(szFilename, iAtClient, pClient->getName(), pInfo);
1070 }
1071 else if (!fByRes)
1072 {
1073 if (PlrData.getSize())
1074 {
1075 // create temp file
1076 StdStrBuf PlayerFilename; PlayerFilename.Format("%s-%s", pClient->getName(), GetFilename(szFilename));
1077 PlayerFilename = Config.AtTempPath(PlayerFilename.getData());
1078 // copy to it
1079 if (PlrData.SaveToFile(PlayerFilename.getData()))
1080 {
1081 Game.JoinPlayer(PlayerFilename.getData(), iAtClient, pClient->getName(), pInfo);
1082 EraseFile(PlayerFilename.getData());
1083 }
1084 }
1085 else if (pInfo->GetType() == C4PT_Script)
1086 {
1087 // script players may join without data
1088 Game.JoinPlayer(nullptr, iAtClient, pClient->getName(), pInfo);
1089 }
1090 else
1091 {
1092 // no player data for user player present: Must not happen
1093 LogF("ERROR: Ghost player join: No player data for %s", (const char*)pInfo->GetName());
1094 assert(false);
1095 }
1096 }
1097 else if (::Control.isNetwork())
1098 {
1099 // Find resource
1100 C4Network2Res::Ref pRes = ::Network.ResList.getRefRes(ResCore.getID());
1101 if (pRes && pRes->isComplete())
1102 Game.JoinPlayer(pRes->getFile(), iAtClient, pClient->getName(), pInfo);
1103 }
1104 else if (::Control.isReplay())
1105 {
1106 // Expect player in scenario file
1107 StdStrBuf PlayerFilename; PlayerFilename.Format("%s" DirSep "%d-%s", Game.ScenarioFilename, ResCore.getID(), GetFilename(ResCore.getFileName()));
1108 Game.JoinPlayer(PlayerFilename.getData(), iAtClient, pClient ? pClient->getName() : "Unknown", pInfo);
1109 }
1110 else
1111 {
1112 // Shouldn't happen
1113 assert(false);
1114 }
1115 }
1116 // After last of the initial player joins, do a game callback
1117 ::Game.OnPlayerJoinFinished();
1118 }
1119
Strip()1120 void C4ControlJoinPlayer::Strip()
1121 {
1122 // By resource? Can't touch player file, then.
1123 if (fByRes) return;
1124 // create temp file
1125 StdStrBuf PlayerFilename; PlayerFilename = GetFilename(Filename.getData());
1126 PlayerFilename = Config.AtTempPath(PlayerFilename.getData());
1127 // Copy to it
1128 if (PlrData.SaveToFile(PlayerFilename.getData()))
1129 {
1130 // open as group
1131 C4Group Grp;
1132 if (!Grp.Open(PlayerFilename.getData()))
1133 { EraseFile(PlayerFilename.getData()); return; }
1134 // remove bigicon, if the file size is too large
1135 size_t iBigIconSize=0;
1136 if (Grp.FindEntry(C4CFN_BigIcon, nullptr, &iBigIconSize))
1137 if (iBigIconSize > C4NetResMaxBigicon*1024)
1138 Grp.Delete(C4CFN_BigIcon);
1139 Grp.Close();
1140 // Set new data
1141 StdBuf NewPlrData;
1142 if (!NewPlrData.LoadFromFile(PlayerFilename.getData()))
1143 { EraseFile(PlayerFilename.getData()); return; }
1144 PlrData = std::move(NewPlrData);
1145 // Done
1146 EraseFile(PlayerFilename.getData());
1147 }
1148 }
1149
PreExecute() const1150 bool C4ControlJoinPlayer::PreExecute() const
1151 {
1152 // all data included in control packet?
1153 if (!fByRes) return true;
1154 // client lost?
1155 if (!Game.Clients.getClientByID(iAtClient)) return true;
1156 // network only
1157 if (!::Control.isNetwork()) return true;
1158 // search resource
1159 C4Network2Res::Ref pRes = ::Network.ResList.getRefRes(ResCore.getID());
1160 // doesn't exist? start loading
1161 if (!pRes) { pRes = ::Network.ResList.AddByCore(ResCore, true); }
1162 if (!pRes) return true;
1163 // is loading or removed?
1164 return !pRes->isLoading();
1165 }
1166
PreRec(C4Record * pRecord)1167 void C4ControlJoinPlayer::PreRec(C4Record *pRecord)
1168 {
1169 if (!pRecord) return;
1170 if (fByRes)
1171 {
1172 // get local file by id
1173 C4Network2Res::Ref pRes = ::Network.ResList.getRefRes(ResCore.getID());
1174 if (!pRes || pRes->isRemoved()) return;
1175 // create a copy of the resource
1176 StdStrBuf szTemp; szTemp.Copy(pRes->getFile());
1177 MakeTempFilename(&szTemp);
1178 if (C4Group_CopyItem(pRes->getFile(), szTemp.getData()))
1179 {
1180 // add to record
1181 StdStrBuf szTarget = FormatString("%d-%s", ResCore.getID(), GetFilename(ResCore.getFileName()));
1182 pRecord->AddFile(szTemp.getData(), szTarget.getData(), true);
1183 }
1184 }
1185 else
1186 {
1187 // player data raw within control: Will be used directly in record
1188 }
1189 }
1190
CompileFunc(StdCompiler * pComp)1191 void C4ControlJoinPlayer::CompileFunc(StdCompiler *pComp)
1192 {
1193 pComp->Value(mkNamingAdapt(mkNetFilenameAdapt(Filename), "Filename", ""));
1194 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iAtClient), "AtClient", -1));
1195 pComp->Value(mkNamingAdapt(mkIntPackAdapt(idInfo), "InfoID", -1));
1196 pComp->Value(mkNamingAdapt(fByRes, "ByRes", false));
1197 if (fByRes)
1198 pComp->Value(mkNamingAdapt(ResCore, "ResCore"));
1199 else
1200 pComp->Value(mkNamingAdapt(PlrData, "PlrData"));
1201 C4ControlPacket::CompileFunc(pComp);
1202 }
1203
1204 // *** C4ControlEMMoveObject
1205
C4ControlEMMoveObject(C4ControlEMObjectAction eAction,C4Real tx,C4Real ty,C4Object * pTargetObj,int32_t iObjectNum,int32_t * pObjects,const char * szScript,bool drag_finished)1206 C4ControlEMMoveObject::C4ControlEMMoveObject(C4ControlEMObjectAction eAction, C4Real tx, C4Real ty, C4Object *pTargetObj,
1207 int32_t iObjectNum, int32_t *pObjects, const char *szScript, bool drag_finished)
1208 : eAction(eAction), tx(tx), ty(ty), iTargetObj(pTargetObj ? pTargetObj->Number : 0),
1209 iObjectNum(iObjectNum), pObjects(pObjects), StringParam(szScript, true), drag_finished(drag_finished)
1210 {
1211
1212 }
1213
CreateObject(const C4ID & id,C4Real x,C4Real y,C4Object * container)1214 C4ControlEMMoveObject *C4ControlEMMoveObject::CreateObject(const C4ID &id, C4Real x, C4Real y, C4Object *container)
1215 {
1216 #ifdef WITH_QT_EDITOR
1217 ::StartSoundEffect("UI::Click2");
1218 #endif
1219 auto ctl = new C4ControlEMMoveObject(EMMO_Create, x, y, container);
1220 ctl->StringParam = id.ToString();
1221 return ctl;
1222 }
1223
~C4ControlEMMoveObject()1224 C4ControlEMMoveObject::~C4ControlEMMoveObject()
1225 {
1226 delete [] pObjects; pObjects = nullptr;
1227 }
1228
MoveObject(C4Object * moved_object,bool move_forced) const1229 void C4ControlEMMoveObject::MoveObject(C4Object *moved_object, bool move_forced) const
1230 {
1231 // move given object by this->tx/ty and do callbacks
1232 if (!moved_object || !moved_object->Status) return;
1233 int32_t old_x = moved_object->GetX(), old_y = moved_object->GetY();
1234 C4Real tx = this->tx;
1235 if (moved_object->Def->NoHorizontalMove && !move_forced) tx = Fix0;
1236 moved_object->ForcePosition(moved_object->fix_x + tx, moved_object->fix_y + ty);
1237 moved_object->xdir = moved_object->ydir = 0;
1238 moved_object->Mobile = false;
1239 C4AulParSet pars(C4VInt(old_x), C4VInt(old_y), C4VBool(drag_finished));
1240 if (moved_object->Call(PSF_EditCursorMoved, &pars))
1241 {
1242 ::Console.EditCursor.InvalidateSelection();
1243 }
1244 }
1245
Execute() const1246 void C4ControlEMMoveObject::Execute() const
1247 {
1248 bool fLocalCall = LocalControl();
1249 switch (eAction)
1250 {
1251 case EMMO_Move:
1252 case EMMO_MoveForced:
1253 {
1254 if (!pObjects) break;
1255 // move all given objects
1256 C4Object *pObj;
1257 for (int i=0; i<iObjectNum; ++i)
1258 if ((pObj = ::Objects.SafeObjectPointer(pObjects[i])))
1259 if (pObj->Status)
1260 {
1261 MoveObject(pObj, eAction == EMMO_MoveForced);
1262 // attached objects: Also move attachment target
1263 while (pObj->GetProcedure() == DFA_ATTACH)
1264 {
1265 pObj = pObj->Action.Target;
1266 if (!pObj) break; // leftover action cancelled next frame
1267 for (int j = 0; j < iObjectNum; ++j) if (pObjects[j] == pObj->Number) { pObj = nullptr; break; } // ensure we aren't moving twice
1268 if (!pObj) break;
1269 MoveObject(pObj, eAction==EMMO_MoveForced);
1270 }
1271 }
1272 }
1273 break;
1274 case EMMO_Enter:
1275 {
1276 if (!pObjects) break;
1277 // enter all given objects into target
1278 C4Object *pObj, *pTarget = ::Objects.SafeObjectPointer(iTargetObj);
1279 if (pTarget)
1280 for (int i=0; i<iObjectNum; ++i)
1281 if ((pObj = ::Objects.SafeObjectPointer(pObjects[i])))
1282 {
1283 pObj->Enter(pTarget);
1284 if (!pTarget->Status) break;
1285 C4AulParSet pars(C4VObj(pObj));
1286 if (pObj && pObj->Status && pTarget->Status) pTarget->Call(P_EditorCollection, &pars);
1287 if (!pTarget->Status) break;
1288 }
1289 }
1290 break;
1291 case EMMO_Duplicate:
1292 {
1293 if (!pObjects) break;
1294 // perform duplication
1295 ::Console.EditCursor.PerformDuplication(pObjects, iObjectNum, fLocalCall);
1296 }
1297 break;
1298 case EMMO_Script:
1299 {
1300 if (!pObjects) return;
1301 // execute script ...
1302 C4ControlScript ScriptCtrl(StringParam.getData(), C4ControlScript::SCOPE_Global);
1303 ScriptCtrl.SetByClient(iByClient);
1304 // ... for each object in selection
1305 for (int i=0; i<iObjectNum; ++i)
1306 {
1307 ScriptCtrl.SetTargetObj(pObjects[i]);
1308 ScriptCtrl.Execute();
1309 }
1310 }
1311 break;
1312 case EMMO_Remove:
1313 {
1314 if (!pObjects) return;
1315 // remove all objects
1316 C4Object *pObj;
1317 for (int i=0; i<iObjectNum; ++i)
1318 if ((pObj = ::Objects.SafeObjectPointer(pObjects[i])))
1319 pObj->AssignRemoval();
1320 }
1321 break;
1322 case EMMO_Exit:
1323 {
1324 if (!pObjects) return;
1325 // exit all objects
1326 C4Object *pObj;
1327 for (int i=0; i<iObjectNum; ++i)
1328 if ((pObj = ::Objects.SafeObjectPointer(pObjects[i])))
1329 pObj->Exit(pObj->GetX(), pObj->GetY(), pObj->GetR());
1330 }
1331 break;
1332 case EMMO_Create:
1333 {
1334 // Check max object count
1335 C4ID iddef = C4ID(StringParam);
1336 C4Def *def = C4Id2Def(iddef);
1337 if (!def) return;
1338 int32_t placement_limit = def->GetPropertyInt(P_EditorPlacementLimit);
1339 if (placement_limit)
1340 {
1341 if (Game.ObjectCount(iddef) >= placement_limit)
1342 {
1343 // Too many objects
1344 ::Console.Message(FormatString(LoadResStr("IDS_CNS_CREATORTOOMANYINSTANCES"), int(placement_limit)).getData());
1345 return;
1346 }
1347 }
1348 // Create object outside or contained
1349 // If container is desired but not valid, do nothing (don't create object outside instead)
1350 C4Object *container = nullptr;
1351 if (iTargetObj)
1352 {
1353 container = ::Objects.SafeObjectPointer(iTargetObj);
1354 if (!container || !container->Status) return;
1355 }
1356 bool create_centered = false;
1357 #ifdef WITH_QT_EDITOR
1358 // Qt editor: Object creation is done through creator; centered creation is usually more convenient
1359 create_centered = true;
1360 #endif
1361 C4Object *obj = ::Game.CreateObject(iddef, nullptr, NO_OWNER, fixtoi(tx), fixtoi(ty), 0, create_centered);
1362 if (container && obj && container->Status && obj->Status)
1363 {
1364 obj->Enter(container);
1365 C4AulParSet pars(C4VObj(obj));
1366 if (obj && obj->Status) container->Call(P_EditorCollection, &pars);
1367 }
1368 if (obj && obj->Status) obj->Call(P_EditorInitialize); // specific initialization when placed in editor
1369 }
1370 break;
1371 case EMMO_Transform:
1372 {
1373 C4Object *pTarget = ::Objects.SafeObjectPointer(iTargetObj);
1374 if (pTarget)
1375 {
1376 int32_t new_rot = fixtoi(this->tx, 1);
1377 int32_t new_con = fixtoi(this->ty, FullCon/100);
1378 if (pTarget->Def->Rotateable) pTarget->SetRotation(new_rot);
1379 if (pTarget->Def->GrowthType) pTarget->DoCon(new_con - pTarget->GetCon(), false);
1380 }
1381 }
1382 }
1383 // update property dlg & status bar
1384 if (fLocalCall && eAction != EMMO_Move && eAction != EMMO_MoveForced)
1385 Console.EditCursor.OnSelectionChanged();
1386 }
1387
CompileFunc(StdCompiler * pComp)1388 void C4ControlEMMoveObject::CompileFunc(StdCompiler *pComp)
1389 {
1390 pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eAction), "Action"));
1391 pComp->Value(mkNamingAdapt(tx, "tx", 0));
1392 pComp->Value(mkNamingAdapt(ty, "ty", 0));
1393 pComp->Value(mkNamingAdapt(iTargetObj, "TargetObj", -1));
1394 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iObjectNum), "ObjectNum", 0));
1395 if (pComp->isDeserializer()) { delete [] pObjects; pObjects = new int32_t [iObjectNum]; }
1396 pComp->Value(mkNamingAdapt(mkArrayAdapt(pObjects, iObjectNum), "Objs", -1));
1397 if (eAction == EMMO_Script)
1398 pComp->Value(mkNamingAdapt(StringParam, "Script", ""));
1399 else if (eAction == EMMO_Create)
1400 pComp->Value(mkNamingAdapt(StringParam, "ID", ""));
1401 C4ControlPacket::CompileFunc(pComp);
1402 }
1403
1404 // *** C4ControlEMDrawTool
1405
C4ControlEMDrawTool(C4ControlEMDrawAction eAction,LandscapeMode iMode,int32_t iX,int32_t iY,int32_t iX2,int32_t iY2,int32_t iGrade,const char * szMaterial,const char * szTexture,const char * szBackMaterial,const char * szBackTexture)1406 C4ControlEMDrawTool::C4ControlEMDrawTool(C4ControlEMDrawAction eAction, LandscapeMode iMode,
1407 int32_t iX, int32_t iY, int32_t iX2, int32_t iY2, int32_t iGrade,
1408 const char *szMaterial, const char *szTexture, const char *szBackMaterial, const char *szBackTexture)
1409 : eAction(eAction), iMode(iMode), iX(iX), iY(iY), iX2(iX2), iY2(iY2), iGrade(iGrade),
1410 Material(szMaterial, true), Texture(szTexture, true),
1411 BackMaterial(szBackMaterial, true), BackTexture(szBackTexture, true)
1412 {
1413
1414 }
1415
Execute() const1416 void C4ControlEMDrawTool::Execute() const
1417 {
1418 // set new mode
1419 if (eAction == EMDT_SetMode)
1420 {
1421 Console.ToolsDlg.SetLandscapeMode(iMode, iX==1, true);
1422 return;
1423 }
1424 // check current mode
1425 assert(::Landscape.GetMode() == iMode);
1426 if (::Landscape.GetMode() != iMode) return;
1427 // assert validity of parameters
1428 if (!Material.getSize()) return;
1429 const char *szMaterial = Material.getData(),
1430 *szTexture = Texture.getData();
1431 const char *szBackMaterial = BackMaterial.getData(),
1432 *szBackTexture = BackTexture.getData();
1433 // perform action
1434 switch (eAction)
1435 {
1436 case EMDT_Brush: // brush tool
1437 if (!Texture.getSize()) break;
1438 ::Landscape.DrawBrush(iX, iY, iGrade, szMaterial, szTexture, szBackMaterial, szBackTexture);
1439 break;
1440 case EMDT_Line: // line tool
1441 if (!Texture.getSize()) break;
1442 ::Landscape.DrawLine(iX,iY,iX2,iY2, iGrade, szMaterial, szTexture, szBackMaterial, szBackTexture);
1443 break;
1444 case EMDT_Rect: // rect tool
1445 if (!Texture.getSize()) break;
1446 ::Landscape.DrawBox(iX,iY,iX2,iY2, iGrade, szMaterial, szTexture, szBackMaterial, szBackTexture);
1447 break;
1448 case EMDT_Fill: // fill tool
1449 {
1450 int iMat = ::MaterialMap.Get(szMaterial);
1451 if (!MatValid(iMat)) return;
1452 for (int cnt=0; cnt<iGrade; cnt++)
1453 {
1454 int32_t itX=iX+Random(iGrade)-iGrade/2;
1455 int32_t itY=iY+Random(iGrade)-iGrade/2;
1456 ::Landscape.InsertMaterial(iMat,&itX,&itY);
1457 }
1458 }
1459 break;
1460 default:
1461 break;
1462 }
1463 }
1464
1465
CompileFunc(StdCompiler * pComp)1466 void C4ControlEMDrawTool::CompileFunc(StdCompiler *pComp)
1467 {
1468 pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eAction), "Action"));
1469 pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(iMode), "Mode", LandscapeMode::Undefined));
1470 pComp->Value(mkNamingAdapt(iX, "X", 0));
1471 pComp->Value(mkNamingAdapt(iY, "Y", 0));
1472 pComp->Value(mkNamingAdapt(iX2, "X2", 0));
1473 pComp->Value(mkNamingAdapt(iY2, "Y2", 0));
1474 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iGrade), "Grade", 0));
1475 pComp->Value(mkNamingAdapt(Material, "Material", ""));
1476 pComp->Value(mkNamingAdapt(Texture, "Texture", ""));
1477 pComp->Value(mkNamingAdapt(BackMaterial, "BackMaterial", ""));
1478 pComp->Value(mkNamingAdapt(BackTexture, "BackTexture", ""));
1479 C4ControlPacket::CompileFunc(pComp);
1480 }
1481
1482 // *** C4ControlMessage
1483
Execute() const1484 void C4ControlMessage::Execute() const
1485 {
1486 const char *szMessage = Message.getData();
1487 // get player
1488 C4Player *pPlr = (iPlayer < 0 ? nullptr : ::Players.Get(iPlayer));
1489 // security
1490 if (pPlr && pPlr->AtClient != iByClient) return;
1491 // do not record message as control, because it is not synced!
1492 // get lobby to forward to
1493 C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
1494 StdStrBuf str;
1495 switch (eType)
1496 {
1497 case C4CMT_Normal:
1498 case C4CMT_Me:
1499 // log it
1500 if (pPlr)
1501 {
1502 if (pPlr->AtClient != iByClient) break;
1503 str.Format((eType == C4CMT_Normal ? "<c %x><<i></i>%s> %s</c>" : "<c %x> * %s %s</c>"),
1504 pPlr->ColorDw, pPlr->GetName(), szMessage);
1505 }
1506 else
1507 {
1508 C4Client *pClient = Game.Clients.getClientByID(iByClient);
1509 str.Format((eType == C4CMT_Normal ? "<%s> %s" : " * %s %s"),
1510 pClient ? pClient->getNick() : "???", szMessage);
1511 }
1512 // to lobby
1513 if (pLobby)
1514 pLobby->OnMessage(Game.Clients.getClientByID(iByClient), str.getData());
1515 #ifndef USE_CONSOLE
1516 else
1517 #endif
1518 // to log
1519 Log(str.getData());
1520 break;
1521
1522 case C4CMT_Say:
1523 // show as game message above player cursor
1524 if (pPlr && pPlr->Cursor)
1525 {
1526 if (Game.C4S.Head.Film == C4SFilm_Cinematic)
1527 {
1528 StdStrBuf sMessage; sMessage.Format("<%s> %s", pPlr->Cursor->GetName(), szMessage);
1529 uint32_t dwClr = pPlr->Cursor->Color;
1530 if (!dwClr) dwClr = 0xff;
1531 GameMsgObjectDw(sMessage.getData(), pPlr->Cursor, dwClr|0xff000000);
1532 }
1533 else
1534 GameMsgObjectDw(szMessage, pPlr->Cursor, pPlr->ColorDw|0xff000000);
1535 }
1536 break;
1537
1538 case C4CMT_Team:
1539 {
1540 // show only if sending player is allied with a local one
1541 if (pPlr)
1542 {
1543 // for running game mode, check actual hostility
1544 C4Player *pLocalPlr;
1545 for (int cnt = 0; (pLocalPlr = ::Players.GetLocalByIndex(cnt)); cnt++)
1546 if (!Hostile(pLocalPlr->Number, iPlayer))
1547 break;
1548 if (pLocalPlr) Log(FormatString("<c %x>{%s} %s</c>", pPlr->ColorDw, pPlr->GetName(), szMessage).getData());
1549 }
1550 else if (pLobby)
1551 {
1552 // in lobby mode, no player has joined yet - check teams of unjoined players
1553 if (!Game.PlayerInfos.HasSameTeamPlayers(iByClient, Game.Clients.getLocalID())) break;
1554 // OK - permit message
1555 C4Client *pClient = Game.Clients.getClientByID(iByClient);
1556 pLobby->OnMessage(Game.Clients.getClientByID(iByClient),
1557 FormatString("{%s} %s", pClient ? pClient->getNick() : "???", szMessage).getData());
1558 }
1559 }
1560 break;
1561
1562 case C4CMT_Private:
1563 {
1564 if (!pPlr) break;
1565 // show only if the target player is local
1566 C4Player *pLocalPlr;
1567 for (int cnt = 0; (pLocalPlr = ::Players.GetLocalByIndex(cnt)); cnt++)
1568 if (pLocalPlr->ID == iToPlayer)
1569 break;
1570 if (pLocalPlr)
1571 {
1572 Log(FormatString("<c %x>[%s] %s</c>", pPlr->ColorDw, pPlr->GetName(), szMessage).getData());
1573 }
1574 }
1575 break;
1576
1577 case C4CMT_Sound:
1578 {
1579 // tehehe, sound!
1580 C4Client *singer = Game.Clients.getClientByID(iByClient);
1581 if (!singer || !singer->IsIgnored())
1582 if (!StartSoundEffect(szMessage, false, 100, nullptr))
1583 // probably wrong sound file name
1584 break;
1585 // Sound icon even if someone you ignored just tried. So you know you still need to ignore.
1586 if (pLobby) pLobby->OnClientSound(singer);
1587 if (C4Network2ClientListDlg::GetInstance()) C4Network2ClientListDlg::GetInstance()->OnSound(singer);
1588 break;
1589 }
1590
1591 case C4CMT_Alert:
1592 // notify inactive users
1593 Application.NotifyUserIfInactive();
1594 break;
1595
1596 case C4CMT_System:
1597 // sender must be host
1598 if (!HostControl()) break;
1599 // show
1600 LogF("Network: %s", szMessage);
1601 break;
1602
1603 }
1604 }
1605
CompileFunc(StdCompiler * pComp)1606 void C4ControlMessage::CompileFunc(StdCompiler *pComp)
1607 {
1608 pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eType), "Type", C4CMT_Normal));
1609 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iPlayer), "Player", -1));
1610 if (eType == C4CMT_Private)
1611 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iToPlayer), "ToPlayer", -1));
1612 pComp->Value(mkNamingAdapt(Message, "Message", ""));
1613 C4ControlPacket::CompileFunc(pComp);
1614 }
1615
1616 // *** C4ControlPlayerInfo
1617
Execute() const1618 void C4ControlPlayerInfo::Execute() const
1619 {
1620 // join to player info list
1621 // replay and local control: direct join
1622 if (::Control.isReplay() || !::Control.isNetwork())
1623 {
1624 // add info directly
1625 Game.PlayerInfos.AddInfo(new C4ClientPlayerInfos(PlrInfo));
1626 // make sure team list reflects teams set in player infos
1627 Game.Teams.RecheckPlayers();
1628 // replay: actual player join packet will follow
1629 // offline game: Issue the join
1630 if (::Control.isLocal())
1631 Game.PlayerInfos.LocalJoinUnjoinedPlayersInQueue();
1632 }
1633 else
1634 // network:
1635 ::Network.Players.HandlePlayerInfo(PlrInfo);
1636 }
1637
CompileFunc(StdCompiler * pComp)1638 void C4ControlPlayerInfo::CompileFunc(StdCompiler *pComp)
1639 {
1640 pComp->Value(PlrInfo);
1641 C4ControlPacket::CompileFunc(pComp);
1642 }
1643
1644 // *** C4ControlRemovePlr
1645
Execute() const1646 void C4ControlRemovePlr::Execute() const
1647 {
1648 // host only
1649 if (iByClient != C4ClientIDHost) return;
1650 // remove
1651 ::Players.Remove(iPlr, fDisconnected, false);
1652 }
1653
CompileFunc(StdCompiler * pComp)1654 void C4ControlRemovePlr::CompileFunc(StdCompiler *pComp)
1655 {
1656 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iPlr), "Plr", -1));
1657 pComp->Value(mkNamingAdapt(fDisconnected, "Disconnected", false));
1658 C4ControlPacket::CompileFunc(pComp);
1659 }
1660
1661 // *** C4ControlDebugRec
1662
Execute() const1663 void C4ControlDebugRec::Execute() const
1664 {
1665
1666 }
1667
CompileFunc(StdCompiler * pComp)1668 void C4ControlDebugRec::CompileFunc(StdCompiler *pComp)
1669 {
1670 pComp->Value(Data);
1671 }
1672
1673 // *** C4ControlVote
1674
getDesc() const1675 StdStrBuf C4ControlVote::getDesc() const
1676 {
1677 // Describe action
1678 StdStrBuf Action;
1679 switch (eType)
1680 {
1681 case VT_Cancel:
1682 Action = LoadResStr("IDS_VOTE_CANCELTHEROUND"); break;
1683 case VT_Kick:
1684 if (iData == iByClient)
1685 Action = LoadResStr("IDS_VOTE_LEAVETHEGAME");
1686 else
1687 {
1688 C4Client *pTargetClient = Game.Clients.getClientByID(iData);
1689 Action.Format(LoadResStr("IDS_VOTE_KICKCLIENT"), pTargetClient ? pTargetClient->getName() : "???");
1690 }
1691 break;
1692 case VT_Pause:
1693 if (iData)
1694 Action = LoadResStr("IDS_TEXT_PAUSETHEGAME");
1695 else
1696 Action = LoadResStr("IDS_TEXT_UNPAUSETHEGAME");
1697 break;
1698 default:
1699 Action = "perform some mysterious action"; break;
1700 }
1701 return Action;
1702 }
1703
getDescWarning() const1704 StdStrBuf C4ControlVote::getDescWarning() const
1705 {
1706 StdStrBuf Warning;
1707 switch (eType)
1708 {
1709 case VT_Cancel:
1710 Warning = LoadResStr("IDS_TEXT_WARNINGIFTHEGAMEISCANCELL"); break;
1711 case VT_Kick:
1712 Warning = LoadResStr("IDS_TEXT_WARNINGNOLEAGUEPOINTSWILL"); break;
1713 default:
1714 Warning = ""; break;
1715 }
1716 return Warning;
1717 }
1718
Execute() const1719 void C4ControlVote::Execute() const
1720 {
1721 // Log
1722 C4Client *pClient = Game.Clients.getClientByID(iByClient);
1723 if (fApprove)
1724 LogF(LoadResStr("IDS_VOTE_WANTSTO"), pClient->getName(), getDesc().getData());
1725 else
1726 LogF(LoadResStr("IDS_VOTE_DOESNOTWANTTO"), pClient->getName(), getDesc().getData());
1727 // Save vote back
1728 if (::Network.isEnabled())
1729 ::Network.AddVote(*this);
1730 // Vote done?
1731 if (::Control.isCtrlHost())
1732 {
1733 // Count votes
1734 int32_t iPositive = 0, iNegative = 0, iVotes = 0;
1735 // If there are no teams, count as if all were in the same team
1736 // (which happens to be equivalent to "everyone is in his own team" here)
1737 for (int32_t i = 0; i < std::max<int32_t>(Game.Teams.GetTeamCount(), 1); i++)
1738 {
1739 C4Team *pTeam = Game.Teams.GetTeamByIndex(i);
1740 // Votes for this team
1741 int32_t iPositiveTeam = 0, iNegativeTeam = 0, iVotesTeam = 0;
1742 // Check each player
1743 for (int32_t j = 0; j < (pTeam ? pTeam->GetPlayerCount() : Game.PlayerInfos.GetPlayerCount()); j++)
1744 {
1745 int32_t iClientID = C4ClientIDUnknown;
1746 C4PlayerInfo *pNfo;
1747 if (!pTeam)
1748 {
1749 pNfo = Game.PlayerInfos.GetPlayerInfoByIndex(j);
1750 if (!pNfo) continue; // shouldn't happen
1751 iClientID = Game.PlayerInfos.GetClientInfoByPlayerID(pNfo->GetID())->GetClientID();
1752 }
1753 else
1754 {
1755 pNfo = Game.PlayerInfos.GetPlayerInfoByID(pTeam->GetIndexedPlayer(j), &iClientID);
1756 if (!pNfo) continue; // shouldn't happen
1757 }
1758 if (iClientID < 0) continue;
1759 // Client disconnected?
1760 if (!Game.Clients.getClientByID(iClientID)) continue;
1761 // Player eliminated or never joined?
1762 if (!pNfo->IsJoined()) continue;
1763 // Okay, this player can vote
1764 iVotesTeam++;
1765 // Search vote of this client on the subject
1766 C4IDPacket *pPkt; C4ControlVote *pVote;
1767 if ((pPkt = ::Network.GetVote(iClientID, eType, iData)))
1768 if ((pVote = static_cast<C4ControlVote *>(pPkt->getPkt())))
1769 {
1770 if (pVote->isApprove())
1771 iPositiveTeam++;
1772 else
1773 iNegativeTeam++;
1774 }
1775 }
1776 // Any votes available?
1777 if (iVotesTeam)
1778 {
1779 iVotes++;
1780 // Approval by team? More then 50% needed
1781 if (iPositiveTeam * 2 > iVotesTeam)
1782 iPositive++;
1783 // Disapproval by team? More then 50% needed
1784 else if (iNegativeTeam * 2 >= iVotesTeam)
1785 iNegative++;
1786 }
1787 }
1788 // Approval? More then 50% needed
1789 if (iPositive * 2 > iVotes)
1790 ::Control.DoInput(CID_VoteEnd,
1791 new C4ControlVoteEnd(eType, true, iData),
1792 CDT_Sync);
1793 // Disapproval?
1794 else if (iNegative * 2 >= iVotes)
1795 ::Control.DoInput(CID_VoteEnd,
1796 new C4ControlVoteEnd(eType, false, iData),
1797 CDT_Sync);
1798 }
1799 }
1800
CompileFunc(StdCompiler * pComp)1801 void C4ControlVote::CompileFunc(StdCompiler *pComp)
1802 {
1803 pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eType), "Type", VT_None));
1804 pComp->Value(mkNamingAdapt(fApprove, "Approve", true));
1805 pComp->Value(mkNamingAdapt(iData, "Data", 0));
1806 C4ControlPacket::CompileFunc(pComp);
1807 }
1808
1809 // *** C4ControlVoteEnd
1810
Execute() const1811 void C4ControlVoteEnd::Execute() const
1812 {
1813 // End the voting process
1814 if (!HostControl()) return;
1815 if (::Network.isEnabled())
1816 ::Network.EndVote(getType(), isApprove(), getData());
1817 // Log
1818 StdStrBuf sMsg;
1819 if (isApprove())
1820 sMsg.Format(LoadResStr("IDS_TEXT_ITWASDECIDEDTO"), getDesc().getData());
1821 else
1822 sMsg.Format(LoadResStr("IDS_TEXT_ITWASDECIDEDNOTTO"), getDesc().getData());
1823 Log(sMsg.getData());
1824 // Approved?
1825 if (!isApprove()) return;
1826 // Do it
1827 C4ClientPlayerInfos *pInfos; C4PlayerInfo *pInfo;
1828 int iClient, iInfo;
1829 switch (getType())
1830 {
1831 case VT_Cancel:
1832 // Flag players
1833 if (!Game.GameOver)
1834 for (iClient = 0; (pInfos = Game.PlayerInfos.GetIndexedInfo(iClient)); iClient++)
1835 for (iInfo = 0; (pInfo = pInfos->GetPlayerInfo(iInfo)); iInfo++)
1836 if (!pInfo->IsRemoved())
1837 pInfo->SetVotedOut();
1838 // Abort the game
1839 Game.Abort(true);
1840 break;
1841 case VT_Kick:
1842 // Flag players
1843 pInfos = Game.PlayerInfos.GetInfoByClientID(getData());
1844 if (!Game.GameOver)
1845 if (pInfos)
1846 for (iInfo = 0; (pInfo = pInfos->GetPlayerInfo(iInfo)); iInfo++)
1847 if (!pInfo->IsRemoved())
1848 pInfo->SetVotedOut();
1849 // Remove the client
1850 if (::Control.isCtrlHost())
1851 {
1852 C4Client *pClient = Game.Clients.getClientByID(getData());
1853 if (pClient)
1854 Game.Clients.CtrlRemove(pClient, LoadResStr("IDS_VOTE_VOTEDOUT"));
1855 }
1856 // It is ourselves that have been voted out?
1857 if (getData() == Game.Clients.getLocalID())
1858 {
1859 // otherwise, we have been kicked by the host.
1860 // Do a regular disconnect and display reason in game over dialog, so the client knows what has happened!
1861 Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, FormatString(LoadResStr("IDS_ERR_YOUHAVEBEENREMOVEDBYVOTIN"), sMsg.getData()).getData());
1862 ::Network.Clear();
1863 // Game over immediately, so poor player won't continue game alone
1864 Game.DoGameOver();
1865 }
1866 break;
1867 default:
1868 // TODO
1869 break;
1870 }
1871 }
1872
CompileFunc(StdCompiler * pComp)1873 void C4ControlVoteEnd::CompileFunc(StdCompiler *pComp)
1874 {
1875 C4ControlVote::CompileFunc(pComp);
1876 }
1877
1878
1879 // *** C4ControlReInitScenario
1880
C4ControlReInitScenario()1881 C4ControlReInitScenario::C4ControlReInitScenario()
1882 {
1883 // Create a temp file with the scenario files to be loaded as a section
1884 char *tmp_fn = const_cast<char *>(Config.AtTempPath("ReinitSectionSave.ocs"));
1885 MakeTempFilename(tmp_fn);
1886 C4Group grp;
1887 grp.Open(tmp_fn, true);
1888 StdBuf buf;
1889 bool success = true;
1890 const char *section_components[] = { C4CFN_ScenarioCore, C4CFN_ScenarioObjects, C4CFN_ScenarioObjectsScript, C4CFN_Map, C4CFN_MapFg, C4CFN_MapBg };
1891 for (const char *section_component : section_components)
1892 {
1893 if (::Game.ScenarioFile.LoadEntry(section_component, &buf))
1894 {
1895 if (!grp.Add(section_component, buf, false, true)) success = false;
1896 }
1897 buf.Clear();
1898 }
1899 if (!grp.Save(false)) success = false;
1900 if (!success) return;
1901 // Move into buffer to be sent via queue
1902 success = data.LoadFromFile(tmp_fn);
1903 EraseFile(tmp_fn);
1904 if (!success) return;
1905 }
1906
CompileFunc(StdCompiler * comp)1907 void C4ControlReInitScenario::CompileFunc(StdCompiler *comp)
1908 {
1909 comp->Value(data);
1910 }
1911
Execute() const1912 void C4ControlReInitScenario::Execute() const
1913 {
1914 // Valid?
1915 if (!data.getSize()) return;
1916 // Store section group to temp file
1917 char *tmp_fn = const_cast<char *>(Config.AtTempPath("ReinitSection.ocs"));
1918 MakeTempFilename(tmp_fn);
1919 if (!data.SaveToFile(tmp_fn)) return;
1920 // Group to section
1921 const char *reinit_section_name = "EditorReloadSection";
1922 if (!::Game.CreateSectionFromTempFile(reinit_section_name, tmp_fn))
1923 {
1924 EraseFile(tmp_fn);
1925 return;
1926 }
1927 // Load that section!
1928 ::Game.LoadScenarioSection(reinit_section_name, C4S_REINIT_SCENARIO);
1929 }
1930
CompileFunc(StdCompiler * comp)1931 void C4ControlEditGraph::CompileFunc(StdCompiler *comp)
1932 {
1933 comp->Value(mkNamingAdapt(path, "Path", StdCopyStrBuf()));
1934 comp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(action), "Action", CEG_None));
1935 comp->Value(mkNamingAdapt(index, "Index", -1));
1936 comp->Value(mkNamingAdapt(x, "x", 0));
1937 comp->Value(mkNamingAdapt(y, "y", 0));
1938 C4ControlPacket::CompileFunc(comp);
1939 }
1940
Execute() const1941 void C4ControlEditGraph::Execute() const
1942 {
1943 // Forward to console for execution
1944 ::Console.EditGraphControl(this);
1945 }
1946