openclonk/src/control/C4GameParameters.cpp

578 lines
15 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* 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.
*/
#include "C4Include.h"
#include "control/C4GameParameters.h"
#include "lib/C4Log.h"
#include "c4group/C4Components.h"
#include "object/C4Def.h"
#include "object/C4DefList.h"
#include "game/C4Game.h"
#include "network/C4Network2.h"
// *** C4GameRes
C4GameRes::C4GameRes()
: eType(NRT_Null), pResCore(NULL), pNetRes(NULL)
{
}
C4GameRes::C4GameRes(const C4GameRes &Res)
: eType(Res.getType()), File(Res.getFile()), pResCore(Res.getResCore()), pNetRes(Res.getNetRes())
{
if (pResCore && !pNetRes)
pResCore = new C4Network2ResCore(*pResCore);
}
C4GameRes::~C4GameRes()
{
Clear();
}
C4GameRes &C4GameRes::operator = (const C4GameRes &Res)
{
Clear();
eType = Res.getType();
File = Res.getFile();
pResCore = Res.getResCore();
pNetRes = Res.getNetRes();
if (pResCore && !pNetRes)
pResCore = new C4Network2ResCore(*pResCore);
return *this;
}
void C4GameRes::Clear()
{
eType = NRT_Null;
File.Clear();
if (pResCore && !pNetRes)
delete pResCore;
pResCore = NULL;
pNetRes = NULL;
}
void C4GameRes::SetFile(C4Network2ResType enType, const char *sznFile)
{
assert(!pNetRes && !pResCore);
eType = enType;
File = sznFile;
}
void C4GameRes::SetResCore(C4Network2ResCore *pnResCore)
{
assert(!pNetRes);
pResCore = pnResCore;
eType = pResCore->getType();
}
void C4GameRes::SetNetRes(C4Network2Res::Ref pnNetRes)
{
Clear();
pNetRes = pnNetRes;
eType = pNetRes->getType();
File = pNetRes->getFile();
pResCore = &pNetRes->getCore();
}
void C4GameRes::CompileFunc(StdCompiler *pComp)
{
bool fCompiler = pComp->isCompiler();
// Clear previous data for compiling
if (fCompiler) Clear();
// Core is needed to decompile something meaningful
if (!fCompiler) assert(pResCore);
// De-/Compile core
pComp->Value(mkPtrAdaptNoNull(const_cast<C4Network2ResCore * &>(pResCore)));
// Compile: Set type accordingly
if (fCompiler)
eType = pResCore->getType();
}
bool C4GameRes::Publish(C4Network2ResList *pNetResList)
{
assert(isPresent());
// Already present?
if (pNetRes) return true;
// determine whether it's loadable
bool fAllowUnloadable = false;
if (eType == NRT_Definitions) fAllowUnloadable = true;
// Add to network resource list
C4Network2Res::Ref pNetRes = pNetResList->AddByFile(File.getData(), false, eType, -1, NULL, fAllowUnloadable);
if (!pNetRes) return false;
// Set resource
SetNetRes(pNetRes);
return true;
}
bool C4GameRes::Load(C4Network2ResList *pNetResList)
{
assert(pResCore);
// Already present?
if (pNetRes) return true;
// Add to network resource list
C4Network2Res::Ref pNetRes = pNetResList->AddByCore(*pResCore);
if (!pNetRes) return false;
// Set resource
SetNetRes(pNetRes);
return true;
}
bool C4GameRes::InitNetwork(C4Network2ResList *pNetResList)
{
// Already initialized?
if (getNetRes())
return true;
// Present? [Host]
if (isPresent())
{
// Publish on network
if (!Publish(pNetResList))
{
LogFatal(FormatString(LoadResStr("IDS_NET_NOFILEPUBLISH"), getFile()).getData());
return false;
}
}
// Got a core? [Client]
else if (pResCore)
{
// Search/Load it
if (!Load(pNetResList))
{
// Give some hints to why this might happen.
const char *szFilename = pResCore->getFileName();
if (!pResCore->isLoadable())
if (pResCore->getType() == NRT_System)
LogFatal(FormatString(LoadResStr("IDS_NET_NOSAMESYSTEM"), szFilename).getData());
else
LogFatal(FormatString(LoadResStr("IDS_NET_NOSAMEANDTOOLARGE"), szFilename).getData());
// Should not happen
else
LogFatal(FormatString(LoadResStr("IDS_NET_NOVALIDCORE"), szFilename).getData());
return false;
}
}
// Okay
return true;
}
void C4GameRes::CalcHash()
{
if (!pNetRes) return;
pNetRes->CalculateSHA();
}
// *** C4GameResList
C4GameResList &C4GameResList::operator = (const C4GameResList &List)
{
Clear();
// Copy the list
iResCount = iResCapacity = List.iResCount;
pResList = new C4GameRes *[iResCapacity];
for (int i = 0; i < iResCount; i++)
pResList[i] = new C4GameRes(*List.pResList[i]);
return *this;
}
C4GameRes *C4GameResList::iterRes(C4GameRes *pLast, C4Network2ResType eType)
{
for (int i = 0; i < iResCount; i++)
if (!pLast)
{
if (eType == NRT_Null || pResList[i]->getType() == eType)
return pResList[i];
}
else if (pLast == pResList[i])
pLast = NULL;
return NULL;
}
void C4GameResList::Clear()
{
// clear them
for (int32_t i = 0; i < iResCount; i++)
delete pResList[i];
delete [] pResList;
pResList = NULL;
iResCount = iResCapacity = 0;
}
void C4GameResList::LoadFoldersWithLocalDefs(const char *szPath)
{
// Scan path for folder names
int32_t iBackslash;
char szFoldername[_MAX_PATH+1];
C4Group hGroup;
#ifdef _WIN32
// Allow both backward and forward slashes when searching because the path
// may be given with forward slashes. We would skip loading some definitions
// if we didn't handle this properly and the user would have no clue what was
// going on. See also http://forum.openclonk.org/topic_show.pl?tid=905.
char control[3] = { DirectorySeparator, AltDirectorySeparator, '\0' };
const int32_t len = (int32_t)strlen(szPath);
for (int32_t iPrev=0; (iBackslash = strcspn(szPath+iPrev, control) + iPrev) < len; iPrev = iBackslash + 1)
#else
for (int32_t cnt=0; (iBackslash=SCharPos(DirectorySeparator,szPath,cnt)) > -1; cnt++)
#endif
{
// Get folder name
SCopy(szPath,szFoldername,iBackslash);
// Open folder
if (SEqualNoCase(GetExtension(szFoldername),"ocf"))
if (hGroup.Open(szFoldername))
{
// Check for contained defs
// do not, however, add them to the group set:
// parent folders are added by OpenScenario already!
int32_t iContents;
if ((iContents = Game.GroupSet.CheckGroupContents(hGroup, C4GSCnt_Definitions)))
{
// Add folder to list
CreateByFile(NRT_Definitions, szFoldername);
}
// Close folder
hGroup.Close();
}
}
}
bool C4GameResList::Load(C4Group &hGroup, C4Scenario *pScenario, const char * szDefinitionFilenames)
{
// clear any prev
Clear();
// no defs to be added? that's OK (LocalOnly)
if (szDefinitionFilenames && *szDefinitionFilenames)
{
// add them
char szSegment[_MAX_PATH+1];
for (int32_t cseg=0; SCopySegment(szDefinitionFilenames,cseg,szSegment,';',_MAX_PATH); ++cseg)
if (*szSegment)
CreateByFile(NRT_Definitions, szSegment);
}
LoadFoldersWithLocalDefs(pScenario->Head.Origin ? pScenario->Head.Origin.getData() : hGroup.GetFullName().getData());
// add System.ocg
CreateByFile(NRT_System, C4CFN_System);
// add all instances of Material.ocg, except those inside the scenario file
C4Group *pMatParentGrp = NULL;
while ((pMatParentGrp = Game.GroupSet.FindGroup(C4GSCnt_Material, pMatParentGrp)))
if (pMatParentGrp != &Game.ScenarioFile)
{
StdStrBuf MaterialPath = pMatParentGrp->GetFullName() + DirSep C4CFN_Material;
CreateByFile(NRT_Material, (pMatParentGrp->GetFullName() + DirSep C4CFN_Material).getData());
}
// add global Material.ocg
CreateByFile(NRT_Material, C4CFN_Material);
// done; success
return true;
}
C4GameRes *C4GameResList::CreateByFile(C4Network2ResType eType, const char *szFile)
{
// Create & set
C4GameRes *pRes = new C4GameRes();
pRes->SetFile(eType, szFile);
// Add to list
Add(pRes);
return pRes;
}
C4GameRes *C4GameResList::CreateByNetRes(C4Network2Res::Ref pNetRes)
{
// Create & set
C4GameRes *pRes = new C4GameRes();
pRes->SetNetRes(pNetRes);
// Add to list
Add(pRes);
return pRes;
}
bool C4GameResList::InitNetwork(C4Network2ResList *pNetResList)
{
// Check all resources without attached network resource object
for (int i = 0; i < iResCount; i++)
if (!pResList[i]->InitNetwork(pNetResList))
return false;
// Success
return true;
}
void C4GameResList::CalcHashes()
{
for (int32_t i = 0; i < iResCount; i++)
pResList[i]->CalcHash();
}
bool C4GameResList::RetrieveFiles()
{
// wait for all resources
for (int32_t i = 0; i < iResCount; i++)
{
const C4Network2ResCore &Core = *pResList[i]->getResCore();
StdStrBuf ResNameBuf = FormatString("%s: %s", LoadResStr("IDS_DLG_DEFINITION"), GetFilename(Core.getFileName()));
if (!::Network.RetrieveRes(Core, C4NetResRetrieveTimeout, ResNameBuf.getData()))
return false;
}
return true;
}
void C4GameResList::Add(C4GameRes *pRes)
{
// Enlarge
if (iResCount >= iResCapacity)
{
iResCapacity += 10;
C4GameRes **pnResList = new C4GameRes *[iResCapacity];
for (int i = 0; i < iResCount; i++)
pnResList[i] = pResList[i];
pResList = pnResList;
}
// Add
pResList[iResCount++] = pRes;
}
void C4GameResList::CompileFunc(StdCompiler *pComp)
{
bool fCompiler = pComp->isCompiler();
// Clear previous data
if (fCompiler) Clear();
// Compile resource count
pComp->Value(mkNamingCountAdapt(iResCount, "Resource"));
// Create list
if (fCompiler)
{
pResList = new C4GameRes *[iResCapacity = iResCount];
ZeroMem(pResList, sizeof(*pResList) * iResCount);
}
// Compile list
pComp->Value(
mkNamingAdapt(
mkArrayAdaptMap(pResList, iResCount, mkPtrAdaptNoNull<C4GameRes>),
"Resource"));
mkPtrAdaptNoNull<C4GameRes>(*pResList);
}
// *** C4GameParameters
C4GameParameters::C4GameParameters()
{
}
C4GameParameters::~C4GameParameters()
{
}
void C4GameParameters::Clear()
{
League.Clear();
LeagueAddress.Clear();
Rules.Clear();
Goals.Clear();
Scenario.Clear();
GameRes.Clear();
Clients.Clear();
PlayerInfos.Clear();
RestorePlayerInfos.Clear();
Teams.Clear();
ScenarioParameters.Clear();
}
bool C4GameParameters::Load(C4Group &hGroup, C4Scenario *pScenario, const char *szGameText, C4LangStringTable *pLang, const char *DefinitionFilenames, C4ScenarioParameters *pStartupScenarioParameters)
{
// Clear previous data
Clear();
// Scenario
Scenario.SetFile(NRT_Scenario, hGroup.GetFullName().getData());
// Additional game resources
if (!GameRes.Load(hGroup, pScenario, DefinitionFilenames))
return false;
// Player infos (replays only)
if (pScenario->Head.Replay)
if (hGroup.FindEntry(C4CFN_PlayerInfos))
PlayerInfos.Load(hGroup, C4CFN_PlayerInfos);
// Savegame restore infos: Used for savegames to rejoin joined players
if (hGroup.FindEntry(C4CFN_SavePlayerInfos))
{
// load to savegame info list
RestorePlayerInfos.Load(hGroup, C4CFN_SavePlayerInfos, pLang);
// transfer counter to allow for additional player joins in savegame resumes
PlayerInfos.SetIDCounter(RestorePlayerInfos.GetIDCounter());
// in network mode, savegame players may be reassigned in the lobby
// in any mode, the final player restoration will be done in InitPlayers()
// dropping any players that could not be restored
}
// Load teams
if (!Teams.Load(hGroup, pScenario, pLang))
{ LogFatal(LoadResStr("IDS_PRC_ERRORLOADINGTEAMS")); return false; }
// Compile data
StdStrBuf Buf;
if (hGroup.LoadEntryString(C4CFN_Parameters, &Buf))
{
if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(
mkNamingAdapt(mkParAdapt(*this, pScenario), "Parameters"),
Buf,
C4CFN_Parameters))
return false;
}
else
{
// Set default values
StdCompilerNull DefaultCompiler;
DefaultCompiler.Compile(mkParAdapt(*this, pScenario));
// Set control rate default
if (ControlRate < 0)
ControlRate = Config.Network.ControlRate;
// network game?
IsNetworkGame = Game.NetworkActive;
// Auto frame skip by options
AutoFrameSkip = !!::Config.Graphics.AutoFrameSkip;
// custom parameters from startup
if (pStartupScenarioParameters) ScenarioParameters = *pStartupScenarioParameters;
}
// enforce league settings
if (isLeague()) EnforceLeagueRules(pScenario);
// Done
return true;
}
void C4GameParameters::EnforceLeagueRules(C4Scenario *pScenario)
{
Scenario.CalcHash();
GameRes.CalcHashes();
Teams.EnforceLeagueRules();
AllowDebug = false;
if (pScenario) MaxPlayers = pScenario->Head.MaxPlayerLeague;
// forced league values in custom scenario parameters
size_t idx=0; const C4ScenarioParameterDef *pdef; int32_t val;
while ((pdef = ::Game.ScenarioParameterDefs.GetParameterDefByIndex(idx++)))
if ((val = pdef->GetLeagueValue()))
ScenarioParameters.SetValue(pdef->GetID(), val, false);
}
bool C4GameParameters::Save(C4Group &hGroup, C4Scenario *pScenario)
{
// Write Parameters.txt
StdStrBuf ParData = DecompileToBuf<StdCompilerINIWrite>(
mkNamingAdapt(mkParAdapt(*this, pScenario), "Parameters"));
if (!hGroup.Add(C4CFN_Parameters, ParData, false, true))
return false;
// Done
return true;
}
bool C4GameParameters::InitNetwork(C4Network2ResList *pResList)
{
// Scenario & material resource
if (!Scenario.InitNetwork(pResList))
return false;
// Other game resources
if (!GameRes.InitNetwork(pResList))
return false;
// Done
return true;
}
void C4GameParameters::CompileFunc(StdCompiler *pComp, C4Scenario *pScenario)
{
pComp->Value(mkNamingAdapt(MaxPlayers, "MaxPlayers", !pScenario ? 0 : pScenario->Head.MaxPlayer));
pComp->Value(mkNamingAdapt(AllowDebug, "AllowDebug", true));
pComp->Value(mkNamingAdapt(IsNetworkGame, "IsNetworkGame", false));
pComp->Value(mkNamingAdapt(ControlRate, "ControlRate", -1));
pComp->Value(mkNamingAdapt(AutoFrameSkip, "AutoFrameSkip", false));
pComp->Value(mkNamingAdapt(Rules, "Rules", !pScenario ? C4IDList() : pScenario->Game.Rules));
pComp->Value(mkNamingAdapt(Goals, "Goals", !pScenario ? C4IDList() : pScenario->Game.Goals));
pComp->Value(mkNamingAdapt(League, "League", StdStrBuf()));
// These values are either stored separately (see Load/Save) or
// don't make sense for savegames.
if (!pScenario)
{
pComp->Value(mkNamingAdapt(LeagueAddress, "LeagueAddress", ""));
pComp->Value(mkNamingAdapt(Scenario, "Scenario" ));
pComp->Value(GameRes);
pComp->Value(mkNamingAdapt(PlayerInfos, "PlayerInfos" ));
pComp->Value(mkNamingAdapt(RestorePlayerInfos,"RestorePlayerInfos"));
pComp->Value(mkNamingAdapt(Teams, "Teams" ));
}
pComp->Value(Clients);
pComp->Value(mkNamingAdapt(ScenarioParameters, "ScenarioParameters"));
}
StdStrBuf C4GameParameters::GetGameGoalString() const
{
// getting game goals from the ID list
// unfortunately, names cannot be deduced before object definitions are loaded
StdStrBuf sResult;
C4ID idGoal;
for (int32_t i=0; i<Goals.GetNumberOfIDs(); ++i)
if ((idGoal = Goals.GetID(i))) if (idGoal != C4ID::None)
{
if (Game.IsRunning)
{
C4Def *pDef = C4Id2Def(idGoal);
if (pDef)
{
if (sResult.getLength()) sResult.Append(", ");
sResult.Append(pDef->GetName());
}
}
else
{
if (sResult.getLength()) sResult.Append(", ");
sResult.Append(idGoal.ToString());
}
}
// Max length safety
if (sResult.getLength() > C4MaxTitle) sResult.SetLength(C4MaxTitle);
// Compose desc string
if (sResult.getLength())
return FormatString("%s: %s", LoadResStr("IDS_MENU_CPGOALS"), sResult.getData());
else
return StdCopyStrBuf(LoadResStr("IDS_CTL_NOGOAL"), true);
}