Map Database  •  FAQ  •  RSS  •  Login

Dynamic Script Usage Questions

<<

Lewin

User avatar

KaM Remake Developer

Posts: 3822

Joined: 16 Sep 2007, 22:00

KaM Skill Level: Skilled

ICQ: 269127056

Website: http://lewin.hodgman.id.au

Yahoo Messenger: lewinlewinhodgman

Location: Australia

Post 02 Jul 2013, 00:16

Re: Dynamic Script Usage

Tef, the difference between the thing I've written and your version is that mine checks the unit which has died, and yours checks the soldier that has killed the specified unit. And I think he needs mine by looking at his script :)
No your script shouldn't work at all, OnUnitDied always needs both parameters like this: OnUnitDied(aUnitID, aKillerIndex: integer). If one is missing you'll just get an error.
<<

Bence791

Knight

Posts: 618

Joined: 20 Jul 2012, 20:25

KaM Skill Level: Beginner

Location: Hungary

Post 02 Jul 2013, 09:10

Re: Dynamic Script Usage

Oh ok xd ><
The Kamper is always taking my colour!

<<

Islar

Pikeman

Posts: 180

Joined: 22 Apr 2013, 20:18

KaM Skill Level: Average

Location: The Netherlands

Post 02 Jul 2013, 12:17

Re: Dynamic Script Usage

Hi,

i have a question. When i use this code i want to give a storehouse 1 stone when a quarry made his stone. But i know OnTick is not the good one. How can i make it so that only 1 stone comes in the storehouse?
  Code:
Procedure OnTick; begin if States.HouseResourceAmount (States.HouseAt (20, 9), 1) = 3 then begin actions.GiveWares (1, 1, 1); end; end;
<<

Thorakh

Recruit

Posts: 31

Joined: 27 Jan 2011, 23:00

Post 02 Jul 2013, 12:45

Re: Dynamic Script Usage

Thanks guys, forgetting the parameters for the procedure was indeed the problem.
<<

Tef

User avatar

Lance Carrier

Posts: 64

Joined: 15 Apr 2013, 15:12

KaM Skill Level: Skilled

Post 02 Jul 2013, 14:24

Re: Dynamic Script Usage

@ Islar:

I made a script for you that should explain it for you. It will work if you do the following:
- open the map editor
- make a new, empty singleplayer map
- put a storehouse with the entrance at (5,5)
- give it some trunks and leather
- put a sawmill with the entrance at (9,5)
- put a tannery with the entrance at (9,8)
- plop a serf, carpenter and butcher.
- connect the buildings with roads
- put a second storehouse at (18,5)
- save the map under some name
It should look like in the figure attached to this post. Now paste this code into a script file.
  Code:
const TIMBER = 2; LEATHER = 12; var HouseWareMemory: array of integer; // this is where you will store the amount of wares as a memory Procedure onMissionStart; begin // this says: make the array become 2 long. Now you can store 2 variables in it! setLength(HouseWareMemory,2); // Since there are no wares into the houses on missionstart, just say that there are 0 wares in the houses HouseWareMemory[0] := 0 HouseWareMemory[1] := 0 end; Procedure onTick; var SecondStorehouse, Sawmill, Tannery: integer; begin // determine the house IDs SecondStorehouse := States.HouseAt(18,5); Sawmill := States.HouseAt(9,5); Tannery := States.HouseAt(9,8); // this says: if the actual amount of resources is bigger than the one I stored in HouseWareMemory[0], then begin ... if States.HouseResourceAmount(Sawmill, TIMBER) > HouseWareMemory[0] then begin // give two timber to the second storehouse Actions.HouseAddWaresTo(SecondStorehouse, TIMBER, 2); // put the actual amount of resources into the array, which serves as memory HouseWareMemory[0] := States.HouseResourceAmount(Sawmill, TIMBER); end; // however, the amount of wares can also decrease, if collected by a serf! if States.HouseResourceAmount(Sawmill, TIMBER) < HouseWareMemory[0] then begin // so you need to make sure that your memory corresponds with reality: HouseWareMemory[0] := States.HouseResourceAmount(Sawmill, TIMBER); end; // we can do the same for the tannery. make sure you access HouseWareMemory[1] instead of HouseWareMemory[0] : if States.HouseResourceAmount(Tannery, LEATHER) > HouseWareMemory[1] then begin Actions.HouseAddWaresTo(SecondStorehouse, LEATHER, 2); HouseWareMemory[1] := States.HouseResourceAmount(Tannery, LEATHER); end; if States.HouseResourceAmount(Tannery, LEATHER) < HouseWareMemory[1] then begin HouseWareMemory[1] := States.HouseResourceAmount(Tannery, LEATHER); end; end;
Does this help you?
You do not have the required permissions to view the files attached to this post.
<<

Thorakh

Recruit

Posts: 31

Joined: 27 Jan 2011, 23:00

Post 02 Jul 2013, 15:21

Re: Dynamic Script Usage

How would I go about scripting better AI attacks. The default attacks will attack in a straight line, which isn't really dangerous if you put your men in a chokepoint. I've thought about making the groups I want the AI to attack with move to a location first (with dynamic scripting), get into formation and then time a default AI attack (in the map editor) to begin after the groups have reformed. However, once those groups reach the specified location, they will automatically return to their backline positions and what's more, getting the IDs of the groups I want to move is pretty hard (and very unreliable, because the groups might not be in the correct position, there might be less groups produced yet, etc.).

Is it even possible to do something like this, because the AI is pretty limited. I could script both the moving and the attacks but the group ID problem would still exist.

Also, how do I make local variables? Can I only define them like this
  Code:
procedure OnTick; var BlaBla : Integer; begin code and stuff end;
or can I define them anywhere within the procedure as well, because when I try to do this
  Code:
procedure OnTick; begin var BlaBla : Integer; code and stuff end;
it gives me an error.
<<

Islar

Pikeman

Posts: 180

Joined: 22 Apr 2013, 20:18

KaM Skill Level: Average

Location: The Netherlands

Post 02 Jul 2013, 15:38

Re: Dynamic Script Usage

@Tef:

Thanks, it works like i want it. But is this also possible in multiplayer where you don't know were the buildings are placed since the player choose its position for hisself.
And thank you really for the script and explained how it works. It helped my a lot. :D
<<

Tef

User avatar

Lance Carrier

Posts: 64

Joined: 15 Apr 2013, 15:12

KaM Skill Level: Skilled

Post 02 Jul 2013, 16:55

Re: Dynamic Script Usage

But is this also possible in multiplayer where you don't know were the buildings are placed since the player choose its position for hisself.
But off course! I already expected a question like this, that's why I made the following choices in my script example, so it helps you further adjust it:
- why did I not just use 2 seperate variables to store the amount of wares? Because storing them in an array makes it easier to manage many values, for more buildings!
- why did I not just make a 'fixed' array with a length of 2, but made a 'dynamic' array with an unknown length? So you can fill it with as many values as you need, for example if you do not know in advance how many values you want to put in it!
- why did I write things like 'TIMBER = 2'? Because it makes it easier to manage your script if you want to do this for multiple kinds of resources!

Now, I know that this doesn't help you any further, but I am not going to give you the a solution straight away (Nederlands: de oplossing aan je voorkauwen :wink:) because this will be excellent learning material! My suggestion to tackle this problem is as follows:
1 - you figured out the essential trick a possible approach: storing the amount of wares into something and compare them with the actual amount. If the actual amount is bigger, move something to a storehouse and update your memory.
2 - you figured out how to do this for two predefined buildings, on two predefined locations; and to move it to a predefined storehouse at a predefined location.
3 - next step: change your script so that it works without telling it manually where your sawmill and tannery is (in my example). Think about how you could get the houseIDs! You need to understand two essential things for this: how to use a 'for loop' to search in an array, and how to use States.PlayerGetAllHouses.
4 - once you figure out step 3, it will probably be very easy for you to do the trick for an unknown number of sawmills, tanneries, or any other building, with other wares of your choice.
5 - when you know how to do this for any sort of building, for any ware, then you could extend it to make it work for any player!
6 - if you know how to make it work for any player, you could now try to manipulate your script so that it also moves wares to storehouses of your allies, just like you requested in your original question!

Step 3 is by far the most difficult. Do you need help on using for loops or searching arrays?
Last edited by Tef on 02 Jul 2013, 17:46, edited 2 times in total.
<<

Islar

Pikeman

Posts: 180

Joined: 22 Apr 2013, 20:18

KaM Skill Level: Average

Location: The Netherlands

Post 02 Jul 2013, 17:30

Re: Dynamic Script Usage


Step 3 is by far the most difficult. Do you need help on using for loops or searching arrays?
You right of what you say. ''Je hoeft de oplossing niet voor te kauwen.'' :mrgreen:
I would like it if you learn my how to use a for loops and searching arrays.
<<

Bence791

Knight

Posts: 618

Joined: 20 Jul 2012, 20:25

KaM Skill Level: Beginner

Location: Hungary

Post 02 Jul 2013, 17:31

Re: Dynamic Script Usage

Thorakh, to get a local variable, the easiest way to do it is to put it between the procedure and the first "begin". Assigning it to something can be either between the procedure and begin/begin and end/end and begin I think.
The Kamper is always taking my colour!

<<

Tef

User avatar

Lance Carrier

Posts: 64

Joined: 15 Apr 2013, 15:12

KaM Skill Level: Skilled

Post 02 Jul 2013, 17:49

Re: Dynamic Script Usage

Let me help you just a little bit more Islar, because this was something that took me forever to find out because handy tutorials are not easy to find. Suppose you want to collect the number of sawmills, when you have no clue where they are and how many there are. You can now use PlayerGetAllHouses. Voila! There are all the houseIDs. But how should you begin? Just take a look at this
  Code:
var Searcher: integer; // create a variable that 'walks' through an array AllHouseIDs: array of integer; // create an empty array to store houseIDs into begin AllHouseIDs := States.PlayerGetAllHouses(0); // store all of the IDS of player 1 into this array // you can access all four houseIDs now by typing: AllHouseIDs[0], AllHouseIDs[1], AllHouseIDs[2], and AllHouseIDs[3] // you could do this manually, but using a for loop is better, so you can extend it: // you define a start value and end value in a for loop. You should want to go from index 0 to 3, so you could write: // for Searcher := 0 to 3 do begin ... but it would be better to do it like this: for Searcher := 0 to (length(AllHouseIDs)-1) do begin // now it will go up to the length of AllHouseIDs. It is (length -1) because the indexes are 0..3 while the length=4. // test this: Actions.ShowMsg(0, 'HouseID of the house at index ' + Searcher + ' is:' IntToStr(AllHouseIDs[Searcher]); // count the sawmills and put their IDs and HouseResourceAmount into an array // probably the best thing to do is to define a new type and record: search back into this topic to learn how to define this! end end
this should get you started a little..
<<

Siegfried

User avatar

Knight

Posts: 494

Joined: 24 Jul 2009, 22:00

Post 03 Jul 2013, 21:50

Re: Dynamic Script Usage

What is the PlayerID of the replay 'see all' player?

Would be nice to use OverlayTextSet in the replay :)
<<

Lewin

User avatar

KaM Remake Developer

Posts: 3822

Joined: 16 Sep 2007, 22:00

KaM Skill Level: Skilled

ICQ: 269127056

Website: http://lewin.hodgman.id.au

Yahoo Messenger: lewinlewinhodgman

Location: Australia

Post 04 Jul 2013, 07:13

Re: Dynamic Script Usage

What is the PlayerID of the replay 'see all' player?

Would be nice to use OverlayTextSet in the replay :)
Currently it's always the first player I think (no matter which player you are looking at), but I'd like to make it change depending on which player you have selected (and use -1 for "see all" maybe?). I've added this to the todo list. So currently there's no way to use OverlayTextSet properly in the replay unfortunately.
<<

Tef

User avatar

Lance Carrier

Posts: 64

Joined: 15 Apr 2013, 15:12

KaM Skill Level: Skilled

Post 06 Jul 2013, 00:07

Re: Dynamic Script Usage

Hi guys,

Does anyone know how (cross)bowmen currently detect units within their range? When I initially made a script for this, I used an 'if UnitAt(States.UnitPostionX,States.UnitPositionY)' approach, but I changed this because calculations exponentially blow up for large detection ranges. So now I am calculating x and y differences by going through all the UnitIDs in the States.PlayerGetAllUnits array. However, this is also very laggy if the amount of 'detectors' and units gets big. For a big 8-player map, it should work smoothly for 2400 units (each village has 100 serfs, 100 citizens, 100 soldiers) and 100 detectors. I've also noticed in general that the States.UnitPositionX and States.UnitPositionY commands are pretty nasty things for KAM Remake to handle, I am encountering problems with this for other scripts as well. So my main question is:
- how can I create an efficient unit detection algorithm? (range may be fixed, same range as for bowmen)
<<

Lewin

User avatar

KaM Remake Developer

Posts: 3822

Joined: 16 Sep 2007, 22:00

KaM Skill Level: Skilled

ICQ: 269127056

Website: http://lewin.hodgman.id.au

Yahoo Messenger: lewinlewinhodgman

Location: Australia

Post 06 Jul 2013, 01:29

Re: Dynamic Script Usage

We didn't really design the script to do what you are trying to do, we imagined much simpler uses of the script. To make a really efficient algorithm you would need us to expose more of the inner workings of the engine (which we don't want to expose because we often change them).

What do you mean States.UnitPositionX and States.UnitPositionY are nasty for the Remake? They should just be simple commands that tell you where the unit is.

Or did you mean UnitAt is nasty? It should be quite efficient because we keep track of which unit is standing on which tile. By comparison HouseAt is slow because it scans through all houses checking to see if they are at the right place (we don't keep track of which house is on each tile because it's not used much compared to units). But scanning a large area with UnitAt or HouseAt is always going to be slow. Even just a small 10x10 area is 100 tiles to be checked.

Basically there are two methods you can use:
a) Use UnitAt to scan each tile in the area.
b) Scan through GetAllUnits and check their positions.

a) is best for smaller areas. UnitAt is an effective function (but that doesn't mean it's going to cope well if you are scanning a massive 100x100 area). b) is best for cases where there are not many total units and cases where the area is too large to scan every tile (e.g. the entire map). Going though GetAllUnits is not very efficient if there are a lot of units belonging to that player.

If you only care about soldiers you might find it more efficient to make b) use GetAllGroups and check each unit with GroupMember.

It is also possible to spread out processing of a) over multiple ticks (if that is accepting for your usage of the command). You can use tricks like processing every 2nd row of the map (e.g. Y=2,4,6 one tick, then Y=1,3,5 next tick). That's ultimately the best way to make it efficient, we do this internally in some places like making trees/corn grow.

You should also spread out your usage of your unit detection function. Say you have 100 units who will each be scanning in front of them (like bowmen or something), then you should do their detection on different ticks. A neat way to do this is to use their UnitID (the unique ID the game gives every unit to identify it), because those numbers will be well distributed. Lets say you have an array of units called UnitsArray, here's how you can spread it out:
  Code:
for I:=0 to Length(UnitsArray) do if UnitsArray[I]+States.GameTime mod 10 = 0 then DoDetection(UnitsArray[I]);
This means each unit will be processed every 10 ticks, and the distribution should be fairly random because you add their ID. Obviously if you can make 10 larger it will spread it out even further, but you might need them checking more often than that.

In the KaM Remake engine bowmen/crossbowmen use the method a), however it's more efficient than it would be for you from the script since it's using lower level access into the engine, and the code is natively compiled instead of using byte code like the script does. It is actually one of the most CPU consuming aspects of the game (when you have a lot of ranged units) so we've put quite a lot of effort into optimising it. Each unit checks every 8 ticks, using a similar method as the one I outlined above.

Return to “Dynamic Scripting”

Who is online

Users browsing this forum: No registered users and 5 guests