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