Add support for #IF, #ELSE, #ELSEIF, #ENDIF for BMS.

This commit is contained in:
zardoru
2015-01-28 19:55:57 -03:00
parent e0a4fe9db5
commit 49bda1e0e5
+348 -52
View File
@@ -178,6 +178,7 @@ struct BMSMeasure
float size;
};
const int MaxBMSElements = 1296; // ZZ in b36
typedef map<RString, RString> BMSHeaders;
typedef map<int, BMSMeasure> BMSMeasures;
typedef vector<BMSObject> BMSObjects;
@@ -209,8 +210,297 @@ bool BMSChart::GetHeader( const RString &header, RString &out )
return true;
}
// az: Implement #RANDOM, #IF, #ELSE, #ELSEIF and #ENDIF.
struct bmsCommandTree
{
struct bmsNodeS { // Each of these imply one branching level.
int branchHeight;
enum {
CT_NULL,
CT_CONDITIONALCHAIN,
CT_IF,
CT_ELSEIF,
CT_ELSE
} conditionType; // #IF or #ELSE?
union {
int conditionValue; // value which we got for this #random
int conditionTriggerValue; // value which triggers this branch. does not apply to #ELSE
};
bool Triggered;
BMSHeaders Commands;
vector<RString> ChannelCommands;
vector<bmsNodeS*> branches;
bmsNodeS* parent;
bmsNodeS()
{
Triggered = true;
parent = NULL;
conditionValue = 0;
conditionType = CT_NULL;
}
~bmsNodeS()
{
FOREACH(bmsNodeS*, branches, b)
{
delete *b;
}
}
};
bmsNodeS *currentNode;
bmsNodeS root;
vector<unsigned int> randomStack;
int line;
RString path;
bmsCommandTree()
{
line = 0;
root.branchHeight = 0;
root.conditionValue = 0;
root.conditionTriggerValue = -1;
root.parent = NULL;
root.conditionType = bmsNodeS::CT_NULL;
currentNode = &root;
}
~bmsCommandTree()
{
}
bmsNodeS *addConditionalChain()
{
bmsNodeS *newNode = new bmsNodeS;
newNode->conditionValue = randomStack[currentNode->branchHeight];
newNode->parent = currentNode;
newNode->branchHeight = currentNode->branchHeight + 1;
newNode->conditionType = bmsNodeS::CT_CONDITIONALCHAIN;
currentNode->branches.push_back(newNode);
return newNode;
}
bmsNodeS* createIfNode(bmsNodeS *Chain, int value)
{
bmsNodeS *newNode = new bmsNodeS;
newNode->conditionValue = randomStack[currentNode->branchHeight];
newNode->parent = Chain;
newNode->branchHeight = currentNode->branchHeight + 1;
newNode->conditionTriggerValue = value;
newNode->conditionType = bmsNodeS::CT_IF;
Chain->branches.push_back(newNode);
return newNode;
}
bmsNodeS* createElseIfNode(bmsNodeS *Chain, int value)
{
bmsNodeS *newNode = new bmsNodeS;
newNode->conditionValue = randomStack[currentNode->branchHeight];
newNode->parent = Chain;
newNode->branchHeight = currentNode->branchHeight + 1;
newNode->conditionTriggerValue = value;
newNode->conditionType = bmsNodeS::CT_ELSEIF;
Chain->branches.push_back(newNode);
return newNode;
}
bmsNodeS* createElseNode(bmsNodeS *Chain)
{
bmsNodeS *newNode = new bmsNodeS;
newNode->conditionValue = randomStack[currentNode->branchHeight];
newNode->parent = Chain;
newNode->branchHeight = currentNode->branchHeight + 1;
newNode->conditionType = bmsNodeS::CT_ELSE;
Chain->branches.push_back(newNode);
return newNode;
}
/*
A condition chain can't ever be the current node.
A conditional chain will never have more than one #IF.
The current node is always an #IF/#ELSE/#ELSEIF node or the root node.
All #IFs must be parented by a condition chain for interpreting #ELSE and #ELSEIF founds on that chain.
Likewise, all #ELSE and #ELSEIF commands must be parented by a condition chain node.
Therefore, the root node will only have conditional chains as branches
and all commands that must be evaluated without question.
-az
*/
void appendNodeElements(bmsNodeS* node, BMSHeaders &headersOut, vector<RString> &linesOut)
{
for (BMSHeaders::iterator i = node->Commands.begin(); i != node->Commands.end(); ++i)
{
headersOut[i->first] = i->second;
}
for (vector<RString>::iterator i = node->ChannelCommands.begin(); i != node->ChannelCommands.end(); ++i)
{
linesOut.push_back(*i);
}
}
bool evaluateNode(bmsNodeS* node, BMSHeaders &headersOut, vector<RString> &linesOut)
{
switch (node->conditionType)
{
case bmsNodeS::CT_CONDITIONALCHAIN:
FOREACH(bmsNodeS*, node->branches, b)
if (evaluateNode(*b, headersOut, linesOut))
{
node->Triggered = true;
return true;
}
break;
case bmsNodeS::CT_IF:
case bmsNodeS::CT_ELSEIF: // Their differences are solved at node creation time.
if (node->parent->conditionValue == node->conditionTriggerValue)
{
appendNodeElements(node, headersOut, linesOut);
return true;
}
break;
case bmsNodeS::CT_ELSE:
if (!node->parent->Triggered) // we're the only branch left, so okay, evaluate.
{
appendNodeElements(node, headersOut, linesOut);
return true;
}
break;
case bmsNodeS::CT_NULL:
appendNodeElements(node, headersOut, linesOut);
FOREACH(bmsNodeS*, node->branches, b)
{
evaluateNode(*b, headersOut, linesOut);
}
default:
break;
}
return false;
}
void evaluateBMSTree(BMSHeaders &headersOut, vector<RString> &linesOut)
{
evaluateNode(&root, headersOut, linesOut);
}
void doStatement(RString statement)
{
line++;
if (statement.length() == 0 || statement[0] != '#') // Skip.
return;
size_t space = statement.find(' ');
RString name = statement.substr(0, space);
RString value = "";
if (space != statement.npos)
value = statement.substr(space + 1);
name.MakeLower();
if (name == "#if")
{
if (randomStack.size() < currentNode->branchHeight + 1)
{
LOG->UserLog("Song file", path, "Line %d: Missing #RANDOM. Warning: processing as part of parent branch!", line);
return;
}
bmsNodeS *chain = addConditionalChain();
currentNode = createIfNode(chain, atoi(value.c_str()));
}
else if (name == "#else")
{
if (currentNode->parent != NULL) // Not the root node.
{
if (currentNode->parent->conditionType == bmsNodeS::CT_CONDITIONALCHAIN)
{
currentNode = createElseNode(currentNode->parent);
return;
}
else
LOG->UserLog("Song file", path, "Line %d: #else without matching #if chain.\n", line);
} else
LOG->UserLog("Song file", path, "Line %d: #else used at root level.\n", line);
}
else if (name == "#elseif")
{
if (currentNode->parent != NULL) // Not the root node.
{
if (currentNode->parent->conditionType == bmsNodeS::CT_CONDITIONALCHAIN)
{
currentNode = createElseIfNode(currentNode->parent, atoi(value.c_str()));
}else
LOG->UserLog("Song file", path, "Line %d: #elseif without matching #if chain.\n", line);
}else
LOG->UserLog("Song file", path, "Line %d: #elseif used at root level.\n", line);
}
else if (name == "#endif" || name == "#end")
{
if (currentNode->parent != NULL) // not the root node
{
currentNode = currentNode->parent;
}
if (currentNode->conditionType != bmsNodeS::CT_CONDITIONALCHAIN)
{
LOG->UserLog("Song file", path, "Line %d: #endif without a matching #if!", line);
return;
}
// We're in a conditional chain, so that means we can go one level up to our *real* parent.
currentNode = currentNode->parent;
}
else if (name == "#random")
{
while (randomStack.size() < currentNode->branchHeight + 1) // if we're on branch level N we need N+1 values.
randomStack.push_back(0);
randomStack[currentNode->branchHeight] = rand() % StringToInt(value) + 1;
}
else
{
if (statement.size() >= 7 &&
('0' <= statement[1] && statement[1] <= '9') &&
('0' <= statement[2] && statement[2] <= '9') &&
('0' <= statement[3] && statement[3] <= '9') &&
('0' <= statement[4] && statement[4] <= '9') &&
('0' <= statement[5] && statement[5] <= '9') &&
statement[6] == ':')
{
currentNode->ChannelCommands.push_back(statement);
}
else
{
currentNode->Commands[name] = value;
}
}
// we're done.
}
};
bool BMSChart::Load( const RString &chartPath )
{
bmsCommandTree Tree;
Tree.path = chartPath;
path = chartPath;
RageFile file;
@@ -220,70 +510,59 @@ bool BMSChart::Load( const RString &chartPath )
return false;
}
while( !file.AtEOF() )
while (!file.AtEOF())
{
RString line;
if( file.GetLine(line) == -1 )
if (file.GetLine(line) == -1)
{
LOG->UserLog( "Song file", path, "had a read error: %s", file.GetError().c_str() );
LOG->UserLog("Song file", path, "had a read error: %s", file.GetError().c_str());
return false;
}
StripCrnl( line );
StripCrnl(line);
if( line.size() > 1 && line[0] == '#' )
Tree.doStatement(line);
}
vector<RString> lines;
Tree.evaluateBMSTree(headers, lines);
for (vector<RString>::iterator i = lines.begin(); i != lines.end(); ++i)
{
RString line = *i;
RString data = line.substr(7);
int measure = atoi(line.substr(1, 3).c_str());
int channel = atoi(line.substr(4, 2).c_str());
bool flag = false;
if (channel == 2)
{
if( line.size() >= 7 &&
( '0' <= line[1] && line[1] <= '9' ) &&
( '0' <= line[2] && line[2] <= '9' ) &&
( '0' <= line[3] && line[3] <= '9' ) &&
( '0' <= line[4] && line[4] <= '9' ) &&
( '0' <= line[5] && line[5] <= '9' ) &&
line[6] == ':' )
// special channel: time signature
BMSMeasure m = { StringToFloat(data) };
this->measures[measure] = m;
}
else
{
if (channel >= 51)
{
RString data = line.substr(7);
int measure = atoi( line.substr(1, 3).c_str() );
int channel = atoi( line.substr(4, 2).c_str() );
bool flag = false;
if( channel == 2 )
{
// special channel: time signature
BMSMeasure m = { StringToFloat(data) };
this->measures[measure] = m;
}
else
{
if( channel >= 51 )
{
channel -= 40;
flag = true;
}
int count = data.size() / 2;
for( int i = 0; i < count; i ++ )
{
RString value = data.substr( 2 * i, 2 );
if( value != "00" )
{
value.MakeLower();
BMSObject o = { channel, measure, (float)i / count, flag, value };
objects.push_back( o );
}
}
}
channel -= 40;
flag = true;
}
else
int count = data.size() / 2;
for (int i = 0; i < count; i++)
{
size_t space = line.find(' ');
RString name = line.substr( 0, space );
RString value = "";
if( space != line.npos )
value = line.substr( space+1 );
name.MakeLower();
headers[name] = value;
RString value = data.substr(2 * i, 2);
if (value != "00")
{
value.MakeLower();
BMSObject o = { channel, measure, (float)i / count, flag, value };
objects.push_back(o);
}
}
}
}
TidyUpData();
return true;
@@ -458,7 +737,7 @@ void BMSSong::PrecacheBackgrounds(const RString &dir)
for( unsigned i = 0; i < arrayPossibleFiles.size(); i++ )
{
for (unsigned j = 0; exts[j] != NULL && j < exts.size(); ++j)
for (unsigned j = 0; j < exts.size(); j++)
{
RString fn = SetExtension( arrayPossibleFiles[i], exts[j] );
mapBackground[fn] = arrayPossibleFiles[i];
@@ -476,7 +755,8 @@ struct BMSChartInfo {
RString backgroundFile;
RString stageFile;
RString musicFile;
RString overrideMusicFile;
map<int, RString> backgroundChanges;
};
@@ -592,6 +872,18 @@ void BMSChartReader::ReadHeaders()
{
out->SetMeter( StringToInt(it->second) );
}
else if (it->first == "#music")
{
info.overrideMusicFile = it->second;
}
else if (it->first == "#offset")
{
out->m_Timing.m_fBeat0OffsetInSeconds = -StringToFloat(it->second);
}
else if (it->first == "#maker")
{
out->SetCredit(it->second);
}
}
}
@@ -927,7 +1219,7 @@ bool BMSChartReader::ReadNoteData()
search = ssprintf( "#bmp%s", obj.value.c_str() );
it = in->headers.find( search );
if (it != in->headers.end())
if (it != in->headers.end()) // To elaborate, this means this is an unknown key.
{
RString bg;
if (song->GetBackground(it->second, in->path, bg))
@@ -935,6 +1227,10 @@ bool BMSChartReader::ReadNoteData()
info.backgroundChanges[row] = bg;
}
}
else
{
LOG->UserLog("Song file", in->path.c_str(), "uses key \"%s\" for a bmp change which is undefined.", obj.value.c_str());
}
}
}
else if( channel == 8 ) // bpm change (extended)