- Added "loseSubtypesOf(type)" ability. For example, loseSubtypesOf(land) means "target loses all its land subtypes"

- Added Evil Presence, as an example of the new keywords loseabilities and losesubtypes. It's quite experimental but I added 3 tests that cover the basics. Please report if you find bugs.
- moved the "lands produce mana" rules outside of the primitives, and into the external rules. This was a necessary step to create cards such as Evil Presence. 
- real support for subtypes. Needs some more testing, but there are now functions in Subtypes.cpp to know if a given subtype is a creature subtype, or a land subtype, etc...
- minor refactor of MTGDeck.cpp

Notes:
- I checked that the AI can still use lands
- This change has a bad impact on primitives loading performance (thanks Wil for the loading time output). This is probably due to suboptimal algorithms and data structures for subtypes. If the impact is strong on lowend devices, I can probably optimize a bit (the map subtypesOf could be changed into a vector with some work)
- The test suite passes, added 3 tests for evil presence.
This commit is contained in:
wagic.the.homebrew
2011-05-04 04:04:03 +00:00
parent 494bcf3315
commit d922d4fe06
15 changed files with 250 additions and 118 deletions

View File

@@ -9,6 +9,14 @@ auto=draw:7
auto=@each my draw:draw:1
auto=maxPlay(land)1
#Lands Mana Rules
auto=lord(Swamp|MyBattlefield) {T}:Add{B}
auto=lord(Forest|MyBattlefield) {T}:Add{G}
auto=lord(Island|MyBattlefield) {T}:Add{U}
auto=lord(Mountains|MyBattlefield) {T}:Add{R}
auto=lord(Plains|MyBattlefield) {T}:Add{W}
#Mana Empties from manapool at the end of each phase
auto=@each untap:removeMana(*)
auto=@each upkeep:removeMana(*)

View File

@@ -7,6 +7,13 @@ life:20
auto=@each my draw:draw:1
auto=maxPlay(land)1
#Lands Mana Rules
auto=lord(Swamp|MyBattlefield) {T}:Add{B}
auto=lord(Forest|MyBattlefield) {T}:Add{G}
auto=lord(Island|MyBattlefield) {T}:Add{U}
auto=lord(Mountains|MyBattlefield) {T}:Add{R}
auto=lord(Plains|MyBattlefield) {T}:Add{W}
#Mana Empties from manapool at the end of each phase
auto=@each untap:removeMana(*)
auto=@each upkeep:removeMana(*)

View File

@@ -4269,8 +4269,6 @@ type=Land
[/card]
[card]
name=Badlands
auto={T}:Add {R}
auto={T}:Add {B}
type=Land
subtype=Swamp Mountain
[/card]
@@ -5252,8 +5250,6 @@ toughness=1
[/card]
[card]
name=Bayou
auto={T}:Add {B}
auto={T}:Add {G}
type=Land
subtype=Swamp Forest
[/card]
@@ -6678,8 +6674,6 @@ toughness=2
name=Blood Crypt
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {B}
auto={T}:Add {R}
text=({T}: Add {B} or {R} to your mana pool.) -- As Blood Crypt enters the battlefield, you may pay 2 life. If you don't, Blood Crypt enters the battlefield tapped.
type=Land
subtype=Swamp Mountain
@@ -8203,8 +8197,6 @@ type=Enchantment
name=Breeding Pool
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {G}
auto={T}:Add {U}
text=({T}: Add {G} or {U} to your mana pool.) -- As Breeding Pool enters the battlefield, you may pay 2 life. If you don't, Breeding Pool enters the battlefield tapped.
type=Land
subtype=Forest Island
@@ -17855,10 +17847,9 @@ type=Sorcery
[/card]
[card]
name=Dryad Arbor
auto={T}:add{G}
text=(Dryad Arbor isn't a spell, it's affected by summoning sickness, and it has "{T}: Add {G} to your mana pool.") -- Dryad Arbor is green.
color=green
type=Land Creature
type=Creature Land
subtype=Forest Dryad
power=1
toughness=1
@@ -20461,6 +20452,17 @@ power=6
toughness=3
[/card]
[card]
name=Evil Presence
text=Enchant land -- Enchanted land is a Swamp.
target=land
auto=loseabilities
auto=losesubtypesof(land)
auto=transforms((swamp,))
mana={B}
type=Enchantment
subtype=Aura
[/card]
[card]
name=Evincar's Justice
auto=damage:2 all(creature,player)
buyback={2}{B}{B}{3}
@@ -23529,7 +23531,6 @@ subtype=Aura
[/card]
[card]
name=Forest
auto={T}: Add {G}
text=G
type=Basic Land
subtype=Forest
@@ -26788,8 +26789,6 @@ toughness=4
name=Godless Shrine
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {W}
auto={T}:Add {B}
text=({T}: Add {W} or {B} to your mana pool.) -- As Godless Shrine enters the battlefield, you may pay 2 life. If you don't, Godless Shrine enters the battlefield tapped.
type=Land
subtype=Plains Swamp
@@ -28498,8 +28497,6 @@ toughness=3
name=Hallowed Fountain
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {W}
auto={T}:Add {U}
text=({T}: Add {W} or {U} to your mana pool.) -- As Hallowed Fountain enters the battlefield, you may pay 2 life. If you don't, Hallowed Fountain enters the battlefield tapped.
type=Land
subtype=Plains Island
@@ -32457,7 +32454,6 @@ toughness=1
[/card]
[card]
name=Island
auto={T}: Add {U}
text=U
type=Basic Land
subtype=Island
@@ -36645,7 +36641,6 @@ toughness=5
[card]
name=Leechridden Swamp
auto=tap
auto={T}:Add {B}
auto=aslongas(*[black]|myBattlefield) {B}{T}:life:-1 opponent >1
text=({T}: Add {B} to your mana pool.) -- Leechridden Swamp enters the battlefield tapped. -- {B}, {T}: Each opponent loses 1 life. Activate this ability only if you control two or more black permanents.
type=Land
@@ -42130,7 +42125,6 @@ toughness=2
[/card]
[card]
name=Mountain
auto={T}: Add {R}
text=R
type=Basic Land
subtype=Mountain
@@ -42437,7 +42431,6 @@ toughness=4
name=Murmuring Bosk
auto=tap
auto=aslongas(treefolk|myhand) untap
auto={T}:Add{G}
auto={T}:Add{W} && damage:1 controller
auto={T}:Add{B} && damage:1 controller
text=({T}: Add {G} to your mana pool.) -- As Murmuring Bosk enters the battlefield, you may reveal a Treefolk card from your hand. If you don't, Murmuring Bosk enters the battlefield tapped. -- {T}: Add {W} or {B} to your mana pool. Murmuring Bosk deals 1 damage to you.
@@ -45853,8 +45846,6 @@ type=Enchantment
name=Overgrown Tomb
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {B}
auto={T}:Add {G}
text=({T}: Add {B} or {G} to your mana pool.) -- As Overgrown Tomb enters the battlefield, you may pay 2 life. If you don't, Overgrown Tomb enters the battlefield tapped.
type=Land
subtype=Swamp Forest
@@ -48100,7 +48091,6 @@ toughness=1
[/card]
[card]
name=Plains
auto={T}: Add {W}
text=W
type=Basic Land
subtype=Plains
@@ -48185,8 +48175,6 @@ toughness=1
[/card]
[card]
name=Plateau
auto={T}:Add {R}
auto={T}:Add {W}
type=Land
subtype=Mountain Plains
[/card]
@@ -54017,8 +54005,6 @@ toughness=3
name=Sacred Foundry
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {R}
auto={T}:Add {W}
text=({T}: Add {R} or {W} to your mana pool.) -- As Sacred Foundry enters the battlefield, you may pay 2 life. If you don't, Sacred Foundry enters the battlefield tapped.
type=Land
subtype=Mountain Plains
@@ -54662,7 +54648,6 @@ type=Land
[card]
name=Sapseep Forest
auto=tap
auto={T}:Add {G}
auto=aslongas(*[green]|myBattlefield) {G}{T}:life:1 controller >1
text=({T}: Add {G} to your mana pool.) -- Sapseep Forest enters the battlefield tapped. -- {G}, {T}: You gain 1 life. Activate this ability only if you control two or more green permanents.
type=Land
@@ -54844,8 +54829,6 @@ type=Sorcery
[/card]
[card]
name=Savannah
auto={T}:Add {W}
auto={T}:Add {G}
type=Land
subtype=Forest Plains
[/card]
@@ -55540,8 +55523,6 @@ toughness=3
[/card]
[card]
name=Scrubland
auto={T}:Add {W}
auto={T}:Add {B}
type=Land
subtype=Plains Swamp
[/card]
@@ -60057,35 +60038,30 @@ toughness=1
[/card]
[card]
name=Snow-Covered Forest
auto={T}:Add {G}
text=G
type=Basic Snow Land
subtype=Forest
[/card]
[card]
name=Snow-Covered Island
auto={T}:Add {U}
text=U
type=Basic Snow Land
subtype=Island
[/card]
[card]
name=Snow-Covered Mountain
auto={T}:Add {R}
text=R
type=Basic Snow Land
subtype=Mountain
[/card]
[card]
name=Snow-Covered Plains
auto={T}:Add {W}
text=W
type=Basic Snow Land
subtype=Plains
[/card]
[card]
name=Snow-Covered Swamp
auto={T}:Add {B}
text=B
type=Basic Snow Land
subtype=Swamp
@@ -62691,8 +62667,6 @@ toughness=5
name=Steam Vents
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {U}
auto={T}:Add {R}
text=({T}: Add {U} or {R} to your mana pool.) -- As Steam Vents enters the battlefield, you may pay 2 life. If you don't, Steam Vents enters the battlefield tapped.
type=Land
subtype=Island Mountain
@@ -62995,8 +62969,6 @@ toughness=3
name=Stomping Ground
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {R}
auto={T}:Add {G}
text=({T}: Add {R} or {G} to your mana pool.) -- As Stomping Ground enters the battlefield, you may pay 2 life. If you don't, Stomping Ground enters the battlefield tapped.
type=Land
subtype=Mountain Forest
@@ -64418,7 +64390,6 @@ subtype=Arcane
[/card]
[card]
name=Swamp
auto={T}: Add {B}
text=B
type=Basic Land
subtype=Swamp
@@ -64851,8 +64822,6 @@ toughness=5
[/card]
[card]
name=Taiga
auto={T}:Add {G}
auto={T}:Add {R}
type=Land
subtype=Mountain Forest
[/card]
@@ -65855,8 +65824,6 @@ toughness=2
name=Temple Garden
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {G}
auto={T}:Add {W}
text=({T}: Add {G} or {W} to your mana pool.) -- As Temple Garden enters the battlefield, you may pay 2 life. If you don't, Temple Garden enters the battlefield tapped.
type=Land
subtype=Forest Plains
@@ -69106,8 +69073,6 @@ toughness=3
[/card]
[card]
name=Tropical Island
auto={T}:Add {G}
auto={T}:Add {U}
type=Land
subtype=Forest Island
[/card]
@@ -69258,8 +69223,6 @@ type=Artifact
[/card]
[card]
name=Tundra
auto={T}:Add {U}
auto={T}:Add {W}
type=Land
subtype=Plains Island
[/card]
@@ -69690,8 +69653,6 @@ type=Land
[/card]
[card]
name=Underground Sea
auto={T}:Add {B}
auto={T}:Add {U}
type=Land
subtype=Island Swamp
[/card]
@@ -72406,8 +72367,6 @@ type=Sorcery
[/card]
[card]
name=Volcanic Island
auto={T}:Add {U}
auto={T}:Add {R}
type=Land
subtype=Island Mountain
[/card]
@@ -73773,8 +73732,6 @@ type=Land
name=Watery Grave
auto=tap
auto=may untap && life:-2 controller
auto={T}:Add {U}
auto={T}:Add {B}
text=({T}: Add {U} or {B} to your mana pool.) -- As Watery Grave enters the battlefield, you may pay 2 life. If you don't, Watery Grave enters the battlefield tapped.
type=Land
subtype=Island Swamp

View File

@@ -6574,18 +6574,6 @@ power=2
toughness=2
[/card]
[card]
#Need a way for the target to lose its land subtypes.
#Need to move the {T}:Add{X} rules for basic lands outside of the cards and into the rules <-- easy?
name=Evil Presence
text=Enchant land -- Enchanted land is a Swamp.
target=land
auto=loseabilities
auto=transforms((swamp,))
mana={B}
type=Enchantment
subtype=Aura
[/card]
[card]
name=Excavation
text={1}, Sacrifice a land: Draw a card. Any player may activate this ability.
mana={1}{U}

View File

@@ -257,6 +257,9 @@ enervate.txt
enchantress_s_presence.txt
Eradicate.txt
erg_raiders_i157.txt
evil_presence.txt
evil_presence2.txt
evil_presence3.txt
explore.txt
Faceless_Butcher.txt
fangren_pathcutter.txt

View File

@@ -0,0 +1,19 @@
#Testing Evil Presence on Gaea's Cradle
[INIT]
FIRSTMAIN
[PLAYER1]
hand:Evil PResence
inplay:Gaea's Cradle,raging goblin, grizzly bears
manapool:{B}
[PLAYER2]
[DO]
Evil PResence
Gaea's Cradle
Gaea's Cradle
[ASSERT]
FIRSTMAIN
[PLAYER1]
inplay:Gaea's Cradle,raging goblin, grizzly bears,Evil PResence
manapool:{B}
[PLAYER2]
[END]

View File

@@ -0,0 +1,22 @@
#Testing Evil Presence on Gaea's Cradle
[INIT]
FIRSTMAIN
[PLAYER1]
hand:Evil PResence, disenchant
inplay:Gaea's Cradle,raging goblin,grizzly bears
manapool:{B}{1}{W}
[PLAYER2]
[DO]
Evil PResence
Gaea's Cradle
Disenchant
Evil PResence
Gaea's Cradle
[ASSERT]
FIRSTMAIN
[PLAYER1]
inplay:Gaea's Cradle,raging goblin, grizzly bears
graveyard:Evil PResence,Disenchant
manapool:{G}{G}
[PLAYER2]
[END]

View File

@@ -0,0 +1,19 @@
#Testing Evil Presence on a Forest
[INIT]
FIRSTMAIN
[PLAYER1]
hand:Evil PResence
inplay:Forest
manapool:{B}
[PLAYER2]
[DO]
Evil PResence
Forest
Forest
[ASSERT]
FIRSTMAIN
[PLAYER1]
inplay:Forest,Evil Presence
manapool:{B}
[PLAYER2]
[END]

View File

@@ -3617,6 +3617,18 @@ public:
ALoseAbilities * clone() const;
};
//Remove subtypes (of a given type) from target
class ALoseSubtypes: public MTGAbility
{
public:
int parentType;
vector <int> storedSubtypes;
ALoseSubtypes(int id, MTGCardInstance * source, MTGCardInstance * target, int parentType);
int addToGame();
int destroy();
ALoseSubtypes * clone() const;
};
//Adds types/abilities/P/T to a card (until end of turn)
class APreventDamageTypesUEOT: public InstantAbility
{

View File

@@ -19,20 +19,34 @@ public:
TYPE_LAND = 5,
TYPE_ARTIFACT = 6,
TYPE_LEGENDARY = 7,
TYPE_EQUIPMENT = 8,
TYPE_AURA = 9,
TYPE_PLANESWALKER = 10,
LAST_TYPE = TYPE_PLANESWALKER,
TYPE_SNOW = 8,
TYPE_BASIC = 9,
TYPE_WORLD = 10,
TYPE_EQUIPMENT = 11,
TYPE_AURA = 12,
TYPE_PLANESWALKER = 13,
TYPE_TRIBAL = 14,
TYPE_PLANE = 15,
TYPE_SCHEME = 16,
TYPE_VANGUARD = 17,
LAST_TYPE = TYPE_VANGUARD,
};
protected:
map<string, int> values;
vector<string> valuesById;
map<int,int> subtypesToType;
public:
static Subtypes * subtypesList;
Subtypes();
int find(string subtype, bool forceAdd = true);
string find(unsigned int id);
bool isSubtypeOfType(string subtype, string type);
bool isSubtypeOfType(unsigned int subtype, unsigned int type);
bool isSuperType(int type);
bool isType(int type);
bool isSubType(int type);
int add(string value, int parentType);
};
#endif

View File

@@ -2857,6 +2857,51 @@ ALoseAbilities * ALoseAbilities::clone() const
return a;
}
//ALoseSubtypes
ALoseSubtypes::ALoseSubtypes(int id, MTGCardInstance * source, MTGCardInstance * _target, int parentType) :
MTGAbility(id, source), parentType(parentType)
{
target = _target;
}
int ALoseSubtypes::addToGame()
{
if (storedSubtypes.size())
{
DebugTrace("FATAL:storedSubtypes shouldn't be already set inALoseSubtypes\n");
return 0;
}
MTGCardInstance * _target = (MTGCardInstance *)target;
for (int i = ((int)_target->types.size())-1; i >= 0; --i)
{
int subtype = _target->types[i];
if (Subtypes::subtypesList->isSubtypeOfType(subtype, parentType))
{
storedSubtypes.push_back(subtype);
_target->removeType(subtype);
}
}
return MTGAbility::addToGame();
}
int ALoseSubtypes::destroy()
{
MTGCardInstance * _target = (MTGCardInstance *)target;
for (size_t i = 0; i < storedSubtypes.size(); ++i)
_target->addType(storedSubtypes[i]);
storedSubtypes.clear();
return 1;
}
ALoseSubtypes * ALoseSubtypes::clone() const
{
ALoseSubtypes * a = NEW ALoseSubtypes(*this);
a->isClone = 1;
return a;
}
//APreventDamageTypes
APreventDamageTypes::APreventDamageTypes(int id, MTGCardInstance * source, string to, string from, int type) :
MTGAbility(id, source), to(to), from(from), type(type)

View File

@@ -196,7 +196,17 @@ void CardPrimitive::addType(char * _type_text)
void CardPrimitive::setSubtype(const string& value)
{
int id = Subtypes::subtypesList->find(value);
//find the parent type for this card
int parentType = 0;
for (size_t i = 0; i < types.size(); ++i)
{
if (Subtypes::subtypesList->isType(types[i]))
{
parentType = types[i];
break;
}
}
int id = Subtypes::subtypesList->add(value, parentType);
addType(id);
}

View File

@@ -1814,6 +1814,14 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG
return a;
}
//Lose subtypes of a given type
vector<string> splitLoseTypes = parseBetween(s, "losesubtypesof(", ")");
if (splitLoseTypes.size())
{
int parentType = Subtypes::subtypesList->find(splitLoseTypes[1]);
return NEW ALoseSubtypes(id, card, target, parentType);
}
//Cast/Play Restrictions
for (size_t i = 0; i < kMaxCastKeywordsCount; ++i)
{

View File

@@ -78,25 +78,13 @@ int MTGAllCards::processConfLine(string &s, MTGCard *card, CardPrimitive * primi
string value = val;
//Specific Abilities
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
while (value.size())
vector<string> values = split(value, ',');
for (size_t values_i = 0; values_i < values.size(); ++values_i)
{
string attribute;
size_t found2 = value.find(',');
if (found2 != string::npos)
{
attribute = value.substr(0, found2);
value = value.substr(found2 + 1);
}
else
{
attribute = value;
value = "";
}
for (int j = Constants::NB_BASIC_ABILITIES - 1; j >= 0; --j)
{
size_t found = attribute.find(Constants::MTGBasicAbilities[j]);
if (found != string::npos)
if (values[values_i].find(Constants::MTGBasicAbilities[j]) != string::npos)
{
primitive->basicAbilities[j] = 1;
break;
@@ -249,7 +237,7 @@ int MTGAllCards::processConfLine(string &s, MTGCard *card, CardPrimitive * primi
card->setRarity(val[0]);
}
break;
case 's': //subtype
case 's': //subtype, suspend
{
if (s.find("suspend") != string::npos)
{
@@ -269,21 +257,9 @@ int MTGAllCards::processConfLine(string &s, MTGCard *card, CardPrimitive * primi
else
{
if (!primitive) primitive = NEW CardPrimitive();
while (true)
{
char* found = strchr(val, ' ');
if (found)
{
string value(val, found - val);
primitive->setSubtype(value);
val = found + 1;
}
else
{
primitive->setSubtype(val);
break;
}
}
vector<string> values = split(val, ' ');
for (size_t values_i = 0; values_i < values.size(); ++values_i)
primitive->setSubtype(values[values_i]);
}
break;
}
@@ -299,21 +275,9 @@ int MTGAllCards::processConfLine(string &s, MTGCard *card, CardPrimitive * primi
primitive->setText(val);
else if (0 == strcmp("type", key))
{
while (true)
{
char* found = strchr(val, ' ');
if (found)
{
string value(val, found - val);
primitive->setType(value);
val = found + 1;
}
else
{
primitive->setType(val);
break;
}
}
vector<string> values = split(val, ' ');
for (size_t values_i = 0; values_i < values.size(); ++values_i)
primitive->setType(values[values_i]);
}
else if (0 == strcmp("toughness", key)) primitive->setToughness(atoi(val));
break;

View File

@@ -15,9 +15,16 @@ Subtypes::Subtypes()
find("Land");
find("Artifact");
find("Legendary");
find("Snow");
find("Basic");
find("World");
find("Equipment");
find("Aura");
find("Planeswalker");
find("Tribal");
find("Plane");
find("Scheme");
find("Vanguard");
}
int Subtypes::find(string value, bool forceAdd)
@@ -32,8 +39,57 @@ int Subtypes::find(string value, bool forceAdd)
return id;
}
// Adds a subtype to the list, and associated it with a parent type.
//The association can happen only once, a subtype is then definitely associated to its parent type.
// If you associate "goblin" to "creature", trying to associate "goblin" to "land" afterwards will fail. "goblin" will stay associated to its first parent.
int Subtypes::add(string value, int parentType)
{
int subtype = find(value);
if (parentType && isSubType(subtype) && !subtypesToType[subtype])
subtypesToType[subtype] = parentType;
return subtype;
}
string Subtypes::find(unsigned int id)
{
if (valuesById.size() < id || !id) return "";
return valuesById[id - 1];
}
bool Subtypes::isSubtypeOfType(string subtype, string type)
{
unsigned int subtypeInt = find(subtype);
unsigned int typeInt = find(type);
return isSubtypeOfType(subtypeInt, typeInt);
}
bool Subtypes::isSubtypeOfType(unsigned int subtype, unsigned int type)
{
return (subtypesToType[subtype] == type);
}
bool Subtypes::isSuperType(int type)
{
return (type == TYPE_BASIC || type == TYPE_WORLD || type == TYPE_SNOW || type == TYPE_LEGENDARY);
}
bool Subtypes::isType(int type)
{
return (
type == TYPE_CREATURE ||
type == TYPE_ENCHANTMENT ||
type == TYPE_SORCERY ||
type == TYPE_INSTANT ||
type == TYPE_LAND ||
type == TYPE_ARTIFACT ||
type ==TYPE_PLANESWALKER ||
type == TYPE_TRIBAL ||
type == TYPE_PLANE ||
type == TYPE_SCHEME ||
type == TYPE_VANGUARD
);
}
bool Subtypes::isSubType(int type)
{
return (!isSuperType(type) && !isType(type));
}