Central Build AI as lua widget! - Page 3

Central Build AI as lua widget!

Discuss Lua based Spring scripts (LuaUI widgets, mission scripts, gaia scripts, mod-rules scripts, scripted keybindings, etc...)

Moderator: Moderators

User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

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.
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.
User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

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...).
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: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
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.)

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.
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)
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.
User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

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?
User avatar
Niobium
Posts: 456
Joined: 07 Dec 2008, 02:35

Re: Central Build AI as lua widget!

Post by Niobium »

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.
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Central Build AI as lua widget!

Post by hoijui »

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.
User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

Niobium wrote:gl.Rotate(Degrees, 0.0, 1.0, 0.0) will rotate in correct plane. (Around the y, i.e. vertical, axis)
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.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.
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:

Code: Select all

 |A|  ->  \A/  ->  _A_  (don't you just love ASCII art?)
So if you place another build where it looks like it will fit right up against the solar, it won't, and one or the other won't get built. I was hoping gl.UnitShape had some option I hadn't noticed for displaying the active version of the unit instead of the inactive one.
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.
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.
User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

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.
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.

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.
User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

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...
User avatar
Niobium
Posts: 456
Joined: 07 Dec 2008, 02:35

Re: Central Build AI as lua widget!

Post by Niobium »

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:

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
Would simply become...

Code: Select all

function widget:UnitDestroyed(unitID, unitDefID, unitTeam)
	myUnits[unitID] = nil
end
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)
User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

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.
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Central Build AI as lua widget!

Post by hoijui »

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)
User avatar
Niobium
Posts: 456
Joined: 07 Dec 2008, 02:35

Re: Central Build AI as lua widget!

Post by Niobium »

troycheek wrote: Not so sure about the centralized build queue. What could I use as a unique key for each entry?
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.
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.)
This more common than you would think, UnitDestroyed() + UnitTaken() should both remove units from tables.
User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

hoijui wrote:Maybe you know this already, and maybe it does not matter, but unitIds are reused.
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.

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.
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.
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.
SkyStar
Engines Of War Developer
Posts: 23
Joined: 08 Jul 2009, 15:05

Re: Central Build AI as lua widget!

Post by SkyStar »

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.
User avatar
Niobium
Posts: 456
Joined: 07 Dec 2008, 02:35

Re: Central Build AI as lua widget!

Post by Niobium »

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?
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.

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.
User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

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.
SkyStar
Engines Of War Developer
Posts: 23
Joined: 08 Jul 2009, 15:05

Re: Central Build AI as lua widget!

Post by SkyStar »

No problem,

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
and changing all of these

Code: Select all

nextFrame = spGetGameFrame() + 3
to

Code: Select all

nextFrame = spGetGameFrame() + ping()
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.
User avatar
troycheek
Posts: 80
Joined: 22 May 2009, 19:13

Re: Central Build AI as lua widget!

Post by troycheek »

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.
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.

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?
User avatar
Niobium
Posts: 456
Joined: 07 Dec 2008, 02:35

Re: Central Build AI as lua widget!

Post by Niobium »

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?
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.
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Central Build AI as lua widget!

Post by hoijui »

really??
what is the logic behind this?
as there is no logical widget order, there can not really be a logic behind this, right?
Post Reply

Return to “Lua Scripts”