Some time ago I had a look at moving creature types to xml again like I've done for vehicles and items. One problem with creature types is that there are a lot more special cases than for items and vehicles, but an idea had been fermenting in my head that I thought would take care of that. It turned out my idea didn't really solve it but I got something that partially worked and I've been thinking I should post it here for comments. So some description of it and thoughts I can remember having about it:
What I have now is an xml file for creature types that contains information on how much money they should get. Elements can look like these:
<creaturetype idname="CREATURE_SCIENTIST_EMINENT">
<money>20-60</money>
</creaturetype>
<creaturetype idname="CREATURE_GUARDDOG">
<money>
<compare_law>
<law>LAW_ANIMALRESEARCH</law>
<comparison>EQUAL</comparison>
<comparison_value>2</comparison_value>
<value>20-40</value>
</compare_law>
<value>0</value>
</money>
</creaturetype>
The amount of money a creature type can have can either be written as just one value or an interval like for the scientist above or conditions can be given allowing different values depending on (so far only) the laws. Dogs in the example above would get between $20 and $40 if animal research law is L+ and nothing otherwise. It's possible to have more than one condition as well as using 'and' and 'or' operators.
In the code this works through a family of class templates that can build conditional trees. The root of the tree is a ConditionalBranch that can hold other conditionals. When asked for its value it will check if the branches test true or false and return the value of the first branch to test true, or its own value if all test false or it has no branches. Other conditionals are ConditionalAnd, ConditionalOr, ConditionalLaw and ConditionalRandom. And and Or can also hold other conditionals as logical operators. Law will test a value against a law in some manner. Random is random.
Some cut down code:
// Class templates for making a tree of conditions that act like a variable
// which value depends on those conditions every time it is checked.
// ConditionalBranch should always be used as root node.
// Base class
template <typename T>
class ConditionalBase
{
public:
ConditionalBase<T>() {}
ConditionalBase<T>(T a) : value_(a) {}
virtual ~ConditionalBase() {}
virtual ConditionalBase<T>* clone() const = 0;
virtual T get_value() const { return value_; }
virtual bool test() const = 0;
protected:
// Function for interpreting the string in an xml file as the the value.
// It has to be specialized if template parameter datatype can not be
// assigned from a string.
virtual void interpret_value(string v)
{
value_ = v;
}
T value_;
};
//Specializations
template < >
inline
void ConditionalBase<int>::interpret_value(string v)
{
value_ = atoi(v.c_str());
}
template < >
inline
void ConditionalBase<random_integer>::interpret_value(string v)
{
value_.interval(v);
}
template < >
inline
void ConditionalBase<bool>::interpret_value(string v)
{
int b = stringtobool(v);
if (b == 1)
value_ = true;
else if (b == 0)
value_ = false;
//else
}
// Creates a conditional object from an xml element.
template <typename T>
ConditionalBase<T>* make_conditional(string const& xmlstring);
// ConditionalBranch can hold branches to create a structure like if-else if-else.
// It will return the value of the first branch to test true when asked for its
// value with the get_value function. If all branches test false or it has no
// branches then it will return its own value.
template <typename T>
class ConditionalBranch : public ConditionalBase<T>
{
public:
ConditionalBranch<T>(T a) : ConditionalBase<T>(a) {}
// Constructor to create an object from xml.
ConditionalBranch<T>(string xmlstring) { [...] }
~ConditionalBranch() { [...] }
ConditionalBranch<T>(ConditionalBranch<T> const& c) { [...] }
ConditionalBranch<T>& operator=(ConditionalBranch<T> const& c) { [...] }
virtual ConditionalBranch<T>* clone() const { [...] }
virtual T get_value() const
{
for (typename std::vector<ConditionalBase<T>*>::const_iterator it = branches_.begin();
it != branches_.end();
++it)
{
if ((*it)->test())
{
return (*it)->get_value();
}
}
return this->value_;
}
virtual bool test() const
{
bool r = false;
for (typename std::vector<ConditionalBase<T>*>::const_iterator it = branches_.begin();
it != branches_.end();
++it)
{
r = r || (*it)->test();
}
return r;
}
protected:
std::vector<ConditionalBase<T>*> branches_;
void copy(ConditionalBranch<T> const& c) { [...] }
};
// CondtionalAnd can hold other conditionals as operands for an and statement.
// It will test true if all its operands test true. If asked for its value it
// will always return its own and those of its operands are never used.
template <typename T>
class ConditionalAnd : public ConditionalBase<T>
{
public:
ConditionalAnd<T>(string xmlstring) { [...] }
~ConditionalAnd() { [...] }
ConditionalAnd<T>(ConditionalAnd<T> const& c) { [...] }
ConditionalAnd<T>& operator=(ConditionalAnd<T> const& c) { [...] }
virtual ConditionalAnd<T>* clone() const { [...] }
virtual bool test() const
{
bool t = true;
for (typename std::vector<ConditionalBase<T>*>::const_iterator it = operands_.begin();
it != operands_.end();
++it)
{
t = t && (*it)->test();
}
return t;
}
protected:
std::vector<ConditionalBase<T>*> operands_;
void copy(ConditionalAnd<T> const& c) { [...] }
};
// CondtionalOr can hold other conditionals as operands for an or statement.
// It will test true if any of its operands test true. If asked for its value it
// will always return its own and those of its operands are never used.
template <typename T>
class ConditionalOr : public ConditionalBase<T>
{
public:
ConditionalOr<T>(string xmlstring) { [...] }
~ConditionalOr() { [...] }
ConditionalOr<T>(ConditionalOr<T> const& c) { [...] }
ConditionalOr<T>& operator=(ConditionalOr<T> const& c) { [...] }
virtual ConditionalOr<T>* clone() const { [...] }
virtual bool test() const
{
bool t = false;
for (typename std::vector<ConditionalBase<T>*>::const_iterator it = operands_.begin();
it != operands_.end();
++it)
{
t = t || (*it)->test();
}
return t;
}
protected:
std::vector<ConditionalBase<T>*> operands_;
void copy(ConditionalOr<T> const& c) { [...] }
};
enum ComparisonEnum
{
COMPARISON_EQUAL,
COMPARISON_LESS,
COMPARISON_GREATER,
COMPARISON_LESS_OR_EQUAL,
COMPARISON_GREATER_OR_EQUAL,
COMPARISON_NOT_EQUAL
};
// Base class for conditionals comparing a value with a global value when tested.
template <typename T>
class ConditionalCompare : public ConditionalBase<T>
{
public:
ConditionalCompare<T>(string xmlstring) { [...] }
virtual short get_property_value() const = 0;
virtual bool test() const
{
bool r = false;
switch (comparison_)
{
case COMPARISON_EQUAL: r = (get_property_value() == compared_value_); break;
case COMPARISON_LESS: r = (get_property_value() < compared_value_); break;
case COMPARISON_GREATER: r = (get_property_value() > compared_value_); break;
case COMPARISON_LESS_OR_EQUAL: r = (get_property_value() <= compared_value_); break;
case COMPARISON_GREATER_OR_EQUAL: r = (get_property_value() >= compared_value_); break;
case COMPARISON_NOT_EQUAL: r = (get_property_value() != compared_value_); break;
}
return r;
}
protected:
short property_;
short comparison_;
short compared_value_;
};
// CondtionalLaw compares a value with a law.
template <typename T>
class ConditionalLaw : public ConditionalCompare<T>
{
public:
ConditionalLaw<T>(string xmlstring)
: ConditionalCompare<T>(xmlstring) { [...] }
virtual ConditionalLaw<T>* clone() const { [...] }
short get_property_value() const
{
return get_law(this->property_);
}
protected:
};
// Creates a conditional object from an xml element.
template <typename T>
ConditionalBase<T>* make_conditional(string const& xmlstring)
{
ConditionalBase<T>* r = NULL;
CMarkup xml;
xml.SetDoc(xmlstring);
xml.FindElem();
string tag = xml.GetTagName();
if (tag == "branch")
r = new ConditionalBranch<T>(xmlstring);
else if (tag == "and")
r = new ConditionalAnd<T>(xmlstring);
else if (tag == "or")
r = new ConditionalOr<T>(xmlstring);
else if (tag == "random")
r = new ConditionalRandom<T>(xmlstring);
else if (tag == "compare_law")
r = new ConditionalLaw<T>(xmlstring);
//else
return r;
}
Full changes:
http://www.2shared.com/file/_8_6_WMp/lcs.htmlThe problem that I didn't see this approach solving is when a creature's properties depend on its other properties. To do that the conditionals must have access to the creature's data and when a creature is created the properties would have to have their values assigned in the right order.
Although because some time has passed since I wrote this I do have a new idea now on how to continue. But it would involve adding a template parameter to the conditionals for the creature, some kind of dynamic data type and could perhaps suffer circular dependancy so it's not a sure thing. Also though because some time has passed, I might not be remembering the real problem.
One bad thing with the current approach is that if two properties depend on the same conditions, then you have to write the same conditional tree twice. One time for each property. It gets more complicated if two properties depend on one, and the same, random outcome. Then you have to first have one of the properties depend on the random outcome and the other depend on what value the first ended up with.
I also think the structure of the conditional trees look a little odd compared to equivalent if-else structures. Eg.
if (a && (b || c)) //a,b,c,d are for the example not specified conditions.
v = 1;
else if (c || d)
v = 2;
else
v = 3;
will look something (pseudo-)like
<v>
<and>
<a>
[...]
</a>
<or>
<b>
[...]
</b>
<c>
[...]
</c>
</or>
<value>1</value>
</and>
<or>
<c>
[...]
</c>
<d>
[...]
</d>
<value>2</value>
</or>
<value>3</value>
</v>
Maybe it'd look better if and and or were called conjunction and disjunction instead.
Another thought I've had when working on this is that it might be skirting so close to scripting languages that it'd be better to just use one of those instead. I have looked a little at luabind and boost::python but I haven't really been able to tell how easy it would be to use them instead.
As I've been writing this, I have been considering to just have one comparison class template instead of one each for laws, sites and whatmore, and put the differences between laws, sites and other things into the operands of the comparison. It might also work better for creature properties.
If you look through the code you may also notice I added a little piece of logging. My thought there was that it would be nice if the user was alerted when the game couldn't make sense of something in an xml file. It was just supposed to be something small and simple but then I got stuck trying to make it the best thing ever so I abandoned it to focus on the conditionals that was the real thing I intended to work on.