- Joined
- Nov 4, 2007
- Messages
- 337
This is an interface between the object editor and the trigger editor. By using the tool GMSI it provides a nice functionality: It can read out object editor data from units.
The nice thing is that this is flexible and some nice ideas are behind it.
The static list
The system is able to return structs with the function GetUnitProperty(u) that contain many useful object editor data about the units, like damage or armor. But if all the data are stored into structs directly, it would be extremely bad for your memory. So this has got a nice trick:
All information the GMSI code reads out about units are stored in a big function, called the static list. If a user wants to get the UnitProperty struct of a unit the first time, a huge function is called (the static list) that runs a lot of ifs which check the unit type of the unit. At a certain point, where the unit type is 'found' the static list returns a struct using the .create method. Then the struct is stored in a table with the unit id as key.
The next time a user wants to get the information about the unit, the system reacts very quickly - because it does not have to run all the 1700 ifs. And the memory is kept low, making this system actually 'possible'.
The Flexibility
Another nice thing about this system is the flexibilty:
It would be crappy to store all the data of the units in a struct, because it would use up too much memory and runtime and most fields are probably never gonna be used, but it would be a waste to just make some fields undetectable.
So the solution I got should probably make everybody happy (Thanks for Kruemel for the idea of making this dynamic):
Users can add their own fields directly into the struct.
Each time GMSI compiles, it checks the members of the struct and generates the create method from them and fills them with the correct data. But how shall the system know the rawcode of the fields? You simply add a comment behind the member that equals to the rawcode name.
Example:
The accessable member will be named attackRange and equal to the rangeN1 field of the unit.
The safety
Actually this is pretty safe. The problem is that users get a lot of access to the system. Idiots can break it easily by doing things like adding commands to the fields where no commands should be (the member field for example), formatting their own members incorrectly or storing them by the wrong type (store a string in a real variable).
However, I dealt with some flaws:
I hope there aren't that many idiots.
The other 'features'
Requirements
GMSI, Table
Basic Members
GMSI Code
vJass Code
Why the name 'UnitDataInterface'?
Well, UnitProperty was already reserved.
ObjectEditorData wouldn't fit so much, because users could think it's for all kinds of objects (like abilities)
Actually this is the interface between jass and the object editor.
Theoretical usages
The nice thing is that this is flexible and some nice ideas are behind it.
The static list
The system is able to return structs with the function GetUnitProperty(u) that contain many useful object editor data about the units, like damage or armor. But if all the data are stored into structs directly, it would be extremely bad for your memory. So this has got a nice trick:
All information the GMSI code reads out about units are stored in a big function, called the static list. If a user wants to get the UnitProperty struct of a unit the first time, a huge function is called (the static list) that runs a lot of ifs which check the unit type of the unit. At a certain point, where the unit type is 'found' the static list returns a struct using the .create method. Then the struct is stored in a table with the unit id as key.
The next time a user wants to get the information about the unit, the system reacts very quickly - because it does not have to run all the 1700 ifs. And the memory is kept low, making this system actually 'possible'.
The Flexibility
Another nice thing about this system is the flexibilty:
It would be crappy to store all the data of the units in a struct, because it would use up too much memory and runtime and most fields are probably never gonna be used, but it would be a waste to just make some fields undetectable.
So the solution I got should probably make everybody happy (Thanks for Kruemel for the idea of making this dynamic):
Users can add their own fields directly into the struct.
Each time GMSI compiles, it checks the members of the struct and generates the create method from them and fills them with the correct data. But how shall the system know the rawcode of the fields? You simply add a comment behind the member that equals to the rawcode name.
Example:
JASS:
integer attackRange // rangeN1
The safety
Actually this is pretty safe. The problem is that users get a lot of access to the system. Idiots can break it easily by doing things like adding commands to the fields where no commands should be (the member field for example), formatting their own members incorrectly or storing them by the wrong type (store a string in a real variable).
However, I dealt with some flaws:
- The string \ is replaced by \\ making this actually compile
- A field isn't just empty. There is either null or "" in it, if the Object Editor field is empty
- Tabs can be placed in the system (" ") to have a nice formatting
- It uses a smart way of detecting if sth. is a string in warcraft or not. So it places "" correctly.
I hope there aren't that many idiots.
The other 'features'
- This got a very nice speed bonus because of nice string usage. Reading out big strings was reduced drastically and smartly
- You can also select the option 'Only Custom Ids' at the GMSI Script to improve the compile time.
- This is the only system of it's kind
- The system is dynamic: You can use any OE field you like
- It doesn't use up a lot of memory and it doesn't use up a lot of performance either
- This can't be used multiple times on the same file. It will delete all the old data before adding new data.
Requirements
GMSI, Table
Basic Members
JASS:
real dmgplus // dmgplus1
real dice // dice1
real sides // sides1
real attackRate // cool1
integer attackRange // rangeN1
string dat // atkType1
real armor // def
real lifeRegeneration // regenHP
real manaRegeneration // regenMana
real aoe // Farea1
string targets1 // targs1
string targets2 // targs2
real castPoint // castpt
real backswing // backSw1
real damagePoint // dmgpt1
integer buildTime // bldtm
real collisionSize // collision
string model // file
string icon // Art
string missileModel // Missileart
integer sightRange // sight
integer sightRangeNight // nsight
string sourceRace // race
real bounty // bountyplus
string moveType // movetp
real missileSpeed // Missilespeed
string attribute // Primary
GMSI Code
Code:
/**
@author Mr.Malte
*/
include ("string.gsl");
include ("objects.gsl");
include ("dialog.gsl");
float CalcAverage(int basis, int dice, int sides)
{
return ((basis*2+sides+(sides*dice))/2);
}
// " real Bla // HELO" ==> "real Bla" or "Bla"
string getMemberName(string member, bool cutType)
{
int add = 0;
if (strpos(member,"real ") != -1){
if (cutType) add = 5;
return substr(member,strpos(member,"real ")+add,strpos(member," //"));}
else if (strpos(member,"integer ") != -1){
if (cutType) add = 8;
return substr(member,strpos(member,"integer ")+add,strpos(member," //"));}
else if (strpos(member,"string ") != -1){
if (cutType) add = 7;
return substr(member,strpos(member,"string ")+add,strpos(member," //"));}
return "FAIL";
}
string getMemberField(string member)
{
return( substr(member,strpos(member,"// ")+3,-1));
}
string cut(string which, string start, string end)
{
return substr(which,0,strpos(which,start)+strlen(start))+substr(which,strpos(which,end),-1);
}
array options = array();
bool onlyCustomIds = confirmDialog("ObjectData","Do you only need data from custom units?",BUTTONS_YES_NO);
string Path = (@args[0] != null ? @args[0] : fileDialog("Choose your map",@inputPath,".w3x"));
Map map = loadMap(Path);
string s;
int i = 0;
string code = map.script.headerCode;
string tempString;
Trigger ObjectData;
echo("\nFinding ObjectData library...\n");
for(i : map.script.triggers){
if (map.script.triggers[i].name == "ObjectData")
ObjectData = map.script.triggers[i];
break;
}
echo("Flushing old Data...\n");
ObjectData.code = cut(ObjectData.code,"// CREATE","// ENDCREATE");
ObjectData.code = cut(ObjectData.code,"// SYNC","// ENDSYNC");
if (strpos(code,"library UnitStaticList") != -1){
if (strpos(code,"endlibrary // X47K589687UHGFV8") == -1)
fail("\n ObjectData library has got wrong signature.\n");
code = substr(code,0,strpos(code,"library UnitStaticList")) + substr(code,strpos(code,"endlibrary // X47K589687UHGFV8")+strlen("endlibrary // X47K589687UHGFV8"),-1);
map.script.headerCode = code;
}
echo("Compiling desired Data...\n");
int createIndex = strpos(ObjectData.code,"// CREATE")+9;
string createMethodCode = " static method create takes integer id, ";
string syncCode = "";
array members = splitstr(substr(ObjectData.code,strpos(ObjectData.code,"// MEMBERS")+10,strpos(ObjectData.code,"// ENDMEMBERS")),"\n");
int fieldCounter = 0;
array fields = array();
string member;
for(i : members){
// Getting the members unformatted.
members[i] = replaceAll(substr(members[i],0,strlen(members[i])-1)," ","");
if (strlen(members[i]) > 5)
{
fieldCounter = fieldCounter + 1;
fields[fieldCounter] = getMemberField(members[i]);
member = getMemberName(members[i],true);
if (member != "FAIL")
{
syncCode = syncCode + " set up."+member+" = "+member+"\n";
member = getMemberName(members[i],false);
createMethodCode = createMethodCode + member + ", ";
}
}
}
createMethodCode = substr(createMethodCode,0,strlen(createMethodCode)-2) + " returns UnitProperty";
ObjectData.code = substr(ObjectData.code,0,createIndex) + "\n" + createMethodCode+"\n" + substr(ObjectData.code,createIndex,-1);
int syncIndex = strpos(ObjectData.code,"// SYNC")+7;
ObjectData.code = substr(ObjectData.code,0,syncIndex) + "\n" + syncCode+"\n" + substr(ObjectData.code,syncIndex,-1);
i = 0;
int fieldIndex;
s = "";
code = "";
echo("\nInstalling Static List...\n\n");
code = code + "\nlibrary UnitStaticList";
code = code + "\nfunction GetUnitPropertyStatic takes integer id returns UnitProperty";
code = code + "\n" + "local UnitProperty up";
code = code + "\n" + "if id == 0 then";
code = code + "\n" + "return 0";
for(s: map.objects){
Wc3obj obj = map.objects[s];
if(!(obj instanceof Unit)) continue;
if(obj.race == "") continue; // There is exactly one unit that has no race.
if(isIdPredefined(s) == true && onlyCustomIds == true) continue;
code = code + "\n elseif id == '"+s+"' then \n return UnitProperty.create(id";
fieldIndex = 0;
for(fieldIndex:fields)
{
if ((string)obj[fields[fieldIndex]] == "")
code = code + ", null";
else if (toUpperCase(obj[fields[fieldIndex]]) != (string)obj[fields[fieldIndex]]
|| toLowerCase(obj[fields[fieldIndex]]) != (string)obj[fields[fieldIndex]])
code = code + ", \"" + replaceAll(obj[fields[fieldIndex]],"\\\\","\\\\\\\\") + "\"";
else
code = code + ", "+obj[fields[fieldIndex]];
}
code = code + ")";
// Warcraft can't handle too many ifs. It was hard to find the bug, but I could fix it.
i = i + 1;
if (i%50 == 0)
{
code = code + "\nendif";
code = code + "\n\nif 0 == 1 then\nreturn 0";
echo("=");
map.script.headerCode = map.script.headerCode + code;
code = "";
}
}
echo("\nFinishing library...");
code = code + "\n" + "endif";
code = code + "\n" + "return -1";
code = code + "\n" + "endfunction";
code = code + "\n" + "endlibrary";
echo("\nSetting library signature.");
code = code + " // X47K589687UHGFV8";
map.script.headerCode = map.script.headerCode + code;
saveMap(map,"output/UDIOutput.w3x");
vJass Code
JASS:
library UnitDataInterface initializer init requires Table, UnitStaticList
//===========================================================================
// Credits:
// =======
// I'd really like to thank gekko for his script language GSL
// and his programm GMSI. Without that stuff this system would not have been
// possible.
//
// The same thing goes for vJass.
//
// This consumed some nasty hours for finding the sticks warcraft throws between my legs.
// Some wc3 bugs are really bad...
// Also it was a lot of work to make it accurate, flexible and save against idiots.
//
//===========================================================================
// Information:
//==============
//
// This is the only jass system in existance that can return unit data from the
// object editor like damage, sight range and attackrate.
// This can also be used in addition with the library BonusMod.
// It works by using gekkos script language GSL - i've written a script in that
// language that reads all the object editor data out and stores them in UnitProperty
// structs.
//
// The UnitProperty structs are dynamic! So if you remove fields from it, because
// you don't need them, it will not break the system. You can also
// add new fields to be read out. Read 'Adding new fields' in order to do that.
//
//===========================================================================
// Functions:
// ==========
//
// GetUnitProperty(unit) : Returns a struct that contains all important OE data about the unit.
// UnitProperty[unit] : The same thing with a different syntax.
//
//===========================================================================
// Implementation:
// ===============
//
// 1. Make sure you have the latest JassHelper or JassNewGen:
// [url]http://www.wc3c.net/showthread.php?t=90999[/url]
//
// 2. Install GMSI like described here:
// [url]http://www.wc3c.net/showthread.php?t=106485[/url]
//
// 3. Make sure you installed the vJass system 'Table':
// [url]http://www.wc3c.net/showthread.php?t=101246[/url]
//
// 4. Convert a new trigger to custom text and insert this whole code.
// Then save the map.
//
// 5. Download the UnitDataInterfaceGMSI file from this link:
// ===
//
// 6. Start GMSI and choose the UnitDataInterface GMSI file from where you copied it.
// Press two times on it and select your map with a double click.
//
// 7. The map will be saved in the folder output with the name 'UDIOutput.w3x'
//
//===========================================================================
// Adding new fields:
// =================
//
// If you want to read out additional data, you can do these steps to do it:
//
// 1. Add the member name to the struct.
//
// 2. Go into the object editor
//
// 3. Select the field you want to read out
//
// 4. Press 'Strg + D'
//
// 5. Now the names of all fields have changed.
// Write '// ' + the name of the field behind the member name.
//
// Example: startMana // mana0
//===========================================================================
// GMSI Script:
// ===========
//
// Can be found here:
// [url]http://peeeq.de/code.php?id=2881[/url]
//
// It is neccesary!
//
//===========================================================================
// FAQ:
// ===
//
// 1. If the systems stores so many object editor data in a struct for each unit type.
// Wont that use huge amounts of memory?
// Answer: No it wont. The GMSI script generates a function that contains a list with all unit
// types. This list is static. I need many ifs to find out the correct unit and create
// a correct UnitProperty struct, but if you used a UnitProperty struct for a unit once,
// it is saved in a Table.
//
//===========================================================================
globals
private Table Data
endglobals
private function GetUnitPropertyPrivate takes integer id returns UnitProperty
local UnitProperty up = Data[id]
if up != 0 then
return up
else
set up = GetUnitPropertyStatic(id)
if up == 0 then
call BJDebugMsg("UnitDataInterface: Choosed invalid id.")
endif
set Data[id] = up
return up
endif
endfunction
function GetUnitProperty takes unit who returns UnitProperty
return GetUnitPropertyPrivate(GetUnitTypeId(who))
endfunction
function StringContains takes string u, string targetType returns boolean
local integer i = 0
local integer length = StringLength(targetType)
loop
exitwhen i > StringLength(u)
if SubString(u,i,i+length) == targetType then
return true
endif
set i = i + 1
endloop
return false
endfunction
function UnitAttacksType takes unit u, string targetType returns boolean
local UnitProperty up = GetUnitProperty(u)
return StringContains(up.targets1+up.targets2,targetType)
endfunction
// I hate blizzard for some reasons..
function ConvertAttribute takes string s returns integer
if s == "INT" then
return bj_HEROSTAT_INT
elseif s == "AGI" then
return bj_HEROSTAT_AGI
elseif s == "STR" then
return bj_HEROSTAT_STR
endif
debug call BJDebugMsg("UnitDataInterface: Invalid string in 'ConvertAttribute'")
return 0
endfunction
// Returns the average damage of a unit.
// If a user has got bonusMod in his map it has to be changed.
function GetUnitDamage takes unit u returns real
local UnitProperty up = GetUnitProperty(u)
if IsUnitType(u,UNIT_TYPE_HERO) then
return 0.5*(up.minDamage + up.maxDamage)+GetHeroStatBJ(ConvertAttribute(up.attribute),u,true)
else
return 0.5*(up.minDamage + up.maxDamage)
endif
endfunction
function GetUnitAttacktype takes unit u returns attacktype
local UnitProperty up = GetUnitProperty(u)
if up.dat == "normal" then
return ATTACK_TYPE_NORMAL
elseif up.dat == "pierce" then
return ATTACK_TYPE_PIERCE
elseif up.dat == "hero" then
return ATTACK_TYPE_HERO
elseif up.dat == "siege" then
return ATTACK_TYPE_SIEGE
elseif up.dat == "magic" then
return ATTACK_TYPE_MAGIC
elseif up.dat == "spells" then
return ATTACK_TYPE_MAGIC
elseif up.dat == "chaos" then
return ATTACK_TYPE_CHAOS
elseif up.dat == "unknown" then
return null
endif
debug call BJDebugMsg("UnitDataInterface: Can't get the attacktype of "+GetUnitName(u)+".")
return null
endfunction
struct UnitProperty
static method operator[] takes unit key returns UnitProperty
return GetUnitPropertyPrivate(GetUnitTypeId(key))
endmethod
// These members shall not be registered by the system: They're special.
// We place them outside of the system.
real minDamage
real maxDamage
// DO NOT PLACE ANYTHING ELSE (LIKE COMMENTS) BUT MEMBERS IN THE MEMBERS SECTION!
// MEMBERS
integer dmgplus // dmgplus1
integer dice // dice1
integer sides // sides1
real attackRate // cool1
integer attackRange // rangeN1
string dat // atkType1
integer armor // def
real lifeRegeneration // regenHP
real manaRegeneration // regenMana
real aoe // Farea1
string targets1 // targs1
string targets2 // targs2
real castPoint // castpt
real backswing // backSw1
real damagePoint // dmgpt1
integer buildTime // bldtm
real collisionSize // collision
string model // file
string icon // Art
string missileModel // Missileart
integer sightRange // sight
integer sightRangeNight // nsight
string sourceRace // race
integer bounty // bountyplus
string moveType // movetp
real missileSpeed // Missilespeed
string attribute // Primary
// ENDMEMBERS
// CREATE
static method create takes integer id returns UnitProperty
// ENDCREATE
local UnitProperty up = UnitProperty.allocate()
// SYNC
// ENDSYNC
set up.minDamage = up.dmgplus+up.dice
set up.maxDamage = up.dmgplus+(up.dice*up.sides)
set up.dice = 0
set up.sides = 0
set up.dmgplus = 0
set up.model = up.model + ".mdl"
set Data[id] = up
return up
endmethod
endstruct
private function init takes nothing returns nothing
set Data = Table.create()
endfunction
endlibrary
Why the name 'UnitDataInterface'?
Well, UnitProperty was already reserved.
ObjectEditorData wouldn't fit so much, because users could think it's for all kinds of objects (like abilities)
Actually this is the interface between jass and the object editor.
Theoretical usages
- You can make a trigger that stops ranged attacks and imitates them. The missiles are triggered and you can access all missiles then.
Attachments
Last edited: