/*
* 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 "Mod.h"
#include <algorithm>
#include <sstream>
#include <climits>
#include <cassert>
#include "../Engine/CrossPlatform.h"
#include "../Engine/FileMap.h"
#include "../Engine/Palette.h"
#include "../Engine/Font.h"
#include "../Engine/Surface.h"
#include "../Engine/SurfaceSet.h"
#include "../Engine/Music.h"
#include "../Engine/GMCat.h"
#include "../Engine/SoundSet.h"
#include "../Engine/Sound.h"
#include "../Interface/TextButton.h"
#include "../Interface/Window.h"
#include "MapDataSet.h"
#include "RuleMusic.h"
#include "../Engine/ShaderDraw.h"
#include "../Engine/ShaderMove.h"
#include "../Engine/Exception.h"
#include "../Engine/Logger.h"
#include "SoundDefinition.h"
#include "ExtraSprites.h"
#include "ExtraSounds.h"
#include "../Engine/AdlibMusic.h"
#include "../fmath.h"
#include "../Engine/RNG.h"
#include "../Engine/Options.h"
#include "../Battlescape/Pathfinding.h"
#include "RuleCountry.h"
#include "RuleRegion.h"
#include "RuleBaseFacility.h"
#include "RuleCraft.h"
#include "RuleCraftWeapon.h"
#include "RuleItem.h"
#include "RuleUfo.h"
#include "RuleTerrain.h"
#include "MapScript.h"
#include "RuleSoldier.h"
#include "RuleCommendations.h"
#include "AlienRace.h"
#include "AlienDeployment.h"
#include "Armor.h"
#include "ArticleDefinition.h"
#include "RuleInventory.h"
#include "RuleResearch.h"
#include "RuleManufacture.h"
#include "ExtraStrings.h"
#include "RuleInterface.h"
#include "RuleMissionScript.h"
#include "../Geoscape/Globe.h"
#include "../Savegame/SavedGame.h"
#include "../Savegame/Region.h"
#include "../Savegame/Base.h"
#include "../Savegame/Country.h"
#include "../Savegame/Soldier.h"
#include "../Savegame/Craft.h"
#include "../Savegame/Vehicle.h"
#include "../Savegame/ItemContainer.h"
#include "../Savegame/Transfer.h"
#include "../Ufopaedia/Ufopaedia.h"
#include "../Savegame/AlienStrategy.h"
#include "../Savegame/GameTime.h"
#include "../Savegame/SoldierDiary.h"
#include "UfoTrajectory.h"
#include "RuleAlienMission.h"
#include "MCDPatch.h"
#include "StatString.h"
#include "RuleGlobe.h"
#include "RuleVideo.h"
#include "RuleConverter.h"
#define ARRAYLEN(x) (sizeof(x) / sizeof(x[0]))
namespace OpenXcom
{
int Mod::DOOR_OPEN;
int Mod::SLIDING_DOOR_OPEN;
int Mod::SLIDING_DOOR_CLOSE;
int Mod::SMALL_EXPLOSION;
int Mod::LARGE_EXPLOSION;
int Mod::EXPLOSION_OFFSET;
int Mod::SMOKE_OFFSET;
int Mod::UNDERWATER_SMOKE_OFFSET;
int Mod::ITEM_DROP;
int Mod::ITEM_THROW;
int Mod::ITEM_RELOAD;
int Mod::WALK_OFFSET;
int Mod::FLYING_SOUND;
int Mod::BUTTON_PRESS;
int Mod::WINDOW_POPUP[3];
int Mod::UFO_FIRE;
int Mod::UFO_HIT;
int Mod::UFO_CRASH;
int Mod::UFO_EXPLODE;
int Mod::INTERCEPTOR_HIT;
int Mod::INTERCEPTOR_EXPLODE;
int Mod::GEOSCAPE_CURSOR;
int Mod::BASESCAPE_CURSOR;
int Mod::BATTLESCAPE_CURSOR;
int Mod::UFOPAEDIA_CURSOR;
int Mod::GRAPHS_CURSOR;
int Mod::DAMAGE_RANGE;
int Mod::EXPLOSIVE_DAMAGE_RANGE;
int Mod::FIRE_DAMAGE_RANGE[2];
std::string Mod::DEBRIEF_MUSIC_GOOD;
std::string Mod::DEBRIEF_MUSIC_BAD;
int Mod::DIFFICULTY_COEFFICIENT[5];
/// Predefined name for first loaded mod that have all original data
const std::string ModNameMaster = "master";
/// Predefined name for current mod that is loading rulesets.
const std::string ModNameCurrent = "current";
/// Reduction of size allocated for transparcey LUTs.
const size_t ModTransparceySizeReduction = 100;
void Mod::resetGlobalStatics()
{
DOOR_OPEN = 3;
SLIDING_DOOR_OPEN = 20;
SLIDING_DOOR_CLOSE = 21;
SMALL_EXPLOSION = 2;
LARGE_EXPLOSION = 5;
EXPLOSION_OFFSET = 0;
SMOKE_OFFSET = 8;
UNDERWATER_SMOKE_OFFSET = 0;
ITEM_DROP = 38;
ITEM_THROW = 39;
ITEM_RELOAD = 17;
WALK_OFFSET = 22;
FLYING_SOUND = 15;
BUTTON_PRESS = 0;
WINDOW_POPUP[0] = 1;
WINDOW_POPUP[1] = 2;
WINDOW_POPUP[2] = 3;
UFO_FIRE = 8;
UFO_HIT = 12;
UFO_CRASH = 10;
UFO_EXPLODE = 11;
INTERCEPTOR_HIT = 10;
INTERCEPTOR_EXPLODE = 13;
GEOSCAPE_CURSOR = 252;
BASESCAPE_CURSOR = 252;
BATTLESCAPE_CURSOR = 144;
UFOPAEDIA_CURSOR = 252;
GRAPHS_CURSOR = 252;
DAMAGE_RANGE = 100;
EXPLOSIVE_DAMAGE_RANGE = 50;
FIRE_DAMAGE_RANGE[0] = 5;
FIRE_DAMAGE_RANGE[1] = 10;
DEBRIEF_MUSIC_GOOD = "GMMARS";
DEBRIEF_MUSIC_BAD = "GMMARS";
Globe::OCEAN_COLOR = Palette::blockOffset(12);
Globe::OCEAN_SHADING = true;
Globe::COUNTRY_LABEL_COLOR = 239;
Globe::LINE_COLOR = 162;
Globe::CITY_LABEL_COLOR = 138;
Globe::BASE_LABEL_COLOR = 133;
TextButton::soundPress = 0;
Window::soundPopup[0] = 0;
Window::soundPopup[1] = 0;
Window::soundPopup[2] = 0;
Pathfinding::red = 3;
Pathfinding::yellow = 10;
Pathfinding::green = 4;
DIFFICULTY_COEFFICIENT[0] = 0;
DIFFICULTY_COEFFICIENT[1] = 1;
DIFFICULTY_COEFFICIENT[2] = 2;
DIFFICULTY_COEFFICIENT[3] = 3;
DIFFICULTY_COEFFICIENT[4] = 4;
}
/**
* Creates an empty mod.
*/
Mod::Mod() : _costEngineer(0), _costScientist(0), _timePersonnel(0), _initialFunding(0), _turnAIUseGrenade(3), _turnAIUseBlaster(3), _defeatScore(0), _defeatFunds(0), _difficultyDemigod(false), _startingTime(6, 1, 1, 1999, 12, 0, 0),
_facilityListOrder(0), _craftListOrder(0), _itemListOrder(0), _researchListOrder(0), _manufactureListOrder(0), _ufopaediaListOrder(0), _invListOrder(0), _modCurrent(0), _statePalette(0)
{
_muteMusic = new Music();
_muteSound = new Sound();
_globe = new RuleGlobe();
_converter = new RuleConverter();
_statAdjustment[0].aimAndArmorMultiplier = 0.5;
_statAdjustment[0].growthMultiplier = 0;
for (int i = 1; i != 5; ++i)
{
_statAdjustment[i].aimAndArmorMultiplier = 1.0;
_statAdjustment[i].growthMultiplier = i;
}
}
/**
* Deletes all the mod data from memory.
*/
Mod::~Mod()
{
delete _muteMusic;
delete _muteSound;
delete _globe;
delete _converter;
for (std::map<std::string, Font*>::iterator i = _fonts.begin(); i != _fonts.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, Surface*>::iterator i = _surfaces.begin(); i != _surfaces.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, SurfaceSet*>::iterator i = _sets.begin(); i != _sets.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, Palette*>::iterator i = _palettes.begin(); i != _palettes.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, Music*>::iterator i = _musics.begin(); i != _musics.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, SoundSet*>::iterator i = _sounds.begin(); i != _sounds.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleCountry*>::iterator i = _countries.begin(); i != _countries.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleRegion*>::iterator i = _regions.begin(); i != _regions.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleBaseFacility*>::iterator i = _facilities.begin(); i != _facilities.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleCraft*>::iterator i = _crafts.begin(); i != _crafts.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleCraftWeapon*>::iterator i = _craftWeapons.begin(); i != _craftWeapons.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleItem*>::iterator i = _items.begin(); i != _items.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleUfo*>::iterator i = _ufos.begin(); i != _ufos.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleTerrain*>::iterator i = _terrains.begin(); i != _terrains.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, MapDataSet*>::iterator i = _mapDataSets.begin(); i != _mapDataSets.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleSoldier*>::iterator i = _soldiers.begin(); i != _soldiers.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, Unit*>::iterator i = _units.begin(); i != _units.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, AlienRace*>::iterator i = _alienRaces.begin(); i != _alienRaces.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, AlienDeployment*>::iterator i = _alienDeployments.begin(); i != _alienDeployments.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, Armor*>::iterator i = _armors.begin(); i != _armors.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, ArticleDefinition*>::iterator i = _ufopaediaArticles.begin(); i != _ufopaediaArticles.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleInventory*>::iterator i = _invs.begin(); i != _invs.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleResearch *>::const_iterator i = _research.begin(); i != _research.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleManufacture *>::const_iterator i = _manufacture.begin(); i != _manufacture.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, UfoTrajectory *>::const_iterator i = _ufoTrajectories.begin(); i != _ufoTrajectories.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleAlienMission *>::const_iterator i = _alienMissions.begin(); i != _alienMissions.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, MCDPatch *>::const_iterator i = _MCDPatches.begin(); i != _MCDPatches.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, std::vector<ExtraSprites*> >::iterator i = _extraSprites.begin(); i != _extraSprites.end(); ++i)
{
for (std::vector<ExtraSprites*>::iterator j = i->second.begin(); j != i->second.end(); ++j)
{
delete *j;
}
}
for (std::vector< std::pair<std::string, ExtraSounds *> >::const_iterator i = _extraSounds.begin(); i != _extraSounds.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, ExtraStrings *>::const_iterator i = _extraStrings.begin(); i != _extraStrings.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleInterface *>::const_iterator i = _interfaces.begin(); i != _interfaces.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, std::vector<MapScript*> >::iterator i = _mapScripts.begin(); i != _mapScripts.end(); ++i)
{
for (std::vector<MapScript*>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j)
{
delete *j;
}
}
for (std::map<std::string, RuleVideo *>::const_iterator i = _videos.begin(); i != _videos.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleMusic *>::const_iterator i = _musicDefs.begin(); i != _musicDefs.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, RuleMissionScript*>::const_iterator i = _missionScripts.begin(); i != _missionScripts.end(); ++i)
{
delete i->second;
}
for (std::map<std::string, SoundDefinition*>::const_iterator i = _soundDefs.begin(); i != _soundDefs.end(); ++i)
{
delete i->second;
}
for (std::vector<StatString*>::const_iterator i = _statStrings.begin(); i != _statStrings.end(); ++i)
{
delete (*i);
}
for (std::map<std::string, RuleCommendations *>::const_iterator i = _commendations.begin(); i != _commendations.end(); ++i)
{
delete i->second;
}
}
/**
* Gets a specific rule element by ID.
* @param id String ID of the rule element.
* @param name Human-readable name of the rule type.
* @param map Map associated to the rule type.
* @param error Throw an error if not found.
* @return Pointer to the rule element, or NULL if not found.
*/
template <typename T>
T *Mod::getRule(const std::string &id, const std::string &name, const std::map<std::string, T*> &map, bool error) const
{
if (id.empty())
{
return 0;
}
typename std::map<std::string, T*>::const_iterator i = map.find(id);
if (i != map.end() && i->second != 0)
{
return i->second;
}
else
{
if (error)
{
throw Exception(name + " " + id + " not found");
}
return 0;
}
}
/**
* Returns a specific font from the mod.
* @param name Name of the font.
* @return Pointer to the font.
*/
Font *Mod::getFont(const std::string &name, bool error) const
{
return getRule(name, "Font", _fonts, error);
}
/**
* Loads any extra sprites associated to a surface when
* it's first requested.
* @param name Surface name.
*/
void Mod::lazyLoadSurface(const std::string &name)
{
if (Options::lazyLoadResources)
{
std::map<std::string, std::vector<ExtraSprites *> >::const_iterator i = _extraSprites.find(name);
if (i != _extraSprites.end())
{
for (std::vector<ExtraSprites*>::const_iterator j = i->second.begin(); j != i->second.end(); ++j)
{
loadExtraSprite(*j);
}
}
}
}
/**
* Returns a specific surface from the mod.
* @param name Name of the surface.
* @return Pointer to the surface.
*/
Surface *Mod::getSurface(const std::string &name, bool error)
{
lazyLoadSurface(name);
return getRule(name, "Sprite", _surfaces, error);
}
/**
* Returns a specific surface set from the mod.
* @param name Name of the surface set.
* @return Pointer to the surface set.
*/
SurfaceSet *Mod::getSurfaceSet(const std::string &name, bool error)
{
lazyLoadSurface(name);
return getRule(name, "Sprite Set", _sets, error);
}
/**
* Returns a specific music from the mod.
* @param name Name of the music.
* @return Pointer to the music.
*/
Music *Mod::getMusic(const std::string &name, bool error) const
{
if (Options::mute)
{
return _muteMusic;
}
else
{
return getRule(name, "Music", _musics, error);
}
}
/**
* Returns a random music from the mod.
* @param name Name of the music to pick from.
* @return Pointer to the music.
*/
Music *Mod::getRandomMusic(const std::string &name) const
{
if (Options::mute)
{
return _muteMusic;
}
else
{
std::vector<Music*> music;
for (std::map<std::string, Music*>::const_iterator i = _musics.begin(); i != _musics.end(); ++i)
{
if (i->first.find(name) != std::string::npos)
{
music.push_back(i->second);
}
}
if (music.empty())
{
return _muteMusic;
}
else
{
return music[RNG::seedless(0, music.size() - 1)];
}
}
}
/**
* Plays the specified track if it's not already playing.
* @param name Name of the music.
* @param id Id of the music, 0 for random.
*/
void Mod::playMusic(const std::string &name, int id)
{
if (!Options::mute && _playingMusic != name)
{
int loop = -1;
// hacks
if (!Options::musicAlwaysLoop && (name == "GMSTORY" || name == "GMWIN" || name == "GMLOSE"))
{
loop = 0;
}
Music *music = 0;
if (id == 0)
{
music = getRandomMusic(name);
}
else
{
std::ostringstream ss;
ss << name << id;
music = getMusic(ss.str());
}
music->play(loop);
if (music != _muteMusic)
{
_playingMusic = name;
}
}
}
/**
* Returns a specific sound set from the mod.
* @param name Name of the sound set.
* @return Pointer to the sound set.
*/
SoundSet *Mod::getSoundSet(const std::string &name, bool error) const
{
return getRule(name, "Sound Set", _sounds, error);
}
/**
* Returns a specific sound from the mod.
* @param set Name of the sound set.
* @param sound ID of the sound.
* @return Pointer to the sound.
*/
Sound *Mod::getSound(const std::string &set, unsigned int sound, bool error) const
{
if (Options::mute)
{
return _muteSound;
}
else
{
SoundSet *ss = getSoundSet(set, error);
if (ss != 0)
{
Sound *s = ss->getSound(sound);
if (s == 0 && error)
{
std::ostringstream err;
err << "Sound " << sound << " in " << set << " not found";
throw Exception(err.str());
}
return s;
}
else
{
return 0;
}
}
}
/**
* Returns a specific palette from the mod.
* @param name Name of the palette.
* @return Pointer to the palette.
*/
Palette *Mod::getPalette(const std::string &name, bool error) const
{
return getRule(name, "Palette", _palettes, error);
}
/**
* Changes the palette of all the graphics in the mod.
* @param colors Pointer to the set of colors.
* @param firstcolor Offset of the first color to replace.
* @param ncolors Amount of colors to replace.
*/
void Mod::setPalette(SDL_Color *colors, int firstcolor, int ncolors)
{
_statePalette = colors;
for (std::map<std::string, Font*>::iterator i = _fonts.begin(); i != _fonts.end(); ++i)
{
i->second->setPalette(colors, firstcolor, ncolors);
}
for (std::map<std::string, Surface*>::iterator i = _surfaces.begin(); i != _surfaces.end(); ++i)
{
if (!CrossPlatform::compareExt(i->first, "LBM"))
i->second->setPalette(colors, firstcolor, ncolors);
}
for (std::map<std::string, SurfaceSet*>::iterator i = _sets.begin(); i != _sets.end(); ++i)
{
i->second->setPalette(colors, firstcolor, ncolors);
}
}
/**
* Returns the list of voxeldata in the mod.
* @return Pointer to the list of voxeldata.
*/
std::vector<Uint16> *Mod::getVoxelData()
{
return &_voxelData;
}
/**
* Returns a specific sound from either the land or underwater sound set.
* @param depth the depth of the battlescape.
* @param sound ID of the sound.
* @return Pointer to the sound.
*/
Sound *Mod::getSoundByDepth(unsigned int depth, unsigned int sound, bool error) const
{
if (depth == 0)
return getSound("BATTLE.CAT", sound, error);
else
return getSound("BATTLE2.CAT", sound, error);
}
/**
* Returns the list of color LUTs in the mod.
* @return Pointer to the list of LUTs.
*/
const std::vector<std::vector<Uint8> > *Mod::getLUTs() const
{
return &_transparencyLUTs;
}
/**
* Returns the current mod-based offset for resources.
* @return Mod offset.
*/
int Mod::getModOffset() const
{
return _modCurrent->offset;
}
/**
* Get offset and index for sound set or sprite set.
* @param parent Name of parent node, used for better error message
* @param offset Member to load new value.
* @param node Node with data
* @param shared Max offset limit that is shared for every mod
* @param multiplier Value used by `projectile` surface set to convert projectile offset to index offset in surface.
* @param sizeScale Value used by transparency colors, reduce total number of avaialbe space for offset.
*/
void Mod::loadOffsetNode(const std::string &parent, int& offset, const YAML::Node &node, int shared, const std::string &set, size_t multiplier, size_t sizeScale) const
{
assert(_modCurrent);
const ModData* curr = _modCurrent;
if (node.IsScalar())
{
offset = node.as<int>();
}
else if (node.IsMap())
{
offset = node["index"].as<int>();
std::string mod = node["mod"].as<std::string>();
if (mod == ModNameMaster)
{
curr = &_modData.at(0);
}
else if (mod == ModNameCurrent)
{
//nothing
}
else
{
const ModData* n = 0;
for (size_t i = 0; i < _modData.size(); ++i)
{
const ModData& d = _modData[i];
if (d.name == mod)
{
n = &d;
break;
}
}
if (n)
{
curr = n;
}
else
{
std::ostringstream err;
err << "Error for '" << parent << "': unknown mod '" << mod << "' used";
throw Exception(err.str());
}
}
}
if (offset < -1)
{
std::ostringstream err;
err << "Error for '" << parent << "': offset '" << offset << "' has incorrect value in set '" << set << "' at line " << node.Mark().line;
throw Exception(err.str());
}
else if (offset == -1)
{
//ok
}
else
{
int f = offset;
f *= multiplier;
if ((size_t)f > curr->size / sizeScale)
{
std::ostringstream err;
err << "Error for '" << parent << "': offset '" << offset << "' exceeds mod size limit " << (curr->size / multiplier / sizeScale) << " in set '" << set << "'";
throw Exception(err.str());
}
if (f >= shared)
f += curr->offset / sizeScale;
offset = f;
}
}
/**
* Returns the appropriate mod-based offset for a sprite.
* If the ID is bigger than the surfaceset contents, the mod offset is applied.
* @param parent Name of parent node, used for better error message
* @param sprite Member to load new sprite ID index.
* @param node Node with data
* @param set Name of the surfaceset to lookup.
* @param multiplier Value used by `projectile` surface set to convert projectile offset to index offset in surface.
*/
void Mod::loadSpriteOffset(const std::string &parent, int& sprite, const YAML::Node &node, const std::string &set, size_t multiplier) const
{
if (node)
{
loadOffsetNode(parent, sprite, node, getRule(set, "Sprite Set", _sets, true)->getMaxSharedFrames(), set, multiplier);
}
}
/**
* Returns the appropriate mod-based offset for a sound.
* If the ID is bigger than the soundset contents, the mod offset is applied.
* @param parent Name of parent node, used for better error message
* @param sound Member to load new sound ID index.
* @param node Node with data
* @param set Name of the soundset to lookup.
*/
void Mod::loadSoundOffset(const std::string &parent, int& sound, const YAML::Node &node, const std::string &set) const
{
if (node)
{
loadOffsetNode(parent, sound, node, getSoundSet(set)->getMaxSharedSounds(), set, 1);
}
}
/**
* Gets the mod offset array for a certain transparency index.
* @param parent Name of parent node, used for better error message.
* @param index Member to load new transparency index.
* @param node Node with data.
*/
void Mod::loadTransparencyOffset(const std::string &parent, int& index, const YAML::Node &node) const
{
if (node)
{
loadOffsetNode(parent, index, node, 0, "TransparencyLUTs", 1, ModTransparceySizeReduction);
}
}
/**
* Gets the mod offset array for a certain sound.
* @param parent Name of parent node, used for better error message
* @param sounds Member to load new list of sound ID indexes.
* @param node Node with data
* @param set Name of the soundset to lookup.
*/
void Mod::loadSoundOffset(const std::string &parent, std::vector<int>& sounds, const YAML::Node &node, const std::string &set) const
{
if (node)
{
int maxShared = getSoundSet(set)->getMaxSharedSounds();
sounds.clear();
if (node.IsSequence())
{
for (YAML::const_iterator i = node.begin(); i != node.end(); ++i)
{
sounds.push_back(-1);
loadOffsetNode(parent, sounds.back(), *i, maxShared, set, 1);
}
}
else
{
sounds.push_back(-1);
loadOffsetNode(parent, sounds.back(), node, maxShared, set, 1);
}
}
}
/**
* Returns the appropriate mod-based offset for a generic ID.
* If the ID is bigger than the max, the mod offset is applied.
* @param id Numeric ID.
* @param max Maximum vanilla value.
*/
int Mod::getOffset(int id, int max) const
{
assert(_modCurrent);
if (id > max)
return id + _modCurrent->offset;
else
return id;
}
/**
* Helper function used to disable invalid mod and throw exception to quit game
* @param modId Mod id
* @param error Error message
*/
static void throwModOnErrorHelper(const std::string& modId, const std::string& error)
{
std::ostringstream errorStream;
errorStream << "failed to load '"
<< Options::getModInfos().at(modId).getName()
<< "'";
if (!Options::debug)
{
Log(LOG_WARNING) << "disabling mod with invalid ruleset: " << modId;
std::vector<std::pair<std::string, bool> >::iterator it =
std::find(Options::mods.begin(), Options::mods.end(),
std::pair<std::string, bool>(modId, true));
if (it == Options::mods.end())
{
Log(LOG_ERROR) << "cannot find broken mod in mods list: " << modId;
Log(LOG_ERROR) << "clearing mods list";
Options::mods.clear();
}
else
{
it->second = false;
}
Options::save();
errorStream << "; mod disabled";
}
errorStream << std::endl << error;
throw Exception(errorStream.str());
}
/**
* Loads a list of mods specified in the options.
* @param mods List of <modId, rulesetFiles> pairs.
*/
void Mod::loadAll(const std::vector< std::pair< std::string, std::vector<std::string> > > &mods)
{
Log(LOG_INFO) << "Loading rulesets...";
_modData.clear();
_modData.resize(mods.size());
std::set<std::string> usedModNames;
usedModNames.insert(ModNameMaster);
usedModNames.insert(ModNameCurrent);
// calculated offsets and other things for all mods
size_t offset = 0;
for (size_t i = 0; mods.size() > i; ++i)
{
const std::string& modId = mods[i].first;
if (usedModNames.insert(modId).second == false)
{
throwModOnErrorHelper(modId, "this mod name is already used");
}
const ModInfo *modInfo = &Options::getModInfos().at(modId);
size_t size = modInfo->getReservedSpace();
_modData[i].name = modId;
_modData[i].offset = 1000 * offset;
_modData[i].info = modInfo;
_modData[i].size = 1000 * size;
offset += size;
}
// load rulesets that can affect loading vanilla resources
for (size_t i = 0; _modData.size() > i; ++i)
{
_modCurrent = &_modData.at(i);
const ModInfo *info = _modCurrent->info;
if (info->isMaster() && !info->getResourceConfigFile().empty())
{
std::string path = info->getPath() + "/" + info->getResourceConfigFile();
if (CrossPlatform::fileExists(path))
{
loadResourceConfigFile(path);
}
}
}
// vanilla resources load
_modCurrent = &_modData.at(0);
loadVanillaResources();
// load rest rulesets
for (size_t i = 0; mods.size() > i; ++i)
{
try
{
_modCurrent = &_modData.at(i);
loadMod(mods[i].second);
}
catch (Exception &e)
{
const std::string &modId = mods[i].first;
throwModOnErrorHelper(modId, e.what());
}
}
//back master
_modCurrent = &_modData.at(0);
sortLists();
loadExtraResources();
modResources();
}
/**
* Loads a list of rulesets from YAML files for the mod at the specified index. The first
* mod loaded should be the master at index 0, then 1, and so on.
* @param rulesetFiles List of rulesets to load.
*/
void Mod::loadMod(const std::vector<std::string> &rulesetFiles)
{
for (std::vector<std::string>::const_iterator i = rulesetFiles.begin(); i != rulesetFiles.end(); ++i)
{
Log(LOG_VERBOSE) << "- " << *i;
try
{
loadFile(*i);
}
catch (YAML::Exception &e)
{
throw Exception((*i) + ": " + std::string(e.what()));
}
}
// these need to be validated, otherwise we're gonna get into some serious trouble down the line.
// it may seem like a somewhat arbitrary limitation, but there is a good reason behind it.
// i'd need to know what results are going to be before they are formulated, and there's a hierarchical structure to
// the order in which variables are determined for a mission, and the order is DIFFERENT for regular missions vs
// missions that spawn a mission site. where normally we pick a region, then a mission based on the weights for that region.
// a terror-type mission picks a mission type FIRST, then a region based on the criteria defined by the mission.
// there is no way i can conceive of to reconcile this difference to allow mixing and matching,
// short of knowing the results of calls to the RNG before they're determined.
// the best solution i can come up with is to disallow it, as there are other ways to achieve what this would amount to anyway,
// and they don't require time travel. - Warboy
for (std::map<std::string, RuleMissionScript*>::iterator i = _missionScripts.begin(); i != _missionScripts.end(); ++i)
{
RuleMissionScript *rule = (*i).second;
std::set<std::string> missions = rule->getAllMissionTypes();
if (!missions.empty())
{
std::set<std::string>::const_iterator j = missions.begin();
if (!getAlienMission(*j))
{
throw Exception("Error with MissionScript: " + (*i).first + ": alien mission type: " + *j + " not defined, do not incite the judgement of Amaunator.");
}
bool isSiteType = getAlienMission(*j)->getObjective() == OBJECTIVE_SITE;
rule->setSiteType(isSiteType);
for (;j != missions.end(); ++j)
{
if (getAlienMission(*j) && (getAlienMission(*j)->getObjective() == OBJECTIVE_SITE) != isSiteType)
{
throw Exception("Error with MissionScript: " + (*i).first + ": cannot mix terror/non-terror missions in a single command, so sayeth the wise Alaundo.");
}
}
}
}
// instead of passing a pointer to the region load function and moving the alienMission loading before region loading
// and sanitizing there, i'll sanitize here, i'm sure this sanitation will grow, and will need to be refactored into
// its own function at some point, but for now, i'll put it here next to the missionScript sanitation, because it seems
// the logical place for it, given that this sanitation is required as a result of moving all terror mission handling
// into missionScripting behaviour. apologies to all the modders that will be getting errors and need to adjust their
// rulesets, but this will save you weird errors down the line.
for (std::map<std::string, RuleRegion*>::iterator i = _regions.begin(); i != _regions.end(); ++i)
{
// bleh, make copies, const correctness kinda screwed me here.
WeightedOptions weights = (*i).second->getAvailableMissions();
std::vector<std::string> names = weights.getNames();
for (std::vector<std::string>::iterator j = names.begin(); j != names.end(); ++j)
{
if (!getAlienMission(*j))
{
throw Exception("Error with MissionWeights: Region: " + (*i).first + ": alien mission type: " + *j + " not defined, do not incite the judgement of Amaunator.");
}
if (getAlienMission(*j)->getObjective() == OBJECTIVE_SITE)
{
throw Exception("Error with MissionWeights: Region: " + (*i).first + " has " + *j + " listed. Terror mission can only be invoked via missionScript, so sayeth the Spider Queen.");
}
}
}
}
/**
* Loads a ruleset from a YAML file that have basic resources configuration.
* @param filename YAML filename.
*/
void Mod::loadResourceConfigFile(const std::string &filename)
{
YAML::Node doc = YAML::LoadFile(filename);
for (YAML::const_iterator i = doc["soundDefs"].begin(); i != doc["soundDefs"].end(); ++i)
{
SoundDefinition *rule = loadRule(*i, &_soundDefs);
if (rule != 0)
{
rule->load(*i);
}
}
if (const YAML::Node& luts = doc["transparencyLUTs"])
{
const size_t start = _modCurrent->offset / ModTransparceySizeReduction;
const size_t limit = _modCurrent->size / ModTransparceySizeReduction;
size_t curr = 0;
_transparencies.resize(start + limit);
for (YAML::const_iterator i = luts.begin(); i != luts.end(); ++i)
{
const YAML::Node& c = (*i)["colors"];
if (c.IsSequence())
{
for (YAML::const_iterator j = c.begin(); j != c.end(); ++j)
{
if (curr == limit)
{
throw Exception("transparencyLUTs mod limit reach");
}
SDL_Color color;
color.r = (*j)[0].as<int>(0);
color.g = (*j)[1].as<int>(0);
color.b = (*j)[2].as<int>(0);
color.unused = (*j)[3].as<int>(2);
// technically its breaking change as it always overwritte from offset `start + 0` but no two mods could work correctly before this change.
_transparencies[start + curr++] = color;
}
}
else
{
throw Exception("unknown transparencyLUTs node type");
}
}
}
}
/**
* Loads "constants" node.
*/
void Mod::loadConstants(const YAML::Node &node)
{
loadSoundOffset("constants", DOOR_OPEN, node["doorSound"], "BATTLE.CAT");
loadSoundOffset("constants", SLIDING_DOOR_OPEN, node["slidingDoorSound"], "BATTLE.CAT");
loadSoundOffset("constants", SLIDING_DOOR_CLOSE, node["slidingDoorClose"], "BATTLE.CAT");
loadSoundOffset("constants", SMALL_EXPLOSION, node["smallExplosion"], "BATTLE.CAT");
loadSoundOffset("constants", LARGE_EXPLOSION, node["largeExplosion"], "BATTLE.CAT");
loadSpriteOffset("constants", EXPLOSION_OFFSET, node["explosionOffset"], "X1.PCK");
loadSpriteOffset("constants", SMOKE_OFFSET, node["smokeOffset"], "SMOKE.PCK");
loadSpriteOffset("constants", UNDERWATER_SMOKE_OFFSET, node["underwaterSmokeOffset"], "SMOKE.PCK");
loadSoundOffset("constants", ITEM_DROP, node["itemDrop"], "BATTLE.CAT");
loadSoundOffset("constants", ITEM_THROW, node["itemThrow"], "BATTLE.CAT");
loadSoundOffset("constants", ITEM_RELOAD, node["itemReload"], "BATTLE.CAT");
loadSoundOffset("constants", WALK_OFFSET, node["walkOffset"], "BATTLE.CAT");
loadSoundOffset("constants", FLYING_SOUND, node["flyingSound"], "BATTLE.CAT");
loadSoundOffset("constants", BUTTON_PRESS, node["buttonPress"], "GEO.CAT");
if (node["windowPopup"])
{
int k = 0;
for (YAML::const_iterator j = node["windowPopup"].begin(); j != node["windowPopup"].end() && k < 3; ++j, ++k)
{
loadSoundOffset("constants", WINDOW_POPUP[k], (*j), "GEO.CAT");
}
}
loadSoundOffset("constants", UFO_FIRE, node["ufoFire"], "GEO.CAT");
loadSoundOffset("constants", UFO_HIT, node["ufoHit"], "GEO.CAT");
loadSoundOffset("constants", UFO_CRASH, node["ufoCrash"], "GEO.CAT");
loadSoundOffset("constants", UFO_EXPLODE, node["ufoExplode"], "GEO.CAT");
loadSoundOffset("constants", INTERCEPTOR_HIT, node["interceptorHit"], "GEO.CAT");
loadSoundOffset("constants", INTERCEPTOR_EXPLODE, node["interceptorExplode"], "GEO.CAT");
GEOSCAPE_CURSOR = node["geoscapeCursor"].as<int>(GEOSCAPE_CURSOR);
BASESCAPE_CURSOR = node["basescapeCursor"].as<int>(BASESCAPE_CURSOR);
BATTLESCAPE_CURSOR = node["battlescapeCursor"].as<int>(BATTLESCAPE_CURSOR);
UFOPAEDIA_CURSOR = node["ufopaediaCursor"].as<int>(UFOPAEDIA_CURSOR);
GRAPHS_CURSOR = node["graphsCursor"].as<int>(GRAPHS_CURSOR);
DAMAGE_RANGE = node["damageRange"].as<int>(DAMAGE_RANGE);
EXPLOSIVE_DAMAGE_RANGE = node["explosiveDamageRange"].as<int>(EXPLOSIVE_DAMAGE_RANGE);
size_t num = 0;
for (YAML::const_iterator j = node["fireDamageRange"].begin(); j != node["fireDamageRange"].end() && num < 2; ++j)
{
FIRE_DAMAGE_RANGE[num] = (*j).as<int>(FIRE_DAMAGE_RANGE[num]);
++num;
}
DEBRIEF_MUSIC_GOOD = node["goodDebriefingMusic"].as<std::string>(DEBRIEF_MUSIC_GOOD);
DEBRIEF_MUSIC_BAD = node["badDebriefingMusic"].as<std::string>(DEBRIEF_MUSIC_BAD);
}
/**
* Loads a ruleset's contents from a YAML file.
* Rules that match pre-existing rules overwrite them.
* @param filename YAML filename.
*/
void Mod::loadFile(const std::string &filename)
{
YAML::Node doc = YAML::LoadFile(filename);
for (YAML::const_iterator i = doc["countries"].begin(); i != doc["countries"].end(); ++i)
{
RuleCountry *rule = loadRule(*i, &_countries, &_countriesIndex);
if (rule != 0)
{
rule->load(*i);
}
}
for (YAML::const_iterator i = doc["regions"].begin(); i != doc["regions"].end(); ++i)
{
RuleRegion *rule = loadRule(*i, &_regions, &_regionsIndex);
if (rule != 0)
{
rule->load(*i);
}
}
for (YAML::const_iterator i = doc["facilities"].begin(); i != doc["facilities"].end(); ++i)
{
RuleBaseFacility *rule = loadRule(*i, &_facilities, &_facilitiesIndex);
if (rule != 0)
{
_facilityListOrder += 100;
rule->load(*i, this, _facilityListOrder);
}
}
for (YAML::const_iterator i = doc["crafts"].begin(); i != doc["crafts"].end(); ++i)
{
RuleCraft *rule = loadRule(*i, &_crafts, &_craftsIndex);
if (rule != 0)
{
_craftListOrder += 100;
rule->load(*i, this, _craftListOrder);
}
}
for (YAML::const_iterator i = doc["craftWeapons"].begin(); i != doc["craftWeapons"].end(); ++i)
{
RuleCraftWeapon *rule = loadRule(*i, &_craftWeapons, &_craftWeaponsIndex);
if (rule != 0)
{
rule->load(*i, this);
}
}
for (YAML::const_iterator i = doc["items"].begin(); i != doc["items"].end(); ++i)
{
RuleItem *rule = loadRule(*i, &_items, &_itemsIndex);
if (rule != 0)
{
_itemListOrder += 100;
rule->load(*i, this, _itemListOrder);
}
}
for (YAML::const_iterator i = doc["ufos"].begin(); i != doc["ufos"].end(); ++i)
{
RuleUfo *rule = loadRule(*i, &_ufos, &_ufosIndex);
if (rule != 0)
{
rule->load(*i, this);
}
}
for (YAML::const_iterator i = doc["invs"].begin(); i != doc["invs"].end(); ++i)
{
RuleInventory *rule = loadRule(*i, &_invs, &_invsIndex, "id");
if (rule != 0)
{
_invListOrder += 10;
rule->load(*i, _invListOrder);
}
}
for (YAML::const_iterator i = doc["terrains"].begin(); i != doc["terrains"].end(); ++i)
{
RuleTerrain *rule = loadRule(*i, &_terrains, &_terrainIndex, "name");
if (rule != 0)
{
rule->load(*i, this);
}
}
for (YAML::const_iterator i = doc["armors"].begin(); i != doc["armors"].end(); ++i)
{
Armor *rule = loadRule(*i, &_armors, &_armorsIndex);
if (rule != 0)
{
rule->load(*i);
}
}
for (YAML::const_iterator i = doc["soldiers"].begin(); i != doc["soldiers"].end(); ++i)
{
RuleSoldier *rule = loadRule(*i, &_soldiers, &_soldiersIndex);
if (rule != 0)
{
rule->load(*i, this);
}
}
for (YAML::const_iterator i = doc["units"].begin(); i != doc["units"].end(); ++i)
{
Unit *rule = loadRule(*i, &_units);
if (rule != 0)
{
rule->load(*i, this);
}
}
for (YAML::const_iterator i = doc["alienRaces"].begin(); i != doc["alienRaces"].end(); ++i)
{
AlienRace *rule = loadRule(*i, &_alienRaces, &_aliensIndex, "id");
if (rule != 0)
{
rule->load(*i);
}
}
for (YAML::const_iterator i = doc["alienDeployments"].begin(); i != doc["alienDeployments"].end(); ++i)
{
AlienDeployment *rule = loadRule(*i, &_alienDeployments, &_deploymentsIndex);
if (rule != 0)
{
rule->load(*i, this);
}
}
for (YAML::const_iterator i = doc["research"].begin(); i != doc["research"].end(); ++i)
{
RuleResearch *rule = loadRule(*i, &_research, &_researchIndex, "name");
if (rule != 0)
{
_researchListOrder += 100;
rule->load(*i, _researchListOrder);
if ((*i)["unlockFinalMission"].as<bool>(false))
{
_finalResearch = (*i)["name"].as<std::string>(_finalResearch);
}
}
}
for (YAML::const_iterator i = doc["manufacture"].begin(); i != doc["manufacture"].end(); ++i)
{
RuleManufacture *rule = loadRule(*i, &_manufacture, &_manufactureIndex, "name");
if (rule != 0)
{
_manufactureListOrder += 100;
rule->load(*i, _manufactureListOrder);
}
}
for (YAML::const_iterator i = doc["ufopaedia"].begin(); i != doc["ufopaedia"].end(); ++i)
{
if ((*i)["id"])
{
std::string id = (*i)["id"].as<std::string>();
ArticleDefinition *rule;
if (_ufopaediaArticles.find(id) != _ufopaediaArticles.end())
{
rule = _ufopaediaArticles[id];
}
else
{
UfopaediaTypeId type = (UfopaediaTypeId)(*i)["type_id"].as<int>();
switch (type)
{
case UFOPAEDIA_TYPE_CRAFT: rule = new ArticleDefinitionCraft(); break;
case UFOPAEDIA_TYPE_CRAFT_WEAPON: rule = new ArticleDefinitionCraftWeapon(); break;
case UFOPAEDIA_TYPE_VEHICLE: rule = new ArticleDefinitionVehicle(); break;
case UFOPAEDIA_TYPE_ITEM: rule = new ArticleDefinitionItem(); break;
case UFOPAEDIA_TYPE_ARMOR: rule = new ArticleDefinitionArmor(); break;
case UFOPAEDIA_TYPE_BASE_FACILITY: rule = new ArticleDefinitionBaseFacility(); break;
case UFOPAEDIA_TYPE_TEXTIMAGE: rule = new ArticleDefinitionTextImage(); break;
case UFOPAEDIA_TYPE_TEXT: rule = new ArticleDefinitionText(); break;
case UFOPAEDIA_TYPE_UFO: rule = new ArticleDefinitionUfo(); break;
case UFOPAEDIA_TYPE_TFTD:
case UFOPAEDIA_TYPE_TFTD_CRAFT:
case UFOPAEDIA_TYPE_TFTD_CRAFT_WEAPON:
case UFOPAEDIA_TYPE_TFTD_VEHICLE:
case UFOPAEDIA_TYPE_TFTD_ITEM:
case UFOPAEDIA_TYPE_TFTD_ARMOR:
case UFOPAEDIA_TYPE_TFTD_BASE_FACILITY:
case UFOPAEDIA_TYPE_TFTD_USO:
rule = new ArticleDefinitionTFTD();
break;
default: rule = 0; break;
}
_ufopaediaArticles[id] = rule;
_ufopaediaIndex.push_back(id);
}
_ufopaediaListOrder += 100;
rule->load(*i, _ufopaediaListOrder);
if (rule->section != UFOPAEDIA_NOT_AVAILABLE)
{
if (_ufopaediaSections.find(rule->section) == _ufopaediaSections.end())
{
_ufopaediaSections[rule->section] = rule->getListOrder();
_ufopaediaCatIndex.push_back(rule->section);
}
else
{
_ufopaediaSections[rule->section] = std::min(_ufopaediaSections[rule->section], rule->getListOrder());
}
}
}
else if ((*i)["delete"])
{
std::string type = (*i)["delete"].as<std::string>();
std::map<std::string, ArticleDefinition*>::iterator j = _ufopaediaArticles.find(type);
if (j != _ufopaediaArticles.end())
{
_ufopaediaArticles.erase(j);
}
std::vector<std::string>::iterator idx = std::find(_ufopaediaIndex.begin(), _ufopaediaIndex.end(), type);
if (idx != _ufopaediaIndex.end())
{
_ufopaediaIndex.erase(idx);
}
}
}
// Bases can't be copied, so for savegame purposes we store the node instead
YAML::Node base = doc["startingBase"];
if (base)
{
for (YAML::const_iterator i = base.begin(); i != base.end(); ++i)
{
_startingBase[i->first.as<std::string>()] = YAML::Node(i->second);
}
}
if (doc["startingTime"])
{
_startingTime.load(doc["startingTime"]);
}
_costEngineer = doc["costEngineer"].as<int>(_costEngineer);
_costScientist = doc["costScientist"].as<int>(_costScientist);
_timePersonnel = doc["timePersonnel"].as<int>(_timePersonnel);
_initialFunding = doc["initialFunding"].as<int>(_initialFunding);
_alienFuel = doc["alienFuel"].as<std::pair<std::string, int> >(_alienFuel);
_fontName = doc["fontName"].as<std::string>(_fontName);
_turnAIUseGrenade = doc["turnAIUseGrenade"].as<int>(_turnAIUseGrenade);
_turnAIUseBlaster = doc["turnAIUseBlaster"].as<int>(_turnAIUseBlaster);
_defeatScore = doc["defeatScore"].as<int>(_defeatScore);
_defeatFunds = doc["defeatFunds"].as<int>(_defeatFunds);
_difficultyDemigod = doc["difficultyDemigod"].as<bool>(_difficultyDemigod);
if (doc["difficultyCoefficient"])
{
size_t num = 0;
for (YAML::const_iterator i = doc["difficultyCoefficient"].begin(); i != doc["difficultyCoefficient"].end() && num < 5; ++i)
{
DIFFICULTY_COEFFICIENT[num] = (*i).as<int>(DIFFICULTY_COEFFICIENT[num]);
_statAdjustment[num].growthMultiplier = DIFFICULTY_COEFFICIENT[num];
++num;
}
}
for (YAML::const_iterator i = doc["ufoTrajectories"].begin(); i != doc["ufoTrajectories"].end(); ++i)
{
UfoTrajectory *rule = loadRule(*i, &_ufoTrajectories, 0, "id");
if (rule != 0)
{
rule->load(*i);
}
}
for (YAML::const_iterator i = doc["alienMissions"].begin(); i != doc["alienMissions"].end(); ++i)
{
RuleAlienMission *rule = loadRule(*i, &_alienMissions, &_alienMissionsIndex);
if (rule != 0)
{
rule->load(*i);
}
}
_alienItemLevels = doc["alienItemLevels"].as< std::vector< std::vector<int> > >(_alienItemLevels);
for (YAML::const_iterator i = doc["MCDPatches"].begin(); i != doc["MCDPatches"].end(); ++i)
{
std::string type = (*i)["type"].as<std::string>();
if (_MCDPatches.find(type) != _MCDPatches.end())
{
_MCDPatches[type]->load(*i);
}
else
{
MCDPatch *patch = new MCDPatch();
patch->load(*i);
_MCDPatches[type] = patch;
}
}
for (YAML::const_iterator i = doc["extraSprites"].begin(); i != doc["extraSprites"].end(); ++i)
{
if ((*i)["type"])
{
std::string type = (*i)["type"].as<std::string>();
ExtraSprites *extraSprites = new ExtraSprites();
const ModData* data = _modCurrent;
// doesn't support modIndex
if (type == "TEXTURE.DAT")
data = &_modData.at(0);
extraSprites->load(*i, data);
_extraSprites[type].push_back(extraSprites);
}
else if ((*i)["delete"])
{
std::string type = (*i)["delete"].as<std::string>();
std::map<std::string, std::vector<ExtraSprites*> >::iterator j = _extraSprites.find(type);
if (j != _extraSprites.end())
{
_extraSprites.erase(j);
}
}
}
for (YAML::const_iterator i = doc["extraSounds"].begin(); i != doc["extraSounds"].end(); ++i)
{
std::string type = (*i)["type"].as<std::string>();
ExtraSounds *extraSounds = new ExtraSounds();
extraSounds->load(*i, _modCurrent);
_extraSounds.push_back(std::make_pair(type, extraSounds));
}
for (YAML::const_iterator i = doc["extraStrings"].begin(); i != doc["extraStrings"].end(); ++i)
{
std::string type = (*i)["type"].as<std::string>();
if (_extraStrings.find(type) != _extraStrings.end())
{
_extraStrings[type]->load(*i);
}
else
{
ExtraStrings *extraStrings = new ExtraStrings();
extraStrings->load(*i);
_extraStrings[type] = extraStrings;
}
}
for (YAML::const_iterator i = doc["statStrings"].begin(); i != doc["statStrings"].end(); ++i)
{
StatString *statString = new StatString();
statString->load(*i);
_statStrings.push_back(statString);
}
for (YAML::const_iterator i = doc["interfaces"].begin(); i != doc["interfaces"].end(); ++i)
{
RuleInterface *rule = loadRule(*i, &_interfaces);
if (rule != 0)
{
rule->load(*i);
}
}
if (doc["globe"])
{
_globe->load(doc["globe"]);
}
if (doc["converter"])
{
_converter->load(doc["converter"]);
}
if (const YAML::Node& constants = doc["constants"])
{
//backward compatibility version
if (constants.IsSequence())
{
for (YAML::const_iterator i = constants.begin(); i != constants.end(); ++i)
{
loadConstants((*i));
}
}
else
{
loadConstants(constants);
}
}
for (YAML::const_iterator i = doc["mapScripts"].begin(); i != doc["mapScripts"].end(); ++i)
{
std::string type = (*i)["type"].as<std::string>();
if ((*i)["delete"])
{
type = (*i)["delete"].as<std::string>(type);
}
if (_mapScripts.find(type) != _mapScripts.end())
{
for (std::vector<MapScript*>::iterator j = _mapScripts[type].begin(); j != _mapScripts[type].end();)
{
delete *j;
j = _mapScripts[type].erase(j);
}
}
for (YAML::const_iterator j = (*i)["commands"].begin(); j != (*i)["commands"].end(); ++j)
{
MapScript *mapScript = new MapScript();
mapScript->load(*j);
_mapScripts[type].push_back(mapScript);
}
}
for (YAML::const_iterator i = doc["missionScripts"].begin(); i != doc["missionScripts"].end(); ++i)
{
RuleMissionScript *rule = loadRule(*i, &_missionScripts, &_missionScriptIndex, "type");
if (rule != 0)
{
rule->load(*i);
}
}
// refresh _psiRequirements for psiStrengthEval
for (std::vector<std::string>::const_iterator i = _facilitiesIndex.begin(); i != _facilitiesIndex.end(); ++i)
{
RuleBaseFacility *rule = getBaseFacility(*i);
if (rule->getPsiLaboratories() > 0)
{
_psiRequirements = rule->getRequirements();
break;
}
}
for (YAML::const_iterator i = doc["cutscenes"].begin(); i != doc["cutscenes"].end(); ++i)
{
RuleVideo *rule = loadRule(*i, &_videos);
if (rule != 0)
{
rule->load(*i);
}
}
for (YAML::const_iterator i = doc["musics"].begin(); i != doc["musics"].end(); ++i)
{
RuleMusic *rule = loadRule(*i, &_musicDefs);
if (rule != 0)
{
rule->load(*i);
}
}
for (YAML::const_iterator i = doc["commendations"].begin(); i != doc["commendations"].end(); ++i)
{
std::string type = (*i)["type"].as<std::string>();
RuleCommendations *commendations = new RuleCommendations();
commendations->load(*i);
_commendations[type] = commendations;
}
size_t count = 0;
for (YAML::const_iterator i = doc["aimAndArmorMultipliers"].begin(); i != doc["aimAndArmorMultipliers"].end() && count < 5; ++i)
{
_statAdjustment[count].aimAndArmorMultiplier = (*i).as<double>(_statAdjustment[count].aimAndArmorMultiplier);
++count;
}
if (doc["statGrowthMultipliers"])
{
_statAdjustment[0].statGrowth = doc["statGrowthMultipliers"].as<UnitStats>(_statAdjustment[0].statGrowth);
for (size_t i = 1; i != 5; ++i)
{
_statAdjustment[i].statGrowth = _statAdjustment[0].statGrowth;
}
}
}
/**
* Loads a rule element, adding/removing from vectors as necessary.
* @param node YAML node.
* @param map Map associated to the rule type.
* @param index Index vector for the rule type.
* @param key Rule key name.
* @return Pointer to new rule if one was created, or NULL if one was removed.
*/
template <typename T>
T *Mod::loadRule(const YAML::Node &node, std::map<std::string, T*> *map, std::vector<std::string> *index, const std::string &key) const
{
T *rule = 0;
if (node[key])
{
std::string type = node[key].as<std::string>();
typename std::map<std::string, T*>::const_iterator i = map->find(type);
if (i != map->end())
{
rule = i->second;
}
else
{
rule = new T(type);
(*map)[type] = rule;
if (index != 0)
{
index->push_back(type);
}
}
}
else if (node["delete"])
{
std::string type = node["delete"].as<std::string>();
typename std::map<std::string, T*>::iterator i = map->find(type);
if (i != map->end())
{
map->erase(i);
}
if (index != 0)
{
std::vector<std::string>::iterator idx = std::find(index->begin(), index->end(), type);
if (idx != index->end())
{
index->erase(idx);
}
}
}
return rule;
}
/**
* Generates a brand new saved game with starting data.
* @return A new saved game.
*/
SavedGame *Mod::newSave() const
{
SavedGame *save = new SavedGame();
// Add countries
for (std::vector<std::string>::const_iterator i = _countriesIndex.begin(); i != _countriesIndex.end(); ++i)
{
RuleCountry *country = getCountry(*i);
if (!country->getLonMin().empty())
save->getCountries()->push_back(new Country(country));
}
// Adjust funding to total $6M
int missing = ((_initialFunding - save->getCountryFunding()/1000) / (int)save->getCountries()->size()) * 1000;
for (std::vector<Country*>::iterator i = save->getCountries()->begin(); i != save->getCountries()->end(); ++i)
{
int funding = (*i)->getFunding().back() + missing;
if (funding < 0)
{
funding = (*i)->getFunding().back();
}
(*i)->setFunding(funding);
}
save->setFunds(save->getCountryFunding());
// Add regions
for (std::vector<std::string>::const_iterator i = _regionsIndex.begin(); i != _regionsIndex.end(); ++i)
{
RuleRegion *region = getRegion(*i);
if (!region->getLonMin().empty())
save->getRegions()->push_back(new Region(region));
}
// Set up starting base
Base *base = new Base(this);
base->load(_startingBase, save, true);
save->getBases()->push_back(base);
// Correct IDs
for (std::vector<Craft*>::const_iterator i = base->getCrafts()->begin(); i != base->getCrafts()->end(); ++i)
{
save->getId((*i)->getRules()->getType());
}
// Determine starting transport craft
Craft *transportCraft = 0;
for (std::vector<Craft*>::iterator c = base->getCrafts()->begin(); c != base->getCrafts()->end(); ++c)
{
if ((*c)->getRules()->getSoldiers() > 0)
{
transportCraft = (*c);
break;
}
}
// Determine starting soldier types
std::vector<std::string> soldierTypes = _soldiersIndex;
for (std::vector<std::string>::iterator i = soldierTypes.begin(); i != soldierTypes.end();)
{
if (getSoldier(*i)->getRequirements().empty())
{
++i;
}
else
{
i = soldierTypes.erase(i);
}
}
const YAML::Node &node = _startingBase["randomSoldiers"];
std::vector<std::string> randomTypes;
if (node)
{
// Starting soldiers specified by type
if (node.IsMap())
{
std::map<std::string, int> randomSoldiers = node.as< std::map<std::string, int> >(std::map<std::string, int>());
for (std::map<std::string, int>::iterator i = randomSoldiers.begin(); i != randomSoldiers.end(); ++i)
{
for (int s = 0; s < i->second; ++s)
{
randomTypes.push_back(i->first);
}
}
}
// Starting soldiers specified by amount
else if (node.IsScalar())
{
int randomSoldiers = node.as<int>(0);
for (int s = 0; s < randomSoldiers; ++s)
{
randomTypes.push_back(soldierTypes[RNG::generate(0, soldierTypes.size() - 1)]);
}
}
// Generate soldiers
unsigned maxSoldiersInTransportCraft = 0;
if (transportCraft != 0)
{
maxSoldiersInTransportCraft = transportCraft->getRules()->getSoldiers();
for (std::vector<Vehicle*>::iterator v = transportCraft->getVehicles()->begin(); v != transportCraft->getVehicles()->end();)
{
if ((int)maxSoldiersInTransportCraft < (*v)->getSize())
{
base->getStorageItems()->addItem((*v)->getRules()->getType(), 1);
if ((*v)->getAmmo() > 0 && !(*v)->getRules()->getCompatibleAmmo()->empty())
{
base->getStorageItems()->addItem(
(*v)->getRules()->getCompatibleAmmo()->front(),
(*v)->getAmmo() / getItem((*v)->getRules()->getCompatibleAmmo()->front())->getClipSize());
}
delete (*v);
v = transportCraft->getVehicles()->erase(v);
}
else
{
maxSoldiersInTransportCraft -= (*v)->getSize();
++v;
}
}
}
for (size_t i = 0; i < randomTypes.size(); ++i)
{
Soldier *soldier = genSoldier(save, randomTypes[i]);
if (transportCraft != 0 && i < maxSoldiersInTransportCraft)
{
soldier->setCraft(transportCraft);
}
base->getSoldiers()->push_back(soldier);
// Award soldier a special 'original eight' commendation
if (_commendations.find("STR_MEDAL_ORIGINAL8_NAME") != _commendations.end())
{
SoldierDiary *diary = soldier->getDiary();
diary->awardOriginalEightCommendation();
for (std::vector<SoldierCommendations*>::iterator comm = diary->getSoldierCommendations()->begin(); comm != diary->getSoldierCommendations()->end(); ++comm)
{
(*comm)->makeOld();
}
}
}
}
// Setup alien strategy
save->getAlienStrategy().init(this);
save->setTime(_startingTime);
return save;
}
/**
* Returns the rules for the specified country.
* @param id Country type.
* @return Rules for the country.
*/
RuleCountry *Mod::getCountry(const std::string &id, bool error) const
{
return getRule(id, "Country", _countries, error);
}
/**
* Returns the list of all countries
* provided by the mod.
* @return List of countries.
*/
const std::vector<std::string> &Mod::getCountriesList() const
{
return _countriesIndex;
}
/**
* Returns the rules for the specified region.
* @param id Region type.
* @return Rules for the region.
*/
RuleRegion *Mod::getRegion(const std::string &id, bool error) const
{
return getRule(id, "Region", _regions, error);
}
/**
* Returns the list of all regions
* provided by the mod.
* @return List of regions.
*/
const std::vector<std::string> &Mod::getRegionsList() const
{
return _regionsIndex;
}
/**
* Returns the rules for the specified base facility.
* @param id Facility type.
* @return Rules for the facility.
*/
RuleBaseFacility *Mod::getBaseFacility(const std::string &id, bool error) const
{
return getRule(id, "Facility", _facilities, error);
}
/**
* Returns the list of all base facilities
* provided by the mod.
* @return List of base facilities.
*/
const std::vector<std::string> &Mod::getBaseFacilitiesList() const
{
return _facilitiesIndex;
}
/**
* Returns the rules for the specified craft.
* @param id Craft type.
* @return Rules for the craft.
*/
RuleCraft *Mod::getCraft(const std::string &id, bool error) const
{
return getRule(id, "Craft", _crafts, error);
}
/**
* Returns the list of all crafts
* provided by the mod.
* @return List of crafts.
*/
const std::vector<std::string> &Mod::getCraftsList() const
{
return _craftsIndex;
}
/**
* Returns the rules for the specified craft weapon.
* @param id Craft weapon type.
* @return Rules for the craft weapon.
*/
RuleCraftWeapon *Mod::getCraftWeapon(const std::string &id, bool error) const
{
return getRule(id, "Craft Weapon", _craftWeapons, error);
}
/**
* Returns the list of all craft weapons
* provided by the mod.
* @return List of craft weapons.
*/
const std::vector<std::string> &Mod::getCraftWeaponsList() const
{
return _craftWeaponsIndex;
}
/**
* Returns the rules for the specified item.
* @param id Item type.
* @return Rules for the item, or 0 when the item is not found.
*/
RuleItem *Mod::getItem(const std::string &id, bool error) const
{
if (id == Armor::NONE)
{
return 0;
}
return getRule(id, "Item", _items, error);
}
/**
* Returns the list of all items
* provided by the mod.
* @return List of items.
*/
const std::vector<std::string> &Mod::getItemsList() const
{
return _itemsIndex;
}
/**
* Returns the rules for the specified UFO.
* @param id UFO type.
* @return Rules for the UFO.
*/
RuleUfo *Mod::getUfo(const std::string &id, bool error) const
{
return getRule(id, "UFO", _ufos, error);
}
/**
* Returns the list of all ufos
* provided by the mod.
* @return List of ufos.
*/
const std::vector<std::string> &Mod::getUfosList() const
{
return _ufosIndex;
}
/**
* Returns the rules for the specified terrain.
* @param name Terrain name.
* @return Rules for the terrain.
*/
RuleTerrain *Mod::getTerrain(const std::string &name, bool error) const
{
return getRule(name, "Terrain", _terrains, error);
}
/**
* Returns the list of all terrains
* provided by the mod.
* @return List of terrains.
*/
const std::vector<std::string> &Mod::getTerrainList() const
{
return _terrainIndex;
}
/**
* Returns the info about a specific map data file.
* @param name Datafile name.
* @return Rules for the datafile.
*/
MapDataSet *Mod::getMapDataSet(const std::string &name)
{
std::map<std::string, MapDataSet*>::iterator map = _mapDataSets.find(name);
if (map == _mapDataSets.end())
{
MapDataSet *set = new MapDataSet(name);
_mapDataSets[name] = set;
return set;
}
else
{
return map->second;
}
}
/**
* Returns the info about a specific unit.
* @param name Unit name.
* @return Rules for the units.
*/
RuleSoldier *Mod::getSoldier(const std::string &name, bool error) const
{
return getRule(name, "Soldier", _soldiers, error);
}
/**
* Returns the list of all soldiers
* provided by the mod.
* @return List of soldiers.
*/
const std::vector<std::string> &Mod::getSoldiersList() const
{
return _soldiersIndex;
}
/**
* Returns the rules for the specified commendation.
* @param id Commendation type.
* @return Rules for the commendation.
*/
RuleCommendations *Mod::getCommendation(const std::string &id, bool error) const
{
return getRule(id, "Commendation", _commendations, error);
}
/**
* Gets the list of commendations provided by the mod.
* @return The list of commendations.
*/
const std::map<std::string, RuleCommendations *> &Mod::getCommendationsList() const
{
return _commendations;
}
/**
* Returns the info about a specific unit.
* @param name Unit name.
* @return Rules for the units.
*/
Unit *Mod::getUnit(const std::string &name, bool error) const
{
return getRule(name, "Unit", _units, error);
}
/**
* Returns the info about a specific alien race.
* @param name Race name.
* @return Rules for the race.
*/
AlienRace *Mod::getAlienRace(const std::string &name, bool error) const
{
return getRule(name, "Alien Race", _alienRaces, error);
}
/**
* Returns the list of all alien races.
* provided by the mod.
* @return List of alien races.
*/
const std::vector<std::string> &Mod::getAlienRacesList() const
{
return _aliensIndex;
}
/**
* Returns the info about a specific deployment.
* @param name Deployment name.
* @return Rules for the deployment.
*/
AlienDeployment *Mod::getDeployment(const std::string &name, bool error) const
{
return getRule(name, "Alien Deployment", _alienDeployments, error);
}
/**
* Returns the list of all alien deployments
* provided by the mod.
* @return List of alien deployments.
*/
const std::vector<std::string> &Mod::getDeploymentsList() const
{
return _deploymentsIndex;
}
/**
* Returns the info about a specific armor.
* @param name Armor name.
* @return Rules for the armor.
*/
Armor *Mod::getArmor(const std::string &name, bool error) const
{
return getRule(name, "Armor", _armors, error);
}
/**
* Returns the list of all armors
* provided by the mod.
* @return List of armors.
*/
const std::vector<std::string> &Mod::getArmorsList() const
{
return _armorsIndex;
}
/**
* Returns the cost of an individual engineer
* for purchase/maintenance.
* @return Cost.
*/
int Mod::getEngineerCost() const
{
return _costEngineer;
}
/**
* Returns the cost of an individual scientist
* for purchase/maintenance.
* @return Cost.
*/
int Mod::getScientistCost() const
{
return _costScientist;
}
/**
* Returns the time it takes to transfer personnel
* between bases.
* @return Time in hours.
*/
int Mod::getPersonnelTime() const
{
return _timePersonnel;
}
/**
* Returns the article definition for a given name.
* @param name Article name.
* @return Article definition.
*/
ArticleDefinition *Mod::getUfopaediaArticle(const std::string &name, bool error) const
{
return getRule(name, "UFOpaedia Article", _ufopaediaArticles, error);
}
/**
* Returns the list of all articles
* provided by the mod.
* @return List of articles.
*/
const std::vector<std::string> &Mod::getUfopaediaList() const
{
return _ufopaediaIndex;
}
/**
* Returns the list of all article categories
* provided by the mod.
* @return List of categories.
*/
const std::vector<std::string> &Mod::getUfopaediaCategoryList() const
{
return _ufopaediaCatIndex;
}
/**
* Returns the list of inventories.
* @return Pointer to inventory list.
*/
std::map<std::string, RuleInventory*> *Mod::getInventories()
{
return &_invs;
}
/**
* Returns the rules for a specific inventory.
* @param id Inventory type.
* @return Inventory ruleset.
*/
RuleInventory *Mod::getInventory(const std::string &id, bool error) const
{
return getRule(id, "Inventory", _invs, error);
}
/**
* Returns the list of inventories.
* @return The list of inventories.
*/
const std::vector<std::string> &Mod::getInvsList() const
{
return _invsIndex;
}
/**
* Returns the rules for the specified research project.
* @param id Research project type.
* @return Rules for the research project.
*/
RuleResearch *Mod::getResearch (const std::string &id, bool error) const
{
return getRule(id, "Research", _research, error);
}
/**
* Returns the list of research projects.
* @return The list of research projects.
*/
const std::vector<std::string> &Mod::getResearchList() const
{
return _researchIndex;
}
/**
* Returns the rules for the specified manufacture project.
* @param id Manufacture project type.
* @return Rules for the manufacture project.
*/
RuleManufacture *Mod::getManufacture (const std::string &id, bool error) const
{
return getRule(id, "Manufacture", _manufacture, error);
}
/**
* Returns the list of manufacture projects.
* @return The list of manufacture projects.
*/
const std::vector<std::string> &Mod::getManufactureList() const
{
return _manufactureIndex;
}
/**
* Generates and returns a list of facilities for custom bases.
* The list contains all the facilities that are listed in the 'startingBase'
* part of the ruleset.
* @return The list of facilities for custom bases.
*/
std::vector<RuleBaseFacility*> Mod::getCustomBaseFacilities() const
{
std::vector<RuleBaseFacility*> placeList;
for (YAML::const_iterator i = _startingBase["facilities"].begin(); i != _startingBase["facilities"].end(); ++i)
{
std::string type = (*i)["type"].as<std::string>();
RuleBaseFacility *facility = getBaseFacility(type, true);
if (!facility->isLift())
{
placeList.push_back(facility);
}
}
return placeList;
}
/**
* Returns the data for the specified ufo trajectory.
* @param id Ufo trajectory id.
* @return A pointer to the data for the specified ufo trajectory.
*/
const UfoTrajectory *Mod::getUfoTrajectory(const std::string &id, bool error) const
{
return getRule(id, "Trajectory", _ufoTrajectories, error);
}
/**
* Returns the rules for the specified alien mission.
* @param id Alien mission type.
* @return Rules for the alien mission.
*/
const RuleAlienMission *Mod::getAlienMission(const std::string &id, bool error) const
{
return getRule(id, "Alien Mission", _alienMissions, error);
}
/**
* Returns the rules for a random alien mission based on a specific objective.
* @param objective Alien mission objective.
* @return Rules for the alien mission.
*/
const RuleAlienMission *Mod::getRandomMission(MissionObjective objective, size_t monthsPassed) const
{
int totalWeight = 0;
std::map<int, RuleAlienMission*> possibilities;
for (std::map<std::string, RuleAlienMission *>::const_iterator i = _alienMissions.begin(); i != _alienMissions.end(); ++i)
{
if (i->second->getObjective() == objective && i->second->getWeight(monthsPassed) > 0)
{
totalWeight += i->second->getWeight(monthsPassed);
possibilities[totalWeight] = i->second;
}
}
if (totalWeight > 0)
{
int pick = RNG::generate(1, totalWeight);
for (std::map<int, RuleAlienMission*>::const_iterator i = possibilities.begin(); i != possibilities.end(); ++i)
{
if (pick <= i->first)
{
return i->second;
}
}
}
return 0;
}
/**
* Returns the list of alien mission types.
* @return The list of alien mission types.
*/
const std::vector<std::string> &Mod::getAlienMissionList() const
{
return _alienMissionsIndex;
}
/**
* Gets the alien item level table.
* @return A deep array containing the alien item levels.
*/
const std::vector<std::vector<int> > &Mod::getAlienItemLevels() const
{
return _alienItemLevels;
}
/**
* Gets the defined starting base.
* @return The starting base definition.
*/
const YAML::Node &Mod::getStartingBase() const
{
return _startingBase;
}
/**
* Gets the defined starting time.
* @return The time the game starts in.
*/
const GameTime &Mod::getStartingTime() const
{
return _startingTime;
}
/**
* Gets an MCDPatch.
* @param id The ID of the MCDPatch we want.
* @return The MCDPatch based on ID, or 0 if none defined.
*/
MCDPatch *Mod::getMCDPatch(const std::string &id) const
{
std::map<std::string, MCDPatch*>::const_iterator i = _MCDPatches.find(id);
if (_MCDPatches.end() != i) return i->second; else return 0;
}
/**
* Gets the list of external sprites.
* @return The list of external sprites.
*/
const std::map<std::string, std::vector<ExtraSprites *> > &Mod::getExtraSprites() const
{
return _extraSprites;
}
/**
* Gets the list of external sounds.
* @return The list of external sounds.
*/
const std::vector<std::pair<std::string, ExtraSounds *> > &Mod::getExtraSounds() const
{
return _extraSounds;
}
/**
* Gets the list of external strings.
* @return The list of external strings.
*/
const std::map<std::string, ExtraStrings *> &Mod::getExtraStrings() const
{
return _extraStrings;
}
/**
* Gets the list of StatStrings.
* @return The list of StatStrings.
*/
const std::vector<StatString *> &Mod::getStatStrings() const
{
return _statStrings;
}
/**
* Compares rules based on their list orders.
*/
template <typename T>
struct compareRule : public std::binary_function<const std::string&, const std::string&, bool>
{
Mod *_mod;
typedef T*(Mod::*RuleLookup)(const std::string &id, bool error);
RuleLookup _lookup;
compareRule(Mod *mod, RuleLookup lookup) : _mod(mod), _lookup(lookup)
{
}
bool operator()(const std::string &r1, const std::string &r2) const
{
T *rule1 = (_mod->*_lookup)(r1, true);
T *rule2 = (_mod->*_lookup)(r2, true);
return (rule1->getListOrder() < rule2->getListOrder());
}
};
/**
* Craft weapons use the list order of their launcher item.
*/
template <>
struct compareRule<RuleCraftWeapon> : public std::binary_function<const std::string&, const std::string&, bool>
{
Mod *_mod;
compareRule(Mod *mod) : _mod(mod)
{
}
bool operator()(const std::string &r1, const std::string &r2) const
{
RuleItem *rule1 = _mod->getItem(_mod->getCraftWeapon(r1)->getLauncherItem(), true);
RuleItem *rule2 = _mod->getItem(_mod->getCraftWeapon(r2)->getLauncherItem(), true);
return (rule1->getListOrder() < rule2->getListOrder());
}
};
/**
* Armor uses the list order of their store item.
* Itemless armor comes before all else.
*/
template <>
struct compareRule<Armor> : public std::binary_function<const std::string&, const std::string&, bool>
{
Mod *_mod;
compareRule(Mod *mod) : _mod(mod)
{
}
bool operator()(const std::string &r1, const std::string &r2) const
{
Armor* armor1 = _mod->getArmor(r1);
Armor* armor2 = _mod->getArmor(r2);
RuleItem *rule1 = _mod->getItem(armor1->getStoreItem());
RuleItem *rule2 = _mod->getItem(armor2->getStoreItem());
if (!rule1 && !rule2)
return (armor1 < armor2); // tiebreaker, don't care about order, pointers are as good as any
else if (!rule1)
return true;
else if (!rule2)
return false;
else
return (rule1->getListOrder() < rule2->getListOrder());
}
};
/**
* Ufopaedia articles use section and list order.
*/
template <>
struct compareRule<ArticleDefinition> : public std::binary_function<const std::string&, const std::string&, bool>
{
Mod *_mod;
const std::map<std::string, int> &_sections;
compareRule(Mod *mod) : _mod(mod), _sections(mod->getUfopaediaSections())
{
}
bool operator()(const std::string &r1, const std::string &r2) const
{
ArticleDefinition *rule1 = _mod->getUfopaediaArticle(r1);
ArticleDefinition *rule2 = _mod->getUfopaediaArticle(r2);
if (rule1->section == rule2->section)
return (rule1->getListOrder() < rule2->getListOrder());
else
return (_sections.at(rule1->section) < _sections.at(rule2->section));
}
};
/**
* Ufopaedia sections use article list order.
*/
struct compareSection : public std::binary_function<const std::string&, const std::string&, bool>
{
Mod *_mod;
const std::map<std::string, int> &_sections;
compareSection(Mod *mod) : _mod(mod), _sections(mod->getUfopaediaSections())
{
}
bool operator()(const std::string &r1, const std::string &r2) const
{
return _sections.at(r1) < _sections.at(r2);
}
};
/**
* Sorts all our lists according to their weight.
*/
void Mod::sortLists()
{
std::sort(_itemsIndex.begin(), _itemsIndex.end(), compareRule<RuleItem>(this, (compareRule<RuleItem>::RuleLookup)&Mod::getItem));
std::sort(_craftsIndex.begin(), _craftsIndex.end(), compareRule<RuleCraft>(this, (compareRule<RuleCraft>::RuleLookup)&Mod::getCraft));
std::sort(_facilitiesIndex.begin(), _facilitiesIndex.end(), compareRule<RuleBaseFacility>(this, (compareRule<RuleBaseFacility>::RuleLookup)&Mod::getBaseFacility));
std::sort(_researchIndex.begin(), _researchIndex.end(), compareRule<RuleResearch>(this, (compareRule<RuleResearch>::RuleLookup)&Mod::getResearch));
std::sort(_manufactureIndex.begin(), _manufactureIndex.end(), compareRule<RuleManufacture>(this, (compareRule<RuleManufacture>::RuleLookup)&Mod::getManufacture));
std::sort(_invsIndex.begin(), _invsIndex.end(), compareRule<RuleInventory>(this, (compareRule<RuleInventory>::RuleLookup)&Mod::getInventory));
// special cases
std::sort(_craftWeaponsIndex.begin(), _craftWeaponsIndex.end(), compareRule<RuleCraftWeapon>(this));
std::sort(_armorsIndex.begin(), _armorsIndex.end(), compareRule<Armor>(this));
_ufopaediaSections[UFOPAEDIA_NOT_AVAILABLE] = 0;
std::sort(_ufopaediaIndex.begin(), _ufopaediaIndex.end(), compareRule<ArticleDefinition>(this));
std::sort(_ufopaediaCatIndex.begin(), _ufopaediaCatIndex.end(), compareSection(this));
}
/**
* Gets the research-requirements for Psi-Lab (it's a cache for psiStrengthEval)
*/
const std::vector<std::string> &Mod::getPsiRequirements() const
{
return _psiRequirements;
}
/**
* Creates a new randomly-generated soldier.
* @param save Saved game the soldier belongs to.
* @param type The soldier type to generate.
* @return Newly generated soldier.
*/
Soldier *Mod::genSoldier(SavedGame *save, std::string type) const
{
Soldier *soldier = 0;
int newId = save->getId("STR_SOLDIER");
if (type.empty())
{
type = _soldiersIndex.front();
}
// Check for duplicates
// Original X-COM gives up after 10 tries so might as well do the same here
bool duplicate = true;
for (int tries = 0; tries < 10 && duplicate; ++tries)
{
delete soldier;
soldier = new Soldier(getSoldier(type, true), getArmor(getSoldier(type, true)->getArmor(), true), newId);
duplicate = false;
for (std::vector<Base*>::iterator i = save->getBases()->begin(); i != save->getBases()->end() && !duplicate; ++i)
{
for (std::vector<Soldier*>::iterator j = (*i)->getSoldiers()->begin(); j != (*i)->getSoldiers()->end() && !duplicate; ++j)
{
if ((*j)->getName() == soldier->getName())
{
duplicate = true;
}
}
for (std::vector<Transfer*>::iterator k = (*i)->getTransfers()->begin(); k != (*i)->getTransfers()->end() && !duplicate; ++k)
{
if ((*k)->getType() == TRANSFER_SOLDIER && (*k)->getSoldier()->getName() == soldier->getName())
{
duplicate = true;
}
}
}
}
// calculate new statString
soldier->calcStatString(getStatStrings(), (Options::psiStrengthEval && save->isResearched(getPsiRequirements())));
return soldier;
}
/**
* Gets the name of the item to be used as alien fuel.
* @return the name of the fuel.
*/
std::string Mod::getAlienFuelName() const
{
return _alienFuel.first;
}
/**
* Gets the amount of alien fuel to recover.
* @return the amount to recover.
*/
int Mod::getAlienFuelQuantity() const
{
return _alienFuel.second;
}
/**
* Gets name of font collection.
* @return the name of YAML-file with font data
*/
std::string Mod::getFontName() const
{
return _fontName;
}
/**
* Returns the smallest facility's radar range.
* @return The minimum range.
*/
int Mod::getMinRadarRange() const
{
int minRadarRange = 0;
{
for (std::vector<std::string>::const_iterator i = _facilitiesIndex.begin(); i != _facilitiesIndex.end(); ++i)
{
RuleBaseFacility *f = getBaseFacility(*i);
if (f == 0) continue;
int radarRange = f->getRadarRange();
if (radarRange > 0 && (minRadarRange == 0 || minRadarRange > radarRange))
{
minRadarRange = radarRange;
}
}
}
return minRadarRange;
}
/**
* Gets information on an interface.
* @param id the interface we want info on.
* @return the interface.
*/
RuleInterface *Mod::getInterface(const std::string &id, bool error) const
{
return getRule(id, "Interface", _interfaces, error);
}
/**
* Gets the rules for the Geoscape globe.
* @return Pointer to globe rules.
*/
RuleGlobe *Mod::getGlobe() const
{
return _globe;
}
/**
* Gets the rules for the Save Converter.
* @return Pointer to converter rules.
*/
RuleConverter *Mod::getConverter() const
{
return _converter;
}
const std::map<std::string, SoundDefinition *> *Mod::getSoundDefinitions() const
{
return &_soundDefs;
}
const std::vector<SDL_Color> *Mod::getTransparencies() const
{
return &_transparencies;
}
const std::vector<MapScript*> *Mod::getMapScript(const std::string& id) const
{
std::map<std::string, std::vector<MapScript*> >::const_iterator i = _mapScripts.find(id);
if (_mapScripts.end() != i)
{
return &i->second;
}
else
{
return 0;
}
}
/**
* Returns the data for the specified video cutscene.
* @param id Video id.
* @return A pointer to the data for the specified video.
*/
RuleVideo *Mod::getVideo(const std::string &id, bool error) const
{
return getRule(id, "Video", _videos, error);
}
const std::map<std::string, RuleMusic *> *Mod::getMusic() const
{
return &_musicDefs;
}
const std::vector<std::string> *Mod::getMissionScriptList() const
{
return &_missionScriptIndex;
}
RuleMissionScript *Mod::getMissionScript(const std::string &name, bool error) const
{
return getRule(name, "Mission Script", _missionScripts, error);
}
std::string Mod::getFinalResearch() const
{
return _finalResearch;
}
namespace
{
const Uint8 ShadeMax = 15;
/**
* Recolor class used in UFO
*/
struct HairXCOM1
{
static const Uint8 Hair = 9 << 4;
static const Uint8 Face = 6 << 4;
static inline void func(Uint8& src, const Uint8& cutoff, int, int, int)
{
if (src > cutoff && src <= Face + ShadeMax)
{
src = Hair + (src & ShadeMax) - 6; //make hair color like male in xcom_0.pck
}
}
};
/**
* Recolor class used in TFTD
*/
struct HairXCOM2
{
static const Uint8 ManHairColor = 4 << 4;
static const Uint8 WomanHairColor = 1 << 4;
static inline void func(Uint8& src, int, int, int, int)
{
if (src >= WomanHairColor && src <= WomanHairColor + ShadeMax)
{
src = ManHairColor + (src & ShadeMax);
}
}
};
/**
* Recolor class used in TFTD
*/
struct FaceXCOM2
{
static const Uint8 FaceColor = 10 << 4;
static const Uint8 PinkColor = 14 << 4;
static inline void func(Uint8& src, int, int, int, int)
{
if (src >= FaceColor && src <= FaceColor + ShadeMax)
{
src = PinkColor + (src & ShadeMax);
}
}
};
/**
* Recolor class used in TFTD
*/
struct BodyXCOM2
{
static const Uint8 IonArmorColor = 8 << 4;
static inline void func(Uint8& src, int, int, int, int)
{
if (src == 153)
{
src = IonArmorColor + 12;
}
else if (src == 151)
{
src = IonArmorColor + 10;
}
else if (src == 148)
{
src = IonArmorColor + 4;
}
else if (src == 147)
{
src = IonArmorColor + 2;
}
else if (src >= HairXCOM2::WomanHairColor && src <= HairXCOM2::WomanHairColor + ShadeMax)
{
src = IonArmorColor + (src & ShadeMax);
}
}
};
/**
* Recolor class used in TFTD
*/
struct FallXCOM2
{
static const Uint8 RoguePixel = 151;
static inline void func(Uint8& src, int, int, int, int)
{
if (src == RoguePixel)
{
src = FaceXCOM2::PinkColor + (src & ShadeMax) + 2;
}
else if (src >= BodyXCOM2::IonArmorColor && src <= BodyXCOM2::IonArmorColor + ShadeMax)
{
src = FaceXCOM2::PinkColor + (src & ShadeMax);
}
}
};
}
/**
* Loads the vanilla resources required by the game.
*/
void Mod::loadVanillaResources()
{
// Create Geoscape surface
_sets["GlobeMarkers"] = new SurfaceSet(3, 3);
// Create Sound sets
_sounds["GEO.CAT"] = new SoundSet();
_sounds["BATTLE.CAT"] = new SoundSet();
_sounds["BATTLE2.CAT"] = new SoundSet();
// Load palettes
const char *pal[] = { "PAL_GEOSCAPE", "PAL_BASESCAPE", "PAL_GRAPHS", "PAL_UFOPAEDIA", "PAL_BATTLEPEDIA" };
for (size_t i = 0; i < ARRAYLEN(pal); ++i)
{
std::string s = "GEODATA/PALETTES.DAT";
_palettes[pal[i]] = new Palette();
_palettes[pal[i]]->loadDat(FileMap::getFilePath(s), 256, Palette::palOffset(i));
}
{
std::string s1 = "GEODATA/BACKPALS.DAT";
std::string s2 = "BACKPALS.DAT";
_palettes[s2] = new Palette();
_palettes[s2]->loadDat(FileMap::getFilePath(s1), 128);
}
// Correct Battlescape palette
{
std::string s1 = "GEODATA/PALETTES.DAT";
std::string s2 = "PAL_BATTLESCAPE";
_palettes[s2] = new Palette();
_palettes[s2]->loadDat(FileMap::getFilePath(s1), 256, Palette::palOffset(4));
// Last 16 colors are a greyish gradient
SDL_Color gradient[] = { { 140, 152, 148, 255 },
{ 132, 136, 140, 255 },
{ 116, 124, 132, 255 },
{ 108, 116, 124, 255 },
{ 92, 104, 108, 255 },
{ 84, 92, 100, 255 },
{ 76, 80, 92, 255 },
{ 56, 68, 84, 255 },
{ 48, 56, 68, 255 },
{ 40, 48, 56, 255 },
{ 32, 36, 48, 255 },
{ 24, 28, 32, 255 },
{ 16, 20, 24, 255 },
{ 8, 12, 16, 255 },
{ 3, 4, 8, 255 },
{ 3, 3, 6, 255 } };
for (size_t i = 0; i < ARRAYLEN(gradient); ++i)
{
SDL_Color *color = _palettes[s2]->getColors(Palette::backPos + 16 + i);
*color = gradient[i];
}
}
// Load surfaces
{
std::string s1 = "GEODATA/INTERWIN.DAT";
std::string s2 = "INTERWIN.DAT";
_surfaces[s2] = new Surface(160, 600);
_surfaces[s2]->loadScr(FileMap::getFilePath(s1));
}
const std::set<std::string> &geographFiles(FileMap::getVFolderContents("GEOGRAPH"));
std::set<std::string> scrs = FileMap::filterFiles(geographFiles, "SCR");
for (std::set<std::string>::iterator i = scrs.begin(); i != scrs.end(); ++i)
{
std::string fname = *i;
std::transform(i->begin(), i->end(), fname.begin(), toupper);
_surfaces[fname] = new Surface(320, 200);
_surfaces[fname]->loadScr(FileMap::getFilePath("GEOGRAPH/" + fname));
}
std::set<std::string> bdys = FileMap::filterFiles(geographFiles, "BDY");
for (std::set<std::string>::iterator i = bdys.begin(); i != bdys.end(); ++i)
{
std::string fname = *i;
std::transform(i->begin(), i->end(), fname.begin(), toupper);
_surfaces[fname] = new Surface(320, 200);
_surfaces[fname]->loadBdy(FileMap::getFilePath("GEOGRAPH/" + fname));
}
std::set<std::string> spks = FileMap::filterFiles(geographFiles, "SPK");
for (std::set<std::string>::iterator i = spks.begin(); i != spks.end(); ++i)
{
std::string fname = *i;
std::transform(i->begin(), i->end(), fname.begin(), toupper);
_surfaces[fname] = new Surface(320, 200);
_surfaces[fname]->loadSpk(FileMap::getFilePath("GEOGRAPH/" + fname));
}
// Load surface sets
std::string sets[] = { "BASEBITS.PCK",
"INTICON.PCK",
"TEXTURE.DAT" };
for (size_t i = 0; i < ARRAYLEN(sets); ++i)
{
std::ostringstream s;
s << "GEOGRAPH/" << sets[i];
std::string ext = sets[i].substr(sets[i].find_last_of('.') + 1, sets[i].length());
if (ext == "PCK")
{
std::string tab = CrossPlatform::noExt(sets[i]) + ".TAB";
std::ostringstream s2;
s2 << "GEOGRAPH/" << tab;
_sets[sets[i]] = new SurfaceSet(32, 40);
_sets[sets[i]]->loadPck(FileMap::getFilePath(s.str()), FileMap::getFilePath(s2.str()));
}
else
{
_sets[sets[i]] = new SurfaceSet(32, 32);
_sets[sets[i]]->loadDat(FileMap::getFilePath(s.str()));
}
}
{
std::string s1 = "GEODATA/SCANG.DAT";
std::string s2 = "SCANG.DAT";
_sets[s2] = new SurfaceSet(4, 4);
_sets[s2]->loadDat(FileMap::getFilePath(s1));
}
if (!Options::mute)
{
// Load sounds
const std::set<std::string> &soundFiles(FileMap::getVFolderContents("SOUND"));
if (_soundDefs.empty())
{
std::string catsId[] = { "GEO.CAT", "BATTLE.CAT" };
std::string catsDos[] = { "SOUND2.CAT", "SOUND1.CAT" };
std::string catsWin[] = { "SAMPLE.CAT", "SAMPLE2.CAT" };
// Try the preferred format first, otherwise use the default priority
std::string *cats[] = { 0, catsWin, catsDos };
if (Options::preferredSound == SOUND_14)
cats[0] = catsWin;
else if (Options::preferredSound == SOUND_10)
cats[0] = catsDos;
Options::currentSound = SOUND_AUTO;
for (size_t i = 0; i < ARRAYLEN(catsId); ++i)
{
SoundSet *sound = 0;
for (size_t j = 0; j < ARRAYLEN(cats) && sound == 0; ++j)
{
bool wav = true;
if (cats[j] == 0)
continue;
else if (cats[j] == catsDos)
wav = false;
std::string fname = cats[j][i];
std::transform(fname.begin(), fname.end(), fname.begin(), tolower);
std::set<std::string>::iterator file = soundFiles.find(fname);
if (file != soundFiles.end())
{
sound = _sounds[catsId[i]];
sound->loadCat(FileMap::getFilePath("SOUND/" + cats[j][i]), wav);
Options::currentSound = (wav) ? SOUND_14 : SOUND_10;
}
}
if (sound == 0)
{
std::ostringstream ss;
ss << catsId[i] << " not found: " << catsWin[i] + " or " + catsDos[i] + " required";
throw Exception(ss.str());
}
}
}
else
{
for (std::map<std::string, SoundDefinition*>::const_iterator i = _soundDefs.begin(); i != _soundDefs.end(); ++i)
{
std::string fname = i->second->getCATFile();
std::transform(fname.begin(), fname.end(), fname.begin(), tolower);
std::set<std::string>::iterator file = soundFiles.find(fname);
if (file != soundFiles.end())
{
if (_sounds.find((*i).first) == _sounds.end())
{
_sounds[(*i).first] = new SoundSet();
}
for (std::vector<int>::const_iterator j = (*i).second->getSoundList().begin(); j != (*i).second->getSoundList().end(); ++j)
{
_sounds[(*i).first]->loadCatbyIndex(FileMap::getFilePath("SOUND/" + fname), *j);
}
}
else
{
throw Exception(fname + " not found");
}
}
}
std::set<std::string>::iterator file = soundFiles.find("intro.cat");
if (file != soundFiles.end())
{
SoundSet *s = _sounds["INTRO.CAT"] = new SoundSet();
s->loadCat(FileMap::getFilePath("SOUND/INTRO.CAT"), false);
}
file = soundFiles.find("sample3.cat");
if (file != soundFiles.end())
{
SoundSet *s = _sounds["SAMPLE3.CAT"] = new SoundSet();
s->loadCat(FileMap::getFilePath("SOUND/SAMPLE3.CAT"), true);
}
}
loadBattlescapeResources(); // TODO load this at battlescape start, unload at battlescape end?
//update number of shared indexes in surface sets and sound sets
{
std::string surfaceNames[] =
{
"BIGOBS.PCK",
"FLOOROB.PCK",
"HANDOB.PCK",
"SMOKE.PCK",
"HIT.PCK",
"BASEBITS.PCK",
"INTICON.PCK",
};
for (size_t i = 0; i < ARRAYLEN(surfaceNames); ++i)
{
SurfaceSet* s = _sets[surfaceNames[i]];
s->setMaxSharedFrames((int)s->getTotalFrames());
}
//special case for surface set that is loaded later
{
SurfaceSet* s = _sets["Projectiles"];
s->setMaxSharedFrames(385);
}
{
SurfaceSet* s = _sets["UnderwaterProjectiles"];
s->setMaxSharedFrames(385);
}
{
SurfaceSet* s = _sets["GlobeMarkers"];
s->setMaxSharedFrames(9);
}
//HACK: because of value "hitAnimation" from item that is used as offet in "X1.PCK", this set need have same number of shared frames as "SMOKE.PCK".
{
SurfaceSet* s = _sets["X1.PCK"];
s->setMaxSharedFrames((int)_sets["SMOKE.PCK"]->getMaxSharedFrames());
}
}
{
std::string soundNames[] =
{
"BATTLE.CAT",
"GEO.CAT",
};
for (size_t i = 0; i < ARRAYLEN(soundNames); ++i)
{
SoundSet* s = _sounds[soundNames[i]];
s->setMaxSharedSounds((int)s->getTotalSounds());
}
//HACK: case for underwater surface, it should share same offsets as "BATTLE.CAT"
{
SoundSet* s = _sounds["BATTLE2.CAT"];
s->setMaxSharedSounds((int)_sounds["BATTLE.CAT"]->getTotalSounds());
}
}
}
/**
* Loads the resources required by the Battlescape.
*/
void Mod::loadBattlescapeResources()
{
// Load Battlescape ICONS
_sets["SPICONS.DAT"] = new SurfaceSet(32, 24);
_sets["SPICONS.DAT"]->loadDat(FileMap::getFilePath("UFOGRAPH/SPICONS.DAT"));
_sets["CURSOR.PCK"] = new SurfaceSet(32, 40);
_sets["CURSOR.PCK"]->loadPck(FileMap::getFilePath("UFOGRAPH/CURSOR.PCK"), FileMap::getFilePath("UFOGRAPH/CURSOR.TAB"));
_sets["SMOKE.PCK"] = new SurfaceSet(32, 40);
_sets["SMOKE.PCK"]->loadPck(FileMap::getFilePath("UFOGRAPH/SMOKE.PCK"), FileMap::getFilePath("UFOGRAPH/SMOKE.TAB"));
_sets["HIT.PCK"] = new SurfaceSet(32, 40);
_sets["HIT.PCK"]->loadPck(FileMap::getFilePath("UFOGRAPH/HIT.PCK"), FileMap::getFilePath("UFOGRAPH/HIT.TAB"));
_sets["X1.PCK"] = new SurfaceSet(128, 64);
_sets["X1.PCK"]->loadPck(FileMap::getFilePath("UFOGRAPH/X1.PCK"), FileMap::getFilePath("UFOGRAPH/X1.TAB"));
_sets["MEDIBITS.DAT"] = new SurfaceSet(52, 58);
_sets["MEDIBITS.DAT"]->loadDat(FileMap::getFilePath("UFOGRAPH/MEDIBITS.DAT"));
_sets["DETBLOB.DAT"] = new SurfaceSet(16, 16);
_sets["DETBLOB.DAT"]->loadDat(FileMap::getFilePath("UFOGRAPH/DETBLOB.DAT"));
_sets["Projectiles"] = new SurfaceSet(3, 3);
_sets["UnderwaterProjectiles"] = new SurfaceSet(3, 3);
// Load Battlescape Terrain (only blanks are loaded, others are loaded just in time)
_sets["BLANKS.PCK"] = new SurfaceSet(32, 40);
_sets["BLANKS.PCK"]->loadPck(FileMap::getFilePath("TERRAIN/BLANKS.PCK"), FileMap::getFilePath("TERRAIN/BLANKS.TAB"));
// Load Battlescape units
std::set<std::string> unitsContents = FileMap::getVFolderContents("UNITS");
std::set<std::string> usets = FileMap::filterFiles(unitsContents, "PCK");
for (std::set<std::string>::iterator i = usets.begin(); i != usets.end(); ++i)
{
std::string path = FileMap::getFilePath("UNITS/" + *i);
std::string tab = FileMap::getFilePath("UNITS/" + CrossPlatform::noExt(*i) + ".TAB");
std::string fname = *i;
std::transform(i->begin(), i->end(), fname.begin(), toupper);
if (fname != "BIGOBS.PCK")
_sets[fname] = new SurfaceSet(32, 40);
else
_sets[fname] = new SurfaceSet(32, 48);
_sets[fname]->loadPck(path, tab);
}
// incomplete chryssalid set: 1.0 data: stop loading.
if (_sets.find("CHRYS.PCK") != _sets.end() && !_sets["CHRYS.PCK"]->getFrame(225))
{
Log(LOG_FATAL) << "Version 1.0 data detected";
throw Exception("Invalid CHRYS.PCK, please patch your X-COM data to the latest version");
}
// TFTD uses the loftemps dat from the terrain folder, but still has enemy unknown's version in the geodata folder, which is short by 2 entries.
std::set<std::string> terrainContents = FileMap::getVFolderContents("TERRAIN");
if (terrainContents.find("loftemps.dat") != terrainContents.end())
{
MapDataSet::loadLOFTEMPS(FileMap::getFilePath("TERRAIN/LOFTEMPS.DAT"), &_voxelData);
}
else
{
MapDataSet::loadLOFTEMPS(FileMap::getFilePath("GEODATA/LOFTEMPS.DAT"), &_voxelData);
}
std::string scrs[] = { "TAC00.SCR" };
for (size_t i = 0; i < ARRAYLEN(scrs); ++i)
{
_surfaces[scrs[i]] = new Surface(320, 200);
_surfaces[scrs[i]]->loadScr(FileMap::getFilePath("UFOGRAPH/" + scrs[i]));
}
// lower case so we can find them in the contents map
std::string lbms[] = { "d0.lbm",
"d1.lbm",
"d2.lbm",
"d3.lbm" };
std::string pals[] = { "PAL_BATTLESCAPE",
"PAL_BATTLESCAPE_1",
"PAL_BATTLESCAPE_2",
"PAL_BATTLESCAPE_3" };
SDL_Color backPal[] = { { 0, 5, 4, 255 },
{ 0, 10, 34, 255 },
{ 2, 9, 24, 255 },
{ 2, 0, 24, 255 } };
std::set<std::string> ufographContents = FileMap::getVFolderContents("UFOGRAPH");
for (size_t i = 0; i < ARRAYLEN(lbms); ++i)
{
if (ufographContents.find(lbms[i]) == ufographContents.end())
{
continue;
}
if (!i)
{
delete _palettes["PAL_BATTLESCAPE"];
}
Surface *tempSurface = new Surface(1, 1);
tempSurface->loadImage(FileMap::getFilePath("UFOGRAPH/" + lbms[i]));
_palettes[pals[i]] = new Palette();
SDL_Color *colors = tempSurface->getPalette();
colors[255] = backPal[i];
_palettes[pals[i]]->setColors(colors, 256);
createTransparencyLUT(_palettes[pals[i]]);
delete tempSurface;
}
std::string spks[] = { "TAC01.SCR",
"DETBORD.PCK",
"DETBORD2.PCK",
"ICONS.PCK",
"MEDIBORD.PCK",
"SCANBORD.PCK",
"UNIBORD.PCK" };
for (size_t i = 0; i < ARRAYLEN(spks); ++i)
{
std::string fname = spks[i];
std::transform(fname.begin(), fname.end(), fname.begin(), tolower);
if (ufographContents.find(fname) == ufographContents.end())
{
continue;
}
_surfaces[spks[i]] = new Surface(320, 200);
_surfaces[spks[i]]->loadSpk(FileMap::getFilePath("UFOGRAPH/" + spks[i]));
}
std::set<std::string> bdys = FileMap::filterFiles(ufographContents, "BDY");
for (std::set<std::string>::iterator i = bdys.begin(); i != bdys.end(); ++i)
{
std::string idxName = *i;
std::transform(i->begin(), i->end(), idxName.begin(), toupper);
idxName = idxName.substr(0, idxName.length() - 3);
if (idxName.substr(0, 3) == "MAN")
{
idxName = idxName + "SPK";
}
else if (idxName == "TAC01.")
{
idxName = idxName + "SCR";
}
else
{
idxName = idxName + "PCK";
}
_surfaces[idxName] = new Surface(320, 200);
_surfaces[idxName]->loadBdy(FileMap::getFilePath("UFOGRAPH/" + *i));
}
// Load Battlescape inventory
std::set<std::string> invs = FileMap::filterFiles(ufographContents, "SPK");
for (std::set<std::string>::iterator i = invs.begin(); i != invs.end(); ++i)
{
std::string fname = *i;
std::transform(i->begin(), i->end(), fname.begin(), toupper);
_surfaces[fname] = new Surface(320, 200);
_surfaces[fname]->loadSpk(FileMap::getFilePath("UFOGRAPH/" + fname));
}
//"fix" of color index in original solders sprites
if (Options::battleHairBleach)
{
std::string name;
//personal armor
name = "XCOM_1.PCK";
if (_sets.find(name) != _sets.end())
{
SurfaceSet *xcom_1 = _sets[name];
for (int i = 0; i < 8; ++i)
{
//chest frame
Surface *surf = xcom_1->getFrame(4 * 8 + i);
ShaderMove<Uint8> head = ShaderMove<Uint8>(surf);
GraphSubset dim = head.getBaseDomain();
surf->lock();
dim.beg_y = 6;
dim.end_y = 9;
head.setDomain(dim);
ShaderDraw<HairXCOM1>(head, ShaderScalar<Uint8>(HairXCOM1::Face + 5));
dim.beg_y = 9;
dim.end_y = 10;
head.setDomain(dim);
ShaderDraw<HairXCOM1>(head, ShaderScalar<Uint8>(HairXCOM1::Face + 6));
surf->unlock();
}
for (int i = 0; i < 3; ++i)
{
//fall frame
Surface *surf = xcom_1->getFrame(264 + i);
ShaderMove<Uint8> head = ShaderMove<Uint8>(surf);
GraphSubset dim = head.getBaseDomain();
dim.beg_y = 0;
dim.end_y = 24;
dim.beg_x = 11;
dim.end_x = 20;
head.setDomain(dim);
surf->lock();
ShaderDraw<HairXCOM1>(head, ShaderScalar<Uint8>(HairXCOM1::Face + 6));
surf->unlock();
}
}
//all TFTD armors
name = "TDXCOM_?.PCK";
for (int j = 0; j < 3; ++j)
{
name[7] = '0' + j;
if (_sets.find(name) != _sets.end())
{
SurfaceSet *xcom_2 = _sets[name];
for (int i = 0; i < 16; ++i)
{
//chest frame without helm
Surface *surf = xcom_2->getFrame(262 + i);
surf->lock();
if (i < 8)
{
//female chest frame
ShaderMove<Uint8> head = ShaderMove<Uint8>(surf);
GraphSubset dim = head.getBaseDomain();
dim.beg_y = 6;
dim.end_y = 18;
head.setDomain(dim);
ShaderDraw<HairXCOM2>(head);
if (j == 2)
{
//fix some pixels in ION armor that was overwrite by previous function
if (i == 0)
{
surf->setPixel(18, 14, 16);
}
else if (i == 3)
{
surf->setPixel(19, 12, 20);
}
else if (i == 6)
{
surf->setPixel(13, 14, 16);
}
}
}
//we change face to pink, to prevent mixup with ION armor backpack that have same color group.
ShaderDraw<FaceXCOM2>(ShaderMove<Uint8>(surf));
surf->unlock();
}
for (int i = 0; i < 2; ++i)
{
//fall frame (first and second)
Surface *surf = xcom_2->getFrame(256 + i);
surf->lock();
ShaderMove<Uint8> head = ShaderMove<Uint8>(surf);
GraphSubset dim = head.getBaseDomain();
dim.beg_y = 0;
if (j == 3)
{
dim.end_y = 11 + 5 * i;
}
else
{
dim.end_y = 17;
}
head.setDomain(dim);
ShaderDraw<FallXCOM2>(head);
//we change face to pink, to prevent mixup with ION armor backpack that have same color group.
ShaderDraw<FaceXCOM2>(ShaderMove<Uint8>(surf));
surf->unlock();
}
//Palette fix for ION armor
if (j == 2)
{
int size = xcom_2->getTotalFrames();
for (int i = 0; i < size; ++i)
{
Surface *surf = xcom_2->getFrame(i);
surf->lock();
ShaderDraw<BodyXCOM2>(ShaderMove<Uint8>(surf));
surf->unlock();
}
}
}
}
}
}
/**
* Loads the extra resources defined in rulesets.
*/
void Mod::loadExtraResources()
{
// Load fonts
YAML::Node doc = YAML::LoadFile(FileMap::getFilePath("Language/" + _fontName));
Log(LOG_INFO) << "Loading fonts... " << _fontName;
for (YAML::const_iterator i = doc["fonts"].begin(); i != doc["fonts"].end(); ++i)
{
std::string id = (*i)["id"].as<std::string>();
Font *font = new Font();
font->load(*i);
_fonts[id] = font;
}
#ifndef __NO_MUSIC
// Load musics
if (!Options::mute)
{
const std::set<std::string> &soundFiles(FileMap::getVFolderContents("SOUND"));
// Check which music version is available
CatFile *adlibcat = 0, *aintrocat = 0;
GMCatFile *gmcat = 0;
for (std::set<std::string>::iterator i = soundFiles.begin(); i != soundFiles.end(); ++i)
{
if (0 == i->compare("adlib.cat"))
{
adlibcat = new CatFile(FileMap::getFilePath("SOUND/" + *i).c_str());
}
else if (0 == i->compare("aintro.cat"))
{
aintrocat = new CatFile(FileMap::getFilePath("SOUND/" + *i).c_str());
}
else if (0 == i->compare("gm.cat"))
{
gmcat = new GMCatFile(FileMap::getFilePath("SOUND/" + *i).c_str());
}
}
// Try the preferred format first, otherwise use the default priority
MusicFormat priority[] = { Options::preferredMusic, MUSIC_FLAC, MUSIC_OGG, MUSIC_MP3, MUSIC_MOD, MUSIC_WAV, MUSIC_ADLIB, MUSIC_GM, MUSIC_MIDI };
for (std::map<std::string, RuleMusic *>::const_iterator i = _musicDefs.begin(); i != _musicDefs.end(); ++i)
{
Music *music = 0;
for (size_t j = 0; j < ARRAYLEN(priority) && music == 0; ++j)
{
music = loadMusic(priority[j], (*i).first, (*i).second->getCatPos(), (*i).second->getNormalization(), adlibcat, aintrocat, gmcat);
}
if (music)
{
_musics[(*i).first] = music;
}
}
delete gmcat;
delete adlibcat;
delete aintrocat;
}
#endif
if (!Options::lazyLoadResources)
{
Log(LOG_INFO) << "Loading extra resources from ruleset...";
for (std::map<std::string, std::vector<ExtraSprites *> >::const_iterator i = _extraSprites.begin(); i != _extraSprites.end(); ++i)
{
for (std::vector<ExtraSprites*>::const_iterator j = i->second.begin(); j != i->second.end(); ++j)
{
loadExtraSprite(*j);
}
}
}
if (!Options::mute)
{
for (std::vector< std::pair<std::string, ExtraSounds *> >::const_iterator i = _extraSounds.begin(); i != _extraSounds.end(); ++i)
{
std::string setName = i->first;
ExtraSounds *soundPack = i->second;
SoundSet *set = 0;
std::map<std::string, SoundSet*>::iterator j = _sounds.find(setName);
if (j != _sounds.end())
{
set = j->second;
}
_sounds[setName] = soundPack->loadSoundSet(set);
}
}
TextButton::soundPress = getSound("GEO.CAT", Mod::BUTTON_PRESS);
Window::soundPopup[0] = getSound("GEO.CAT", Mod::WINDOW_POPUP[0]);
Window::soundPopup[1] = getSound("GEO.CAT", Mod::WINDOW_POPUP[1]);
Window::soundPopup[2] = getSound("GEO.CAT", Mod::WINDOW_POPUP[2]);
}
void Mod::loadExtraSprite(ExtraSprites *spritePack)
{
if (spritePack->isLoaded())
return;
if (spritePack->getSingleImage())
{
Surface *surface = 0;
std::map<std::string, Surface*>::iterator i = _surfaces.find(spritePack->getType());
if (i != _surfaces.end())
{
surface = i->second;
}
_surfaces[spritePack->getType()] = spritePack->loadSurface(surface);
if (_statePalette)
{
_surfaces[spritePack->getType()]->setPalette(_statePalette);
}
}
else
{
SurfaceSet *set = 0;
std::map<std::string, SurfaceSet*>::iterator i = _sets.find(spritePack->getType());
if (i != _sets.end())
{
set = i->second;
}
_sets[spritePack->getType()] = spritePack->loadSurfaceSet(set);
if (_statePalette)
{
_sets[spritePack->getType()]->setPalette(_statePalette);
}
}
}
/**
* Applies necessary modifications to vanilla resources.
*/
void Mod::modResources()
{
// we're gonna need these
getSurface("GEOBORD.SCR");
getSurface("ALTGEOBORD.SCR", false);
getSurface("BACK07.SCR");
getSurface("ALTBACK07.SCR", false);
getSurface("BACK06.SCR");
getSurface("UNIBORD.PCK");
getSurfaceSet("HANDOB.PCK");
getSurfaceSet("FLOOROB.PCK");
getSurfaceSet("BIGOBS.PCK");
// embiggen the geoscape background by mirroring the contents
// modders can provide their own backgrounds via ALTGEOBORD.SCR
if (_surfaces.find("ALTGEOBORD.SCR") == _surfaces.end())
{
int newWidth = 320 - 64, newHeight = 200;
Surface *newGeo = new Surface(newWidth * 3, newHeight * 3);
Surface *oldGeo = _surfaces["GEOBORD.SCR"];
for (int x = 0; x < newWidth; ++x)
{
for (int y = 0; y < newHeight; ++y)
{
newGeo->setPixel(newWidth + x, newHeight + y, oldGeo->getPixel(x, y));
newGeo->setPixel(newWidth - x - 1, newHeight + y, oldGeo->getPixel(x, y));
newGeo->setPixel(newWidth * 3 - x - 1, newHeight + y, oldGeo->getPixel(x, y));
newGeo->setPixel(newWidth + x, newHeight - y - 1, oldGeo->getPixel(x, y));
newGeo->setPixel(newWidth - x - 1, newHeight - y - 1, oldGeo->getPixel(x, y));
newGeo->setPixel(newWidth * 3 - x - 1, newHeight - y - 1, oldGeo->getPixel(x, y));
newGeo->setPixel(newWidth + x, newHeight * 3 - y - 1, oldGeo->getPixel(x, y));
newGeo->setPixel(newWidth - x - 1, newHeight * 3 - y - 1, oldGeo->getPixel(x, y));
newGeo->setPixel(newWidth * 3 - x - 1, newHeight * 3 - y - 1, oldGeo->getPixel(x, y));
}
}
_surfaces["ALTGEOBORD.SCR"] = newGeo;
}
// here we create an "alternate" background surface for the base info screen.
if (_surfaces.find("ALTBACK07.SCR") == _surfaces.end())
{
_surfaces["ALTBACK07.SCR"] = new Surface(320, 200);
_surfaces["ALTBACK07.SCR"]->loadScr(FileMap::getFilePath("GEOGRAPH/BACK07.SCR"));
for (int y = 172; y >= 152; --y)
for (int x = 5; x <= 314; ++x)
_surfaces["ALTBACK07.SCR"]->setPixel(x, y + 4, _surfaces["ALTBACK07.SCR"]->getPixel(x, y));
for (int y = 147; y >= 134; --y)
for (int x = 5; x <= 314; ++x)
_surfaces["ALTBACK07.SCR"]->setPixel(x, y + 9, _surfaces["ALTBACK07.SCR"]->getPixel(x, y));
for (int y = 132; y >= 109; --y)
for (int x = 5; x <= 314; ++x)
_surfaces["ALTBACK07.SCR"]->setPixel(x, y + 10, _surfaces["ALTBACK07.SCR"]->getPixel(x, y));
}
// we create extra rows on the soldier stat screens by shrinking them all down one pixel.
// first, let's do the base info screen
// erase the old lines, copying from a +2 offset to account for the dithering
for (int y = 91; y < 199; y += 12)
for (int x = 0; x < 149; ++x)
_surfaces["BACK06.SCR"]->setPixel(x, y, _surfaces["BACK06.SCR"]->getPixel(x, y + 2));
// drawn new lines, use the bottom row of pixels as a basis
for (int y = 89; y < 199; y += 11)
for (int x = 0; x < 149; ++x)
_surfaces["BACK06.SCR"]->setPixel(x, y, _surfaces["BACK06.SCR"]->getPixel(x, 199));
// finally, move the top of the graph up by one pixel, offset for the last iteration again due to dithering.
for (int y = 72; y < 80; ++y)
for (int x = 0; x < 320; ++x)
{
_surfaces["BACK06.SCR"]->setPixel(x, y, _surfaces["BACK06.SCR"]->getPixel(x, y + (y == 79 ? 2 : 1)));
}
// now, let's adjust the battlescape info screen.
// erase the old lines, no need to worry about dithering on this one.
for (int y = 39; y < 199; y += 10)
for (int x = 0; x < 169; ++x)
_surfaces["UNIBORD.PCK"]->setPixel(x, y, _surfaces["UNIBORD.PCK"]->getPixel(x, 30));
// drawn new lines, use the bottom row of pixels as a basis
for (int y = 190; y > 37; y -= 9)
for (int x = 0; x < 169; ++x)
_surfaces["UNIBORD.PCK"]->setPixel(x, y, _surfaces["UNIBORD.PCK"]->getPixel(x, 199));
// move the top of the graph down by eight pixels to erase the row we don't need (we actually created ~1.8 extra rows earlier)
for (int y = 37; y > 29; --y)
for (int x = 0; x < 320; ++x)
{
_surfaces["UNIBORD.PCK"]->setPixel(x, y, _surfaces["UNIBORD.PCK"]->getPixel(x, y - 8));
_surfaces["UNIBORD.PCK"]->setPixel(x, y - 8, 0);
}
// copy constructor doesn't like doing this directly, so let's make a second handobs file the old fashioned way.
// handob2 is used for all the left handed sprites.
_sets["HANDOB2.PCK"] = new SurfaceSet(_sets["HANDOB.PCK"]->getWidth(), _sets["HANDOB.PCK"]->getHeight());
std::map<int, Surface*> *handob = _sets["HANDOB.PCK"]->getFrames();
for (std::map<int, Surface*>::const_iterator i = handob->begin(); i != handob->end(); ++i)
{
Surface *surface1 = _sets["HANDOB2.PCK"]->addFrame(i->first);
Surface *surface2 = i->second;
surface1->setPalette(surface2->getPalette());
surface2->blit(surface1);
}
}
/**
* Loads the specified music file format.
* @param fmt Format of the music.
* @param file Filename of the music.
* @param track Track number of the music, if stored in a CAT.
* @param volume Volume modifier of the music, if stored in a CAT.
* @param adlibcat Pointer to ADLIB.CAT if available.
* @param aintrocat Pointer to AINTRO.CAT if available.
* @param gmcat Pointer to GM.CAT if available.
* @return Pointer to the music file, or NULL if it couldn't be loaded.
*/
Music *Mod::loadMusic(MusicFormat fmt, const std::string &file, int track, float volume, CatFile *adlibcat, CatFile *aintrocat, GMCatFile *gmcat) const
{
/* MUSIC_AUTO, MUSIC_FLAC, MUSIC_OGG, MUSIC_MP3, MUSIC_MOD, MUSIC_WAV, MUSIC_ADLIB, MUSIC_GM, MUSIC_MIDI */
static const std::string exts[] = { "", ".flac", ".ogg", ".mp3", ".mod", ".wav", "", "", ".mid" };
Music *music = 0;
std::set<std::string> soundContents = FileMap::getVFolderContents("SOUND");
try
{
// Try Adlib music
if (fmt == MUSIC_ADLIB)
{
if (adlibcat && Options::audioBitDepth == 16)
{
music = new AdlibMusic(volume);
if (track < adlibcat->getAmount())
{
music->load(adlibcat->load(track, true), adlibcat->getObjectSize(track));
}
// separate intro music
else if (aintrocat)
{
track -= adlibcat->getAmount();
if (track < aintrocat->getAmount())
{
music->load(aintrocat->load(track, true), aintrocat->getObjectSize(track));
}
else
{
delete music;
music = 0;
}
}
}
}
// Try MIDI music (from GM.CAT)
else if (fmt == MUSIC_GM)
{
// DOS MIDI
if (gmcat && track < gmcat->getAmount())
{
music = gmcat->loadMIDI(track);
}
}
// Try digital tracks
else
{
std::string fname = file + exts[fmt];
std::transform(fname.begin(), fname.end(), fname.begin(), ::tolower);
if (soundContents.find(fname) != soundContents.end())
{
music = new Music();
music->load(FileMap::getFilePath("SOUND/" + fname));
}
}
}
catch (Exception &e)
{
Log(LOG_INFO) << e.what();
if (music) delete music;
music = 0;
}
return music;
}
/**
* Preamble:
* this is the most horrible function i've ever written, and it makes me sad.
* this is, however, a necessary evil, in order to save massive amounts of time in the draw function.
* when used with the default TFTD mod, this function loops 4,194,304 times
* (4 palettes, 4 tints, 4 levels of opacity, 256 colors, 256 comparisons per)
* each additional tint in the rulesets will result in over a million iterations more.
* @param pal the palette to base the lookup table on.
*/
void Mod::createTransparencyLUT(Palette *pal)
{
const int opacityMax = 4;
const SDL_Color* palColors = pal->getColors(0);
std::vector<Uint8> lookUpTable;
// start with the color sets
lookUpTable.reserve(_transparencies.size() * 256 * opacityMax);
for (std::vector<SDL_Color>::const_iterator tint = _transparencies.begin(); tint != _transparencies.end(); ++tint)
{
// then the opacity levels, using the alpha channel as the step
for (int opacity = 1; opacity <= opacityMax; ++opacity)
{
// pseudo interpolation of palette color with tint
// for small values `op` its should behave same as original TFTD
// but for bigger values it make result closer to tint color
const int op = Clamp(opacity * tint->unused, 0, 64);
const float co = 1.0f - Sqr(op / 64.0f); // 1.0 -> 0.0
const float to = op * 1.0f; // 0.0 -> 64.0
// then the palette itself
for (int currentColor = 0; currentColor < 256; ++currentColor)
{
SDL_Color desiredColor;
desiredColor.r = std::min(255, (int)Round((palColors[currentColor].r * co) + (tint->r * to)));
desiredColor.g = std::min(255, (int)Round((palColors[currentColor].g * co) + (tint->g * to)));
desiredColor.b = std::min(255, (int)Round((palColors[currentColor].b * co) + (tint->b * to)));
Uint8 closest = currentColor;
int lowestDifference = INT_MAX;
// if opacity is zero then we stay with current color, transparet color will stay same too
if (op != 0 && currentColor != 0)
{
// now compare each color in the palette to find the closest match to our desired one
for (int comparator = 1; comparator < 256; ++comparator)
{
int currentDifference = Sqr(desiredColor.r - palColors[comparator].r) +
Sqr(desiredColor.g - palColors[comparator].g) +
Sqr(desiredColor.b - palColors[comparator].b);
if (currentDifference < lowestDifference)
{
closest = comparator;
lowestDifference = currentDifference;
}
}
}
lookUpTable.push_back(closest);
}
}
}
_transparencyLUTs.push_back(lookUpTable);
}
StatAdjustment *Mod::getStatAdjustment(int difficulty)
{
if (difficulty >= 4)
{
return &_statAdjustment[4];
}
return &_statAdjustment[difficulty];
}
/**
* Returns the minimum amount of score the player can have,
* otherwise they are defeated. Changes based on difficulty.
* @return Score.
*/
int Mod::getDefeatScore() const
{
return _defeatScore;
}
/**
* Returns the minimum amount of funds the player can have,
* otherwise they are defeated.
* @return Funds.
*/
int Mod::getDefeatFunds() const
{
return _defeatFunds;
}
/**
* Enables non-vanilla difficulty features.
* Dehumanize yourself and face the Warboy.
* @return Is the player screwed?
*/
bool Mod::isDemigod() const
{
return _difficultyDemigod;
}
}
↑ V807 Decreased performance. Consider creating a pointer to avoid using the 'base->getCrafts()' expression repeatedly.
↑ V823 Decreased performance. Object may be created in-place in the '_extraSounds' container. Consider replacing methods: 'push_back' -> 'emplace_back'.
↑ V831 Decreased performance. Consider replacing the call to the 'at()' method with the 'operator[]'.
↑ V807 Decreased performance. Consider creating a pointer to avoid using the 'save->getCountries()' expression repeatedly.
↑ V807 Decreased performance. Consider creating a pointer to avoid using the 'transportCraft->getVehicles()' expression repeatedly.
↑ V807 Decreased performance. Consider creating a pointer to avoid using the '(* v)->getRules()->getCompatibleAmmo()' expression repeatedly.
↑ V807 Decreased performance. Consider creating a pointer to avoid using the 'i->second' expression repeatedly.
↑ V809 Verifying that a pointer value is not NULL is not required. The 'if (music)' check can be removed.