openclonk/src/control/C4Control.cpp

1437 lines
41 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, 2007 Matthes Bender
* Copyright (c) 2001-2002, 2004-2009 Sven Eberhardt
* Copyright (c) 2001 Michael Käser
* Copyright (c) 2004-2008 Peter Wortmann
* Copyright (c) 2006 Armin Burgmeier
* Copyright (c) 2006 Florian Groß
* Copyright (c) 2006, 2009-2010 Günther Brammer
* Copyright (c) 2009 Nicolas Hake
* Copyright (c) 2010 Benjamin Herr
* Copyright (c) 2010 Martin Plicht
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
/* Control packets contain all player input in the message queue */
#include <C4Include.h>
#include <C4Control.h>
#include <C4Object.h>
#include <C4GameSave.h>
#include <C4GameLobby.h>
#include <C4Random.h>
#include <C4Console.h>
#include <C4Log.h>
#include <C4GraphicsSystem.h>
#include <C4Player.h>
#include <C4RankSystem.h>
#include <C4RoundResults.h>
#include <C4PXS.h>
#include <C4MassMover.h>
#include <C4GameMessage.h>
#include <C4Landscape.h>
#include <C4Game.h>
#include <C4PlayerList.h>
#include <C4GameObjects.h>
#include <C4GameControl.h>
#ifndef NOAULDEBUG
#include <C4AulDebug.h>
#endif
#include <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 = BoundBy<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_AllowDebug: // allow debug mode?
{
bool fSet = !!iData;
// disable debug
if (!fSet && Game.DebugMode)
{
Game.DebugMode=false;
::GraphicsSystem.DeactivateDebugOutput();
}
// save flag, log
Game.Parameters.AllowDebug = fSet;
Log(fSet ? "Debug ON" : "Debug OFF");
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("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 (!fInternal && (iByClient != C4ClientIDHost) && !Console.Active) return;
// execute
C4Object *pObj = NULL;
C4AulScript *pScript;
if (iTargetObj == SCOPE_Console)
pScript = &::GameScript;
else if (iTargetObj == SCOPE_Global)
pScript = &::ScriptEngine;
else if ((pObj = ::Objects.SafeObjectPointer(iTargetObj)))
pScript = &(pObj->Def->Script);
else
// default: Fallback to global context
pScript = &::ScriptEngine;
C4Value rVal(pScript->DirectExec(pObj, szScript, "console script", false, C4AulScript::MAXSTRICT, fUseVarsFromCallerContext ? AulExec.GetContext(AulExec.GetContextDepth()-1) : NULL));
#ifndef NOAULDEBUG
C4AulDebug* pDebug;
if ( (pDebug = C4AulDebug::GetDebugger()) )
{
pDebug->ControlScriptEvaluated(szScript, rVal.GetDataString().getData());
}
#endif
// show messages
if (!fInternal)
{
// print script
if (pObj)
LogF("-> %s::%s", pObj->Def->GetName(), szScript);
else
LogF("-> %s", 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(fInternal, "Internal", false));
pComp->Value(mkNamingAdapt(fUseVarsFromCallerContext, "UseVarsFromCallerContext", false));
pComp->Value(mkNamingAdapt(Script, "Script", ""));
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 (C4ObjectLink *pLnk = Objs.First; pLnk; pLnk = pLnk->Next)
pObjNrs[i++] = pLnk->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(C4VInt(iPlr)));
else
pObj->Call(PSF_MouseSelection, &C4AulParSet(C4VInt(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(fRelease, "Release", false));
pComp->Value(mkNamingAdapt(ExtraData, "ExtraData", C4KeyEventData()));
pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(ControlItems), "Controls", ControlItemVec()));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlPlayerCommand
C4ControlPlayerCommand::C4ControlPlayerCommand(int32_t iPlr, int32_t iCmd, int32_t iX, int32_t iY,
C4Object *pTarget, C4Object *pTarget2, int32_t iData, int32_t iAddMode)
: iPlr(iPlr), iCmd(iCmd), iX(iX), iY(iY),
iTarget(pTarget ? pTarget->Number : 0), iTarget2(pTarget2 ? pTarget2->Number : 0),
iData(iData), iAddMode(iAddMode)
{
}
void C4ControlPlayerCommand::Execute() const
{
C4Player *pPlr=::Players.Get(iPlr);
if (pPlr)
{
pPlr->CountControl(C4Player::PCID_Command, iCmd+iX+iY+iTarget+iTarget2);
pPlr->ObjectCommand(iCmd,
::Objects.ObjectPointer(iTarget),
iX,iY,
::Objects.ObjectPointer(iTarget2),
C4Value(iData),
iAddMode);
}
}
void C4ControlPlayerCommand::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iPlr), "Player", -1));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iCmd), "Cmd", 0));
pComp->Value(mkNamingAdapt(iX, "X", 0));
pComp->Value(mkNamingAdapt(iY, "Y", 0));
pComp->Value(mkNamingAdapt(iTarget, "Target", 0));
pComp->Value(mkNamingAdapt(iTarget2, "Target2", 0));
pComp->Value(mkNamingAdapt(iData, "Data", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iAddMode), "AddMode", 0));
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 (C4ObjectLink *clnk=pPlr->Crew.First; clnk; clnk=clnk->Next)
cpx += fixtoi(clnk->Obj->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("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) 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;
}
}
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));
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 = 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 ressource
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 ressource
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), Script(szScript, true)
{
}
C4ControlEMMoveObject::~C4ControlEMMoveObject()
{
delete [] pObjects; pObjects = NULL;
}
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)
{
pObj->ForcePosition(pObj->fix_x+tx,pObj->fix_y+ty);
pObj->xdir=pObj->ydir=0;
pObj->Mobile = false;
}
}
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;
// local call? adjust selection then
if (fLocalCall) Console.EditCursor.GetSelection().Clear();
// perform duplication
C4Object *pObj;
for (int i=0; i<iObjectNum; ++i)
if ((pObj = ::Objects.SafeObjectPointer(pObjects[i])))
{
pObj = Game.CreateObject(pObj->GetPrototype(), pObj, pObj->Owner, pObj->GetX(), pObj->GetY());
if (pObj && fLocalCall) Console.EditCursor.GetSelection().Add(pObj, C4ObjectList::stNone);
}
// update status
if (fLocalCall)
{
Console.EditCursor.SetHold(true);
Console.EditCursor.OnSelectionChanged();
}
}
break;
case EMMO_Script:
{
if (!pObjects) return;
// execute script ...
C4ControlScript ScriptCtrl(Script.getData(), C4ControlScript::SCOPE_Global, false);
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->r);
}
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(Script, "Script", ""));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlEMDrawTool
C4ControlEMDrawTool::C4ControlEMDrawTool(C4ControlEMDrawAction eAction, int32_t iMode,
int32_t iX, int32_t iY, int32_t iX2, int32_t iY2, int32_t iGrade,
bool fIFT, const char *szMaterial, const char *szTexture)
: eAction(eAction), iMode(iMode), iX(iX), iY(iY), iX2(iX2), iY2(iY2), iGrade(iGrade),
fIFT(fIFT), Material(szMaterial, true), Texture(szTexture, true)
{
}
void C4ControlEMDrawTool::Execute() const
{
// set new mode
if (eAction == EMDT_SetMode)
{
Console.ToolsDlg.SetLandscapeMode(iMode, true);
return;
}
// check current mode
assert(::Landscape.Mode == iMode);
if (::Landscape.Mode != iMode) return;
// assert validity of parameters
if (!Material.getSize()) return;
const char *szMaterial = Material.getData(),
*szTexture = Texture.getData();
// perform action
switch (eAction)
{
case EMDT_Brush: // brush tool
if (!Texture.getSize()) break;
::Landscape.DrawBrush(iX, iY, iGrade, szMaterial, szTexture, fIFT);
break;
case EMDT_Line: // line tool
if (!Texture.getSize()) break;
::Landscape.DrawLine(iX,iY,iX2,iY2, iGrade, szMaterial, szTexture, fIFT);
break;
case EMDT_Rect: // rect tool
if (!Texture.getSize()) break;
::Landscape.DrawBox(iX,iY,iX2,iY2, iGrade, szMaterial, szTexture, fIFT);
break;
case EMDT_Fill: // fill tool
{
int iMat = ::MaterialMap.Get(szMaterial);
if (!MatValid(iMat)) return;
for (int cnt=0; cnt<iGrade; cnt++)
::Landscape.InsertMaterial(iMat,iX+Random(iGrade)-iGrade/2,iY+Random(iGrade)-iGrade/2);
}
break;
default:
break;
}
}
void C4ControlEMDrawTool::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntAdaptT<uint8_t>(eAction), "Action"));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iMode), "Mode", 0));
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(fIFT, "IFT", false));
pComp->Value(mkNamingAdapt(Material, "Material", ""));
pComp->Value(mkNamingAdapt(Texture, "Texture", ""));
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!
//if (pPlr) pPlr->CountControl(C4Player::PCID_Message, Message.GetHash());
// 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!
if (StartSoundEffect(szMessage, false, 100, NULL))
{
if (pLobby) pLobby->OnClientSound(Game.Clients.getClientByID(iByClient));
}
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 < 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);
}