645 lines
20 KiB
C++
645 lines
20 KiB
C++
#include "CTemplateLoader.h"
|
|
#include "CWorldLoader.h"
|
|
#include "../script/EAttribType.h"
|
|
#include <Core/Log.h>
|
|
|
|
// ************ PROPERTY ************
|
|
CPropertyTemplate* CTemplateLoader::LoadPropertyTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName)
|
|
{
|
|
const char *pElemName = pElem->Name();
|
|
|
|
// Load multi-property struct
|
|
if (strcmp(pElemName, "struct") == 0)
|
|
{
|
|
CStructTemplate *pStruct = LoadStructTemplate(pElem, TemplateName);
|
|
|
|
if (pStruct)
|
|
pStruct->mIsSingleProperty = false;
|
|
|
|
return pStruct;
|
|
}
|
|
|
|
else if (strcmp(pElemName, "property") == 0)
|
|
{
|
|
// Get name, type, and ID
|
|
std::string Name;
|
|
EPropertyType Type;
|
|
u32 ID;
|
|
GetPropertyInfo(pElem, Name, Type, ID);
|
|
|
|
// Error check
|
|
if (Type == eInvalidProperty)
|
|
{
|
|
const char *pType = pElem->Attribute("type");
|
|
|
|
if (pType)
|
|
Log::Error("Invalid property type in " + TemplateName + " template: " + pType);
|
|
else
|
|
Log::Error("Property " + Name + " in " + TemplateName + " template has no type");
|
|
}
|
|
|
|
// Load single-property struct
|
|
if (Type == eStructProperty)
|
|
{
|
|
CStructTemplate *pStruct = LoadStructTemplate(pElem, TemplateName);
|
|
pStruct->mIsSingleProperty = true;
|
|
return pStruct;
|
|
}
|
|
|
|
// Load file property
|
|
else if (Type == eFileProperty)
|
|
{
|
|
// Fetch file extension
|
|
CFileTemplate *pFile = nullptr;
|
|
const char *pExt = pElem->Attribute("ext");
|
|
|
|
if (pExt)
|
|
pFile = new CFileTemplate(Name, ID, StringUtil::Tokenize(pExt, ","));
|
|
|
|
else
|
|
{
|
|
CFileTemplate *pSrc = (CFileTemplate*) mpMaster->GetProperty(ID);
|
|
|
|
if (pSrc)
|
|
pFile = new CFileTemplate(Name, ID, pSrc->Extensions());
|
|
}
|
|
|
|
// Check if extensions are valid
|
|
if (!pFile)
|
|
{
|
|
Log::Error("File property " + Name + " in " + TemplateName + " template has no extension");
|
|
return nullptr;
|
|
}
|
|
|
|
else
|
|
return pFile;
|
|
}
|
|
|
|
// Load regular property
|
|
else
|
|
{
|
|
CPropertyTemplate *pProperty = new CPropertyTemplate(Type, Name, ID);
|
|
return pProperty;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CStructTemplate* CTemplateLoader::LoadStructTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName)
|
|
{
|
|
CStructTemplate *pStruct = new CStructTemplate();
|
|
|
|
// Get name, type, and ID
|
|
GetPropertyInfo(pElem, pStruct->mPropName, pStruct->mPropType, pStruct->mPropID);
|
|
const char *pTemp = pElem->Attribute("template");
|
|
if (!pTemp) pTemp = pElem->Attribute("target");
|
|
|
|
// Get source template from the master property list, if it exists
|
|
CStructTemplate *pSrc = (CStructTemplate*) mpMaster->GetProperty(pStruct->mPropID);
|
|
|
|
// "IsSingleProperty" means, does the struct contain multiple properties, each with separate IDs
|
|
// or does the entire struct as a whole count as just one property?
|
|
if (pSrc)
|
|
pStruct->mIsSingleProperty = pSrc->IsSingleProperty();
|
|
else
|
|
pStruct->mIsSingleProperty = (strcmp(pElem->Name(), "property") == 0);
|
|
|
|
// Read struct children. Priority is [Embedded -> Template -> Master].
|
|
// Embedded
|
|
if (!pElem->NoChildren())
|
|
{
|
|
// Get count
|
|
const char *pCount = pElem->Attribute("count");
|
|
|
|
if (pCount)
|
|
pStruct->mPropertyCount = std::stoul(pCount);
|
|
|
|
// Parse sub-elements
|
|
tinyxml2::XMLElement *pChild = pElem->FirstChildElement();
|
|
|
|
while (pChild)
|
|
{
|
|
CPropertyTemplate *pProp = LoadPropertyTemplate(pChild, TemplateName);
|
|
|
|
if (pProp)
|
|
pStruct->mProperties.push_back(pProp);
|
|
|
|
pChild = pChild->NextSiblingElement();
|
|
}
|
|
|
|
}
|
|
|
|
// Template
|
|
else if (pTemp)
|
|
{
|
|
// Get handle for XML
|
|
std::string TempPath = mMasterDir + pTemp;
|
|
|
|
tinyxml2::XMLDocument TempXML;
|
|
TempXML.LoadFile(TempPath.c_str());
|
|
|
|
if (TempXML.Error())
|
|
Log::Error("Couldn't open struct template: " + TempPath);
|
|
|
|
else
|
|
{
|
|
tinyxml2::XMLElement *pVersionElem = TempXML.FirstChildElement()->FirstChildElement("version");
|
|
tinyxml2::XMLElement *pPropertiesElem = TempXML.FirstChildElement()->FirstChildElement("properties");
|
|
|
|
if (!pVersionElem) Log::Error("Struct template has no version element: " + TempPath);
|
|
if (!pPropertiesElem) Log::Error("Struct template has no properties element: " + TempPath);
|
|
|
|
if (pVersionElem && pPropertiesElem)
|
|
{
|
|
// Get version number
|
|
u32 VersionNumber = std::stoul(pVersionElem->GetText());
|
|
|
|
// Get property count
|
|
const char *pCount = pPropertiesElem->Attribute("count");
|
|
|
|
if (pCount)
|
|
pStruct->mPropertyCount = std::stoul(pCount);
|
|
|
|
// Parse properties
|
|
tinyxml2::XMLElement *pPropElem = pPropertiesElem->FirstChildElement();
|
|
|
|
while (pPropElem)
|
|
{
|
|
if (!pPropElem) break;
|
|
|
|
CPropertyTemplate *pProp = LoadPropertyTemplate(pPropElem, TemplateName);
|
|
|
|
if (pProp)
|
|
pStruct->mProperties.push_back(pProp);
|
|
|
|
pPropElem = pPropElem->NextSiblingElement();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Master
|
|
else if (pSrc)
|
|
{
|
|
pStruct->mPropertyCount = pSrc->TemplateCount();
|
|
|
|
for (u32 p = 0; p < pSrc->Count(); p++)
|
|
pStruct->mProperties.push_back(pSrc->PropertyByIndex(p));
|
|
}
|
|
|
|
// If it's none of these things, then it probably has no children because it's a property list entry
|
|
return pStruct;
|
|
}
|
|
|
|
void CTemplateLoader::GetPropertyInfo(tinyxml2::XMLElement *pElem, std::string& Name, EPropertyType& Type, u32& ID)
|
|
{
|
|
const char *pNameStr = pElem->Attribute("name");
|
|
const char *pTypeStr = pElem->Attribute("type");
|
|
const char *pIDStr = pElem->Attribute("ID");
|
|
bool IsBaseStruct = (strcmp(pElem->Name(), "properties") == 0);
|
|
|
|
// Fetch source template, if available
|
|
CPropertyTemplate *pSrcTmp;
|
|
|
|
if (pIDStr)
|
|
{
|
|
ID = std::stoul(pIDStr, 0, 16);
|
|
pSrcTmp = mpMaster->GetProperty(ID);
|
|
}
|
|
else
|
|
{
|
|
ID = 0xFFFFFFFF;
|
|
pSrcTmp = nullptr;
|
|
}
|
|
|
|
// Get name
|
|
if (pNameStr)
|
|
Name = pNameStr;
|
|
else if (pSrcTmp)
|
|
Name = pSrcTmp->Name();
|
|
else if (IsBaseStruct)
|
|
Name = "Base";
|
|
else
|
|
Name = "";
|
|
|
|
// Get type
|
|
if (strcmp(pElem->Name(), "struct") == 0)
|
|
Type = eStructProperty;
|
|
else if (IsBaseStruct)
|
|
Type = eStructProperty;
|
|
else if (pTypeStr)
|
|
Type = PropStringToPropEnum(pTypeStr);
|
|
else if (pSrcTmp)
|
|
Type = pSrcTmp->Type();
|
|
else
|
|
Type = eInvalidProperty;
|
|
}
|
|
|
|
// ************ SCRIPT OBJECT ************
|
|
CScriptTemplate* CTemplateLoader::LoadScriptTemplate(tinyxml2::XMLDocument *pDoc, const std::string& TemplateName, u32 ObjectID)
|
|
{
|
|
tinyxml2::XMLElement *pBaseElement = pDoc->FirstChildElement();
|
|
|
|
CScriptTemplate *pScript = new CScriptTemplate(mpMaster);
|
|
pScript->mObjectID = ObjectID;
|
|
pScript->mTemplateName = pBaseElement->Name();
|
|
|
|
// Properties?
|
|
tinyxml2::XMLElement *pProperties = pBaseElement->FirstChildElement("properties");
|
|
if (pProperties)
|
|
{
|
|
pScript->mpBaseStruct = LoadStructTemplate(pBaseElement->FirstChildElement("properties"), TemplateName);
|
|
pScript->mpBaseStruct->SetName(pScript->mTemplateName);
|
|
}
|
|
|
|
// Attribs?
|
|
tinyxml2::XMLElement *pAttributes = pBaseElement->FirstChildElement("attributes");
|
|
if (pAttributes) LoadScriptAttribs(pAttributes, pScript);
|
|
|
|
return pScript;
|
|
}
|
|
|
|
void CTemplateLoader::LoadScriptAttribs(tinyxml2::XMLElement *pElem, CScriptTemplate *pScript)
|
|
{
|
|
// Parsing attribs
|
|
tinyxml2::XMLElement *pAttrib = pElem->FirstChildElement("attrib");
|
|
while (pAttrib)
|
|
{
|
|
CAttribTemplate Attrib;
|
|
Attrib.ExtraSettings = -1;
|
|
|
|
const char *pType = pAttrib->Attribute("type");
|
|
if (!pType)
|
|
Log::Error("An attrib in " + pScript->TemplateName() + " template has no type set");
|
|
|
|
else
|
|
{
|
|
// Initialize attrib template values
|
|
Attrib.AttribType = AttribStringToAttribEnum(pType);
|
|
|
|
if (Attrib.AttribType == eInvalidAttrib)
|
|
Log::Error("An attrib in " + pScript->TemplateName() + " template has an invalid type: " + pType);
|
|
|
|
else
|
|
{
|
|
bool NoError = ParseAttribExtra(pAttrib, Attrib, pScript->TemplateName());
|
|
|
|
if (NoError)
|
|
{
|
|
Attrib.AttribTarget = pAttrib->Attribute("target");
|
|
CPropertyTemplate *pTargetProp = nullptr;
|
|
|
|
if (Attrib.ResFile.empty())
|
|
{
|
|
pTargetProp = pScript->mpBaseStruct->PropertyByName(Attrib.AttribTarget); // Ensure target is valid if it points to a property
|
|
|
|
if (!pTargetProp)
|
|
Log::Error("An attrib in " + pScript->TemplateName() + " template of type " + pType + " has an invalid target: " + Attrib.AttribTarget);
|
|
}
|
|
|
|
if ((pTargetProp) || (!Attrib.ResFile.empty()))
|
|
pScript->mAttribs.push_back(Attrib);
|
|
}
|
|
}
|
|
}
|
|
|
|
pAttrib = pAttrib->NextSiblingElement("attrib");
|
|
}
|
|
}
|
|
|
|
bool CTemplateLoader::ParseAttribExtra(tinyxml2::XMLElement *pElem, CAttribTemplate& Attrib, const std::string& TemplateName)
|
|
{
|
|
// This function is for parsing extra tags that some attribs have, such as "source" for models or "forcenode" for animsets
|
|
|
|
// AnimSet
|
|
if (Attrib.Type() == eAnimSetAttrib)
|
|
{
|
|
// Check res source
|
|
const char *pSource = pElem->Attribute("source");
|
|
|
|
if ((pSource) && (strcmp(pSource, "file") == 0))
|
|
{
|
|
const char *pFileName = pElem->Attribute("target");
|
|
if (pFileName)
|
|
Attrib.ResFile = std::string("../resources/") + pFileName;
|
|
else
|
|
{
|
|
Log::Error("An attrib in " + TemplateName + " template of type animset has an invalid target: \"" + pFileName + "\"");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check forcenode
|
|
const char *pForceNode = pElem->Attribute("forcenode");
|
|
if (pForceNode)
|
|
{
|
|
if (!StringUtil::IsHexString(pForceNode))
|
|
{
|
|
Log::Error("An animset attrib in " + TemplateName + " has an invalid \"forcenode\" setting: \"" + pForceNode + "\"");
|
|
return false;
|
|
}
|
|
else
|
|
Attrib.ExtraSettings = std::stoul(pForceNode);
|
|
}
|
|
}
|
|
|
|
// Model
|
|
if (Attrib.Type() == eModelAttrib)
|
|
{
|
|
// Check res source
|
|
const char *pSource = pElem->Attribute("source");
|
|
if ((pSource) && (strcmp(pSource, "file") == 0))
|
|
{
|
|
const char *pFileName = pElem->Attribute("target");
|
|
if (pFileName)
|
|
Attrib.ResFile = std::string("../resources/") + pFileName;
|
|
else
|
|
{
|
|
Log::Error("An attrib in " + TemplateName + " template of type model has an invalid target: \"" + pFileName + "\"");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Volume
|
|
if (Attrib.Type() == eVolumeAttrib)
|
|
{
|
|
const char *pShape = pElem->Attribute("shape");
|
|
|
|
if (pShape)
|
|
{
|
|
if (strcmp(pShape, "Box") == 0)
|
|
Attrib.ExtraSettings = 0;
|
|
else if (strcmp(pShape, "OrientedBox") == 0)
|
|
Attrib.ExtraSettings = 1;
|
|
else if (strcmp(pShape, "Sphere") == 0)
|
|
Attrib.ExtraSettings = 2;
|
|
else
|
|
{
|
|
Log::Error("Volume attrib in " + TemplateName + " template has an invalid shape: " + pShape);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log::Error("Volume attrib in " + TemplateName + " template has no shape attribute");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ************ MASTER ************
|
|
void CTemplateLoader::LoadMasterTemplate(tinyxml2::XMLDocument *pDoc)
|
|
{
|
|
tinyxml2::XMLNode *pNode = pDoc->FirstChild()->NextSibling()->FirstChild();
|
|
FILE *f = fopen("a.txt", "w");
|
|
|
|
while (pNode)
|
|
{
|
|
tinyxml2::XMLElement *pElem = pNode->ToElement();
|
|
|
|
// Version
|
|
if (strcmp(pElem->Name(), "version") == 0)
|
|
{
|
|
u32 Version = std::stoul(pElem->GetText());
|
|
mpMaster->mVersion = Version;
|
|
}
|
|
|
|
// Properties
|
|
else if (strcmp(pElem->Name(), "properties") == 0)
|
|
{
|
|
std::string PropListPath = mMasterDir + pElem->GetText();
|
|
|
|
tinyxml2::XMLDocument PropListXML;
|
|
PropListXML.LoadFile(PropListPath.c_str());
|
|
|
|
if (PropListXML.Error())
|
|
Log::Error("Couldn't open property list: " + PropListPath);
|
|
|
|
else
|
|
LoadPropertyList(&PropListXML, PropListPath);
|
|
}
|
|
|
|
// Objects
|
|
else if (strcmp(pElem->Name(), "objects") == 0)
|
|
{
|
|
// Iterate categories
|
|
tinyxml2::XMLElement *pCat = pElem->FirstChildElement("category");
|
|
|
|
while (pCat)
|
|
{
|
|
CTemplateCategory Cat(pCat->Attribute("name"));
|
|
tinyxml2::XMLElement *pObj = pCat->FirstChildElement("object");
|
|
|
|
while (pObj)
|
|
{
|
|
// ID can either be a hex number or an ASCII fourCC
|
|
std::string StrID = pObj->Attribute("ID");
|
|
u32 ID;
|
|
|
|
if (StringUtil::IsHexString(StrID, true))
|
|
ID = std::stoul(StrID, 0, 16);
|
|
else
|
|
ID = CFourCC(StrID).ToLong();
|
|
|
|
// Load up the object
|
|
std::string TemplateName = pObj->Attribute("template");
|
|
std::string TemplatePath = mMasterDir + TemplateName;
|
|
|
|
tinyxml2::XMLDocument ObjectXML;
|
|
ObjectXML.LoadFile(TemplatePath.c_str());
|
|
|
|
if (ObjectXML.Error())
|
|
Log::Error("Couldn't open script template: " + TemplatePath);
|
|
|
|
else
|
|
{
|
|
CScriptTemplate *pTemp = LoadScriptTemplate(&ObjectXML, TemplateName, ID);
|
|
|
|
if (pTemp)
|
|
{
|
|
mpMaster->mTemplates[ID] = pTemp;
|
|
Cat.AddTemplate(pTemp);
|
|
|
|
std::string ID = "| <code>" + CFourCC(pTemp->ObjectID()).ToString() + "</code>\n";
|
|
std::string Name = "| [[" + pTemp->TemplateName() + " (Metroid Prime 2)|" + pTemp->TemplateName() + "]]\n";
|
|
fprintf(f, ID.c_str());
|
|
fprintf(f, Name.c_str());
|
|
fprintf(f, "| \n");
|
|
fprintf(f, "|-\n");
|
|
}
|
|
}
|
|
|
|
pObj = pObj->NextSiblingElement("object");
|
|
}
|
|
|
|
Cat.Sort();
|
|
mpMaster->mCategories.push_back(Cat);
|
|
pCat = pCat->NextSiblingElement("category");
|
|
}
|
|
}
|
|
|
|
// States
|
|
else if (strcmp(pElem->Name(), "states") == 0)
|
|
{
|
|
tinyxml2::XMLElement *pState = pElem->FirstChildElement("state");
|
|
|
|
while (pState)
|
|
{
|
|
std::string StrID = pState->Attribute("ID");
|
|
u32 StateID;
|
|
|
|
if (StringUtil::IsHexString(StrID, true))
|
|
StateID = std::stoul(StrID, 0, 16);
|
|
else
|
|
StateID = CFourCC(StrID).ToLong();
|
|
|
|
std::string StateName = pState->Attribute("name");
|
|
mpMaster->mStates[StateID] = StateName;
|
|
pState = pState->NextSiblingElement("state");
|
|
|
|
std::string ID = "| <code>" + StrID + "</code>\n";
|
|
std::string Name = "| " + StateName + "\n";
|
|
fprintf(f, ID.c_str());
|
|
fprintf(f, Name.c_str());
|
|
fprintf(f, "|-\n");
|
|
}
|
|
}
|
|
|
|
// Messages
|
|
else if (strcmp(pElem->Name(), "messages") == 0)
|
|
{
|
|
tinyxml2::XMLElement *pMessage = pElem->FirstChildElement("message");
|
|
|
|
while (pMessage)
|
|
{
|
|
std::string StrID = pMessage->Attribute("ID");
|
|
u32 MessageID;
|
|
|
|
if (StringUtil::IsHexString(StrID, true))
|
|
MessageID = std::stoul(StrID, 0, 16);
|
|
else
|
|
MessageID = CFourCC(StrID).ToLong();
|
|
|
|
std::string MessageName = pMessage->Attribute("name");
|
|
mpMaster->mMessages[MessageID] = MessageName;
|
|
pMessage = pMessage->NextSiblingElement("message");
|
|
|
|
std::string ID = "| <code>" + StrID + "</code>\n";
|
|
std::string Name = "| " + MessageName + "\n";
|
|
fprintf(f, ID.c_str());
|
|
fprintf(f, Name.c_str());
|
|
fprintf(f, "|-\n");
|
|
}
|
|
}
|
|
|
|
pNode = pNode->NextSibling();
|
|
}
|
|
}
|
|
|
|
void CTemplateLoader::LoadPropertyList(tinyxml2::XMLDocument *pDoc, const std::string& ListName)
|
|
{
|
|
tinyxml2::XMLElement *pElem = pDoc->FirstChildElement()->FirstChildElement();
|
|
|
|
while (pElem)
|
|
{
|
|
CPropertyTemplate *pProp = LoadPropertyTemplate(pElem, ListName);
|
|
|
|
if (pProp)
|
|
mpMaster->mPropertyList[pProp->PropertyID()] = pProp;
|
|
|
|
pElem = pElem->NextSiblingElement();
|
|
}
|
|
}
|
|
|
|
CMasterTemplate* CTemplateLoader::LoadGame(tinyxml2::XMLNode *pNode)
|
|
{
|
|
tinyxml2::XMLElement *pGameElem = pNode->FirstChildElement();
|
|
mpMaster = new CMasterTemplate();
|
|
|
|
// Parse game parameters
|
|
while (pGameElem)
|
|
{
|
|
if (strcmp(pGameElem->Name(), "name") == 0)
|
|
mpMaster->mGameName = pGameElem->GetText();
|
|
|
|
else if (strcmp(pGameElem->Name(), "mlvl") == 0)
|
|
{
|
|
u32 VersionNum = std::stoul(pGameElem->GetText(), 0, 16);
|
|
mpMaster->mGame = CWorldLoader::GetFormatVersion(VersionNum);
|
|
}
|
|
|
|
else if (strcmp(pGameElem->Name(), "master") == 0)
|
|
{
|
|
std::string MasterPath = mTemplatesDir + pGameElem->GetText();
|
|
mMasterDir = StringUtil::GetFileDirectory(MasterPath);
|
|
|
|
tinyxml2::XMLDocument MasterXML;
|
|
MasterXML.LoadFile(MasterPath.c_str());
|
|
|
|
if (MasterXML.Error())
|
|
Log::Error("Couldn't open master template at " + MasterPath + " - error " + std::to_string(MasterXML.ErrorID()));
|
|
else
|
|
LoadMasterTemplate(&MasterXML);
|
|
}
|
|
pGameElem = pGameElem->NextSiblingElement();
|
|
}
|
|
|
|
mpMaster->mFullyLoaded = true;
|
|
return mpMaster;
|
|
}
|
|
|
|
// ************ PUBLIC ************
|
|
void CTemplateLoader::LoadGameList()
|
|
{
|
|
static const std::string TemplatesDir = "../templates/";
|
|
static const std::string GameListPath = TemplatesDir + "GameList.xml";
|
|
Log::Write("Loading game list");
|
|
|
|
// Load Game List XML
|
|
tinyxml2::XMLDocument GameListXML;
|
|
GameListXML.LoadFile(GameListPath.c_str());
|
|
|
|
if (GameListXML.Error())
|
|
{
|
|
Log::Error("Couldn't open game list at " + GameListPath + " - error " + std::to_string(GameListXML.ErrorID()));
|
|
return;
|
|
}
|
|
|
|
// Parse
|
|
tinyxml2::XMLNode *pNode = GameListXML.FirstChild()->NextSibling()->FirstChild();
|
|
|
|
while (pNode)
|
|
{
|
|
tinyxml2::XMLElement *pElement = pNode->ToElement();
|
|
|
|
// Game List version number
|
|
if (strcmp(pElement->Name(), "version") == 0)
|
|
{
|
|
u32 VersionNum = std::stoul(pElement->GetText());
|
|
CMasterTemplate::smGameListVersion = VersionNum;
|
|
}
|
|
|
|
// Games
|
|
else if (strcmp(pElement->Name(), "game") == 0)
|
|
{
|
|
CTemplateLoader Loader(TemplatesDir);
|
|
CMasterTemplate *pMaster = Loader.LoadGame(pNode);
|
|
|
|
if (!pMaster->IsLoadedSuccessfully())
|
|
{
|
|
Log::Error("Master template for " + pMaster->mGameName + " couldn't be loaded");
|
|
delete pMaster;
|
|
}
|
|
|
|
else
|
|
CMasterTemplate::smMasterMap[pMaster->mGame] = pMaster;
|
|
}
|
|
|
|
pNode = pNode->NextSibling();
|
|
}
|
|
}
|