Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: tags #25

Closed
wants to merge 15 commits into from
@@ -61,34 +61,16 @@ bool AttackBoost::isAffected(const Unit *source, const Unit *dest) const {
else {
// All units are affected (including enemies)
if(targetType == abtAll) {
destUnitMightApply = (boostUnitList.empty() == true);

// Specify which units are affected
for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
const UnitType *ut = boostUnitList[i];
if(dest->getType()->getId() == ut->getId()) {
destUnitMightApply = true;
break;
}
}

destUnitMightApply = (boostUnitList.empty() && tags.empty());
destUnitMightApply = isInUnitListOrTags(dest->getType());
Copy link
Author

@KatrinaHoffert KatrinaHoffert Aug 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bodies of these if statements have a number of code clones, which have been extracted into isInUnitListOrTags(const UnitType*).

}
// Only same faction units are affected
else if(targetType == abtFaction) {
//if(boostUnitList.empty() == true) {
if(source->getFactionIndex() == dest->getFactionIndex()) {
//destUnitMightApply = true;
destUnitMightApply = (boostUnitList.empty() == true);

// Specify which units are affected
for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
const UnitType *ut = boostUnitList[i];
if(dest->getType()->getId() == ut->getId()) {
destUnitMightApply = true;
break;
}
}

destUnitMightApply = (boostUnitList.empty() && tags.empty());
destUnitMightApply = isInUnitListOrTags(dest->getType());
}
//}
}
@@ -97,16 +79,8 @@ bool AttackBoost::isAffected(const Unit *source, const Unit *dest) const {
//if(boostUnitList.empty() == true) {
if(source->isAlly(dest) == true) {
//destUnitMightApply = true;
destUnitMightApply = (boostUnitList.empty() == true);

// Specify which units are affected
for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
const UnitType *ut = boostUnitList[i];
if(dest->getType()->getId() == ut->getId()) {
destUnitMightApply = true;
break;
}
}
destUnitMightApply = (boostUnitList.empty() && tags.empty());
destUnitMightApply = isInUnitListOrTags(dest->getType());
}
//}
}
@@ -115,28 +89,13 @@ bool AttackBoost::isAffected(const Unit *source, const Unit *dest) const {
//if(boostUnitList.empty() == true) {
if(source->isAlly(dest) == false) {
//destUnitMightApply = true;
destUnitMightApply = (boostUnitList.empty() == true);

// Specify which units are affected
for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
const UnitType *ut = boostUnitList[i];
if(dest->getType()->getId() == ut->getId()) {
destUnitMightApply = true;
break;
}
}
destUnitMightApply = (boostUnitList.empty() && tags.empty());
destUnitMightApply = isInUnitListOrTags(dest->getType());
}
//}
}
else if(targetType == abtUnitTypes) {
// Specify which units are affected
for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
const UnitType *ut = boostUnitList[i];
if(dest->getType()->getId() == ut->getId()) {
destUnitMightApply = true;
break;
}
}
destUnitMightApply = isInUnitListOrTags(dest->getType());
}
}

@@ -151,6 +110,32 @@ bool AttackBoost::isAffected(const Unit *source, const Unit *dest) const {
return result;
}

bool AttackBoost::isInUnitListOrTags(const UnitType *unitType) const {
// Specify which units are affected
std::set<const UnitType*>::iterator it;
for (it = boostUnitList.begin(); it != boostUnitList.end(); ++it) {
const UnitType *boostUnit = *it;
if(unitType->getId() == boostUnit->getId()) {
return true;
}
}
set<string> unitTags = unitType->getTags();
set<string> intersect;
set_intersection(tags.begin(),tags.end(),unitTags.begin(),unitTags.end(),
std::inserter(intersect,intersect.begin()));
if(!intersect.empty()) return true;

// Otherwise no match
return false;
}

string AttackBoost::getTagName(string tag, bool translatedValue) const {
if(translatedValue == false) return tag;

Lang &lang = Lang::getInstance();
return lang.getTechTreeString("TagName_" + tag, tag.c_str());
}

string AttackBoost::getDesc(bool translatedValue) const{
Lang &lang= Lang::getInstance();
string str= "";
@@ -190,14 +175,31 @@ string AttackBoost::getDesc(bool translatedValue) const{
str+= lang.getString("AffectedUnitsFromAll") +":\n";
}

if(boostUnitList.empty() == false) {
for(int i=0; i < (int)boostUnitList.size(); ++i){
str+= " "+boostUnitList[i]->getName(translatedValue)+"\n";
}
if(boostUnitList.empty() && tags.empty()) {
Copy link
Author

@KatrinaHoffert KatrinaHoffert Aug 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the logic of this condition has been switched around. Now the case where all units are affected comes first, since it's simpler.

str+= lang.getString("All")+"\n";
}
else
{
str+= lang.getString("All")+"\n";
// We want the output to be sorted, so convert the set to a vector and sort that
std::vector<const UnitType*> outputUnits(boostUnitList.begin(), boostUnitList.end());
std::sort(outputUnits.begin(), outputUnits.end(), UnitTypeSorter());

vector<const UnitType*>::iterator unitIter;
for (unitIter = outputUnits.begin(); unitIter != outputUnits.end(); ++unitIter) {
const UnitType *unit = *unitIter;
str+= indent+unit->getName(translatedValue)+"\n";
}

// Do the same for tags
std::vector<string> outputTags(tags.begin(), tags.end());
std::sort(outputTags.begin(), outputTags.end());

vector<string>::iterator tagIter;
for (tagIter = outputTags.begin(); tagIter != outputTags.end(); ++tagIter) {
string tag = *tagIter;
str+= indent + lang.getString("TagDesc", (translatedValue == true ? "" : "english")) +
" " + getTagName(tag,translatedValue) + "\n";
}
}

return str;
@@ -222,10 +224,18 @@ void AttackBoost::loadGame(const XmlNode *rootNode, Faction *faction, const Skil
string unitTypeName = node->getAttribute("name")->getValue();
const UnitType *unitType = faction->getType()->getUnitType(unitTypeName);
if(unitType != NULL) {
boostUnitList.push_back(unitType);
boostUnitList.insert(unitType);
}
}
}
if(attackBoostNode->hasChild("tag")) {
vector<XmlNode *> tagNodeList = attackBoostNode->getChildList("tag");
for(unsigned int i = 0; i < tagNodeList.size(); ++i) {
XmlNode *node = tagNodeList[i];
string tagName = node->getAttribute("name")->getValue();
tags.insert(tagName);
}
}
//boostUpgrade.loadGame(attackBoostNode,faction);
boostUpgrade = skillType->getAttackBoost()->boostUpgrade;

@@ -252,10 +262,17 @@ void AttackBoost::saveGame(XmlNode *rootNode) const {
// AttackBoostTargetType targetType;
attackBoostNode->addAttribute("targetType",intToStr(targetType), mapTagReplacements);
// vector<const UnitType *> boostUnitList;
for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
const UnitType *ut = boostUnitList[i];
std::set<const UnitType*>::iterator unitIter;
for (unitIter = boostUnitList.begin(); unitIter != boostUnitList.end(); ++unitIter) {
const UnitType *unit = *unitIter;
XmlNode *unitTypeNode = attackBoostNode->addChild("UnitType");
unitTypeNode->addAttribute("name",ut->getName(false), mapTagReplacements);
unitTypeNode->addAttribute("name",unit->getName(false), mapTagReplacements);
}
std::set<string>::iterator tagIter;
for (tagIter = tags.begin(); tagIter != tags.end(); ++tagIter) {
string tag = *tagIter;
XmlNode *unitTypeNode = attackBoostNode->addChild("tag");
unitTypeNode->addAttribute("name", tag, mapTagReplacements);
}
// UpgradeTypeBase boostUpgrade;
boostUpgrade.saveGame(attackBoostNode);
@@ -352,45 +369,40 @@ void SkillType::loadAttackBoost(const XmlNode *attackBoostsNode, const XmlNode *

if(targetType == "ally") {
attackBoost.targetType = abtAlly;
for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
}
}
else if(targetType == "foe") {
attackBoost.targetType = abtFoe;
for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
}
}
else if(targetType == "faction") {
attackBoost.targetType = abtFaction;
for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
}
}
else if(targetType == "unit-types") {
attackBoost.targetType = abtUnitTypes;
for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
}
}
else if(targetType == "all") {
attackBoost.targetType = abtAll;
for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
}
}
else {
char szBuf[8096] = "";
snprintf(szBuf, 8096,"Unsupported target [%s] specified for attack boost for skill [%s] in [%s]", targetType.c_str(), name.c_str(), parentLoader.c_str());
throw megaglest_runtime_error(szBuf);
}

// Load the regular targets
const XmlNode *targetNode = attackBoostNode->getChild("target");
vector<XmlNode*> targetNodes = targetNode->getChildList("unit-type");
for(size_t i = 0;i < targetNodes.size(); ++i) {
string unitName = targetNodes.at(i)->getAttribute("name")->getRestrictedValue();
attackBoost.boostUnitList.insert(ft->getUnitType(unitName));
}

// Load tags
vector<XmlNode*> tagNodes = targetNode->getChildList("tag");
for(size_t i = 0;i < tagNodes.size(); ++i) {
string unitName = tagNodes.at(i)->getAttribute("name")->getRestrictedValue();
attackBoost.tags.insert(unitName);
}

attackBoost.boostUpgrade.load(attackBoostNode,attackBoost.name);
if(attackBoostNode->hasChild("particles") == true) {
const XmlNode *particleNode = attackBoostNode->getChild("particles");
@@ -30,6 +30,7 @@
#include "projectile_type.h"
#include "upgrade_type.h"
#include "leak_dumper.h"
#include <set>

using Shared::Sound::StaticSound;
using Shared::Xml::XmlNode;
@@ -98,7 +99,8 @@ class AttackBoost {
bool allowMultipleBoosts;
int radius;
AttackBoostTargetType targetType;
vector<const UnitType *> boostUnitList;
std::set<const UnitType *> boostUnitList;
Copy link
Author

@KatrinaHoffert KatrinaHoffert Aug 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sets are used to simplify handling duplicates. Sets will ignore duplicates. This was a bigger deal back when the implementation was to simply dump all units with a matching tag into the boostUnitList. Although now it's useful to find the intercept of the set of upgrade/attack-boost tags and unit tags, which makes checking if a unit is affected much simpler (if slightly less efficient).

std::set<string> tags;
UpgradeTypeBase boostUpgrade;

UnitParticleSystemType *unitParticleSystemTypeForSourceUnit;
@@ -109,9 +111,20 @@ class AttackBoost {

bool isAffected(const Unit *source, const Unit *dest) const;
virtual string getDesc(bool translatedValue) const;
string getTagName(string tag, bool translatedValue=false) const;

virtual void saveGame(XmlNode *rootNode) const;
virtual void loadGame(const XmlNode *rootNode, Faction *faction, const SkillType *skillType);

private:
/**
* Checks if a unit is affected by the attack boost by checking if either the UnitType is in
* the #boostUnitList or shares a tag with #tags.
* @param unitType The unit type to check.
* @return True if the unit *might* be affected by the attack boost (still have to check if it's
* in range), false otherwise.
*/
bool isInUnitListOrTags(const UnitType *unitType) const;
};

class AnimationAttributes {
@@ -597,7 +597,7 @@ void UnitType::loaddd(int id,const string &dir, const TechTree *techTree,
if(parametersNode->hasChild("resources-death")) {
const XmlNode *deathResourcesNode= parametersNode->getChild("resources-death");

for(int i=0; i < deathResourcesNode->getChildCount(); ++i){
for(size_t i=0; i < deathResourcesNode->getChildCount(); ++i){
const XmlNode *resourceNode= deathResourcesNode->getChild("resource", i);
string name= resourceNode->getAttribute("name")->getRestrictedValue();

@@ -651,6 +651,17 @@ void UnitType::loaddd(int id,const string &dir, const TechTree *techTree,
}
}

// Tags
if(parametersNode->hasChild("tags")) {
const XmlNode *tagsNode= parametersNode->getChild("tags");

for(size_t i=0; i < tagsNode->getChildCount(); ++i){
const XmlNode *resourceNode= tagsNode->getChild("tag", i);
string tag= resourceNode->getAttribute("value")->getRestrictedValue();
tags.insert(tag);
}
}

//image
const XmlNode *imageNode= parametersNode->getChild("image");
image= Renderer::getInstance().newTexture2D(rsGame);
@@ -192,6 +192,7 @@ class UnitType: public ProducibleType, public ValueCheckerVault {
StoredResources storedResources;
Levels levels;
LootableResources lootableResources;
std::set<string> tags;

//meeting point
bool meetingPoint;
@@ -263,6 +264,7 @@ class UnitType: public ProducibleType, public ValueCheckerVault {
inline const Resource *getStoredResource(int i) const {return &storedResources[i];}
int getLootableResourceCount() const {return lootableResources.size();}
inline const LootableResource getLootableResource(int i) const {return lootableResources.at(i);}
const set<string> &getTags() const {return tags;}
Copy link
Author

@KatrinaHoffert KatrinaHoffert Aug 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gave me much grief. This used to return a value, which causes issues with using iterators. The issue is when you use an iterator like so:

for(auto it = unitType->getTags().begin(); it != unitType->getTags().end(); ++it)

The issue is that getTags() would return a copy of the set (an entirely new set), so the iterator (from one set) would not match the end iterator (from a different set), thus causing the conditional to be true even in situations when it shouldn't be.

Changing getTags() to return a reference fixes this by ensuring we always get the same set (instead of a copy). Also faster.

bool getCellMapCell(int x, int y, CardinalDir facing) const;
inline bool getMeetingPoint() const {return meetingPoint;}
inline bool getCountUnitDeathInStats() const {return countUnitDeathInStats;}
@@ -327,6 +329,17 @@ class UnitType: public ProducibleType, public ValueCheckerVault {
void computeFirstCtOfClass();
};

/**
* Used to sort UnitType. Sorts by *translated* unit name. Sorting is case sensitive and done in
* lexical order.
*/
struct UnitTypeSorter
{
bool operator()( const UnitType *left, const UnitType *right ) const {
return left->getName(true) < right->getName(true);
}
};

}}//end namespace