#include <ruleatom.h>
Inheritance diagram for TRuleAtom:
Public Methods | |
virtual | ~TRuleAtom () |
void | addInfo (const string &identifier, TLinkable *obj) |
void | addInfo (const string &identifier, double data) |
void | addInfo (const string &identifier, int2 data) |
virtual bool | checkDependencies (list< TRuleSystem::tError > *errors) const=0 |
virtual bool | execute ()=0 |
double | getInfoDouble (const string &identifier) const |
int2 | getInfoInt2 (const string &identifier) const |
TLinkable * | getInfoObject (const string &identifier) const |
const TDataPackageList * | getResults () const |
virtual bool | validateInfo ()=0 |
Protected Methods | |
TRuleAtom () | |
Protected Attributes | |
TDataPackageList * | fInfo |
map< string, TLinkable *> | fInfoObj |
TDataPackageList * | fResults |
FIXME: some other stuff is outdated as well.
This class is the base class for all rule atoms. The general idea is, that all game rules are seperated into small pieces and implemented as so called "rule atoms". The good thing is that you can write very different implementations for the same rule name, e.g. you are allowed to provide a "stars!", "basic" and a "StellarLegacy" implementation for the rule "fleet movement". The "basic" rule could be implemented in a way that moving fleets are not stopped by any obstacles (such as minefields), while the "StellarLegacy" rule automatically reduces the fleets speed in a gas cloud etc.
Under certain restrictions it is also possible to mix rulesets! So during the game setup you might want to use rules for economy and population growth which take a lot of things into account, and use very simple rules from the "basic" ruleset for moving and building fleets.
For example we need to move a fleet around, and need the rule which is responsible for that. Well, since code tells more than thousand words ;-)
TRuleAtom* ra = TheGame->getRuleSystem()->fetchRule("fleet_movement"); ra->addInfo("fleet", a_fleet); ra->execute( ra ); delete ra;
The first thing you have to do is fetching the right rule from the rule system (since as noted above there might exist different implementations for a rule). After that you almost always have to add information before the rule can be executed. Which information is needed for executing the rule should be written down in the base class description of the rule, e.g. in this case: TRAFleetMovement. Eventually the rule can be executed: the rule will calculate everything and also modify all participating objects if neccessary (for example applying damage to a fleet, reducing the number of mines in a mine field, setting new fleet pos, ...). The last thing one has to do is deleting the rule atom, and that's it. Also note that a rule can return information about the result by using the TDataPackageList mechanism.
First we should make clear again that we are talking of one rule, but that it is possible to write different implementations of that rules for different rule sets (e.g. basic, stars!, SL). For example the rule "fleet_movement" exists in at least three "flavours": TFleetMovementBasic, TFleetMovementStars and TFleetMovementSL. These three implementations of the rule are all derived from the base class TFleetMovement (which is almost only there for documentation purposes and the method provideMissingInfo).
O.k. now we assume that you want to write an implementation of a rule atom. A real world example looks like:
1 bool TRAFleetEngineFailureStars :: execute() 2 { 3 if ( TRAFleetEngineFailure::provideMissingInfo() == false ) return false; 4 5 fResults->set( "engine_failure", false ); 6 TFleet* fleet = dynamic_cast<TFleet*>( getInfo("fleet") ); 7 if ( fleet->getSpeed() >= 50.0 ) { 8 TRace* creator = fleet->getCreator(); 9 if ( TRandom::getDouble() < creator->getFleetEngineFailure() ) { 10 fResults->set( "engine_failure", true ); 11 } 12 } 13 14 return true; 15 }
The first thing an execute method should always do is calling (line 3) the provideMissingInfo() (see more below). Lets not talk about l. 5 here, which is explained in more detail in the section about "return" values. The interesting thing starts in l.6: as we find out in the documentation there exists a piece of information called "fleet" and this fleet is indeed a TFleet object. This fleet is converted to the correct time and used to find out the current speed of this fleet and also some other things.
Note that we refer to the base class of TFleet and not to a special implementation like TFleetStars. While the latter is allowed we have to introduce dependencies from this special module which is explained below. In this case we don't have any dependencies on special modules.
Actually this is all you need to know to write an implementation of a rule. Always check which information (as in l. 6) you can use by reading the documentation of the base class (in this case TFleetEngineFailure). And vice versa document the information which is needed to execute the rule in the base class.
This method is implemented in the base class and tries to find out additional information if needed. It also checks the existence of all neccessary information and therefore if called by execute prevents execution of the rule atom if not all information is gathered. (That is why execute always should call this method).
FIXME rule of thumb: anything which needs more than one simple call (like fleet->getOwner) can be provide by this method. should also check if obj!=0
Some rule atoms might require a special functionality of a module. For example if we have a (part of a) rule like: "if a race has the LRT (lesser racial trait) X then item Y is not allowed for that race" we certainly need the functionality that the "race" module offers a method like "hasLRT". The general interface TRace does not provide this method, but the "stars!" implementation of TRace does.
Now to ensure that rule atoms and modules fit nicely together each rule atom is "asked" in the beginning if the current set of modules is o.k. or not. All you have to do is overwriting the method "checkDependencies(...)", like in the following example code:
bool TRAItemAccessStars :: checkDependencies( list<TRuleSystem::tError>* errors ) const { if ( rules->getModule(TRuleSystem::RACE) == TRuleSystem::STARS ) { return true; } errors->push_back( TRuleSystem::tError("item_access", TRuleSystem::RACE) ); return false; }
This code tests (for the stars! rule set of the rule atom "item_access") if the race module is from the stars! rule set. Of course if the SL rule set also offers the needed "hasLRT()" functionality one can extent the condition and also allow TRuleSystem::SL. If the dependency check fails the method should add an error message to the list of errors, since this list is needed by a client to give some feedback to the user what went wrong. The information you have to provide is: 1. the name of the rule atom, and 2. with which module the conflict exists. It is explicitly allowed (actually it is desired) that more than one error message is added to the list if more conflicts exist.
Note: if you only use methods which are provided by the general interface of a module (this means for example the interface of TRace, but not the interface of TRaceSL) than you don't need to care about dependencies.
Sometimes it is neccessary for the ruleatom to "return" some results to the caller of the ruleatom. Thereto the ruleatom has a TDataPackageList, which stores the results. The caller of a ruleatom is then able to read this list, but of course he has to do it before deleting the rule atom. Note that only some datatypes are allowed for TDataPackageList--namely in this case these are: bool
, double
, int2
, int4
, string
and TPoint which should be sufficent for most cases.
Adding results and getting them is very easy--on the ruleatom side you write:
fResults->set( "engine_failure", false ); fResults->set( "test_string", "some useless string" );
to store the data (nothing else is needed) and on the other side
ra->getResults()->getBool("engine_failure"); ra->getResults()->getString("test_string")
to get the data (where ra
is of course a TRuleAtom). Note that it is o.k. to change the results on the rule atom side, whenever you want, for example the following code is completly o.k.:
fResults->set( "engine_failure", false ); fResults->set( "engine_failure", true ); // overwrites the old result
You should document the possible results of the rule atom in the base class, e.g. in TRAFleetEngineFailure.
|
Deletes the result list. |
|
Initializes member data values. |
|
Adds a piece of information to the rule atom. It is up to you to ensure that you do not use two times the same identifier with a different data type.
|
|
FIXME |
|
After sending all neccessary information to the rule atom (see addInfo) the rule can be executed.
Reimplemented in TRAItemAccess, TRAItemAccessBasic, TRAItemAccessStars, and TRAItemAccessSL. |
|
Note that this command will
|
|
|
|
|
|
Checks if the provided information is o.k. (e.g. no null links etc.). |
|
This list contains the information added by the creator of the rule atom. |
|
This list contains some pointers. The reason for this it that storing pointers in a data package list is not possible and that storing links is very slow when loading back the links (since only the ID is saved and a load calls TLinkUpdate::requestUpdate which is very slow). |
|
A list of data packages. This list is used to "return" values to the executer of a rule atom. |