Ares Developer Scrapbook: Difference between revisions
Massively rewrote this to make it easier to read and understand; D, please check if everything is still correct. |
Remark about stack alignment |
||
(6 intermediate revisions by 2 users not shown) | |||
Line 2: | Line 2: | ||
===How do I best figure out stuff like "Is this object a BuildingType?"=== | ===How do I best figure out stuff like "Is this object a BuildingType?"=== | ||
Using the <code>WhatAmI()</code> member. <code>WhatAmI()</code> returns a numeric value that can be compared to a multitude of predefined constants which tell you exactly what kind of object you're dealing with (more technically, it returns an eAbstractType, which is a typedef of an int).<br> | Using the <code>WhatAmI()</code> member function. <code>WhatAmI()</code> returns a numeric value that can be compared to a multitude of predefined constants which tell you exactly what kind of object you're dealing with (more technically, it returns an eAbstractType, which is a typedef of an int).<br> | ||
<code>WhatAmI()</code> is a pure virtual function of AbstractClass, the highest level of YR's class hierarchy; as such, it should exist on all objects you could possibly encounter. | <code>WhatAmI()</code> is a pure virtual function of AbstractClass, the highest level of YR's class hierarchy; as such, it should exist on all objects you could possibly encounter. | ||
Note that AbstractClass contains both a <code>WhatAmI()</code> and a <code>What_Am_I()</code> , both of whom return the exact same values. However, the official policy is to avoid <code>What_Am_I()</code> for consistency's sake. | |||
====Example==== | ====Example==== | ||
Line 143: | Line 145: | ||
==Hierarchy Traversal== | ==Hierarchy Traversal== | ||
===I have a pointer to a given game object/class - how do I get its type class?=== | ===I have a pointer to a given game object/class - how do I get its type class?=== | ||
==== Generic Type Data ==== | |||
If you are looking for a pointer to a generic class like ObjectTypeClass * or TechnoTypeClass *, use the following: | |||
*{{tt|ptr->GetType()}} returns an {{tt|ObjectTypeClass *}} to the type data | *{{tt|ptr->GetType()}} returns an {{tt|ObjectTypeClass *}} to the type data | ||
*On TechnoClass and its derivates, {{tt|ptr->GetTechnoType()}} returns a {{tt|TechnoTypeClass *}} to the same type data | *On TechnoClass and its derivates, {{tt|ptr->GetTechnoType()}} returns a {{tt|TechnoTypeClass *}} to the same type data | ||
Line 148: | Line 152: | ||
reinterpret_cast<TargetTypeClass *>(ptr->GetTechnoType()) | reinterpret_cast<TargetTypeClass *>(ptr->GetTechnoType()) | ||
and that's it. | and that's it. | ||
==== Specific Type Data ==== | |||
If you have a specific class pointer, say, a BuildingClass *, you can just use ->Type to access its type data (BuildingTypeClass *). All four TechnoClass derivates have this member with the appropriate type. | |||
===I have a Techno-/ObjectClass-derived pointer, how do I get the exact (Building|Infantry|Vehicle|Aircraft)Class pointer?=== | ===I have a Techno-/ObjectClass-derived pointer, how do I get the exact (Building|Infantry|Vehicle|Aircraft)Class pointer?=== | ||
If you need to get a final class like Building or Aircraft, use the {{Tt|specific_cast}} template which works similarly to {{Tt|dynamic_cast}}: | |||
if(BuildingClass * pBuilding = specific_cast<BuildingClass *>(ptr)) { | |||
''// if we got here, that means {{tt|ptr}} was a base pointer pointing to a BuildingClass *. | |||
} | |||
If you want to upcast a generic pointer to another generic pointer (i.e. {{Tt|ObjectClass *}} to {{Tt|TechnoClass *}}), you should use {{tt|generic_cast}} in the same vein: | |||
if(ptr | if(TechnoClass *ptr = generic_cast<TechnoClass *>(ptr)) { | ||
''// if we got here, that means even though {{tt|ptr}} was an {{Tt|ObjectClass *}}, it actually points to a {{Tt|TechnoClass *}} descendant. | |||
} | } | ||
The three non-abstract generic pointer types are {{Tt|ObjectClass *}}, {{Tt|TechnoClass *}} and {{Tt|FootClass *}} - you can convert from right to left directly, and you need this template to convert from left to right. | |||
'''Note:''' The intelligent casts like dynamic won't work, as they rely on the RTTI which is likely to be different between the game's and Ares's objects. | |||
Additionally, do '''not''' just (BuildingClass *)ptr instead. Seriously. | |||
===I have a Techno-/ObjectTypeClass-derived pointer, how do I get the exact (Building|Infantry|Vehicle|Aircraft)TypeClass pointer?=== | ===I have a Techno-/ObjectTypeClass-derived pointer, how do I get the exact (Building|Infantry|Vehicle|Aircraft)TypeClass pointer?=== | ||
Line 188: | Line 198: | ||
:''from BulletExt::ExtData::DamageOccupants()'' | :''from BulletExt::ExtData::DamageOccupants()'' | ||
BulletClass* TheBullet = this->AttachedToObject; | BulletClass* TheBullet = this->AttachedToObject; | ||
== That old black Magic == | |||
This section is intended to explain the more complex code elements that have been added in Ares, such as the Valueable/Customizable templates. | |||
=== Template Valueable<T> === | |||
This template is designed to simplify reading values from the INI files. It should be used for properties that have a simple default value ("simple" as in, a constant number, a string literal or NULL, '''not''' the value of some existing flag in [General] or similar). | |||
Once declared as | |||
class SomeClass { | |||
// stuff | |||
Valueable<int> Property; | |||
}; | |||
and initialized as | |||
SomeClass() : /*stuff, */ Property(999) {}; | |||
it can be read from the INI: | |||
INI_EX exINI(pINI); | |||
this->Property.Read(&exINI, "Section", "Flag"); | |||
, where INI_EX is a helper class associated with the CCINIClass * which we are reading from. | |||
This class currently supports reading values that are booleans, integers, floating point numbers, Colors (R, G, B), SHP files (filename without the extension), Mouse Cursors (see the Ares documentation, section on Custom Superweapon Cursors for syntax). Additional data types may be supported in the future if needed. | |||
In addition, this class can parse IDs of objects derived from AbstractTypeClass. However, this requires a different method call: | |||
class SomeClass { | |||
Valueable<WarheadTypeClass *> MagicWH; | |||
}; | |||
// etc | |||
INI_EX exINI(pINI); | |||
this->MagicWH.Parse(&exINI, "Section", "Flag"); | |||
Mind that unlike Westwood's inconsistent parser, this method will '''not''' create new objects if you refer to non-existant ones. Meaning, if your Warhead (for example) is not listed in [Warheads], this parser is not guaranteed to find it properly. This is intentional and meant as a sanity check. | |||
Since this creates a template rather than an object of type {{Tt|T}}, the compiler will possibly have difficulty figuring out what you're trying to do. If you're having problems, a member function {{Tt|Get()}} is provided to get the actual T value stored in the template, and a {{Tt|GetEx()}} is provided to get a pointer to that T value. | |||
=== Boolean context === | |||
If you want to use the value of a non-boolean (numeric) Valueable in a boolean context, use: | |||
if(!!pData->Property) | |||
or | |||
if(pData->Property != 0) | |||
instead of | |||
if(pData->Property) | |||
. The last version usually results in unexpected results thanks to C++ automagic rules. | |||
=== Template Customizable<T> === | |||
This template is an extension of {{Tt|Valueable}}. If you want a property that defaults to a global flag but can be customized (for example, custom Ivan Bomb Warheads), you can use this. | |||
// declaration | |||
class SomeClass { | |||
// stuff | |||
Customizable<WarheadTypeClass *> BombWH; | |||
}; | |||
// constructor | |||
SomeClass() : /*stuff, */ BombWH(&RulesClass::Instance->IvanBombWH) {}; | |||
// INI reading | |||
INI_EX exINI(pINI); | |||
this->BombWH.Parse(&exINI, "Section", "Flag"); | |||
This uses the same methods to read values from the INI files. Notice that this uses a pointer-to-global-flag - if we were to instantiate the template using the actual value, we would get the value of that flag at that moment in time without any way to update it later! That value will most likely be NULL, or the value from the global Rulesmd.ini, as these objects are created very early in the loading process. By instantiating the template with a pointer-to-flag instead, we retain the ability to read that flag's value at any future point in time, as long as the RulesClass::Instance itself doesn't get destroyed (which will happen after the match, but our objects should not need to access the value at that point, and they'll be destroyed not long after that, so there's no problem there). | |||
== Caution == | |||
When you're extracting data from the stack, make sure that the function isn't doing special stack alignment (<tt>and esp, not 7</tt> is a common idiom to align the stack to 8 bytes, which is required when the function uses local double-precision variables). If it is, you cannot extract data using ESP offsets and must use EBP offsets instead. | |||
[[Category:Ares]] |
Latest revision as of 19:33, 10 January 2011
Identifying Objects
How do I best figure out stuff like "Is this object a BuildingType?"
Using the WhatAmI()
member function. WhatAmI()
returns a numeric value that can be compared to a multitude of predefined constants which tell you exactly what kind of object you're dealing with (more technically, it returns an eAbstractType, which is a typedef of an int).
WhatAmI()
is a pure virtual function of AbstractClass, the highest level of YR's class hierarchy; as such, it should exist on all objects you could possibly encounter.
Note that AbstractClass contains both a WhatAmI()
and a What_Am_I()
, both of whom return the exact same values. However, the official policy is to avoid What_Am_I()
for consistency's sake.
Example
ptr->WhatAmI() == abs_BuildingType
Possible Values
The possible values to compare against are defined in YR++'s GeneralDefinitions.h, and, as of the time of this writing, are the following:
abs_None | abs_Overlay | abs_UnitType | abs_FoggedObject |
abs_Unit | abs_OverlayType | abs_VoxelAnim | abs_AlphaShape |
abs_Aircraft | abs_Particle | abs_VoxelAnimType | abs_VeinholeMonster |
abs_AircraftType | abs_ParticleType | abs_Wave | abs_NavyType |
abs_Anim | abs_ParticleSystem | abs_Tag | abs_SpawnManager |
abs_AnimType | abs_ParticleSystemType | abs_TagType | abs_CaptureManager |
abs_Building | abs_Script | abs_Tiberium | abs_Parasite |
abs_BuildingType | abs_ScriptType | abs_Action | abs_Bomb |
abs_Bullet | abs_Side | abs_Event | abs_RadSite |
abs_BulletType | abs_Smudge | abs_WeaponType | abs_Temporal |
abs_Campaign | abs_SmudgeType | abs_WarheadType | abs_Airstrike |
abs_Cell | abs_Special | abs_Waypoint | abs_SlaveManager |
abs_Factory | abs_SuperWeaponType | abs_Abstract | abs_DiskLaser |
abs_House | abs_TaskForce | abs_Tube | |
abs_HouseType | abs_Team | abs_EMPulse | |
abs_Infantry | abs_TeamType | abs_TacticalMap | |
abs_InfantryType | abs_Terrain | abs_Super | |
abs_Isotile | abs_TerrainType | abs_AITrigger | |
abs_IsotileType | abs_Trigger | abs_AITriggerType | |
abs_LightSource | abs_TriggerType | abs_Neuron |
How do I best figure out stuff like "Is this object of this particular BuildingType?" (e.g. "Is this a GADEPT?")
Each individual object blueprint, i.e. any Building-, Vehicle-, etc. Type defined in the INI, is stored as an instance of *TypeClass.
Each individual object of that type (*Class objects) will point back to that *TypeClass as its type.
Therefore, what you do is obtain a pointer to that particular *TypeClass, and compare it against the pointer an object returns for its type.
If both pointers point to the same *TypeClass, the object is of the type you are comparing against.
The easiest approach is to grab the pointer to the comparison *TypeClass right when reading the INI and storing it; assume we have a section like this:
[SomeVehicle] CanDoStuffWith=GADEPT
Now when we parse the INI, we read "GADEPT" and then obtain the pointer to the general GADEPT type object:
void FooExt::ExtData::LoadFromINIFile(FooClass *pThis, CCINIClass *pINI) { // note that the following read is performed from the owning object's INI section - the same way works in Rules, where an object called MTNK has a section [MTNK] // only store if the flag is actually set to something if(pINI->ReadString(pThis->ID, "CanDoStuffWith", "", Ares::readBuffer, Ares::readLength)) { // ::Find iterates the currently declared BuildingTypes array and returns pointer to the one with the ID given as argument this->BuildingTypeICanDoStuffWith = BuildingTypeClass::Find(Ares::readBuffer); } }
So far so good. Now we have a pointer to the BuildingType GADEPT stored in the property BuildingTypeICanDoStuffWith
. And now all we have to do is compare any given object's type against that:
// assuming pSomeVehicleExt is the pointer to SomeExt::ExtData, where your extension variables should be saved // and pSomeBuilding is the building whose type you want to check if(pSomeBuilding->Type == pSomeVehicleExt->BuildingTypeICanDoStuffWith) { // The building's type is the same as the type you got from the INI }
Hierarchy Traversal
I have a pointer to a given game object/class - how do I get its type class?
Generic Type Data
If you are looking for a pointer to a generic class like ObjectTypeClass * or TechnoTypeClass *, use the following:
- ptr->GetType() returns an ObjectTypeClass * to the type data
- On TechnoClass and its derivates, ptr->GetTechnoType() returns a TechnoTypeClass * to the same type data
..both of them are actually pointers to the final derived type of the object, just downcasted (this is a C++ covariant return type limitation). So if you know the exact type you need and are sure that the object's type is appropriate, you can just do
reinterpret_cast<TargetTypeClass *>(ptr->GetTechnoType())
and that's it.
Specific Type Data
If you have a specific class pointer, say, a BuildingClass *, you can just use ->Type to access its type data (BuildingTypeClass *). All four TechnoClass derivates have this member with the appropriate type.
I have a Techno-/ObjectClass-derived pointer, how do I get the exact (Building|Infantry|Vehicle|Aircraft)Class pointer?
If you need to get a final class like Building or Aircraft, use the specific_cast template which works similarly to dynamic_cast:
if(BuildingClass * pBuilding = specific_cast<BuildingClass *>(ptr)) {
// if we got here, that means ptr was a base pointer pointing to a BuildingClass *.
}
If you want to upcast a generic pointer to another generic pointer (i.e. ObjectClass * to TechnoClass *), you should use generic_cast in the same vein:
if(TechnoClass *ptr = generic_cast<TechnoClass *>(ptr)) { // if we got here, that means even though ptr was an ObjectClass *, it actually points to a TechnoClass * descendant. }
The three non-abstract generic pointer types are ObjectClass *, TechnoClass * and FootClass * - you can convert from right to left directly, and you need this template to convert from left to right.
Note: The intelligent casts like dynamic won't work, as they rely on the RTTI which is likely to be different between the game's and Ares's objects. Additionally, do not just (BuildingClass *)ptr instead. Seriously.
I have a Techno-/ObjectTypeClass-derived pointer, how do I get the exact (Building|Infantry|Vehicle|Aircraft)TypeClass pointer?
I have a pointer to a given game object/class - how do I get that class's Ares extension?
Unlike the game's classes which are in a hierarchy, Ares's extension classes are all siblings. That means that a BuildingExt doesn't contain TechnoExt even though it seems like it should.
Therefore, you need the ExtMap. ExtMap should be a static member of all extension classes, and has a .Find member function that allows you to find the correct ExtData block in the correct type for a given object.
- If you need data from TechnoTypeExt, you call TechnoTypeExt::ExtMap.Find(ptr) and receive a TechnoTypeExt * associated with this object.
- If you need data from BuildingTypeExt, you call BuildingTypeExt::ExtMap.Find(ptr) and receive a BuildingTypeExt * associated with this object.
- If you need data from BuildingExt, you call BuildingExt::ExtMap.Find(ptr) and receive a BuildingExt * associated with this object.
Remember to always ensure you're trying to get the correct ExtData, e.g. don't try to get *TypeExt on a non-type or vice versa.
Find might return NULL if there is no data associated with this object, but that shouldn't happen normally, all objects which can have extension data are attached to the ExtMap when they are created/destroyed. So you don't need to check for NULL when you fetch it.
Example
- from BulletExt::ExtData::DamageOccupants()
The function has a bullet (TheBullet), and needs the Ares extension of the bullet's type:
BulletTypeExt::ExtData* TheBulletTypeExt = BulletTypeExt::ExtMap.Find(TheBullet->Type);
...and that's it.
I have a given ExtData block - how do I get the game object it belongs to?
Every Ares extension has an AttachedToObject
pointer that links back to the game object this extension belongs to.
Example
- from BulletExt::ExtData::DamageOccupants()
BulletClass* TheBullet = this->AttachedToObject;
That old black Magic
This section is intended to explain the more complex code elements that have been added in Ares, such as the Valueable/Customizable templates.
Template Valueable<T>
This template is designed to simplify reading values from the INI files. It should be used for properties that have a simple default value ("simple" as in, a constant number, a string literal or NULL, not the value of some existing flag in [General] or similar).
Once declared as
class SomeClass { // stuff Valueable<int> Property; };
and initialized as
SomeClass() : /*stuff, */ Property(999) {};
it can be read from the INI:
INI_EX exINI(pINI); this->Property.Read(&exINI, "Section", "Flag");
, where INI_EX is a helper class associated with the CCINIClass * which we are reading from.
This class currently supports reading values that are booleans, integers, floating point numbers, Colors (R, G, B), SHP files (filename without the extension), Mouse Cursors (see the Ares documentation, section on Custom Superweapon Cursors for syntax). Additional data types may be supported in the future if needed.
In addition, this class can parse IDs of objects derived from AbstractTypeClass. However, this requires a different method call:
class SomeClass { Valueable<WarheadTypeClass *> MagicWH; }; // etc INI_EX exINI(pINI); this->MagicWH.Parse(&exINI, "Section", "Flag");
Mind that unlike Westwood's inconsistent parser, this method will not create new objects if you refer to non-existant ones. Meaning, if your Warhead (for example) is not listed in [Warheads], this parser is not guaranteed to find it properly. This is intentional and meant as a sanity check.
Since this creates a template rather than an object of type T, the compiler will possibly have difficulty figuring out what you're trying to do. If you're having problems, a member function Get() is provided to get the actual T value stored in the template, and a GetEx() is provided to get a pointer to that T value.
Boolean context
If you want to use the value of a non-boolean (numeric) Valueable in a boolean context, use:
if(!!pData->Property)
or
if(pData->Property != 0)
instead of
if(pData->Property)
. The last version usually results in unexpected results thanks to C++ automagic rules.
Template Customizable<T>
This template is an extension of Valueable. If you want a property that defaults to a global flag but can be customized (for example, custom Ivan Bomb Warheads), you can use this.
// declaration class SomeClass { // stuff Customizable<WarheadTypeClass *> BombWH; }; // constructor SomeClass() : /*stuff, */ BombWH(&RulesClass::Instance->IvanBombWH) {}; // INI reading INI_EX exINI(pINI); this->BombWH.Parse(&exINI, "Section", "Flag");
This uses the same methods to read values from the INI files. Notice that this uses a pointer-to-global-flag - if we were to instantiate the template using the actual value, we would get the value of that flag at that moment in time without any way to update it later! That value will most likely be NULL, or the value from the global Rulesmd.ini, as these objects are created very early in the loading process. By instantiating the template with a pointer-to-flag instead, we retain the ability to read that flag's value at any future point in time, as long as the RulesClass::Instance itself doesn't get destroyed (which will happen after the match, but our objects should not need to access the value at that point, and they'll be destroyed not long after that, so there's no problem there).
Caution
When you're extracting data from the stack, make sure that the function isn't doing special stack alignment (and esp, not 7 is a common idiom to align the stack to 8 bytes, which is required when the function uses local double-precision variables). If it is, you cannot extract data using ESP offsets and must use EBP offsets instead.