Little Big COB improvements
Moderator: Moderators
Little Big COB improvements
As far as I'm aware it is impossible/unfeasible to alter Scriptor's compiler to accept new commands.
Function 1: Callback on unit creation
Whenever a unit is created, other units will have their UnitCreated(id) function called. A unit that wants to receive this call has to set UNIT_CREATE_CALLBACK to 1 to avoid loads of unnecessary script calls (best handled internally by making this add a unit to a linked list that gets waded through on every unit creation).
Purpose: This would enable units to react to the existence of other units, e.g. turrets to a fire control system, without having to regularly check ALL units for the existence of a specific unit among them. It basically reduces the script load a lot and allows even basic ressource structures to watch for things like that.
Function 2: UnitType constant
Each unit will have a UnitType tag. This tag defaults to 0 and can be used in any way the modder chooses, it's constant and can be gotten through get UNIT_TYPE(unitId).
Purpose: This would allow easier identification of units in script, this tag can be used either as a sequential ID to know the exact type or a flagarray to mark certain attributes (by using bit operations), depending on how the modder wants to use it.
Function 3: Public unit variables
These variables would be accessed through get/set UNIT_VAR(unitId) and would be used to communicate values from one unit to another. The last time I proposed this it was deemed unfeasible because get only has one parameter in addition to the constant, a solution would be to encode the number of the variable into the constant (e.g. set 256 would access the first, set 257 the second, ...). I don't know if set takes any parameters at all so these might need to be read-only for units other than the owner but even that's a big improvement over the current situation. Similar variables could exist per-player and globally.
Additionally a tag could be added that can make some of these variables displayed in the GUI.
Purpose: This would be used for inter-unit communication, e.g. having the refinery retrieve the amount of "ressources" stored in the harvester and generating ressources while decreasing that count to unload the harvester.
Function 4: List of nearby units
This would add the set-only variable UNITS_IN_RADIUS and the get-only variable GET_NEXT_UIR. Setting the first to a value would make the engine compile a list of all units in a radius equal to the value set (the value isn't actually stored, it's just a wrapper for a command). Getting the second would return one unitID at a time from that list (could be a stack for this purpose, actually), -1 (or something) when the list is emptied. In essence it iterates through the IDs of all units that were nearby during the set UNITS_IN_RADIUS command.
Optionally a set UNIT_FILTER and set UNIT_TYPE_ONLY could be added that would only return units whose UnitType matches one bit (for the former, if & returns !=0)/ matches the whole number (for the latter) set.
If necessary the list could be valid only in the frame it was created in though of course allowing it to persist for longer would be beneficial.
Purpose: This would allow units to react to the proximity of others while being able to use the engine's faster code for determining what's in range. E.g. for mobile cloak generators all units that can be cloaked by it would have to check regularly if a generator is nearby, obviously this would create a lot of load if we had to iterate through the whole unit list and compare distances all the time.
I believe all of the above would need only small amounts of code and could be done before the next version easily while greatly increasing the power of the script and allowing a large number of new gameplay mechanics.
Function 1: Callback on unit creation
Whenever a unit is created, other units will have their UnitCreated(id) function called. A unit that wants to receive this call has to set UNIT_CREATE_CALLBACK to 1 to avoid loads of unnecessary script calls (best handled internally by making this add a unit to a linked list that gets waded through on every unit creation).
Purpose: This would enable units to react to the existence of other units, e.g. turrets to a fire control system, without having to regularly check ALL units for the existence of a specific unit among them. It basically reduces the script load a lot and allows even basic ressource structures to watch for things like that.
Function 2: UnitType constant
Each unit will have a UnitType tag. This tag defaults to 0 and can be used in any way the modder chooses, it's constant and can be gotten through get UNIT_TYPE(unitId).
Purpose: This would allow easier identification of units in script, this tag can be used either as a sequential ID to know the exact type or a flagarray to mark certain attributes (by using bit operations), depending on how the modder wants to use it.
Function 3: Public unit variables
These variables would be accessed through get/set UNIT_VAR(unitId) and would be used to communicate values from one unit to another. The last time I proposed this it was deemed unfeasible because get only has one parameter in addition to the constant, a solution would be to encode the number of the variable into the constant (e.g. set 256 would access the first, set 257 the second, ...). I don't know if set takes any parameters at all so these might need to be read-only for units other than the owner but even that's a big improvement over the current situation. Similar variables could exist per-player and globally.
Additionally a tag could be added that can make some of these variables displayed in the GUI.
Purpose: This would be used for inter-unit communication, e.g. having the refinery retrieve the amount of "ressources" stored in the harvester and generating ressources while decreasing that count to unload the harvester.
Function 4: List of nearby units
This would add the set-only variable UNITS_IN_RADIUS and the get-only variable GET_NEXT_UIR. Setting the first to a value would make the engine compile a list of all units in a radius equal to the value set (the value isn't actually stored, it's just a wrapper for a command). Getting the second would return one unitID at a time from that list (could be a stack for this purpose, actually), -1 (or something) when the list is emptied. In essence it iterates through the IDs of all units that were nearby during the set UNITS_IN_RADIUS command.
Optionally a set UNIT_FILTER and set UNIT_TYPE_ONLY could be added that would only return units whose UnitType matches one bit (for the former, if & returns !=0)/ matches the whole number (for the latter) set.
If necessary the list could be valid only in the frame it was created in though of course allowing it to persist for longer would be beneficial.
Purpose: This would allow units to react to the proximity of others while being able to use the engine's faster code for determining what's in range. E.g. for mobile cloak generators all units that can be cloaked by it would have to check regularly if a generator is nearby, obviously this would create a lot of load if we had to iterate through the whole unit list and compare distances all the time.
I believe all of the above would need only small amounts of code and could be done before the next version easily while greatly increasing the power of the script and allowing a large number of new gameplay mechanics.
Re: Little Big COB improvements
We'll also need a callback on unit destruction (and ownership change) to reliably track the existence of particular unit type (fire control system in your example).KDR_11k wrote:Function 1: Callback on unit creation
...
Err, you don't need all those new functions.
Function 1:
To react to existence of other units, use a "for" on all units ID. Imo it's much less ressource intensive and much more reliable to do that whenever you need to react to another unit existence, than to inform every single unit every time a new unit is created or killed. Plus it's easier to count at a given time, than to try to maintain the number of each unit kind from the + and -.
Function 2:
get UNIT_HEIGHT(..) Unless you reuse your models too much, this gives you a constant allowing to identify each unit in script. I used it many times already. Works very well. Besides the commander and decoy, I've yet to find two units with the same height.
Function 3:
Ok, for this one I agree, inter-unit is kinda difficult without such public variables. Well, I did manage to script inter unit communication nevertheless, but it involved grabbing other unit which isn't always convenient. So this one is the only of your suggestion I wouldn't be adverse to.
Function 4:
#define ARMFIDO 1660109
var u,n;
n=0;
for(u=1;u<=get MAX_ID;++u)
{
if(get XZ_HYPOT(get PIECE_XZ(base) - get UNIT_XZ(u))<= [50])// Find units in range
{
if(get UNIT_HEIGHT(u)==ARMFIDO)// Find units of type Fido
{
++n;
}
}
}
// Now n contains the number of fido around!
No really, why add all that clutter and all those new confusing functions when what you're asking for is already possible. Stop asking more and more complex hardcoded features, and learn to use what's already available! If you are unable to use something as simple as that little script to count unit around, then you sure won't be able to use something as complex as the engine returning values from a list, one by one, with a -1 to mark the last. I mean, geez, almost no one use the current script feature that have been existing since 8 years ago, and you expect people to understand that list thing? You're completly deluded.
Function 1:
To react to existence of other units, use a "for" on all units ID. Imo it's much less ressource intensive and much more reliable to do that whenever you need to react to another unit existence, than to inform every single unit every time a new unit is created or killed. Plus it's easier to count at a given time, than to try to maintain the number of each unit kind from the + and -.
Function 2:
get UNIT_HEIGHT(..) Unless you reuse your models too much, this gives you a constant allowing to identify each unit in script. I used it many times already. Works very well. Besides the commander and decoy, I've yet to find two units with the same height.
Function 3:
Ok, for this one I agree, inter-unit is kinda difficult without such public variables. Well, I did manage to script inter unit communication nevertheless, but it involved grabbing other unit which isn't always convenient. So this one is the only of your suggestion I wouldn't be adverse to.
Function 4:
#define ARMFIDO 1660109
var u,n;
n=0;
for(u=1;u<=get MAX_ID;++u)
{
if(get XZ_HYPOT(get PIECE_XZ(base) - get UNIT_XZ(u))<= [50])// Find units in range
{
if(get UNIT_HEIGHT(u)==ARMFIDO)// Find units of type Fido
{
++n;
}
}
}
// Now n contains the number of fido around!
No really, why add all that clutter and all those new confusing functions when what you're asking for is already possible. Stop asking more and more complex hardcoded features, and learn to use what's already available! If you are unable to use something as simple as that little script to count unit around, then you sure won't be able to use something as complex as the engine returning values from a list, one by one, with a -1 to mark the last. I mean, geez, almost no one use the current script feature that have been existing since 8 years ago, and you expect people to understand that list thing? You're completly deluded.
If I want to check for other units in a timely matter (means less than a second delay) I'll kill the performance if I have more than 50 of the checking units around. This gets even worse if the unit that uses it is highly spammable. E.g. if a spam mod made its most spammable unit move faster the more of them are available. That'd be O(n²) and unbearably slow if you have to do it often. Spring can do proximity checks a lot faster than the script can do, at times 100 of them per frame without a drop in the framerate.
Imagine e.g. Nanoblobz introduced a cloak generator. Because of the speed each other unit would have to check a few times per second if it's in range and because of the unit numbers in play that would lead to even more slowdown.
Unit height is a very ugly hack and requires that you set different heights for every unit in addition to having to compare the height to all applicable units for something that cares about more than one unittype.
I know how to do 1,2 and 4 in a hacky and slow way but I'd prefer if I could do it in a clean, fast way.
Also Spring starts issuing IDs from 5000 down so 1 to max ID would always be 5000.
Imagine e.g. Nanoblobz introduced a cloak generator. Because of the speed each other unit would have to check a few times per second if it's in range and because of the unit numbers in play that would lead to even more slowdown.
Unit height is a very ugly hack and requires that you set different heights for every unit in addition to having to compare the height to all applicable units for something that cares about more than one unittype.
I know how to do 1,2 and 4 in a hacky and slow way but I'd prefer if I could do it in a clean, fast way.
Also Spring starts issuing IDs from 5000 down so 1 to max ID would always be 5000.
I understand your concern about perfomance, but I disbelieve it's as bad as you claim. For the next version of SWTA they never released, we had:
- All rebel infantry checking for presence of two-three kind of units around (the jedis).
- All cons, solar, fusion, geo, and mexx looking for about 4-5 kind of units (the droids).
And the hit on performance wasn't really noticeable. The key lies in using long enough sleep between each cycle (like, checking only once every second and not 30 times per second), and into doing first the check that are simplest and eliminate the most id.
You seem to believe that everything scripted is ultra slow, and everything hard coded is ultra fast. I'm not certain the difference is that big. I mean I assume the SY are competent coders, and wrote as little wrapping around the script function as possible. Whenever I looked at the c++ Spring source code dealing with script, it seemed a very straigtforward approach, with nearly no intermediary between the cob command and the resulting c++ command, and there's no reason they'd have made script that slow! At least I'd like it you to actually measure it, instead of just assuming script are slow. Like I said, I already used such unit search script loads of time, albeit in the TA engine, and the performance drop wasn't noticeable.
Your saying that the script way is hacky, and that to harcode search untypte in radius would be cleaner.
I think exactly the contrary. The scripted way is as simple as it could be, I mean, just look at my exemple, you can hardly define what you're looking for with fewer terms. Scripted solution is flexible, you can always devise your own search algorithm or criteria, without having to disturb a dev.
You suggestion on the other hand, implies hardcoding the search, which mean:
- Very limited range of what you can do.
- Must call upon a dev, and commit a source change, every time you need it changed.
- FBI tags and new cob function galore, so newcomer would be all lost cause this won't work if you don't remember they exact name.
- It requires FBI, understanding of how hard coded code work, and script, all at once, to do a search, which make it more confusion than just a single script bit doing all.
Hard-coded is ugly, limited, dirty.
New tags commanding the hard coded search is limited and confusing.
Scripted is clean and powerful.
I also would like to stree that I'm not just spouting vague thought, but that I actually did measure the height of all XTA arm mobile unit, and used that in a transport script: Spring_dropship.zip Look a the SpringXTAUnitsHeight.h is you don't believe that they aren't that likely to have the same height as other units.
- All rebel infantry checking for presence of two-three kind of units around (the jedis).
- All cons, solar, fusion, geo, and mexx looking for about 4-5 kind of units (the droids).
And the hit on performance wasn't really noticeable. The key lies in using long enough sleep between each cycle (like, checking only once every second and not 30 times per second), and into doing first the check that are simplest and eliminate the most id.
You seem to believe that everything scripted is ultra slow, and everything hard coded is ultra fast. I'm not certain the difference is that big. I mean I assume the SY are competent coders, and wrote as little wrapping around the script function as possible. Whenever I looked at the c++ Spring source code dealing with script, it seemed a very straigtforward approach, with nearly no intermediary between the cob command and the resulting c++ command, and there's no reason they'd have made script that slow! At least I'd like it you to actually measure it, instead of just assuming script are slow. Like I said, I already used such unit search script loads of time, albeit in the TA engine, and the performance drop wasn't noticeable.
Your saying that the script way is hacky, and that to harcode search untypte in radius would be cleaner.
I think exactly the contrary. The scripted way is as simple as it could be, I mean, just look at my exemple, you can hardly define what you're looking for with fewer terms. Scripted solution is flexible, you can always devise your own search algorithm or criteria, without having to disturb a dev.
You suggestion on the other hand, implies hardcoding the search, which mean:
- Very limited range of what you can do.
- Must call upon a dev, and commit a source change, every time you need it changed.
- FBI tags and new cob function galore, so newcomer would be all lost cause this won't work if you don't remember they exact name.
- It requires FBI, understanding of how hard coded code work, and script, all at once, to do a search, which make it more confusion than just a single script bit doing all.
Hard-coded is ugly, limited, dirty.
New tags commanding the hard coded search is limited and confusing.
Scripted is clean and powerful.
I fail to see why S3O would be more likely to have the same height than 3DO, or why they would made using get UNIT_HEIGHT( ) less practical. It works exactly the same.UnitHeight is not as practical in Spring as it was in OTA. S3O units get their height defined as part of the model, and it's quite likely that units will be the same height.
I also would like to stree that I'm not just spouting vague thought, but that I actually did measure the height of all XTA arm mobile unit, and used that in a transport script: Spring_dropship.zip Look a the SpringXTAUnitsHeight.h is you don't believe that they aren't that likely to have the same height as other units.
WTF? What do you think 3do get their height defined from? You're clearly talking nonsense here. This further prove my point: if you can't understand and making use of the existing features, that are quite simple, it's ludricous to ask for your more obscure, more complex, more hardcoded, (yet more limited) features. Ok maybe you really meant that S3O units height can be edited indepently of the vertices and polygon of the actual model geometry. Well, then it's even more better, that mean you can change the value returned by get UNIT_HEIGHT at will, without it affecting the geometry!S3O units get their height defined as part of the model
The max ID is 4999, not 5000, in Spring. Yes, ID range from 1 to 4999, which means there are 4999 ID, which not a number divisable equally amongst players, but it's like that. Even if maybe now the max ID is fixed (which I'm not even that sure of), it's bound to change. When you do a 16 player game with 500 unit each, I hope max id is higher than 5000, so just write get MAX_ID, it's cleaner, more portable, more maintenable, and more bound to keep on working in future version than writing 5000 in every "for" loop. I mean, it's simply best practice and common sense.Also Spring starts issuing IDs from 5000 down so 1 to max ID would always be 5000.
I'm not saying that MAX_ID is a bad idea but you'll always have to run through all 5000 (or 4999) IDs no matter how many of them can be or are actually used (and I think Spring just crashes if it runs out of IDs). Even if scripts are just as fast as C the function that checks for units in range is much faster than iterating through all units because it partitions the map into sectors and only checks for units within the sectors in range (it obviously won't even try units that don't exist).
zwzsg, when modelers create S3O, they're setting up the radius and height- it's not like 3DO, where that is set by Spring automatically. While we could do minor variations on height, and height has very little effect on gameplay, then you're getting into maintenance problems galore- if you have 10 infantry types, for example, who're all supposed to be the same exact height, you're going to have to use very teeny-tiny increments if you're going to use what you're talking about, without it looking terrible in the game engine, since height + 8 = position of the health bar.
Not to mention that using the Caliper thingie is a pain the butt.
I think that units need to be able to set up an ID that is as specific as designers need it to be, based on whatever criteria they choose, not some arbitrary number derived from the model's geometry or whatever, nor some sort've local-search. I think we need a UnitClass variable, just like the flags we set for InterceptedByShieldType- nice and fast, but flexible.
So, for example, I'd set the variable UnitClass (or whatever) to a number via script, and then I could check for units in range that match the UnitClass using the script, eliminating everything that doesn't share that UnitClass. That'd cut search times down a lot, and it'd be a lot more intuitive and flexible than using UnitHeight- it'd also remove the sheer bloat that will occur if you're using UnitHeight with multiple Units.
This would have a lot of advantages. No code bloat. Anything not in the UnitClass would be immediately dropped from the search. And because it would be scripted, it could be changed via script on the fly. Have a Unit that goes into a state where it's deployed, and can no longer be Transported (ala the Heavy Weapons Imperial Guard in DoW)? Just change the UnitClass, and now Transporters will reject them. Have units whose "morale" has dropped below a threshhold, causing their weapons to lock out? Move your Hero of Amazingness into range, and he/she/it will reset their UnitClass indirectly- the units search, "see" the Hero, and change states. Etc.
Not to mention that using the Caliper thingie is a pain the butt.
I think that units need to be able to set up an ID that is as specific as designers need it to be, based on whatever criteria they choose, not some arbitrary number derived from the model's geometry or whatever, nor some sort've local-search. I think we need a UnitClass variable, just like the flags we set for InterceptedByShieldType- nice and fast, but flexible.
So, for example, I'd set the variable UnitClass (or whatever) to a number via script, and then I could check for units in range that match the UnitClass using the script, eliminating everything that doesn't share that UnitClass. That'd cut search times down a lot, and it'd be a lot more intuitive and flexible than using UnitHeight- it'd also remove the sheer bloat that will occur if you're using UnitHeight with multiple Units.
This would have a lot of advantages. No code bloat. Anything not in the UnitClass would be immediately dropped from the search. And because it would be scripted, it could be changed via script on the fly. Have a Unit that goes into a state where it's deployed, and can no longer be Transported (ala the Heavy Weapons Imperial Guard in DoW)? Just change the UnitClass, and now Transporters will reject them. Have units whose "morale" has dropped below a threshhold, causing their weapons to lock out? Move your Hero of Amazingness into range, and he/she/it will reset their UnitClass indirectly- the units search, "see" the Hero, and change states. Etc.
Argh, as I said, height isn't the critical factor, radius is. Of course you have 65536 steps between each integral number so you can vary it without serious gameplay impact but I don't think UNIT_HEIGHT was intended to be used for determining unit types.
I really think a static UNIT_TYPE to identify the unit and a bunch of variables a unit can communicate with others would work the best. While you could make the first shared variable the unittype that'd still be less clear than getting the unittype in first place.
Also, zwzsg:
Imo it's much less ressource intensive and much more reliable to do that whenever you need to react to another unit existence, than to inform every single unit every time a new unit is created or killed.
The callback would only call functions on those units that have notified the engine that they want to receive these calls (set UNIT_CREATE_CALLBACK to 1). In a worst case scenario (all units want that and all possible units are in play) that causes as many script operations (~5000) as your approach does in the best case (one unit iterating through all 5000 IDs) if we ignore for a moment any time taken up by the unit actually responding to that event (as that would be identical in both approaches anyway). Also your approach generates load at all times while this would only do so when a unit is created which usually happens much slower than once per second. Iterating through a linked list is VERY fast and issuing those script calls can't be that expensive.
I really think a static UNIT_TYPE to identify the unit and a bunch of variables a unit can communicate with others would work the best. While you could make the first shared variable the unittype that'd still be less clear than getting the unittype in first place.
Also, zwzsg:
Imo it's much less ressource intensive and much more reliable to do that whenever you need to react to another unit existence, than to inform every single unit every time a new unit is created or killed.
The callback would only call functions on those units that have notified the engine that they want to receive these calls (set UNIT_CREATE_CALLBACK to 1). In a worst case scenario (all units want that and all possible units are in play) that causes as many script operations (~5000) as your approach does in the best case (one unit iterating through all 5000 IDs) if we ignore for a moment any time taken up by the unit actually responding to that event (as that would be identical in both approaches anyway). Also your approach generates load at all times while this would only do so when a unit is created which usually happens much slower than once per second. Iterating through a linked list is VERY fast and issuing those script calls can't be that expensive.
yuritch: UnitDestroyed is easy, as are UnitTaken/UnitGiven
(these have been lua call-ins since it was committed). Most
of the call-ins are listed in rts/Lua/LuaCallInHandler.h
zwzsg: cob<->lua is faster then the current pure cob solutions
(especially with regards to anything requiring a search through
all units)
Argh: agreed, using UnitHeight is cheesy. Specifying a unitType
value for different types of units would be much better. You could
add a cobID value to the FBI files and write a parser in lua to pull
them out, or have a separate file (in lua format) to list all ids for
all types. Files in lua format do not require special parsers, they
can load config data directly.
P.S. Example cobID formats:
P.P.S. The 'local' and 'return' parts aren't actually required,
I'm just being picky. You could also write a script in LuaUI to
quickly generate a CobIDs file for you from the existing UnitDef
IDs, something like this:
(these have been lua call-ins since it was committed). Most
of the call-ins are listed in rts/Lua/LuaCallInHandler.h
zwzsg: cob<->lua is faster then the current pure cob solutions
(especially with regards to anything requiring a search through
all units)
Argh: agreed, using UnitHeight is cheesy. Specifying a unitType
value for different types of units would be much better. You could
add a cobID value to the FBI files and write a parser in lua to pull
them out, or have a separate file (in lua format) to list all ids for
all types. Files in lua format do not require special parsers, they
can load config data directly.
P.S. Example cobID formats:
Code: Select all
local CobIDs = {
armalpha = 1,
armbeta = 2,
coralpha = 3,
corbeta = 4
}
return CobIDs
I'm just being picky. You could also write a script in LuaUI to
quickly generate a CobIDs file for you from the existing UnitDef
IDs, something like this:
Code: Select all
print("local CobIDs = {")
for _,ud in pairs(UnitDefs) do
local safeName = string.format("[%q]", ud.name)
print(" " .. safeName .. " = " .. ud.id .. ",")
end
print("}")
print("return CobIDs")
While I'm a rambling mood (avoiding housework), I guess you'd also
want to be able to load the same file of unit types constants into cob
files as a header, so the lua format is out. A lua parser to pick off the
"#defines", unit names, and id codes goes something like this:
want to be able to load the same file of unit types constants into cob
files as a header, so the lua format is out. A lua parser to pick off the
"#defines", unit names, and id codes goes something like this:
Code: Select all
local cobIDs = {}
local file = io.open("units/CobIDs.h", "r")
for line in file:lines() do
local parseStr = "^%s*#define%s+([_%w]+)%s+([_%w]+)"
local _,_,name,num = string.find(line, parseStr)
if (name and num) then
cobIDs[name] = tonumber(num)
end
end
file:close()