openclonk/src/control/C4Control.cpp

1847 lines
54 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* Control packets contain all player input in the message queue */
#include "C4Include.h"
#include "control/C4Control.h"
#include "script/C4AulExec.h"
#include "object/C4Object.h"
#include "control/C4GameSave.h"
#include "gui/C4GameLobby.h"
#include "network/C4Network2Dialogs.h"
#include "lib/C4Random.h"
#include "editor/C4Console.h"
#include "lib/C4Log.h"
#include "game/C4GraphicsSystem.h"
#include "player/C4Player.h"
#include "player/C4RankSystem.h"
#include "control/C4RoundResults.h"
#include "landscape/C4PXS.h"
#include "landscape/C4MassMover.h"
#include "gui/C4GameMessage.h"
#include "landscape/C4Landscape.h"
#include "game/C4Game.h"
#include "game/C4GameScript.h"
#include "player/C4PlayerList.h"
#include "object/C4GameObjects.h"
#include "control/C4GameControl.h"
#include "gui/C4ScriptGuiWindow.h"
#include "gui/C4MessageInput.h"
#include "object/C4Def.h"
#include "object/C4DefList.h"
#ifndef NOAULDEBUG
#include "script/C4AulDebug.h"
#endif
#include "script/C4AulExec.h"
// *** C4ControlPacket
C4ControlPacket::C4ControlPacket()
: iByClient(::Control.ClientID())
{
}
C4ControlPacket::~C4ControlPacket()
{
}
bool C4ControlPacket::LocalControl() const
{
return iByClient == ::Control.ClientID();
}
void C4ControlPacket::SetByClient(int32_t inByClient)
{
iByClient = inByClient;
}
void C4ControlPacket::CompileFunc(StdCompiler *pComp)
{
// Section must be set by caller
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iByClient), "ByClient", -1));
}
// *** C4Control
C4Control::C4Control()
{
}
C4Control::~C4Control()
{
Clear();
}
void C4Control::Clear()
{
Pkts.Clear();
}
bool C4Control::PreExecute() const
{
bool fReady = true;
for (C4IDPacket *pPkt = firstPkt(); pPkt; pPkt = nextPkt(pPkt))
{
// recheck packet type: Must be control
if (pPkt->getPktType() & CID_First)
{
C4ControlPacket *pCtrlPkt = static_cast<C4ControlPacket *>(pPkt->getPkt());
if (pCtrlPkt)
fReady &= pCtrlPkt->PreExecute();
}
else
{
LogF("C4Control::PreExecute: WARNING: Ignoring packet type %2x (not control.)", pPkt->getPktType());
}
}
return fReady;
}
void C4Control::Execute() const
{
for (C4IDPacket *pPkt = firstPkt(); pPkt; pPkt = nextPkt(pPkt))
{
// recheck packet type: Must be control
if (pPkt->getPktType() & CID_First)
{
C4ControlPacket *pCtrlPkt = static_cast<C4ControlPacket *>(pPkt->getPkt());
if (pCtrlPkt)
pCtrlPkt->Execute();
}
else
{
LogF("C4Control::Execute: WARNING: Ignoring packet type %2x (not control.)", pPkt->getPktType());
}
}
}
void C4Control::PreRec(C4Record *pRecord) const
{
for (C4IDPacket *pPkt = firstPkt(); pPkt; pPkt = nextPkt(pPkt))
{
C4ControlPacket *pCtrlPkt = static_cast<C4ControlPacket *>(pPkt->getPkt());
if (pCtrlPkt)
pCtrlPkt->PreRec(pRecord);
}
}
void C4Control::CompileFunc(StdCompiler *pComp)
{
pComp->Value(Pkts);
}
// *** C4ControlSet
void C4ControlSet::Execute() const
{
switch (eValType)
{
case C4CVT_None: break;
case C4CVT_ControlRate: // adjust control rate
// host only
if (iByClient != C4ClientIDHost) break;
// adjust control rate
::Control.ControlRate += iData;
::Control.ControlRate = Clamp<int32_t>(::Control.ControlRate, 1, C4MaxControlRate);
Game.Parameters.ControlRate = ::Control.ControlRate;
// write back adjusted control rate to network settings
if (::Control.isCtrlHost() && !::Control.isReplay() && ::Control.isNetwork())
Config.Network.ControlRate = ::Control.ControlRate;
// always show msg
::GraphicsSystem.FlashMessage(FormatString(LoadResStr("IDS_NET_CONTROLRATE"),::Control.ControlRate,Game.FrameCounter).getData());
break;
case C4CVT_DisableDebug: // force debug mode disabled
{
if (Game.DebugMode)
{
Game.DebugMode=false;
::GraphicsSystem.DeactivateDebugOutput();
}
// save flag, log
Game.Parameters.AllowDebug = false;
C4Client *client = ::Game.Clients.getClientByID(iByClient);
LogF("Debug mode forced disabled by %s", client ? client->getName() : "<unknown client>");
break;
}
break;
case C4CVT_MaxPlayer:
// host only
if (iByClient != C4ClientIDHost) break;
// not in league
if (Game.Parameters.isLeague())
{
Log("/set maxplayer disabled in league!");
C4GUI::GUISound("UI::Error");
break;
}
// set it
Game.Parameters.MaxPlayers = iData;
LogF("MaxPlayer = %d", (int)Game.Parameters.MaxPlayers);
break;
case C4CVT_TeamDistribution:
// host only
if (iByClient != C4ClientIDHost) break;
// set new value
Game.Teams.SetTeamDistribution(static_cast<C4TeamList::TeamDist>(iData));
break;
case C4CVT_TeamColors:
// host only
if (!HostControl()) break;
// set new value
Game.Teams.SetTeamColors(!!iData);
break;
}
}
void C4ControlSet::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntAdapt(eValType), "Type", C4CVT_None));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iData), "Data", 0));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlScript
void C4ControlScript::Execute() const
{
const char *szScript = Script.getData();
// user script: from host only
if ((iByClient != C4ClientIDHost) && !Console.Active) return;
// only allow scripts when debug mode is not forbidden
if (!Game.Parameters.AllowDebug) return;
// execute
C4PropList *pPropList = NULL;
if (iTargetObj == SCOPE_Console)
pPropList = ::GameScript.GetPropList();
else if (iTargetObj == SCOPE_Global)
pPropList = ::ScriptEngine.GetPropList();
else if (!(pPropList = ::Objects.SafeObjectPointer(iTargetObj)))
// default: Fallback to global context
pPropList = ::ScriptEngine.GetPropList();
C4Value rVal(AulExec.DirectExec(pPropList, szScript, "console script", false, fUseVarsFromCallerContext ? AulExec.GetContext(AulExec.GetContextDepth()-1) : NULL));
#ifndef NOAULDEBUG
C4AulDebug* pDebug;
if ( (pDebug = C4AulDebug::GetDebugger()) )
{
pDebug->ControlScriptEvaluated(szScript, rVal.GetDataString().getData());
}
#endif
// show messages
// print script
LogF("-> %s::%s", pPropList->GetName(), szScript);
// print result
if (!LocalControl())
{
C4Network2Client *pClient = NULL;
if (::Network.isEnabled())
pClient = ::Network.Clients.GetClientByID(iByClient);
if (pClient)
LogF(" = %s (by %s)", rVal.GetDataString().getData(), pClient->getName());
else
LogF(" = %s (by client %d)", rVal.GetDataString().getData(), iByClient);
}
else
LogF(" = %s", rVal.GetDataString().getData());
}
void C4ControlScript::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(iTargetObj, "TargetObj", -1));
pComp->Value(mkNamingAdapt(fUseVarsFromCallerContext, "UseVarsFromCallerContext", false));
pComp->Value(mkNamingAdapt(Script, "Script", ""));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlMsgBoardReply
void C4ControlMsgBoardReply::Execute() const
{
C4Object *target_object = ::Objects.SafeObjectPointer(target);
C4Player *target_player = ::Players.Get(player);
// remove query
if (!target_player) return;
if (!target_player->RemoveMessageBoardQuery(target_object)) return;
// execute callback if answer present
if (!reply) return;
C4AulParSet pars(C4VString(reply), player);
if (target_object)
target_object->Call(PSF_InputCallback, &pars);
else
::GameScript.Call(PSF_InputCallback, &pars);
}
void C4ControlMsgBoardReply::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(target, "TargetObj", -1));
pComp->Value(mkNamingAdapt(player, "Player", NO_OWNER));
pComp->Value(mkNamingAdapt(reply, "Reply", nullptr));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlMsgBoardCmd
void C4ControlMsgBoardCmd::Execute() const
{
// don't handle this if the game isn't actually running
if (!::Game.IsRunning) return;
// fetch command script
C4MessageBoardCommand *cmd = ::MessageInput.GetCommand(command.getData());
if (!cmd) return;
StdCopyStrBuf script(cmd->Script);
// interpolate parameters as required
script.Replace("%player%", FormatString("%d", player).getData());
if (parameter)
{
script.Replace("%d", FormatString("%d", std::atoi(parameter.getData())).getData());
StdCopyStrBuf escaped_param(parameter);
escaped_param.EscapeString();
script.Replace("%s", escaped_param.getData());
}
// Run script
C4Value rv(::AulExec.DirectExec(::ScriptEngine.GetPropList(), script.getData(), "message board command"));
#ifndef NOAULDEBUG
C4AulDebug* pDebug = C4AulDebug::GetDebugger();
if (pDebug)
pDebug->ControlScriptEvaluated(script.getData(), rv.GetDataString().getData());
#endif
}
void C4ControlMsgBoardCmd::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(player, "Player", NO_OWNER));
pComp->Value(mkNamingAdapt(command, "Command"));
pComp->Value(mkNamingAdapt(parameter, "Parameter"));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlPlayerSelect
C4ControlPlayerSelect::C4ControlPlayerSelect(int32_t iPlr, const C4ObjectList &Objs, bool fIsAlt)
: iPlr(iPlr), fIsAlt(fIsAlt), iObjCnt(Objs.ObjectCount())
{
pObjNrs = new int32_t[iObjCnt];
int32_t i = 0;
for (C4Object *obj : Objs)
pObjNrs[i++] = obj->Number;
assert(i == iObjCnt);
}
void C4ControlPlayerSelect::Execute() const
{
// get player
C4Player *pPlr = ::Players.Get(iPlr);
if (!pPlr) return;
// Check object list
C4Object *pObj;
int32_t iControlChecksum = 0;
for (int32_t i = 0; i < iObjCnt; i++)
if ((pObj = ::Objects.SafeObjectPointer(pObjNrs[i])))
{
iControlChecksum += pObj->Number * (iControlChecksum+4787821);
// user defined object selection: callback to object
if (pObj->Category & C4D_MouseSelect)
{
if (fIsAlt)
pObj->Call(PSF_MouseSelectionAlt, &C4AulParSet(iPlr));
else
pObj->Call(PSF_MouseSelection, &C4AulParSet(iPlr));
}
}
// count
pPlr->CountControl(C4Player::PCID_Command, iControlChecksum);
}
void C4ControlPlayerSelect::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(iPlr, "Player", -1));
pComp->Value(mkNamingAdapt(fIsAlt, "IsAlt", false));
pComp->Value(mkNamingAdapt(iObjCnt, "ObjCnt", 0));
// Compile array
if (pComp->isCompiler())
{ delete[] pObjNrs; pObjNrs = new int32_t [iObjCnt]; }
pComp->Value(mkNamingAdapt(mkArrayAdapt(pObjNrs, iObjCnt), "Objs", 0));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlPlayerControl
void C4ControlPlayerControl::Execute() const
{
C4PlayerControl *pTargetCtrl = NULL;
if (iPlr == -1)
{
// neutral control packet: Execute in global control
}
else
{
// player-based control: Execute on control owned by player
C4Player *pPlr=::Players.Get(iPlr);
if (pPlr)
{
pTargetCtrl = &(pPlr->Control);
}
}
if (pTargetCtrl) pTargetCtrl->ExecuteControlPacket(this);
}
void C4ControlPlayerControl::ControlItem::CompileFunc(StdCompiler *pComp)
{
pComp->Value(iControl);
pComp->Separator();
pComp->Value(iTriggerMode);
}
void C4ControlPlayerControl::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iPlr), "Player", -1));
pComp->Value(mkNamingAdapt(mkIntAdapt(state), "State", 0));
pComp->Value(mkNamingAdapt(ExtraData, "ExtraData", C4KeyEventData()));
pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(ControlItems), "Controls", ControlItemVec()));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlPlayerMouse
C4ControlPlayerMouse *C4ControlPlayerMouse::Hover(const C4Player *player, const C4Object *target, const C4Object *old_target, const C4Object *drag)
{
assert(player != nullptr);
if (!player) return nullptr;
auto control = new C4ControlPlayerMouse();
control->action = CPM_Hover;
control->player = player->Number;
control->drag_obj = drag ? drag->Number : 0;
control->target_obj = target ? target->Number : 0;
control->old_obj = old_target ? old_target->Number : 0;
return control;
}
C4ControlPlayerMouse *C4ControlPlayerMouse::DragDrop(const C4Player *player, const C4Object *target, const C4Object *drag)
{
assert(player != nullptr);
if (!player) return nullptr;
auto control = new C4ControlPlayerMouse();
control->action = CPM_Drop;
control->player = player->Number;
control->drag_obj = drag ? drag->Number : 0;
control->target_obj = target ? target->Number : 0;
return control;
}
void C4ControlPlayerMouse::Execute() const
{
const char *callback_name = nullptr;
C4AulParSet pars(player);
switch (action)
{
case CPM_NoAction:
return;
case CPM_Hover:
// Mouse movement, object hover state changed
callback_name = PSF_MouseHover;
pars[1] = C4VObj(::Objects.SafeObjectPointer(old_obj));
pars[2] = C4VObj(::Objects.SafeObjectPointer(target_obj));
pars[3] = C4VObj(::Objects.SafeObjectPointer(drag_obj));
break;
case CPM_Drop:
// Drag/Drop operation
callback_name = PSF_MouseDragDrop;
pars[1] = C4VObj(::Objects.SafeObjectPointer(drag_obj));
pars[2] = C4VObj(::Objects.SafeObjectPointer(target_obj));
break;
}
// Do call
if (!callback_name) return;
::ScriptEngine.Call(callback_name, &pars);
}
void C4ControlPlayerMouse::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(action, "Action"));
pComp->Value(mkNamingAdapt(player, "Player", NO_OWNER));
pComp->Value(mkNamingAdapt(target_obj, "TargetObj"));
pComp->Value(mkNamingAdapt(drag_obj, "DragObj"));
pComp->Value(mkNamingAdapt(old_obj, "OldObj"));
}
// *** C4ControlMenuCommand
C4ControlMenuCommand::C4ControlMenuCommand(int32_t actionID, int32_t player, int32_t menuID, int32_t subwindowID, C4Object *target, int32_t actionType)
: actionID(actionID), player(player), menuID(menuID), subwindowID(subwindowID), target(target ? target->Number : 0), actionType(actionType)
{
}
void C4ControlMenuCommand::Execute() const
{
// invalid action? The action needs to be in bounds!
if (actionType < 0 || actionType >= C4ScriptGuiWindowPropertyName::_lastProp)
{
// this could only come from a malicious attempt to crash the engine!
Log("Warning: invalid action type for C4ControlMenuCommand!");
return;
}
C4ScriptGuiWindow *menu = ::Game.ScriptGuiRoot->GetChildByID(menuID);
// menu was closed?
if (!menu) return;
C4Object *obj = target ? ::Objects.ObjectPointer(target) : 0;
// target has been removed in the meantime? abort now
if (target && !obj) return;
menu->ExecuteCommand(actionID, player, subwindowID, actionType, obj);
}
void C4ControlMenuCommand::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntPackAdapt(actionID), "ID", -1));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(player), "Player", -1));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(menuID), "Menu", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(subwindowID), "Window", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(actionType), "Action", 0));
pComp->Value(mkNamingAdapt(target, "Target", 0));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlPlayerAction
C4ControlPlayerAction::C4ControlPlayerAction(const C4Player *source)
: action(CPA_NoAction), source(source ? source->Number : NO_OWNER), target(NO_OWNER), param_int(0)
{
}
C4ControlPlayerAction *C4ControlPlayerAction::Surrender(const C4Player *source)
{
assert(source);
C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
control->action = CPA_Surrender;
return control;
}
C4ControlPlayerAction *C4ControlPlayerAction::Eliminate(const C4Player *source)
{
assert(source);
C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
control->action = CPA_Eliminate;
return control;
}
C4ControlPlayerAction *C4ControlPlayerAction::ActivateGoal(const C4Player *source, const C4Object *goal)
{
assert(source);
assert(goal);
C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
control->action = CPA_ActivateGoal;
control->target = goal->Number;
return control;
}
C4ControlPlayerAction *C4ControlPlayerAction::ActivateGoalMenu(const C4Player *source)
{
assert(source);
C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
control->action = CPA_ActivateGoalMenu;
return control;
}
C4ControlPlayerAction *C4ControlPlayerAction::SetHostility(const C4Player *source, const C4Player *target, bool hostile)
{
assert(source);
assert(target);
C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
control->action = CPA_SetHostility;
control->target = target ? target->Number : NO_OWNER;
control->param_int = hostile;
return control;
}
C4ControlPlayerAction *C4ControlPlayerAction::SetTeam(const C4Player *source, int32_t team)
{
assert(source);
C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
control->action = CPA_SetTeam;
control->target = team;
return control;
}
C4ControlPlayerAction *C4ControlPlayerAction::InitScenarioPlayer(const C4Player *source, int32_t team)
{
assert(source);
C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
control->action = CPA_InitScenarioPlayer;
control->target = team;
return control;
}
C4ControlPlayerAction *C4ControlPlayerAction::InitPlayerControl(const C4Player *source, const C4PlayerControlAssignmentSet *ctrl_set)
{
assert(source);
C4ControlPlayerAction *control = new C4ControlPlayerAction(source);
control->action = CPA_InitPlayerControl;
if (ctrl_set)
{
control->param_str = ctrl_set->GetName();
if (ctrl_set->HasKeyboard())
control->param_int |= CPA_IPC_HasKeyboard;
if (ctrl_set->HasMouse())
control->param_int |= CPA_IPC_HasMouse;
if (ctrl_set->HasGamepad())
control->param_int |= CPA_IPC_HasGamepad;
}
return control;
}
void C4ControlPlayerAction::Execute() const
{
// The originating player must exist
C4Player *source_player = ::Players.Get(source);
if (!source_player) return;
switch (action)
{
case CPA_Surrender:
source_player->Surrender();
break;
case CPA_Eliminate:
source_player->Eliminate();
break;
case CPA_ActivateGoal:
{
// Make sure the object actually exists
C4Object *goal = ::Objects.SafeObjectPointer(target);
if (!goal) return;
// Call it
C4AulParSet pars(source_player->Number);
goal->Call("Activate", &pars);
break;
}
case CPA_ActivateGoalMenu:
// open menu
source_player->Menu.ActivateGoals(source_player->Number, source_player->LocalControl && !::Control.isReplay());
break;
case CPA_SetHostility:
{
// Can only set hostility towards a player that exists
C4Player *target_player = ::Players.Get(target);
if (!target_player) return;
// Proxy the hostility change through C4Aul, in case a script wants to capture it
C4AulParSet pars(source_player->Number, target_player->Number, param_int != 0);
::ScriptEngine.Call("SetHostility", &pars);
break;
}
case CPA_SetTeam:
{
// Make sure team switching is allowed in the first place
if (!::Game.Teams.IsTeamSwitchAllowed()) return;
// We can't change teams to one that doesn't exist
C4Team *team = ::Game.Teams.GetTeamByID(target);
if (!team && target != TEAMID_New) return;
// Proxy the team switch through C4Aul, in case a script wants to capture it
C4AulParSet pars(source_player->Number, target);
::ScriptEngine.Call("SetPlayerTeam", &pars);
break;
}
case CPA_InitScenarioPlayer:
{
// Proxy the call through C4Aul, in case a script wants to capture it
C4AulParSet pars(source_player->Number, target);
::ScriptEngine.Call("InitScenarioPlayer", &pars);
break;
}
case CPA_InitPlayerControl:
{
// Notify scripts about player control selection
const char *callback_name = PSF_InitializePlayerControl;
C4AulParSet pars(source_player->Number);
// If the player is using a control set, its name is stored in param_str
if (param_str)
{
pars[1] = C4VString(param_str);
pars[2] = C4VBool(CPA_IPC_HasKeyboard == (param_int & CPA_IPC_HasKeyboard));
pars[3] = C4VBool(CPA_IPC_HasMouse == (param_int & CPA_IPC_HasMouse));
pars[4] = C4VBool(CPA_IPC_HasGamepad == (param_int & CPA_IPC_HasGamepad));
}
::ScriptEngine.Call(callback_name, &pars);
break;
}
case CPA_NoAction: break;
}
}
void C4ControlPlayerAction::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(source, "Player", NO_OWNER));
const StdEnumEntry<Action> ActionNames[] =
{
{ "Surrender", CPA_Surrender },
{ "ActivateGoal", CPA_ActivateGoal },
{ "ActivateGoalMenu", CPA_ActivateGoalMenu },
{ "Eliminate", CPA_Eliminate },
{ "SetHostility", CPA_SetHostility },
{ "SetTeam", CPA_SetTeam },
{ "InitScenarioPlayer", CPA_InitScenarioPlayer },
{ "InitPlayerControl", CPA_InitPlayerControl },
{ NULL, CPA_NoAction }
};
pComp->Value(mkNamingAdapt(mkEnumAdapt<Action, int32_t>(action, ActionNames), "Action", CPA_NoAction));
pComp->Value(mkNamingAdapt(target, "Target", NO_OWNER));
pComp->Value(mkNamingAdapt(param_int, "DataI", 0));
pComp->Value(mkNamingAdapt(param_str, "DataS", nullptr));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlSyncCheck
C4ControlSyncCheck::C4ControlSyncCheck()
{
}
void C4ControlSyncCheck::Set()
{
Frame = Game.FrameCounter;
ControlTick = ::Control.ControlTick;
RandomCount = ::RandomCount;
AllCrewPosX = GetAllCrewPosX();
PXSCount = ::PXS.Count;
MassMoverIndex = ::MassMover.CreatePtr;
ObjectCount = ::Objects.ObjectCount();
ObjectEnumerationIndex = C4PropListNumbered::GetEnumerationIndex();
SectShapeSum = ::Objects.Sectors.getShapeSum();
}
int32_t C4ControlSyncCheck::GetAllCrewPosX()
{
int32_t cpx=0;
for (C4Player *pPlr=::Players.First; pPlr; pPlr=pPlr->Next)
for (C4Object *member : pPlr->Crew)
cpx += fixtoi(member->fix_x, 100);
return cpx;
}
void C4ControlSyncCheck::Execute() const
{
// control host?
if (::Control.isCtrlHost()) return;
// get the saved sync check data
C4ControlSyncCheck* pSyncCheck = ::Control.GetSyncCheck(Frame), &SyncCheck = *pSyncCheck;
if (!pSyncCheck)
{
::Control.SyncChecks.Add(CID_SyncCheck, new C4ControlSyncCheck(*this));
return;
}
// Not equal
if ( Frame != pSyncCheck->Frame
||(ControlTick != pSyncCheck->ControlTick && !::Control.isReplay())
|| RandomCount != pSyncCheck->RandomCount
|| AllCrewPosX != pSyncCheck->AllCrewPosX
|| PXSCount != pSyncCheck->PXSCount
|| MassMoverIndex != pSyncCheck->MassMoverIndex
|| ObjectCount != pSyncCheck->ObjectCount
|| ObjectEnumerationIndex != pSyncCheck->ObjectEnumerationIndex
|| SectShapeSum != pSyncCheck->SectShapeSum)
{
const char *szThis = "Client", *szOther = ::Control.isReplay() ? "Rec ":"Host";
if (iByClient != ::Control.ClientID())
{ const char *szTemp = szThis; szThis = szOther; szOther = szTemp; }
// Message
LogFatal("Network: Synchronization loss!");
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());
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());
StartSoundEffect("UI::SyncError");
#ifdef _DEBUG
// Debug safe
C4GameSaveNetwork SaveGame(false);
SaveGame.Save(Config.AtExePath("Desync.ocs"));
#endif
// league: Notify regular client disconnect within the game
::Network.LeagueNotifyDisconnect(C4ClientIDHost, C4LDR_Desync);
// Deactivate / end
if (::Control.isReplay())
Game.DoGameOver();
else if (::Control.isNetwork())
{
Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, "Network: Synchronization loss!");
::Network.Clear();
}
}
}
void C4ControlSyncCheck::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntPackAdapt(Frame), "Frame", -1));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(ControlTick), "ControlTick", 0));
pComp->Value(mkNamingAdapt(RandomCount, "RandomCount", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(AllCrewPosX), "AllCrewPosX", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(PXSCount), "PXSCount", 0));
pComp->Value(mkNamingAdapt(MassMoverIndex, "MassMoverIndex", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(ObjectCount), "ObjectCount", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(ObjectEnumerationIndex), "ObjectEnumerationIndex", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(SectShapeSum), "SectShapeSum", 0));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlSynchronize
void C4ControlSynchronize::Execute() const
{
Game.Synchronize(fSavePlrFiles);
if (fSyncClearance) Game.SyncClearance();
}
void C4ControlSynchronize::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(fSavePlrFiles, "SavePlrs", false));
pComp->Value(mkNamingAdapt(fSyncClearance, "SyncClear", false));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlClientJoin
void C4ControlClientJoin::Execute() const
{
// host only
if (iByClient != C4ClientIDHost) return;
// add client
C4Client *pClient = Game.Clients.Add(Core);
if (!pClient) return;
// log
LogF(LoadResStr("IDS_NET_CLIENT_JOIN"), Core.getName());
// lobby callback
C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
if (pLobby) pLobby->OnClientJoin(pClient);
// console callback
if (Console.Active) Console.UpdateMenus();
}
void C4ControlClientJoin::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(Core, "ClientCore"));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4Control
void C4ControlClientUpdate::Execute() const
{
// host only
if (iByClient != C4ClientIDHost && eType != CUT_SetReady) return;
// find client
C4Client *pClient = Game.Clients.getClientByID(iID);
if (!pClient) return;
StdCopyStrBuf strClient(LoadResStr(pClient->isLocal() ? "IDS_NET_LOCAL_CLIENT" : "IDS_NET_CLIENT"));
// do whatever specified
switch (eType)
{
case CUT_None: break;
case CUT_Activate:
// nothing to do?
if (pClient->isActivated() == !!iData) break;
// log
LogF(LoadResStr(iData ? "IDS_NET_CLIENT_ACTIVATED" : "IDS_NET_CLIENT_DEACTIVATED"), strClient.getData(), pClient->getName());
// activate/deactivate
pClient->SetActivated(!!iData);
// local?
if (pClient->isLocal())
::Control.SetActivated(!!iData);
break;
case CUT_SetObserver:
// nothing to do?
if (pClient->isObserver()) break;
// log
LogF(LoadResStr("IDS_NET_CLIENT_OBSERVE"), strClient.getData(), pClient->getName());
// set observer (will deactivate)
pClient->SetObserver();
// local?
if (pClient->isLocal())
::Control.SetActivated(false);
// remove all players ("soft kick")
::Players.RemoveAtClient(iID, true);
break;
case CUT_SetReady:
{
// nothing to do?
if (pClient->isLobbyReady() == !!iData) break;
// ready/unready (while keeping track of time)
time_t last_change_time = MinReadyAnnouncementDelay;
pClient->SetLobbyReady(!!iData, &last_change_time);
// log to others, but don't spam
if (last_change_time >= MinReadyAnnouncementDelay)
{
if (!pClient->isLocal())
{
LogF(LoadResStr(iData ? "IDS_NET_CLIENT_READY" : "IDS_NET_CLIENT_UNREADY"), strClient.getData(), pClient->getName());
}
// Also update icons
C4GameLobby::MainDlg *lobby = ::Network.GetLobby();
if (lobby) lobby->OnClientReadyStateChange();
}
break;
}
}
}
void C4ControlClientUpdate::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eType), "Type", CUT_None));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iID), "ClientID", C4ClientIDUnknown));
if (eType == CUT_Activate)
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iData), "Data", 0));
if (eType == CUT_SetReady)
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iData), "Data", 0));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlClientRemove
void C4ControlClientRemove::Execute() const
{
// host only
if (iByClient != C4ClientIDHost) return;
if (iID == C4ClientIDHost) return;
// find client
C4Client *pClient = Game.Clients.getClientByID(iID);
if (!pClient)
{
// TODO: in replays, client list is not yet synchronized
// remove players anyway
if (::Control.isReplay()) ::Players.RemoveAtClient(iID, true);
return;
}
StdCopyStrBuf strClient(LoadResStr(pClient->isLocal() ? "IDS_NET_LOCAL_CLIENT" : "IDS_NET_CLIENT"));
// local?
if (pClient->isLocal())
{
StdStrBuf sMsg;
sMsg.Format(LoadResStr("IDS_NET_CLIENT_REMOVED"), strClient.getData(), pClient->getName(), strReason.getData());
Log(sMsg.getData());
Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, sMsg.getData());
::Control.ChangeToLocal();
return;
}
// remove client
if (!Game.Clients.Remove(pClient)) return;
// log
LogF(LoadResStr("IDS_NET_CLIENT_REMOVED"), strClient.getData(), pClient->getName(), strReason.getData());
// remove all players
::Players.RemoveAtClient(iID, true);
// remove all resources
if (::Network.isEnabled())
::Network.ResList.RemoveAtClient(iID);
// lobby callback
C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
if (pLobby && ::pGUI) pLobby->OnClientPart(pClient);
// player list callback
::Network.Players.OnClientPart(pClient);
// console callback
if (Console.Active) Console.UpdateMenus();
// delete
delete pClient;
}
void C4ControlClientRemove::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iID), "ClientID", CUT_None));
pComp->Value(mkNamingAdapt(strReason, "Reason", ""));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlJoinPlayer
C4ControlJoinPlayer::C4ControlJoinPlayer(const char *szFilename, int32_t iAtClient, int32_t iIDInfo, const C4Network2ResCore &ResCore)
: Filename(szFilename, true), iAtClient(iAtClient),
idInfo(iIDInfo), fByRes(true), ResCore(ResCore)
{
}
C4ControlJoinPlayer::C4ControlJoinPlayer(const char *szFilename, int32_t iAtClient, int32_t iIDInfo)
: Filename(szFilename, true), iAtClient(iAtClient),
idInfo(iIDInfo), fByRes(false)
{
// load from file if filename is given - which may not be the case for script players
StdStrBuf filename;
if (szFilename && Reloc.LocateItem(szFilename, filename))
{
bool file_is_temp = false;
if (DirectoryExists(filename.getData()))
{
// the player file is unpacked - temp pack and read
StdStrBuf filename_buf;
filename_buf.Copy(Config.AtTempPath(GetFilenameOnly(filename.getData())));
MakeTempFilename(&filename_buf);
if (C4Group_PackDirectoryTo(filename.getData(), filename_buf.getData()))
{
filename.Take(filename_buf);
file_is_temp = true;
}
else
{
// pack failed
LogF("[!]Error packing player file %s to %s for join: Pack failed.", filename.getData(), filename_buf.getData());
assert(false);
}
}
bool fSuccess = PlrData.LoadFromFile(filename.getData());
if (!fSuccess)
{
LogF("[!]Error loading player file from %s.", filename.getData());
assert(false);
}
if (file_is_temp) EraseFile(filename.getData());
}
else if(szFilename)
{
LogF("[!]Error loading player file from %s.", szFilename);
assert(false);
}
}
void C4ControlJoinPlayer::Execute() const
{
const char *szFilename = Filename.getData();
// get client
C4Client *pClient = Game.Clients.getClientByID(iAtClient);
if (!pClient) return;
// get info
C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(idInfo);
if (!pInfo)
{
LogF("ERROR: Ghost player join: No info for %d", idInfo);
assert(false);
return;
}
else if (LocalControl())
{
// Local player: Just join from local file
Game.JoinPlayer(szFilename, iAtClient, pClient->getName(), pInfo);
}
else if (!fByRes)
{
if (PlrData.getSize())
{
// create temp file
StdStrBuf PlayerFilename; PlayerFilename.Format("%s-%s",pClient->getName(),GetFilename(szFilename));
PlayerFilename = Config.AtTempPath(PlayerFilename.getData());
// copy to it
if (PlrData.SaveToFile(PlayerFilename.getData()))
{
Game.JoinPlayer(PlayerFilename.getData(), iAtClient, pClient->getName(), pInfo);
EraseFile(PlayerFilename.getData());
}
}
else if (pInfo->GetType() == C4PT_Script)
{
// script players may join without data
Game.JoinPlayer(NULL, iAtClient, pClient->getName(), pInfo);
}
else
{
// no player data for user player present: Must not happen
LogF("ERROR: Ghost player join: No player data for %s", (const char*)pInfo->GetName());
assert(false);
return;
}
}
else if (::Control.isNetwork())
{
// Find resource
C4Network2Res::Ref pRes = ::Network.ResList.getRefRes(ResCore.getID());
if (pRes && pRes->isComplete())
Game.JoinPlayer(pRes->getFile(), iAtClient, pClient->getName(), pInfo);
}
else if (::Control.isReplay())
{
// Expect player in scenario file
StdStrBuf PlayerFilename; PlayerFilename.Format("%s" DirSep "%d-%s", Game.ScenarioFilename, ResCore.getID(), GetFilename(ResCore.getFileName()));
Game.JoinPlayer(PlayerFilename.getData(), iAtClient, pClient ? pClient->getName() : "Unknown", pInfo);
}
else
// Shouldn't happen
assert(false);
}
void C4ControlJoinPlayer::Strip()
{
// By resource? Can't touch player file, then.
if (fByRes) return;
// create temp file
StdStrBuf PlayerFilename; PlayerFilename = GetFilename(Filename.getData());
PlayerFilename = Config.AtTempPath(PlayerFilename.getData());
// Copy to it
if (PlrData.SaveToFile(PlayerFilename.getData()))
{
// open as group
C4Group Grp;
if (!Grp.Open(PlayerFilename.getData()))
{ EraseFile(PlayerFilename.getData()); return; }
// remove bigicon, if the file size is too large
size_t iBigIconSize=0;
if (Grp.FindEntry(C4CFN_BigIcon, NULL, &iBigIconSize))
if (iBigIconSize > C4NetResMaxBigicon*1024)
Grp.Delete(C4CFN_BigIcon);
Grp.Close();
// Set new data
StdBuf NewPlrData;
if (!NewPlrData.LoadFromFile(PlayerFilename.getData()))
{ EraseFile(PlayerFilename.getData()); return; }
PlrData = std::move(NewPlrData);
// Done
EraseFile(PlayerFilename.getData());
}
}
bool C4ControlJoinPlayer::PreExecute() const
{
// all data included in control packet?
if (!fByRes) return true;
// client lost?
if (!Game.Clients.getClientByID(iAtClient)) return true;
// network only
if (!::Control.isNetwork()) return true;
// search resource
C4Network2Res::Ref pRes = ::Network.ResList.getRefRes(ResCore.getID());
// doesn't exist? start loading
if (!pRes) { pRes = ::Network.ResList.AddByCore(ResCore, true); }
if (!pRes) return true;
// is loading or removed?
return !pRes->isLoading();
}
void C4ControlJoinPlayer::PreRec(C4Record *pRecord)
{
if (!pRecord) return;
if (fByRes)
{
// get local file by id
C4Network2Res::Ref pRes = ::Network.ResList.getRefRes(ResCore.getID());
if (!pRes || pRes->isRemoved()) return;
// create a copy of the resource
StdStrBuf szTemp; szTemp.Copy(pRes->getFile());
MakeTempFilename(&szTemp);
if (C4Group_CopyItem(pRes->getFile(), szTemp.getData()))
{
// add to record
StdStrBuf szTarget = FormatString("%d-%s", ResCore.getID(), GetFilename(ResCore.getFileName()));
pRecord->AddFile(szTemp.getData(), szTarget.getData(), true);
}
}
else
{
// player data raw within control: Will be used directly in record
}
}
void C4ControlJoinPlayer::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkNetFilenameAdapt(Filename), "Filename", ""));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iAtClient), "AtClient", -1));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(idInfo), "InfoID", -1));
pComp->Value(mkNamingAdapt(fByRes, "ByRes", false));
if (fByRes)
pComp->Value(mkNamingAdapt(ResCore, "ResCore"));
else
pComp->Value(mkNamingAdapt(PlrData, "PlrData"));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlEMMoveObject
C4ControlEMMoveObject::C4ControlEMMoveObject(C4ControlEMObjectAction eAction, C4Real tx, C4Real ty, C4Object *pTargetObj,
int32_t iObjectNum, int32_t *pObjects, const char *szScript)
: eAction(eAction), tx(tx), ty(ty), iTargetObj(pTargetObj ? pTargetObj->Number : 0),
iObjectNum(iObjectNum), pObjects(pObjects), StringParam(szScript, true)
{
}
C4ControlEMMoveObject *C4ControlEMMoveObject::CreateObject(const C4ID &id, C4Real x, C4Real y)
{
auto ctl = new C4ControlEMMoveObject(EMMO_Create, x, y, nullptr);
ctl->StringParam = id.ToString();
return ctl;
}
C4ControlEMMoveObject::~C4ControlEMMoveObject()
{
delete [] pObjects; pObjects = NULL;
}
void C4ControlEMMoveObject::MoveObject(C4Object *moved_object) const
{
// move given object by this->tx/ty and do callbacks
if (!moved_object || !moved_object->Status) return;
int32_t old_x = moved_object->GetX(), old_y = moved_object->GetY();
C4Real tx = this->tx;
if (moved_object->Def->NoHorizontalMove) tx = Fix0;
moved_object->ForcePosition(moved_object->fix_x + tx, moved_object->fix_y + ty);
moved_object->xdir = moved_object->ydir = 0;
moved_object->Mobile = false;
C4AulParSet pars(C4VInt(old_x), C4VInt(old_y));
moved_object->Call(PSF_EditCursorMoved, &pars);
}
void C4ControlEMMoveObject::Execute() const
{
bool fLocalCall = LocalControl();
switch (eAction)
{
case EMMO_Move:
{
if (!pObjects) break;
// move all given objects
C4Object *pObj;
for (int i=0; i<iObjectNum; ++i)
if ((pObj = ::Objects.SafeObjectPointer(pObjects[i])))
if (pObj->Status)
{
MoveObject(pObj);
// attached objects: Also move attachment target
while (pObj->GetProcedure() == DFA_ATTACH)
{
pObj = pObj->Action.Target;
if (!pObj) break; // leftover action cancelled next frame
for (int j = 0; j < iObjectNum; ++j) if (pObjects[j] == pObj->Number) { pObj = NULL; break; } // ensure we aren't moving twice
if (!pObj) break;
MoveObject(pObj);
}
}
}
break;
case EMMO_Enter:
{
if (!pObjects) break;
// enter all given objects into target
C4Object *pObj, *pTarget = ::Objects.SafeObjectPointer(iTargetObj);
if (pTarget)
for (int i=0; i<iObjectNum; ++i)
if ((pObj = ::Objects.SafeObjectPointer(pObjects[i])))
pObj->Enter(pTarget);
}
break;
case EMMO_Duplicate:
{
if (!pObjects) break;
// perform duplication
C4Object *pOldObj, *pObj;
for (int i=0; i<iObjectNum; ++i)
if ((pOldObj = ::Objects.SafeObjectPointer(pObjects[i])))
{
pObj = Game.CreateObject(pOldObj->GetPrototype(), pOldObj, pOldObj->Owner, pOldObj->GetX(), pOldObj->GetY());
if (pObj && pObj->Status)
{
// local call? adjust selection then
// do callbacks for all clients for sync reasons
if (fLocalCall) Console.EditCursor.GetSelection().Add(pObj, C4ObjectList::stNone);
C4AulParSet pars(C4VObj(pObj));
if (pOldObj->Status) pOldObj->Call(PSF_EditCursorDeselection, &pars);
if (pObj->Status) pObj->Call(PSF_EditCursorSelection);
}
}
// update status
if (fLocalCall)
{
if (fLocalCall)
for (int i = 0; i<iObjectNum; ++i)
if ((pOldObj = ::Objects.SafeObjectPointer(pObjects[i])))
Console.EditCursor.GetSelection().Remove(pOldObj);
Console.EditCursor.SetHold(true);
Console.EditCursor.OnSelectionChanged();
}
}
break;
case EMMO_Script:
{
if (!pObjects) return;
// execute script ...
C4ControlScript ScriptCtrl(StringParam.getData(), C4ControlScript::SCOPE_Global);
ScriptCtrl.SetByClient(iByClient);
// ... for each object in selection
for (int i=0; i<iObjectNum; ++i)
{
ScriptCtrl.SetTargetObj(pObjects[i]);
ScriptCtrl.Execute();
}
}
break;
case EMMO_Remove:
{
if (!pObjects) return;
// remove all objects
C4Object *pObj;
for (int i=0; i<iObjectNum; ++i)
if ((pObj = ::Objects.SafeObjectPointer(pObjects[i])))
pObj->AssignRemoval();
}
break;
case EMMO_Exit:
{
if (!pObjects) return;
// exit all objects
C4Object *pObj;
for (int i=0; i<iObjectNum; ++i)
if ((pObj = ::Objects.SafeObjectPointer(pObjects[i])))
pObj->Exit(pObj->GetX(), pObj->GetY(), pObj->GetR());
}
break;
case EMMO_Select:
{
// callback to script
C4Object *target = ::Objects.SafeObjectPointer(iTargetObj);
if (!target) return;
target->Call(PSF_EditCursorSelection);
}
break;
case EMMO_Deselect:
{
// callback to script
C4Object *target = ::Objects.SafeObjectPointer(iTargetObj), *next_selection = NULL;
if (!target) return;
// next selection may be passed to EditCursorSelection
if (iObjectNum) next_selection = ::Objects.SafeObjectPointer(pObjects[0]);
C4AulParSet pars(C4VObj(next_selection));
target->Call(PSF_EditCursorDeselection, &pars);
}
break;
case EMMO_Create:
{
::Game.CreateObject(C4ID(StringParam), nullptr, NO_OWNER, fixtoi(tx), fixtoi(ty));
}
break;
}
// update property dlg & status bar
if (fLocalCall)
Console.EditCursor.OnSelectionChanged();
}
void C4ControlEMMoveObject::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eAction), "Action"));
pComp->Value(mkNamingAdapt(tx, "tx", 0));
pComp->Value(mkNamingAdapt(ty, "ty", 0));
pComp->Value(mkNamingAdapt(iTargetObj, "TargetObj", -1));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iObjectNum), "ObjectNum", 0));
if (pComp->isCompiler()) { delete [] pObjects; pObjects = new int32_t [iObjectNum]; }
pComp->Value(mkNamingAdapt(mkArrayAdapt(pObjects, iObjectNum), "Objs", -1));
if (eAction == EMMO_Script)
pComp->Value(mkNamingAdapt(StringParam, "Script", ""));
else if (eAction == EMMO_Create)
pComp->Value(mkNamingAdapt(StringParam, "ID", ""));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlEMDrawTool
C4ControlEMDrawTool::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)
: eAction(eAction), iMode(iMode), iX(iX), iY(iY), iX2(iX2), iY2(iY2), iGrade(iGrade),
Material(szMaterial, true), Texture(szTexture, true),
BackMaterial(szBackMaterial, true), BackTexture(szBackTexture, true)
{
}
void C4ControlEMDrawTool::Execute() const
{
// set new mode
if (eAction == EMDT_SetMode)
{
Console.ToolsDlg.SetLandscapeMode(iMode, true);
return;
}
// check current mode
assert(::Landscape.GetMode() == iMode);
if (::Landscape.GetMode() != iMode) return;
// assert validity of parameters
if (!Material.getSize()) return;
const char *szMaterial = Material.getData(),
*szTexture = Texture.getData();
const char *szBackMaterial = BackMaterial.getData(),
*szBackTexture = BackTexture.getData();
// perform action
switch (eAction)
{
case EMDT_Brush: // brush tool
if (!Texture.getSize()) break;
::Landscape.DrawBrush(iX, iY, iGrade, szMaterial, szTexture, szBackMaterial, szBackTexture);
break;
case EMDT_Line: // line tool
if (!Texture.getSize()) break;
::Landscape.DrawLine(iX,iY,iX2,iY2, iGrade, szMaterial, szTexture, szBackMaterial, szBackTexture);
break;
case EMDT_Rect: // rect tool
if (!Texture.getSize()) break;
::Landscape.DrawBox(iX,iY,iX2,iY2, iGrade, szMaterial, szTexture, szBackMaterial, szBackTexture);
break;
case EMDT_Fill: // fill tool
{
int iMat = ::MaterialMap.Get(szMaterial);
if (!MatValid(iMat)) return;
for (int cnt=0; cnt<iGrade; cnt++)
{
int32_t itX=iX+Random(iGrade)-iGrade/2;
int32_t itY=iY+Random(iGrade)-iGrade/2;
::Landscape.InsertMaterial(iMat,&itX,&itY);
}
}
break;
default:
break;
}
}
void C4ControlEMDrawTool::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eAction), "Action"));
pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(iMode), "Mode", LandscapeMode::Undefined));
pComp->Value(mkNamingAdapt(iX, "X", 0));
pComp->Value(mkNamingAdapt(iY, "Y", 0));
pComp->Value(mkNamingAdapt(iX2, "X2", 0));
pComp->Value(mkNamingAdapt(iY2, "Y2", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iGrade), "Grade", 0));
pComp->Value(mkNamingAdapt(Material, "Material", ""));
pComp->Value(mkNamingAdapt(Texture, "Texture", ""));
pComp->Value(mkNamingAdapt(BackMaterial, "BackMaterial", ""));
pComp->Value(mkNamingAdapt(BackTexture, "BackTexture", ""));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlMessage
void C4ControlMessage::Execute() const
{
const char *szMessage = Message.getData();
// get player
C4Player *pPlr = (iPlayer < 0 ? NULL : ::Players.Get(iPlayer));
// security
if (pPlr && pPlr->AtClient != iByClient) return;
// do not record message as control, because it is not synced!
// get lobby to forward to
C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
StdStrBuf str;
switch (eType)
{
case C4CMT_Normal:
case C4CMT_Me:
// log it
if (pPlr)
{
if (pPlr->AtClient != iByClient) break;
str.Format((eType == C4CMT_Normal ? "<c %x><<i></i>%s> %s</c>" : "<c %x> * %s %s</c>"),
pPlr->ColorDw, pPlr->GetName(), szMessage);
}
else
{
C4Client *pClient = Game.Clients.getClientByID(iByClient);
str.Format((eType == C4CMT_Normal ? "<%s> %s" : " * %s %s"),
pClient ? pClient->getNick() : "???", szMessage);
}
// 2 lobby
if (pLobby)
pLobby->OnMessage(Game.Clients.getClientByID(iByClient), str.getData());
// or 2 log
else
Log(str.getData());
break;
case C4CMT_Say:
// show as game message above player cursor
if (pPlr && pPlr->Cursor)
{
if (Game.C4S.Head.Film == C4SFilm_Cinematic)
{
StdStrBuf sMessage; sMessage.Format("<%s> %s", pPlr->Cursor->GetName(), szMessage);
uint32_t dwClr = pPlr->Cursor->Color;
if (!dwClr) dwClr = 0xff;
GameMsgObjectDw(sMessage.getData(), pPlr->Cursor, dwClr|0xff000000);
}
else
GameMsgObjectDw(szMessage, pPlr->Cursor, pPlr->ColorDw|0xff000000);
}
break;
case C4CMT_Team:
{
// show only if sending player is allied with a local one
if (pPlr)
{
// for running game mode, check actual hostility
C4Player *pLocalPlr;
for (int cnt = 0; (pLocalPlr = ::Players.GetLocalByIndex(cnt)); cnt++)
if (!Hostile(pLocalPlr->Number, iPlayer))
break;
if (pLocalPlr) Log(FormatString("<c %x>{%s} %s</c>", pPlr->ColorDw, pPlr->GetName(), szMessage).getData());
}
else if (pLobby)
{
// in lobby mode, no player has joined yet - check teams of unjoined players
if (!Game.PlayerInfos.HasSameTeamPlayers(iByClient, Game.Clients.getLocalID())) break;
// OK - permit message
C4Client *pClient = Game.Clients.getClientByID(iByClient);
pLobby->OnMessage(Game.Clients.getClientByID(iByClient),
FormatString("{%s} %s", pClient ? pClient->getNick() : "???", szMessage).getData());
}
}
break;
case C4CMT_Private:
{
if (!pPlr) break;
// show only if the target player is local
C4Player *pLocalPlr;
for (int cnt = 0; (pLocalPlr = ::Players.GetLocalByIndex(cnt)); cnt++)
if (pLocalPlr->ID == iToPlayer)
break;
if (pLocalPlr)
{
Log(FormatString("<c %x>[%s] %s</c>", pPlr->ColorDw, pPlr->GetName(), szMessage).getData());
}
}
break;
case C4CMT_Sound:
{
// tehehe, sound!
C4Client *singer = Game.Clients.getClientByID(iByClient);
if (!singer || !singer->IsIgnored())
if (!StartSoundEffect(szMessage, false, 100, NULL))
// probably wrong sound file name
break;
// Sound icon even if someone you ignored just tried. So you know you still need to ignore.
if (pLobby) pLobby->OnClientSound(singer);
if (C4Network2ClientListDlg::GetInstance()) C4Network2ClientListDlg::GetInstance()->OnSound(singer);
break;
}
case C4CMT_Alert:
// notify inactive users
Application.NotifyUserIfInactive();
break;
case C4CMT_System:
// sender must be host
if (!HostControl()) break;
// show
LogF("Network: %s", szMessage);
break;
}
}
void C4ControlMessage::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eType), "Type", C4CMT_Normal));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iPlayer), "Player", -1));
if (eType == C4CMT_Private)
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iToPlayer), "ToPlayer", -1));
pComp->Value(mkNamingAdapt(Message, "Message", ""));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlPlayerInfo
void C4ControlPlayerInfo::Execute() const
{
// join to player info list
// replay and local control: direct join
if (::Control.isReplay() || !::Control.isNetwork())
{
// add info directly
Game.PlayerInfos.AddInfo(new C4ClientPlayerInfos(PlrInfo));
// make sure team list reflects teams set in player infos
Game.Teams.RecheckPlayers();
// replay: actual player join packet will follow
// offline game: Issue the join
if (::Control.isLocal())
Game.PlayerInfos.LocalJoinUnjoinedPlayersInQueue();
}
else
// network:
::Network.Players.HandlePlayerInfo(PlrInfo);
}
void C4ControlPlayerInfo::CompileFunc(StdCompiler *pComp)
{
pComp->Value(PlrInfo);
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlRemovePlr
void C4ControlRemovePlr::Execute() const
{
// host only
if (iByClient != C4ClientIDHost) return;
// remove
::Players.Remove(iPlr, fDisconnected, false);
}
void C4ControlRemovePlr::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iPlr), "Plr", -1));
pComp->Value(mkNamingAdapt(fDisconnected, "Disconnected", false));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlDebugRec
void C4ControlDebugRec::Execute() const
{
}
void C4ControlDebugRec::CompileFunc(StdCompiler *pComp)
{
pComp->Value(Data);
}
// *** C4ControlVote
StdStrBuf C4ControlVote::getDesc() const
{
// Describe action
StdStrBuf Action;
switch (eType)
{
case VT_Cancel:
Action = LoadResStr("IDS_VOTE_CANCELTHEROUND"); break;
case VT_Kick:
if (iData == iByClient)
Action = LoadResStr("IDS_VOTE_LEAVETHEGAME");
else
{
C4Client *pTargetClient = Game.Clients.getClientByID(iData);
Action.Format(LoadResStr("IDS_VOTE_KICKCLIENT"), pTargetClient ? pTargetClient->getName() : "???");
}
break;
case VT_Pause:
if (iData)
Action = LoadResStr("IDS_TEXT_PAUSETHEGAME");
else
Action = LoadResStr("IDS_TEXT_UNPAUSETHEGAME");
break;
default:
Action = "perform some mysterious action"; break;
}
return Action;
}
StdStrBuf C4ControlVote::getDescWarning() const
{
StdStrBuf Warning;
switch (eType)
{
case VT_Cancel:
Warning = LoadResStr("IDS_TEXT_WARNINGIFTHEGAMEISCANCELL"); break;
case VT_Kick:
Warning = LoadResStr("IDS_TEXT_WARNINGNOLEAGUEPOINTSWILL"); break;
default:
Warning = ""; break;
}
return Warning;
}
void C4ControlVote::Execute() const
{
// Log
C4Client *pClient = Game.Clients.getClientByID(iByClient);
if (fApprove)
LogF(LoadResStr("IDS_VOTE_WANTSTO"), pClient->getName(), getDesc().getData());
else
LogF(LoadResStr("IDS_VOTE_DOESNOTWANTTO"), pClient->getName(), getDesc().getData());
// Save vote back
if (::Network.isEnabled())
::Network.AddVote(*this);
// Vote done?
if (::Control.isCtrlHost())
{
// Count votes
int32_t iPositive = 0, iNegative = 0, iVotes = 0;
// If there are no teams, count as if all were in the same team
// (which happens to be equivalent to "everyone is in his own team" here)
for (int32_t i = 0; i < std::max<int32_t>(Game.Teams.GetTeamCount(), 1); i++)
{
C4Team *pTeam = Game.Teams.GetTeamByIndex(i);
// Votes for this team
int32_t iPositiveTeam = 0, iNegativeTeam = 0, iVotesTeam = 0;
// Check each player
for (int32_t j = 0; j < (pTeam ? pTeam->GetPlayerCount() : Game.PlayerInfos.GetPlayerCount()); j++)
{
int32_t iClientID = C4ClientIDUnknown;
C4PlayerInfo *pNfo;
if (!pTeam)
{
pNfo = Game.PlayerInfos.GetPlayerInfoByIndex(j);
if (!pNfo) continue; // shouldn't happen
iClientID = Game.PlayerInfos.GetClientInfoByPlayerID(pNfo->GetID())->GetClientID();
}
else
{
pNfo = Game.PlayerInfos.GetPlayerInfoByID(pTeam->GetIndexedPlayer(j), &iClientID);
if (!pNfo) continue; // shouldn't happen
}
if (iClientID < 0) continue;
// Client disconnected?
if (!Game.Clients.getClientByID(iClientID)) continue;
// Player eliminated or never joined?
if (!pNfo->IsJoined()) continue;
// Okay, this player can vote
iVotesTeam++;
// Search vote of this client on the subject
C4IDPacket *pPkt; C4ControlVote *pVote;
if ((pPkt = ::Network.GetVote(iClientID, eType, iData)))
if ((pVote = static_cast<C4ControlVote *>(pPkt->getPkt())))
{
if (pVote->isApprove())
iPositiveTeam++;
else
iNegativeTeam++;
}
}
// Any votes available?
if (iVotesTeam)
{
iVotes++;
// Approval by team? More then 50% needed
if (iPositiveTeam * 2 > iVotesTeam)
iPositive++;
// Disapproval by team? More then 50% needed
else if (iNegativeTeam * 2 >= iVotesTeam)
iNegative++;
}
}
// Approval? More then 50% needed
if (iPositive * 2 > iVotes)
::Control.DoInput(CID_VoteEnd,
new C4ControlVoteEnd(eType, true, iData),
CDT_Sync);
// Disapproval?
else if (iNegative * 2 >= iVotes)
::Control.DoInput(CID_VoteEnd,
new C4ControlVoteEnd(eType, false, iData),
CDT_Sync);
}
}
void C4ControlVote::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eType), "Type", VT_None));
pComp->Value(mkNamingAdapt(fApprove, "Approve", true));
pComp->Value(mkNamingAdapt(iData, "Data", 0));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlVoteEnd
void C4ControlVoteEnd::Execute() const
{
// End the voting process
if (!HostControl()) return;
if (::Network.isEnabled())
::Network.EndVote(getType(), isApprove(), getData());
// Log
StdStrBuf sMsg;
if (isApprove())
sMsg.Format(LoadResStr("IDS_TEXT_ITWASDECIDEDTO"), getDesc().getData());
else
sMsg.Format(LoadResStr("IDS_TEXT_ITWASDECIDEDNOTTO"), getDesc().getData());
Log(sMsg.getData());
// Approved?
if (!isApprove()) return;
// Do it
C4ClientPlayerInfos *pInfos; C4PlayerInfo *pInfo;
int iClient, iInfo;
switch (getType())
{
case VT_Cancel:
// Flag players
if (!Game.GameOver)
for (iClient = 0; (pInfos = Game.PlayerInfos.GetIndexedInfo(iClient)); iClient++)
for (iInfo = 0; (pInfo = pInfos->GetPlayerInfo(iInfo)); iInfo++)
if (!pInfo->IsRemoved())
pInfo->SetVotedOut();
// Abort the game
Game.Abort(true);
break;
case VT_Kick:
// Flag players
pInfos = Game.PlayerInfos.GetInfoByClientID(getData());
if (!Game.GameOver)
if (pInfos)
for (iInfo = 0; (pInfo = pInfos->GetPlayerInfo(iInfo)); iInfo++)
if (!pInfo->IsRemoved())
pInfo->SetVotedOut();
// Remove the client
if (::Control.isCtrlHost())
{
C4Client *pClient = Game.Clients.getClientByID(getData());
if (pClient)
Game.Clients.CtrlRemove(pClient, LoadResStr("IDS_VOTE_VOTEDOUT"));
}
// It is ourselves that have been voted out?
if (getData() == Game.Clients.getLocalID())
{
// otherwise, we have been kicked by the host.
// Do a regular disconnect and display reason in game over dialog, so the client knows what has happened!
Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, FormatString(LoadResStr("IDS_ERR_YOUHAVEBEENREMOVEDBYVOTIN"), sMsg.getData()).getData());
::Network.Clear();
// Game over immediately, so poor player won't continue game alone
Game.DoGameOver();
}
break;
default:
// TODO
break;
}
}
void C4ControlVoteEnd::CompileFunc(StdCompiler *pComp)
{
C4ControlVote::CompileFunc(pComp);
}