// Apply the given change to the universe. void GameData::Change(const DataNode &node) { if(node.Token(0) == "fleet" && node.Size() >= 2) fleets.Get(node.Token(1))->Load(node); else if(node.Token(0) == "galaxy" && node.Size() >= 2) galaxies.Get(node.Token(1))->Load(node); else if(node.Token(0) == "government" && node.Size() >= 2) governments.Get(node.Token(1))->Load(node); else if(node.Token(0) == "outfitter" && node.Size() >= 2) outfitSales.Get(node.Token(1))->Load(node, outfits); else if(node.Token(0) == "planet" && node.Size() >= 2) planets.Get(node.Token(1))->Load(node, shipSales, outfitSales); else if(node.Token(0) == "shipyard" && node.Size() >= 2) shipSales.Get(node.Token(1))->Load(node, ships); else if(node.Token(0) == "system" && node.Size() >= 2) systems.Get(node.Token(1))->Load(node, planets); else if(node.Token(0) == "news" && node.Size() >= 2) news.Get(node.Token(1))->Load(node); else if(node.Token(0) == "link" && node.Size() >= 3) systems.Get(node.Token(1))->Link(systems.Get(node.Token(2))); else if(node.Token(0) == "unlink" && node.Size() >= 3) systems.Get(node.Token(1))->Unlink(systems.Get(node.Token(2))); else node.PrintTrace("Invalid \"event\" data:"); }
void ConditionSet::Add(const DataNode &node) { if(node.Size() == 2) { if(!Add(node.Token(0), node.Token(1))) node.PrintTrace("Unrecognized condition expression:"); } else if(node.Size() == 3) { if(!Add(node.Token(0), node.Token(1), node.Value(2))) node.PrintTrace("Unrecognized condition expression:"); } else if(node.Size() == 1 && node.Token(0) == "never") entries.emplace_back("", "!=", 0); else if(node.Size() == 1 && (node.Token(0) == "and" || node.Token(0) == "or")) { children.emplace_back(); children.back().Load(node); } else node.PrintTrace("Unrecognized condition expression:"); }
// Add a label, pointing to whatever node is created next. void Conversation::AddLabel(const string &label, const DataNode &node) { if(labels.find(label) != labels.end()) { node.PrintTrace("Conversation: label \"" + label + "\" is used more than once:"); return; } // If there are any unresolved references to this label, we can now set // their indices correctly. auto range = unresolved.equal_range(label); for(auto it = range.first; it != range.second; ++it) nodes[it->second.first].data[it->second.second].second = nodes.size(); unresolved.erase(range.first, range.second); // Remember what index this label points to. labels[label] = nodes.size(); }
void Conversation::Load(const DataNode &node) { if(node.Token(0) != "conversation") return; if(node.Size() >= 2) identifier = node.Token(1); // Free any previously loaded data. nodes.clear(); for(const DataNode &child : node) { if(child.Token(0) == "scene" && child.Size() >= 2) { nodes.emplace_back(); int next = nodes.size(); nodes.back().data.emplace_back("", next); nodes.back().scene = SpriteSet::Get(child.Token(1)); nodes.back().sceneName = child.Token(1); } else if(child.Token(0) == "label" && child.Size() >= 2) { // You cannot merge text above a label with text below it. if(!nodes.empty()) nodes.back().canMergeOnto = false; AddLabel(child.Token(1), child); } else if(child.Token(0) == "choice") { // Create a new node with one or more choices in it. nodes.emplace_back(true); for(const DataNode &grand : child) { // Store the text of this choice. By default, the choice will // just bring you to the next node in the script. nodes.back().data.emplace_back(grand.Token(0), nodes.size()); nodes.back().data.back().first += '\n'; // If this choice contains a goto, record it. for(const DataNode &great : grand) { int index = TokenIndex(great.Token(0)); if(!index && great.Size() >= 2) Goto(great.Token(1), nodes.size() - 1, nodes.back().data.size() - 1); else if(index < 0) nodes.back().data.back().second = index; else continue; break; } } if(nodes.back().data.empty()) { child.PrintTrace("Conversation contains an empty \"choice\" node:"); nodes.pop_back(); } } else if(child.Token(0) == "name") nodes.emplace_back(true); else if(child.Token(0) == "branch") { nodes.emplace_back(); nodes.back().canMergeOnto = false; nodes.back().conditions.Load(child); for(int i = 1; i <= 2; ++i) { // If no link is provided, just go to the next node. nodes.back().data.emplace_back("", nodes.size()); if(child.Size() > i) { int index = TokenIndex(child.Token(i)); if(!index) Goto(child.Token(i), nodes.size() - 1, i - 1); else if(index < 0) nodes.back().data.back().second = index; } } } else if(child.Token(0) == "apply") { nodes.emplace_back(); nodes.back().canMergeOnto = false; nodes.back().conditions.Load(child); nodes.back().data.emplace_back("", nodes.size()); if(child.Size() > 1) { int index = TokenIndex(child.Token(1)); if(!index) Goto(child.Token(1), nodes.size() - 1, 0); else if(index < 0) nodes.back().data.back().second = index; } } else { // This is just an ordinary text node. // If the previous node is a choice, or if the previous node ended // in a goto, create a new node. Otherwise, just merge this new // paragraph into the previous node. if(nodes.empty() || !nodes.back().canMergeOnto) { nodes.emplace_back(); int next = nodes.size(); nodes.back().data.emplace_back("", next); } nodes.back().data.back().first += child.Token(0); nodes.back().data.back().first += '\n'; // Check if this node contains a "goto". for(const DataNode &grand : child) { int index = TokenIndex(grand.Token(0)); if(!index && grand.Size() >= 2) Goto(grand.Token(1), nodes.size() - 1); else if(index < 0) nodes.back().data.back().second = index; else continue; nodes.back().canMergeOnto = false; break; } } } // Display a warning if a label was not resolved. if(!unresolved.empty()) for(const auto &it : unresolved) node.PrintTrace("Conversation contains unused label \"" + it.first + "\":"); // Check for any loops in the conversation. for(const auto &it : labels) { int nodeIndex = it.second; while(nodeIndex >= 0 && Choices(nodeIndex) <= 1) { nodeIndex = NextNode(nodeIndex); if(nodeIndex == it.second) { node.PrintTrace("Conversation contains infinite loop beginning with label \"" + it.first + "\":"); nodes.clear(); return; } } } // Free the working buffers that we no longer need. labels.clear(); unresolved.clear(); }
// Load a conversation from file. void Conversation::Load(const DataNode &node) { // Make sure this really is a conversation specification. if(node.Token(0) != "conversation") return; // Free any previously loaded data. nodes.clear(); for(const DataNode &child : node) { if(child.Token(0) == "scene" && child.Size() >= 2) { // A scene always starts a new text node. AddNode(); nodes.back().scene = SpriteSet::Get(child.Token(1)); } else if(child.Token(0) == "label" && child.Size() >= 2) { // You cannot merge text above a label with text below it. if(!nodes.empty()) nodes.back().canMergeOnto = false; AddLabel(child.Token(1), child); } else if(child.Token(0) == "choice") { // Create a new node with one or more choices in it. nodes.emplace_back(true); for(const DataNode &grand : child) { // Store the text of this choice. By default, the choice will // just bring you to the next node in the script. nodes.back().data.emplace_back(grand.Token(0), nodes.size()); nodes.back().data.back().first += '\n'; LoadGotos(grand); } if(nodes.back().data.empty()) { child.PrintTrace("Conversation contains an empty \"choice\" node:"); nodes.pop_back(); } } else if(child.Token(0) == "name") { // A name entry field is just represented as an empty choice node. nodes.emplace_back(true); } else if(child.Token(0) == "branch") { // Don't merge "branch" nodes with any other nodes. nodes.emplace_back(); nodes.back().canMergeOnto = false; nodes.back().conditions.Load(child); // A branch should always specify what node to go to if the test is // true, and may also specify where to go if it is false. for(int i = 1; i <= 2; ++i) { // If no link is provided, just go to the next node. nodes.back().data.emplace_back("", nodes.size()); if(child.Size() > i) { int index = TokenIndex(child.Token(i)); if(!index) Goto(child.Token(i), nodes.size() - 1, i - 1); else if(index < 0) nodes.back().data.back().second = index; } } } else if(child.Token(0) == "apply") { // Don't merge "apply" nodes with any other nodes. AddNode(); nodes.back().canMergeOnto = false; nodes.back().conditions.Load(child); } else { // This is just an ordinary text node. // If the previous node is a choice, or if the previous node ended // in a goto, create a new node. Otherwise, just merge this new // paragraph into the previous node. if(nodes.empty() || !nodes.back().canMergeOnto) AddNode(); // Always append a newline to the end of the text. nodes.back().data.back().first += child.Token(0); nodes.back().data.back().first += '\n'; // Check whether there is a goto attached to this block of text. If // so, future nodes can't merge onto this one. if(LoadGotos(child)) nodes.back().canMergeOnto = false; } } // Display a warning if a label was not resolved. if(!unresolved.empty()) for(const auto &it : unresolved) node.PrintTrace("Conversation contains unrecognized label \"" + it.first + "\":"); // Check for any loops in the conversation. for(const auto &it : labels) { int nodeIndex = it.second; while(nodeIndex >= 0 && Choices(nodeIndex) <= 1) { nodeIndex = NextNode(nodeIndex); if(nodeIndex == it.second) { node.PrintTrace("Conversation contains infinite loop beginning with label \"" + it.first + "\":"); nodes.clear(); return; } } } // Free the working buffers that we no longer need. labels.clear(); unresolved.clear(); }