Add support for #IF, #ELSE, #ELSEIF, #ENDIF for BMS.
This commit is contained in:
+331
-35
@@ -178,6 +178,7 @@ struct BMSMeasure
|
|||||||
float size;
|
float size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const int MaxBMSElements = 1296; // ZZ in b36
|
||||||
typedef map<RString, RString> BMSHeaders;
|
typedef map<RString, RString> BMSHeaders;
|
||||||
typedef map<int, BMSMeasure> BMSMeasures;
|
typedef map<int, BMSMeasure> BMSMeasures;
|
||||||
typedef vector<BMSObject> BMSObjects;
|
typedef vector<BMSObject> BMSObjects;
|
||||||
@@ -209,8 +210,297 @@ bool BMSChart::GetHeader( const RString &header, RString &out )
|
|||||||
return true;
|
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 )
|
bool BMSChart::Load( const RString &chartPath )
|
||||||
{
|
{
|
||||||
|
bmsCommandTree Tree;
|
||||||
|
Tree.path = chartPath;
|
||||||
path = chartPath;
|
path = chartPath;
|
||||||
|
|
||||||
RageFile file;
|
RageFile file;
|
||||||
@@ -220,32 +510,31 @@ bool BMSChart::Load( const RString &chartPath )
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while( !file.AtEOF() )
|
while (!file.AtEOF())
|
||||||
{
|
{
|
||||||
RString line;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
StripCrnl( line );
|
StripCrnl(line);
|
||||||
|
|
||||||
if( line.size() > 1 && line[0] == '#' )
|
Tree.doStatement(line);
|
||||||
{
|
}
|
||||||
if( line.size() >= 7 &&
|
|
||||||
( '0' <= line[1] && line[1] <= '9' ) &&
|
vector<RString> lines;
|
||||||
( '0' <= line[2] && line[2] <= '9' ) &&
|
Tree.evaluateBMSTree(headers, lines);
|
||||||
( '0' <= line[3] && line[3] <= '9' ) &&
|
|
||||||
( '0' <= line[4] && line[4] <= '9' ) &&
|
for (vector<RString>::iterator i = lines.begin(); i != lines.end(); ++i)
|
||||||
( '0' <= line[5] && line[5] <= '9' ) &&
|
|
||||||
line[6] == ':' )
|
|
||||||
{
|
{
|
||||||
|
RString line = *i;
|
||||||
RString data = line.substr(7);
|
RString data = line.substr(7);
|
||||||
int measure = atoi( line.substr(1, 3).c_str() );
|
int measure = atoi(line.substr(1, 3).c_str());
|
||||||
int channel = atoi( line.substr(4, 2).c_str() );
|
int channel = atoi(line.substr(4, 2).c_str());
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
if( channel == 2 )
|
if (channel == 2)
|
||||||
{
|
{
|
||||||
// special channel: time signature
|
// special channel: time signature
|
||||||
BMSMeasure m = { StringToFloat(data) };
|
BMSMeasure m = { StringToFloat(data) };
|
||||||
@@ -253,36 +542,26 @@ bool BMSChart::Load( const RString &chartPath )
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if( channel >= 51 )
|
if (channel >= 51)
|
||||||
{
|
{
|
||||||
channel -= 40;
|
channel -= 40;
|
||||||
flag = true;
|
flag = true;
|
||||||
}
|
}
|
||||||
int count = data.size() / 2;
|
int count = data.size() / 2;
|
||||||
for( int i = 0; i < count; i ++ )
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
RString value = data.substr( 2 * i, 2 );
|
RString value = data.substr(2 * i, 2);
|
||||||
if( value != "00" )
|
if (value != "00")
|
||||||
{
|
{
|
||||||
value.MakeLower();
|
value.MakeLower();
|
||||||
BMSObject o = { channel, measure, (float)i / count, flag, value };
|
BMSObject o = { channel, measure, (float)i / count, flag, value };
|
||||||
objects.push_back( o );
|
objects.push_back(o);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TidyUpData();
|
TidyUpData();
|
||||||
|
|
||||||
@@ -458,7 +737,7 @@ void BMSSong::PrecacheBackgrounds(const RString &dir)
|
|||||||
|
|
||||||
for( unsigned i = 0; i < arrayPossibleFiles.size(); i++ )
|
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] );
|
RString fn = SetExtension( arrayPossibleFiles[i], exts[j] );
|
||||||
mapBackground[fn] = arrayPossibleFiles[i];
|
mapBackground[fn] = arrayPossibleFiles[i];
|
||||||
@@ -476,6 +755,7 @@ struct BMSChartInfo {
|
|||||||
RString backgroundFile;
|
RString backgroundFile;
|
||||||
RString stageFile;
|
RString stageFile;
|
||||||
RString musicFile;
|
RString musicFile;
|
||||||
|
RString overrideMusicFile;
|
||||||
|
|
||||||
map<int, RString> backgroundChanges;
|
map<int, RString> backgroundChanges;
|
||||||
};
|
};
|
||||||
@@ -592,6 +872,18 @@ void BMSChartReader::ReadHeaders()
|
|||||||
{
|
{
|
||||||
out->SetMeter( StringToInt(it->second) );
|
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() );
|
search = ssprintf( "#bmp%s", obj.value.c_str() );
|
||||||
it = in->headers.find( search );
|
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;
|
RString bg;
|
||||||
if (song->GetBackground(it->second, in->path, bg))
|
if (song->GetBackground(it->second, in->path, bg))
|
||||||
@@ -935,6 +1227,10 @@ bool BMSChartReader::ReadNoteData()
|
|||||||
info.backgroundChanges[row] = bg;
|
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)
|
else if( channel == 8 ) // bpm change (extended)
|
||||||
|
|||||||
Reference in New Issue
Block a user