Add support for #IF, #ELSE, #ELSEIF, #ENDIF for BMS.
This commit is contained in:
+348
-52
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user