I see now why I couldn't get that part to work. I had tried if cmdOptions == "shift" and a gazillion variations, but nothing worked. I'd ended up using GetModKeyState() to detect the shift key. I realize now that I should have been using if cmdOptions.shift == true. I just tested that change and it worked, so thank you again.Niobium wrote:cmdOptions is alt/shift/control/meta modifiers. (i.e. cmdOptions.shift). There is also cmdOptions.coded for the functions which require options in that form.
Central Build AI as lua widget!
Moderator: Moderators
Re: Central Build AI as lua widget!
Re: Central Build AI as lua widget!
Sorry if I was a bit harsh in my reply. I just feel like an idiot sometimes for not being able to figure some of this stuff out. I fully expected the first reply to say something like "Good try, but here's a better version with only 42 lines of code..."SeanHeron wrote:Well, sorry if it wasn't that much help - but I must say I'm quite impressed with what you've put together, seeing you've started from scratch! (I tried my hand at a Lua AI, and god, was progress slow...).
As near as I can tell from my own informal tests, GameFrame gets called 30 times a second (Spring's internal game framerate or something) while Update gets called as often as possible (if you're getting 200 fps, then it's called 200 times a second). Central Build only really needs polling a few times a second, so GameFrame was better. (GameSecond would be great, but I don't think that one exists.)SeanHeron wrote:I was under the assumption you didn't know the "bible", cause you hadn't used the "GameFrame" call in, and that is one of the ones that is described - though to be honest I wouldn't know why to choose that over "Update" either
Also, GameFrame doesn't seem to get called until after the game actually starts, while Update gets called starting possibly as soon as LuaUI loads, probably right after Initialize. GameFrame returns the game frame number, while Update returns the amount of time since it was last called (some tiny fraction of a second).
Of course, I know all that now after hours of trial and error and reading old forum posts. I didn't know it when I started coding. I used Update because that's what the other widgets I was stealing code from were using. Now, I just realized there's a couple of old widgets that I want to re-write.
I keep meaning to edit the wiki and add stuff as I learn it, but keep putting it off. Plus, I don't appear to be in the group that can edit the wiki.SeanHeron wrote:I know SpliFF has the Coding Guide on his SVN, I'll have to ask/ check if anythings changed - and of course, we should probably look for a way that the stuff that you found out can be added in the relevant section! (After all that's the way SpliFF put it together)
Re: Central Build AI as lua widget!
In order to show the player where items in the Central Build queue will be built, I've been using gl.UnitShape to display a ghosted image at the appropriate location. I stole the code from very_bad_soldier's Ghost Radar. It's mostly voodoo to me. It works great except for two things which I have no idea how to fix:
1) Units are always displayed facing to the South, regardless of actual facing specified by the player. As some units aren't exactly square, this makes lining up future build orders nearby difficult. I played with gl.Rotate a bit, but it seems to want to rotate things in the wrong plane.
2) Units sometimes have larger footprints than are apparent by their shape as displayed by gl.UnitShape. XTA's Arm solar collectors show in folded up mode, about half the size of the deployed mode. Orders in a unit's standard build queue show an outline of the footprint when you select a builder and hold the shift key, which makes it easy to see where to place the next build.
Any suggestions?
1) Units are always displayed facing to the South, regardless of actual facing specified by the player. As some units aren't exactly square, this makes lining up future build orders nearby difficult. I played with gl.Rotate a bit, but it seems to want to rotate things in the wrong plane.
2) Units sometimes have larger footprints than are apparent by their shape as displayed by gl.UnitShape. XTA's Arm solar collectors show in folded up mode, about half the size of the deployed mode. Orders in a unit's standard build queue show an outline of the footprint when you select a builder and hold the shift key, which makes it easy to see where to place the next build.
Any suggestions?
Re: Central Build AI as lua widget!
gl.Rotate(Degrees, 0.0, 1.0, 0.0) will rotate in correct plane. (Around the y, i.e. vertical, axis)
Note that it actually uses degrees and not radians, all the math.___ functions use radians.
gl.Scale(x, y, z) will help solve the sizing problem, I would say it is a unitdef that is doing the sizing, I would look there first.
And for the lines at base, that's easy, just draw the lines yourself, lots of widgets have code that draws lines through a set of points.
Note that it actually uses degrees and not radians, all the math.___ functions use radians.
gl.Scale(x, y, z) will help solve the sizing problem, I would say it is a unitdef that is doing the sizing, I would look there first.
And for the lines at base, that's easy, just draw the lines yourself, lots of widgets have code that draws lines through a set of points.
Re: Central Build AI as lua widget!
well.. i have no idea about Lua, but in the engine we have a facing parameter in all locations where we have to change unit direction.
it is an int with .. i guess 4 different values. i would say if showing ghosted units does not yet use that, it should be built in there.
it is an int with .. i guess 4 different values. i would say if showing ghosted units does not yet use that, it should be built in there.
Re: Central Build AI as lua widget!
Got it. I had my "1.0" in the wrong slot. Degrees = buildFacing * 90 does the trick in getting the build facing the right direction. Then another call to gl.Rotate with negative that many degrees to reset before drawing something that doesn't need rotated. Or gl.Rotate(Degrees, 0.0, -1.0, 0.0) to reset.Niobium wrote:gl.Rotate(Degrees, 0.0, 1.0, 0.0) will rotate in correct plane. (Around the y, i.e. vertical, axis)
Well, it's not so much a sizing problem as, um, hard to explain. The Arm solar collector folds up when not in use and is built in that folded up state. Once active, it unfolds and takes up about twice as much space:Niobium wrote:gl.Scale(x, y, z) will help solve the sizing problem, I would say it is a unitdef that is doing the sizing, I would look there first.
Code: Select all
|A| -> \A/ -> _A_ (don't you just love ASCII art?)
That's what I figured I'd end up doing, but decided it might be smarter to ask before I started slinging code this time. The game engine already has something which displays the build footprint of the unit when you're specifying the build order, then something which displays an outline later when the builder is selected and shift is pressed. I was hoping there was an easy way to call one or the other for my purposes.Niobium wrote:And for the lines at base, that's easy, just draw the lines yourself, lots of widgets have code that draws lines through a set of points.
Re: Central Build AI as lua widget!
facing values { S = 0, E = 1, N = 2, W = 3 } according to trepan's FactoryGuard widget. These seem to be the values returned from Spring.GetUnitBuildFacing.hoijui wrote:well.. i have no idea about Lua, but in the engine we have a facing parameter in all locations where we have to change unit direction. it is an int with .. i guess 4 different values. i would say if showing ghosted units does not yet use that, it should be built in there.
I'm just faking ghosted units with gl.UnitShape, which seems to always draw the units facing South. I know that at some point in the past I've observed "real" ghosted buildings being shown the same way, but I've not really checked it recently. If they're still being shown without regard to facing, it's easy enough to fix with gl.Rotate or similar. On the other hand, if gl.UnitShape got an extra optional facing parameter added (corresponding to the same values returned by Spring.GetUnitBuildFacing), I certainly wouldn't complain.
Re: Central Build AI as lua widget!
New for version v0.7:
1) Minor change in widget:CommandNotify() to properly read options.shift instead of having to call Spring.GetModKeyState() to see if the shift key was being held down. This saves three lines of code and has zero measurable effect on performance. It just bugged me that I didn't understand how to do it before.
2) Added widget:UnitDestroyed() after realizing I was wrong and Niobiom was right. The widget:GroupChanged() (or more likely the way I'm processing it) is not sufficient in all cases where a builder in the Central Build group is destroyed. I still need to do some work on this. I'll probably end up adding UnitTaken() as well eventually.
3) Ghosted units in the Central Build queue now show in the game world in their proper orientation (aka build facing).
4) Ghosted units in the Central Build queue now have a green "build footprint" outline which appears when the shift key is pressed.
The last one was a bit of a challenge because I'd never drawn anything with gl.BeginEnd before, and because the UnitDefs for a given unit have several contradictory "size" values. I finally settled on .xsize and .zsize which appear to both exist and return useful values in XTA, BA, and CA.
Now, to check out Niobium's Build Split and see if there's any code I need to borrow...
1) Minor change in widget:CommandNotify() to properly read options.shift instead of having to call Spring.GetModKeyState() to see if the shift key was being held down. This saves three lines of code and has zero measurable effect on performance. It just bugged me that I didn't understand how to do it before.
2) Added widget:UnitDestroyed() after realizing I was wrong and Niobiom was right. The widget:GroupChanged() (or more likely the way I'm processing it) is not sufficient in all cases where a builder in the Central Build group is destroyed. I still need to do some work on this. I'll probably end up adding UnitTaken() as well eventually.
3) Ghosted units in the Central Build queue now show in the game world in their proper orientation (aka build facing).
4) Ghosted units in the Central Build queue now have a green "build footprint" outline which appears when the shift key is pressed.
The last one was a bit of a challenge because I'd never drawn anything with gl.BeginEnd before, and because the UnitDefs for a given unit have several contradictory "size" values. I finally settled on .xsize and .zsize which appear to both exist and return useful values in XTA, BA, and CA.
Now, to check out Niobium's Build Split and see if there's any code I need to borrow...
Re: Central Build AI as lua widget!
Looking at the code, I would recommend switching the way you do all your tables.
Currently:
table[1...n] = {uID, <data>}
Proposed:
table[uID] = {<data>}
Basically instead of an array, you have a list of keys (Which are the unit IDs) which point to values (The data)
The reason you would do it this way is to cut down on code significantly.
An example taken from your widget:
Would simply become...
Which is obviously quite an improvement. There is no need to have your tables as arrays, as the order of entries is not important in your widget, and in fact may be slower due to retrieving every builders complete data in order to check each ID for a match. Oh, and the fact that the unitIDs are unique is important, as each key can only have one value.
By using the unit IDs as the keys, accessing a units data is much quicker as it only has to search across the small keys, then returns one data set corresponding to match.
To add an item: table[uID] = <data>
To remove an item: table[uID] = nil
To check if item exists: if table[uID] then...
Also you would use 'for key, value in pairs(table) do' rather than the current ipairs. Sorry if this means you have to start from scratch again :D (Should be easy to convert)
Currently:
table[1...n] = {uID, <data>}
Proposed:
table[uID] = {<data>}
Basically instead of an array, you have a list of keys (Which are the unit IDs) which point to values (The data)
The reason you would do it this way is to cut down on code significantly.
An example taken from your widget:
Code: Select all
function widget:UnitDestroyed(unitID, unitDefID, unitTeam)
for index,unit2 in ipairs(myUnits) do
if ( unitID == unit2 ) then
table.remove(myUnits, index)
return
end
end
end
Code: Select all
function widget:UnitDestroyed(unitID, unitDefID, unitTeam)
myUnits[unitID] = nil
end
By using the unit IDs as the keys, accessing a units data is much quicker as it only has to search across the small keys, then returns one data set corresponding to match.
To add an item: table[uID] = <data>
To remove an item: table[uID] = nil
To check if item exists: if table[uID] then...
Also you would use 'for key, value in pairs(table) do' rather than the current ipairs. Sorry if this means you have to start from scratch again :D (Should be easy to convert)
Re: Central Build AI as lua widget!
I'd seen widgets that use tables both ways (which I shall call here "indexed" and "keyed"), so I'd considered doing it the way you suggest back when I started. Some little coding task I was looking at early in the project (managing the members of the build group, I think) somehow seemed easier indexed than keyed. Once I got started, it kind of took on a life of its own. Looking back, I think I misunderstood what pairs/ipairs did and didn't realize then that I could walk through a keyed array with pairs the same way I was walking through an indexed array with ipairs.
Starting all over from scratch probably wouldn't be a bad idea. But I think I can convert all my unit and group stuff to keyed without any major hassle, seeing as it all uses unitID anyway. Not so sure about the centralized build queue. What could I use as a unique key for each entry?
Oh, and I did find something in Build Split worth stealing: I hadn't figured out I could use "break" to end for-loops until I noticed it in your code.
Starting all over from scratch probably wouldn't be a bad idea. But I think I can convert all my unit and group stuff to keyed without any major hassle, seeing as it all uses unitID anyway. Not so sure about the centralized build queue. What could I use as a unique key for each entry?
Oh, and I did find something in Build Split worth stealing: I hadn't figured out I could use "break" to end for-loops until I noticed it in your code.
Re: Central Build AI as lua widget!
Maybe you know this alreya, and maybe it does notmatter, but unitIds ar reused. so if unit with id 55 dies in frame 100, in frame 101 a new unit could already be asigned id 55 again. but when you use unitDied, i guess you wont have a problem with that anyway. (did not look at your code at all, just though it cant hurt)
Re: Central Build AI as lua widget!
Yeah, for the queue you are best to just use an array, tremove/tinsert etc. Almost everything that uses unit IDs is better to use unit ID as the key. Small code is so nice.troycheek wrote: Not so sure about the centralized build queue. What could I use as a unique key for each entry?
This more common than you would think, UnitDestroyed() + UnitTaken() should both remove units from tables.hoijui wrote:Maybe you know this alreya, and maybe it does notmatter, but unitIds ar reused. so if unit with id 55 dies in frame 100, in frame 101 a new unit could already be asigned id 55 again.)
Re: Central Build AI as lua widget!
I realized this when I started using Ghost Radar and saw a Core Goliath radar blip flying toward me at scout speed. I was a little disappointed with it turned into a scout when it got overhead. I don't think it will be a problem in my case because I already do some filtering (is in my unit group, is mobile, is a builder) and some UnitDestroyed and UnitTake (just added) stuff. I might have to add some more checks. grumble grumble.hoijui wrote:Maybe you know this already, and maybe it does not matter, but unitIds are reused.
The grumbling is because I had hoped to avoid pretty much all the above checks by using GroupChanged() --> "groupID" where groupID is the value of Group whose table value changed. A unit in the group getting destroyed, captured, or given away is a change, right? All that's taken care of together in one little function, right?
I never figured out if I was right or not because of a problem I had with GroupChanged(groupID). If I call GetGroupUnits(groupID) within said function, it doesn't return an accurate list of units. Instead, I seem to get the list of units it had when it last changed. The list returned by GetGroupUnits doesn't seem to be accurate until a gameframe or two later. I still haven't decided if that's a bug or if there's something going on I don't understand.
Small code is nice. Code I can understand and maintain is even better. I'd used table.insert/remove or similar in other languages and had a pretty good understanding of that type of indexed data manipulation. While I'd used some keyed stuff in other widgets, I was just altering what someone else had written instead of coming up with something on my own. I only recently realized what the keyed stuff was doing. Heck, I just realized I've only been at this a few weeks.Niobium wrote:Yeah, for the queue you are best to just use an array, tremove/tinsert etc. Almost everything that uses unit IDs is better to use unit ID as the key. Small code is so nice.
Re: Central Build AI as lua widget!
On my PC it tends to try to find a new job almost immediately after it starts one. So it tries to do the entire que as soon as it idles, obviously removing all the queued items.
I don't think you needed to know this since its possible it only happens to me but you might want to know any way.
-----------------------------------
(Its more noticeable in multi-player, the problem disappears when playing against an AI)
I think the fix is to scale the time between idling and looking for a new job, because the latency(Ping) between the player and the host causes whatever the CBAI tells the constructor to do to happen milliseconds later but in that time the CBAI decides that the Constructor is still "idle" since the CBAI seems to check every 1/10 of a second for idling units and my ping usually is about 200 or 2/10 of a second(CBAI decides it is Idling before the host <-> player connection acknowledges that a command has been made).
(You know when the game sometimes gets very laggy and everyone's ping is 10000 and everything you tell your units to do seems to happen 10 seconds later.)
so it takes a different job in the que, and as the jobs are taken they are removed from the que, so eventually the entire que is finished but nothing is built (except for the last que'ed unit because there is nothing left to switch to).
Hopefully Im not confusing you.
Impressive AI, anyway it takes a lot of work to make a script like this, well done.
I have fixed it myself as not to bother you but I guess you still need to know. Good luck.
I don't think you needed to know this since its possible it only happens to me but you might want to know any way.
-----------------------------------
(Its more noticeable in multi-player, the problem disappears when playing against an AI)
I think the fix is to scale the time between idling and looking for a new job, because the latency(Ping) between the player and the host causes whatever the CBAI tells the constructor to do to happen milliseconds later but in that time the CBAI decides that the Constructor is still "idle" since the CBAI seems to check every 1/10 of a second for idling units and my ping usually is about 200 or 2/10 of a second(CBAI decides it is Idling before the host <-> player connection acknowledges that a command has been made).
(You know when the game sometimes gets very laggy and everyone's ping is 10000 and everything you tell your units to do seems to happen 10 seconds later.)
so it takes a different job in the que, and as the jobs are taken they are removed from the que, so eventually the entire que is finished but nothing is built (except for the last que'ed unit because there is nothing left to switch to).
Hopefully Im not confusing you.
Impressive AI, anyway it takes a lot of work to make a script like this, well done.
I have fixed it myself as not to bother you but I guess you still need to know. Good luck.
Re: Central Build AI as lua widget!
Yeah, you would think so, but particulars like that can get missed by the devs, you can consult the spring source yourself to see where exactly groupchanged is fired, it may only get fired when a function that adds/removes units from a group gets called, and not when it finds units missing and such. Hell, maybe dead units still have their IDs left in the group.troycheek wrote:The grumbling is because I had hoped to avoid pretty much all the above checks by using GroupChanged() --> "groupID" where groupID is the value of Group whose table value changed. A unit in the group getting destroyed, captured, or given away is a change, right? All that's taken care of together in one little function, right?
Or you could just run a bunch of tests, giving/destroying units that are/aren't in groups, and writing down exactly when it gets called.
Re: Central Build AI as lua widget!
SkyStar: I hadn't noticed, but I've pretty much only ever played against AI and I'm certain I've only tested CBAI against AI. I would not have known about this problem if you hadn't mentioned it, so thank you very much for taking the time to write.
Until the last few versions, I was waiting a full second between orders. I had shortened that to a tenth of a second because I thought I had all the kinks worked out. Obviously not. Also, until the last few versions, I had not been removing items from the build queue until after they had been completed. I had changed that to removing the items once the build order had been issued to cut down on redundant orders. My guess is that either/both of these changes is what's causing your problem and that you actually would have been better off with an earlier version of the widget.
I think the underlying problem is that I was lazy and just kept reading builder command queues over and over to see what each Central Builder was doing instead of keeping track of it myself. Latency/ping/lag/whatever means that the commands don't show up in the queue until after I've already issued another (if I understand you correctly). Once I get another day off (these 12 hour shifts are kicking my ass) I'm going to be attempting a rewrite to change how I'm handling some of the data anyway. I might as well work out how to track job assignments a little better while I'm at it.
Thanks for the kind words about the AI (though I'm not sure a purist would say it counts as an AI, that's just what the old one was called). I have put a lot of work into it and probably have quite a bit to go. A lot of my success is simply copying from my betters combined with a little blind luck, but I'll still take any compliment I can get.
I'm glad you were able to fix it yourself, but please always let me know if you find a problem (and what the fix is!). Don't tell anyone, but part of the reason I released the widget when I did was that I was hitting a slump in development and was hoping that other people could look at the code and give me some pointers.
Until the last few versions, I was waiting a full second between orders. I had shortened that to a tenth of a second because I thought I had all the kinks worked out. Obviously not. Also, until the last few versions, I had not been removing items from the build queue until after they had been completed. I had changed that to removing the items once the build order had been issued to cut down on redundant orders. My guess is that either/both of these changes is what's causing your problem and that you actually would have been better off with an earlier version of the widget.
I think the underlying problem is that I was lazy and just kept reading builder command queues over and over to see what each Central Builder was doing instead of keeping track of it myself. Latency/ping/lag/whatever means that the commands don't show up in the queue until after I've already issued another (if I understand you correctly). Once I get another day off (these 12 hour shifts are kicking my ass) I'm going to be attempting a rewrite to change how I'm handling some of the data anyway. I might as well work out how to track job assignments a little better while I'm at it.
Thanks for the kind words about the AI (though I'm not sure a purist would say it counts as an AI, that's just what the old one was called). I have put a lot of work into it and probably have quite a bit to go. A lot of my success is simply copying from my betters combined with a little blind luck, but I'll still take any compliment I can get.
I'm glad you were able to fix it yourself, but please always let me know if you find a problem (and what the fix is!). Don't tell anyone, but part of the reason I released the widget when I did was that I was hitting a slump in development and was hoping that other people could look at the code and give me some pointers.
Re: Central Build AI as lua widget!
No problem,
I fixed it by changing its idle check time to suit whatever ping you have at the time by:
adding this
and changing all of these
to
so that it fixes the bug but doesn't slow its response time too much.
However if you change the way job assignments are tracked, I think it would be a more optimized fix than these little lines.
I fixed it by changing its idle check time to suit whatever ping you have at the time by:
adding this
Code: Select all
function ping()
local playerID = Spring.GetLocalPlayerID()
local tname, _, tspec, tteam, tallyteam, tping, tcpu = Spring.GetPlayerInfo(playerID)
tping = (tping*1000-((tping*1000)%1)) /100 * 4
return tping
end
Code: Select all
nextFrame = spGetGameFrame() + 3
Code: Select all
nextFrame = spGetGameFrame() + ping()
However if you change the way job assignments are tracked, I think it would be a more optimized fix than these little lines.
Re: Central Build AI as lua widget!
I did a quick test with self destruct last night, and self-D did seem to trigger GroupChanged. On the other hand, I was watching a Central Builder the other night when it was destroyed and a Lua error message immediately popped up about issuing orders to a nonexistent unit, removing widget. I think. I ran off to immediately try to fix the problem rather than saving the log. However, I admit that I'm probably using the function for something it was not intended and that I need to keep track of things on my own better.Niobium wrote:Yeah, you would think so, but particulars like that can get missed by the devs, you can consult the spring source yourself to see where exactly groupchanged is fired, it may only get fired when a function that adds/removes units from a group gets called, and not when it finds units missing and such. Hell, maybe dead units still have their IDs left in the group.
A thought just wandered through my mind: are widgets affected by each other? Suppose I have multiple widgets with 'function widget:UnitDestroyed()' in them. When a unit is destroyed, are all the functions in all the widgets triggered, or is the first it finds triggered and then the Lua engine considers the event handled? What if the function returns TRUE or FALSE?
Re: Central Build AI as lua widget!
It gets called for all of them, unless one returns true, at which point it stops calling it for other widgets. The order is most likely alphabetical by file name. I'm actually currently in need of a list of what widgets return true on what commands due to random widgets 'stealing' the call from my widgets that I'm working on.troycheek wrote:A thought just wandered through my mind: are widgets affected by each other? Suppose I have multiple widgets with 'function widget:UnitDestroyed()' in them. When a unit is destroyed, are all the functions in all the widgets triggered, or is the first it finds triggered and then the Lua engine considers the event handled? What if the function returns TRUE or FALSE?
Re: Central Build AI as lua widget!
really??
what is the logic behind this?
as there is no logical widget order, there can not really be a logic behind this, right?
what is the logic behind this?
as there is no logical widget order, there can not really be a logic behind this, right?