/*
* Copyright 2010-2016 OpenXcom Developers.
*
* This file is part of OpenXcom.
*
* OpenXcom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenXcom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenXcom. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MapScript.h"
#include <yaml-cpp/yaml.h>
#include "../Engine/RNG.h"
#include "../Engine/Exception.h"
#include "../Mod/RuleTerrain.h"
namespace OpenXcom
{
MapScript::MapScript() : _type(MSC_UNDEFINED), _sizeX(1), _sizeY(1), _sizeZ(0), _executionChances(100), _executions(1), _cumulativeFrequency(0), _label(0), _direction(MD_NONE), _tunnelData(0)
{
}
MapScript::~MapScript()
{
for (std::vector<SDL_Rect*>::iterator i = _rects.begin(); i != _rects.end();++i)
{
delete *i;
}
delete _tunnelData;
}
/**
* Loads a map script command from YAML.
* @param node the YAML node from which to read.
*/
void MapScript::load(const YAML::Node& node)
{
std::string command;
if (const YAML::Node &map = node["type"])
{
command = map.as<std::string>("");
if (command == "addBlock")
_type = MSC_ADDBLOCK;
else if (command == "addLine")
_type = MSC_ADDLINE;
else if (command == "addCraft")
{
_type = MSC_ADDCRAFT;
_groups.push_back(1); // this is a default, and can be overridden
}
else if (command == "addUFO")
{
_type = MSC_ADDUFO;
_groups.push_back(1); // this is a default, and can be overridden
}
else if (command == "digTunnel")
_type = MSC_DIGTUNNEL;
else if (command == "fillArea")
_type = MSC_FILLAREA;
else if (command == "checkBlock")
_type = MSC_CHECKBLOCK;
else if (command == "removeBlock")
_type = MSC_REMOVE;
else if (command == "resize")
{
_type = MSC_RESIZE;
_sizeX = _sizeY = 0; // defaults: don't resize anything unless specified.
}
else
{
throw Exception("Unknown command: " + command);
}
}
else
{
throw Exception("Missing command type.");
}
if (const YAML::Node &map = node["rects"])
{
for (YAML::const_iterator i = map.begin(); i != map.end(); ++i)
{
SDL_Rect *rect = new SDL_Rect();
rect->x = (*i)[0].as<int>();
rect->y = (*i)[1].as<int>();
rect->w = (*i)[2].as<int>();
rect->h = (*i)[3].as<int>();
_rects.push_back(rect);
}
}
if (const YAML::Node &map = node["tunnelData"])
{
_tunnelData = new TunnelData;
_tunnelData->level = map["level"].as<int>(0);
if (const YAML::Node &data = map["MCDReplacements"])
{
for (YAML::Node::const_iterator i = data.begin(); i != data.end(); ++i)
{
MCDReplacement replacement;
std::string type = (*i)["type"].as<std::string>("");
replacement.entry = (*i)["entry"].as<int>(-1);
replacement.set = (*i)["set"].as<int>(-1);
_tunnelData->replacements[type] = replacement;
}
}
}
if (const YAML::Node &map = node["conditionals"])
{
if (map.Type() == YAML::NodeType::Sequence)
{
_conditionals = map.as<std::vector<int> >(_conditionals);
}
else
{
_conditionals.push_back(map.as<int>(0));
}
}
if (const YAML::Node &map = node["size"])
{
if (map.Type() == YAML::NodeType::Sequence)
{
int *sizes[3] = {&_sizeX, &_sizeY, &_sizeZ};
int entry = 0;
for (YAML::const_iterator i = map.begin(); i != map.end(); ++i)
{
*sizes[entry] = (*i).as<int>(1);
entry++;
if (entry == 3)
{
break;
}
}
}
else
{
_sizeX = map.as<int>(_sizeX);
_sizeY = _sizeX;
}
}
if (const YAML::Node &map = node["groups"])
{
_groups.clear();
if (map.Type() == YAML::NodeType::Sequence)
{
for (YAML::const_iterator i = map.begin(); i != map.end(); ++i)
{
_groups.push_back((*i).as<int>(0));
}
}
else
{
_groups.push_back(map.as<int>(0));
}
}
size_t selectionSize = _groups.size();
if (const YAML::Node &map = node["blocks"])
{
_groups.clear();
if (map.Type() == YAML::NodeType::Sequence)
{
for (YAML::const_iterator i = map.begin(); i != map.end(); ++i)
{
_blocks.push_back((*i).as<int>(0));
}
}
else
{
_blocks.push_back(map.as<int>(0));
}
selectionSize = _blocks.size();
}
_frequencies.resize(selectionSize, 1);
_maxUses.resize(selectionSize, -1);
if (const YAML::Node &map = node["freqs"])
{
if (map.Type() == YAML::NodeType::Sequence)
{
size_t entry = 0;
for (YAML::const_iterator i = map.begin(); i != map.end(); ++i)
{
if (entry == selectionSize)
break;
_frequencies.at(entry) = (*i).as<int>(1);
entry++;
}
}
else
{
_frequencies.at(0) = map.as<int>(1);
}
}
if (const YAML::Node &map = node["maxUses"])
{
if (map.Type() == YAML::NodeType::Sequence)
{
size_t entry = 0;
for (YAML::const_iterator i = map.begin(); i != map.end(); ++i)
{
if (entry == selectionSize)
break;
_maxUses.at(entry) = (*i).as<int>(-1);
entry++;
}
}
else
{
_maxUses.at(0) = map.as<int>(-1);
}
}
if (const YAML::Node &map = node["direction"])
{
std::string direction = map.as<std::string>("");
if (!direction.empty())
{
char dir = toupper(direction[0]);
switch (dir)
{
case 'V':
_direction = MD_VERTICAL;
break;
case 'H':
_direction = MD_HORIZONTAL;
break;
case 'B':
_direction = MD_BOTH;
break;
default:
throw Exception("direction must be [V]ertical, [H]orizontal, or [B]oth, what does " + direction + " mean?");
}
}
}
if (_direction == MD_NONE)
{
if (_type == MSC_DIGTUNNEL || _type == MSC_ADDLINE)
{
throw Exception("no direction defined for " + command + " command, must be [V]ertical, [H]orizontal, or [B]oth");
}
}
_executionChances = node["executionChances"].as<int>(_executionChances);
_executions = node["executions"].as<int>(_executions);
_ufoName = node["UFOName"].as<std::string>(_ufoName);
// take no chances, don't accept negative values here.
_label = std::abs(node["label"].as<int>(_label));
}
/**
* Initializes all the various scratch values and such for the command.
*/
void MapScript::init()
{
_cumulativeFrequency = 0;
_blocksTemp.clear();
_groupsTemp.clear();
_frequenciesTemp.clear();
_maxUsesTemp.clear();
for (std::vector<int>::const_iterator i = _frequencies.begin(); i != _frequencies.end(); ++i)
{
_cumulativeFrequency += *i;
}
_blocksTemp = _blocks;
_groupsTemp = _groups;
_frequenciesTemp = _frequencies;
_maxUsesTemp = _maxUses;
}
/**
* Gets a random group number from the array, accounting for frequencies and max uses.
* If no groups or blocks are defined, this command will return the default" group,
* If all the max uses are used up, it will return "undefined".
* @return Group number.
*/
int MapScript::getGroupNumber()
{
if (!_groups.size())
{
return MT_DEFAULT;
}
if (_cumulativeFrequency > 0)
{
int pick = RNG::generate(0, _cumulativeFrequency-1);
for (size_t i = 0; i != _groupsTemp.size(); ++i)
{
if (pick < _frequenciesTemp.at(i))
{
int retVal = _groupsTemp.at(i);
if (_maxUsesTemp.at(i) > 0)
{
if (--_maxUsesTemp.at(i) == 0)
{
_groupsTemp.erase(_groupsTemp.begin() + i);
_cumulativeFrequency -= _frequenciesTemp.at(i);
_frequenciesTemp.erase(_frequenciesTemp.begin() + i);
_maxUsesTemp.erase(_maxUsesTemp.begin() + i);
}
}
return retVal;
}
pick -= _frequenciesTemp.at(i);
}
}
return MT_UNDEFINED;
}
/**
* Gets a random block number from the array, accounting for frequencies and max uses.
* If no blocks are defined, it will use a group instead.
* @return Block number.
*/
int MapScript::getBlockNumber()
{
if (_cumulativeFrequency > 0)
{
int pick = RNG::generate(0, _cumulativeFrequency-1);
for (size_t i = 0; i != _blocksTemp.size(); ++i)
{
if (pick < _frequenciesTemp.at(i))
{
int retVal = _blocksTemp.at(i);
if (_maxUsesTemp.at(i) > 0)
{
if (--_maxUsesTemp.at(i) == 0)
{
_blocksTemp.erase(_blocksTemp.begin() + i);
_cumulativeFrequency -= _frequenciesTemp.at(i);
_frequenciesTemp.erase(_frequenciesTemp.begin() + i);
_maxUsesTemp.erase(_maxUsesTemp.begin() + i);
}
}
return retVal;
}
pick -= _frequenciesTemp.at(i);
}
}
return MT_UNDEFINED;
}
/**
* Gets a random map block from a given terrain, using either the groups or the blocks defined.
* @param terrain the terrain to pick a block from.
* @return Pointer to a randomly chosen map block, given the options available.
*/
MapBlock *MapScript::getNextBlock(RuleTerrain *terrain)
{
if (_blocks.empty())
{
return terrain->getRandomMapBlock(_sizeX * 10, _sizeY * 10, getGroupNumber());
}
int result = getBlockNumber();
if (result < (int)(terrain->getMapBlocks()->size()) && result != MT_UNDEFINED)
{
return terrain->getMapBlocks()->at((size_t)(result));
}
return 0;
}
/**
* Gets the name of the UFO in the case of "setUFO"
* @return the UFO name.
*/
std::string MapScript::getUFOName() const
{
return _ufoName;
}
}
↑ V537 Consider reviewing the correctness of '_sizeX' item's usage.