Map Database  •  FAQ  •  RSS  •  Login

Dynamic Script Usage Questions

<<

The Dark Lord

User avatar

King Karolus Servant

Posts: 2154

Joined: 29 Aug 2007, 22:00

KaM Skill Level: Veteran

Location: In his dark thunderstormy castle

Post 22 Jun 2013, 18:35

Re: Dynamic Script Usage

Okay, so I continue my adventure. :P
  Code:
function ClosestUnit(aPlayer, X, Y: Integer): Integer; var Groups: array of Integer; i, BestDistanceSqr, ThisDistanceSqr, DX, DY: Integer; begin Result := -1; Groups := States.PlayerGetAllUnits(aPlayer); for i := 0 to Length(Groups) -1 do begin DX := X - States.UnitPositionX(States.GroupMember(Groups[i], 0)); DY := Y - States.UnitPositionY(States.GroupMember(Groups[i], 0)); ThisDistanceSqr := (DX*DX) + (DY*DY); if (Result = -1) or (ThisDistanceSqr < BestDistanceSqr) then begin BestDistanceSqr := ThisDistanceSqr; Result := Groups[i]; end; end; end;
If I use States.PlayerGetAllGroups, nothing happens, if I use States.PlayerGetAllUnits, the AI seems to attack random targets? *Confused!*
<<

Siegfried

User avatar

Knight

Posts: 494

Joined: 24 Jul 2009, 22:00

Post 22 Jun 2013, 18:41

Re: Dynamic Script Usage

If I use States.PlayerGetAllGroups, nothing happens, if I use States.PlayerGetAllUnits, the AI seems to attack random targets? *Confused!*
I can only guess because I don't know the purpose of your script, but I'd say it's the following: GetAllUnits gives an array with all units, not just military ones. So the next best unit is attacked, even if it is a civilianz.

To fix this, check if the unit as civilian and skip those.
  Code:
function ClosestUnit(aPlayer, X, Y: Integer): Integer; var Groups: array of Integer; i, BestDistanceSqr, ThisDistanceSqr, DX, DY: Integer; begin Result := -1; Groups := States.PlayerGetAllUnits(aPlayer); for i := 0 to Length(Groups) -1 do begin if States.UnitType(Groups[i]) < 14 then continue; DX := X - States.UnitPositionX(States.GroupMember(Groups[i], 0)); DY := Y - States.UnitPositionY(States.GroupMember(Groups[i], 0)); ThisDistanceSqr := (DX*DX) + (DY*DY); if (Result = -1) or (ThisDistanceSqr < BestDistanceSqr) then begin BestDistanceSqr := ThisDistanceSqr; Result := Groups[i]; end; end; end;
<<

The Dark Lord

User avatar

King Karolus Servant

Posts: 2154

Joined: 29 Aug 2007, 22:00

KaM Skill Level: Veteran

Location: In his dark thunderstormy castle

Post 22 Jun 2013, 18:47

Re: Dynamic Script Usage

Well thanks, that works perfectly. :P
The initial problem was indeed that this function was scouting for units, including villagers. So the AI tried to attack units inside buidings. I tried to change it so it would only scan the groups, but needed some help with that. But since there aren't that many units on the map, your solution will be fine.
However, the 'random targets' the AI attacked previously were really random... no idea what happened there.
<<

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 22 Jun 2013, 19:06

Re: Dynamic Script Usage

What Siegfried wrote won't work.... the distance calculations won't work because he's still using "States.GroupMember(Groups, 0)" when the array Groups is actually units...

There are three different ID systems: Groups, Units and houses. Do NOT mix them. If a function wants a group ID, do not give it a unit ID, and vice versa. They are not at all compatible. PlayerGetAllUnits gives you an array of units. PlayerGetAllGroups give you an array of groups.

You could correct Siegfried's function, but I would suggest you use PlayerGetAllGroups because it will be more efficient (there's far less groups than units), and it filters it down to only military for you already. Here's the complete function:
  Code:
function ClosestUnit(aPlayer, X, Y: Integer): Integer; var Groups: array of Integer; i, BestDistanceSqr, ThisDistanceSqr, DX, DY, Unit: Integer; begin Result := -1; Groups := States.PlayerGetAllGroups(aPlayer); for i := 0 to Length(Groups) -1 do begin Unit := States.GroupMember(Groups[i], 0); DX := X - States.UnitPositionX(Unit); DY := Y - States.UnitPositionY(Unit); ThisDistanceSqr := (DX*DX) + (DY*DY); if (Result = -1) or (ThisDistanceSqr < BestDistanceSqr) then begin BestDistanceSqr := ThisDistanceSqr; Result := Unit; end; end; end;
Note that this function returns a Unit ID now, you had it returning a group ID.
<<

Siegfried

User avatar

Knight

Posts: 494

Joined: 24 Jul 2009, 22:00

Post 22 Jun 2013, 19:38

Re: Dynamic Script Usage

Lewin is right, there were some mix ups in the ids. I did not even look inside the function. I just saw the description and gave my solution, because I had similar problems quite frequently with my script.

But it made me laugh. TDL described a problem. I only saw a fix to this problem - but Lewin fixed up everything right away :lol:

That's actually support you can only dream of. Nice work! :)
<<

Tef

User avatar

Lance Carrier

Posts: 64

Joined: 15 Apr 2013, 15:12

KaM Skill Level: Skilled

Post 25 Jun 2013, 23:25

Re: Dynamic Script Usage

I'm having this problem with something I thought was going to be very simple. Suppose there is a chosen soldier within an unknown group with an unknown groupsize. This chosen soldier needs to be isolated, so this soldier will become a separate one-man-group with its own flag. I could choose the easy way: kill the chosen unit and recreate it again. However, I wanted to be more realistic and use GroupOrderSplit. I wanted to use the following script, but it does not work. Consider ThisUnitX and ThisUnitY to be known. What am I doing wrong?
  Code:
*if any trigger then begin* // Determine the group id of the chosen unit GroupOfUnit := States.GroupAt(ThisUnitX, ThisUnitY); GroupSize := States.GroupMemberCount(GroupOfUnit); SplitCounter := 0; while GroupSize > 1 do begin // Assign the newly splitted group id to SplittedGroup SplittedGroup := Actions.GroupOrderSplit(GroupOfUnit); // To figure out if the chosen unit is in SplittedGroup or GroupOfUnit, define NewGroupOfUnit NewGroupOfUnit := States.GroupAt(ThisUnitX, ThisUnitY); setLength(SplittedGroups, SplitCounter + 1); // If the chosen unit is in the SplittedGroup, then store the original GroupOfUnit if NewGroupOfUnit = SplittedGroup then SplittedGroups[SplitCounter] := GroupOfUnit; // If the chosen unit is NOT in the SplittedGroup, then store the splittedgroup if NewGroupOfUnit <> SplittedGroup then SplittedGroups[SplitCounter] := SplittedGroup; // Update the GroupOfUnit and GroupSize, because this while loop needs to stop if GroupSize = 1 GroupOfUnit := States.GroupAt(ThisUnitX, ThisUnitY); GroupSize := States.GroupMemberCount(GroupOfUnit); // Increase the SplitCounter, so that the appropriate index in SplittedGroups is targeted SplitCounter := SplitCounter + 1; end; // Now the chosen unit should be isolated as a single unit and all other groups are stored // into SplittedGroups. Now join all other splitted groups together again. for SplittedWalker := 0 to (length(SplittedGroups)-1) do Actions.GroupOrderLink(SplittedGroups[SplittedWalker],SplittedGroups[0]); end;
<<

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 26 Jun 2013, 00:26

Re: Dynamic Script Usage

I was planning to add a command to split a single unit out of a group eventually because it's not easy to implement in the script.

I can't see anything immediately wrong with your code. What happens instead of the desired result? One bad thing that you do is telling a group to link to itself. Change the final for-loop to be "for SplittedWalker := 1" (instead of 0) because it is linking all the groups to element 0.

Another fun solution would be to use recursion: (recursion is always fun)
  Code:
var LinkGroup: Integer; procedure RecursiveGroupSplit(aSplitGroup, aUnit: Integer); var NewGroup, TheUnit: Integer; begin if States.GroupMemberCount(aSplitGroup) = 1 then begin TheUnit := States.GroupMember(aSplitGroup, 0); //Relink all the groups except the unit which we want to leave on his own if TheUnit <> aUnit then begin if LinkGroup = -1 then LinkGroup := aSplitGroup else Actions.GroupOrderLink(aSplitGroup, LinkGroup); end; end else begin //Keep splitting down until we have just 1 unit left in the group NewGroup := Actions.GroupOrderSplit(aSplitGroup); RecursiveGroupSplit(aSplitGroup, aUnit); RecursiveGroupSplit(NewGroup, aUnit); end; end; procedure SplitSingleUnitFromGroup(aGroup, aUnit: Integer); begin LinkGroup := -1; RecursiveGroupSplit(aGroup, aUnit); end;
Just call SplitSingleUnitFromGroup with your group and the unit you want to split out of it and it should hopefully work (don't use RecursiveGroupSplit or LinkGroup in your code, they are only needed internally). This code would be easier to extend to do other tasks (such as linking units of a certain type or something). Another advantage is that I think it won't change the leader of the old group (unless he's the unit being split off of course) because of the order of the recursion.
<<

Tef

User avatar

Lance Carrier

Posts: 64

Joined: 15 Apr 2013, 15:12

KaM Skill Level: Skilled

Post 26 Jun 2013, 11:50

Re: Dynamic Script Usage

Your script behave exactly the same as mine, Lewin EDIT: Obviously not, but my point is: It is also not working. I also think I know why they don't work:

SplittedGroup := Actions.GroupOrderSplit(OriginalGroup). <-- SplittedGroup has the same ID as OriginalGroup!

However, if you ask the splitted units their GroupID afterwards through States.GroupAt(X,Y) then they have different GroupIDs. You can check this by pasting my test code into any empty script file, it will work immediately:
  Code:
const NOTHING = -1; PLAYER_1 = 0; SWORD_FIGHTER = 16; LOOKING_LEFT = 6; var LinkGroup: Integer; Procedure SplitAndJoinThatShit; var GroupOfUnit, GroupSize, SplitCounter, SplittedWalker, SplittedGroup, NewGroupOfUnit: integer; SplittedGroups: array of integer; ThisUnitX, ThisUnitY: integer; begin ThisUnitX := 50; ThisUnitY := 32; GroupOfUnit := States.GroupAt(ThisUnitX, ThisUnitY); GroupSize := States.GroupMemberCount(GroupOfUnit); SplitCounter := 0; Actions.OverlayTextAppend(-1,'|Initial GroupID = ' + inttostr(GroupOfUnit)); while GroupSize > 1 do begin SplittedGroup := Actions.GroupOrderSplit(GroupOfUnit); Actions.OverlayTextAppend(-1,'|ID of SplittedGroup = ' + inttostr(SplittedGroup)); Actions.OverlayTextAppend(-1,'|ID of GroupOfUnit = ' + inttostr(GroupOfUnit)); Actions.OverlayTextAppend(-1,'||GroupID at position 50,32 = ' + inttostr(States.GroupAt(50,32))); Actions.OverlayTextAppend(-1,'|GroupID at position 50,33 = ' + inttostr(States.GroupAt(50,33))); NewGroupOfUnit := States.GroupAt(ThisUnitX, ThisUnitY); setLength(SplittedGroups, SplitCounter + 1); if NewGroupOfUnit = SplittedGroup then SplittedGroups[SplitCounter] := GroupOfUnit; if NewGroupOfUnit <> SplittedGroup then SplittedGroups[SplitCounter] := SplittedGroup; GroupOfUnit := States.GroupAt(ThisUnitX, ThisUnitY); GroupSize := States.GroupMemberCount(GroupOfUnit); SplitCounter := SplitCounter + 1; end; for SplittedWalker := 1 to (length(SplittedGroups)-1) do begin Actions.GroupOrderLink(SplittedGroups[SplittedWalker],SplittedGroups[0]); end; end; Procedure OnTick; begin if states.gametime = 10 then begin Actions.GiveGroup(PLAYER_1,SWORD_FIGHTER,50,32,LOOKING_LEFT,2,2); end; if states.gametime = 40 then begin Actions.OverlayTextAppend(-1,'|Split at gametime = 40:|'); SplitAndJoinThatShit; end; if states.gametime = 60 then begin Actions.OverlayTextAppend(-1,'||Info at gametime = 60:'); Actions.OverlayTextAppend(-1,'|GroupID at position 50,32 = ' + inttostr(States.GroupAt(50,32))); Actions.OverlayTextAppend(-1,'|GroupID at position 50,33 = ' + inttostr(States.GroupAt(50,33))); end; end;
I'd say this is a bug. Strange, because I was pretty sure it worked in any version before the 3rd beta of the scripted demo. So I guess I will just use the GroupAt(X,Y) trick to find all the ID's and paste them back together.
<<

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 27 Jun 2013, 06:14

Re: Dynamic Script Usage

Your script behave exactly the same as mine, Lewin EDIT: Obviously not, but my point is: It is also not working. I also think I know why they don't work:

SplittedGroup := Actions.GroupOrderSplit(OriginalGroup). <-- SplittedGroup has the same ID as OriginalGroup!
Ah you're right, there's a typo in GroupOrderSplit which causes it to return the original group's ID :(
I've fixed it so it will work correctly in the next release/update.
Thanks for your help!
<<

Islar

Pikeman

Posts: 180

Joined: 22 Apr 2013, 20:18

KaM Skill Level: Average

Location: The Netherlands

Post 01 Jul 2013, 15:11

Re: Dynamic Script Usage

Hi,

is it possible with dynamic scripting to give a ware to every allied player's storehouse when for example a Quarry makes his stone, a farm harvest his corn and a sawmail makes his timber? This for a multiplayer map that i want to make.

If anyone knows if this is possible would he/she explain how to use it?
thanks
<<

dicsoupcan

Moorbach's Guard

Posts: 1314

Joined: 12 Feb 2012, 21:36

KaM Skill Level: Fair

Post 01 Jul 2013, 15:21

Re: Dynamic Script Usage

Sado and i are wondering if it is possible to implement scripted becons, they do not have to play a sound or maybe a custom sound can be played with it. It will be nice if you for example spawn forces and want to notify the player of it.
You have enemies? Good. That means you've stood up for something, sometime in your life. ~ Winston Churchill
<<

Thorakh

Recruit

Posts: 31

Joined: 27 Jan 2011, 23:00

Post 01 Jul 2013, 15:59

Re: Dynamic Script Usage

I'm trying to spawn two groups of scouts at the start of a mission, store their group IDs in two variables and when both groups have died, display a message.

When I run the mission it gives me an internal script error and I have no idea what's causing it.

edit: Damn this arcane language syntax, I used = where I should've been using := to assign the group variables. One problem solved, but now I get a type mismatch error in ONUNITDIED at 36:1. There's probably also a better way to check whether a group has died :P
  Code:
var GroupID1, GroupID2 : Integer; MessageShown : Boolean; procedure OnMissionStart; begin MessageShown := False; GroupID1 := Actions.GiveGroup(1, 21, 44, 11, 2, 6, 3); GroupID2 := Actions.GiveGroup(1, 21, 48, 11, 2, 6, 3); end; procedure OnUnitDied; begin if (States.GroupDead(GroupID2) And States.GroupDead(GroupID2) And Not MessageShown) then begin Actions.ShowMsg(0, 'test'); MessageShown := True; end; end;
Does anyone know what's going wrong here?
Last edited by Thorakh on 01 Jul 2013, 16:10, edited 1 time in total.
<<

Bence791

Knight

Posts: 618

Joined: 20 Jul 2012, 20:25

KaM Skill Level: Beginner

Location: Hungary

Post 01 Jul 2013, 16:09

Re: Dynamic Script Usage

You should write "procedure OnUnitDied(aUnitID, Integer);" instead of "procedure OnUnitDied" 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 01 Jul 2013, 21:35

Re: Dynamic Script Usage

Does anyone know what's going wrong here?
Bence791 is almost right. You should write: Procedure OnUnitDied(aUnitID, aKillerIndex: integer). Also, I don't understand why you check if GroupID2 is dead, and GroupID2 is dead (you probably mean GroupID1 and GroupID2).
is it possible with dynamic scripting to give a ware to every allied player's storehouse when for example a Quarry makes his stone, a farm harvest his corn and a sawmail makes his timber? This for a multiplayer map that i want to make.
Well, I think that is not possible when the occupant of the house starts producing. But when it finishes producing (i.e. a ware is added to his own house), you could do that. You could store the number wares of all houses of interest in an array, for example MyHouseWareArray. You can ask for the amount of wares of a house through HouseResourceAmount(HouseID, WareType). Next, you check within Procedure OnTick if the actual ware amount of houses has increased, compared to the stored ware amounts in your array. If so, this means that the occupant has just finished a ware. You could then use GiveWares(PlayerID, WareType, amount) to add that specific ware to your first storehouse, or HouseAddWaresTo(HouseID, WareType, amount) for any other storehouse.
Sado and i are wondering if it is possible to implement scripted becons, they do not have to play a sound or maybe a custom sound can be played with it. It will be nice if you for example spawn forces and want to notify the player of it.
What do you mean with scripted becons? (I assume beacon? Then again, I still do not understand your question :p) Ah, you mean the thing in the minimap when you press B and click somewhere on the map! That's not possible as far as I know...
<<

Bence791

Knight

Posts: 618

Joined: 20 Jul 2012, 20:25

KaM Skill Level: Beginner

Location: Hungary

Post 01 Jul 2013, 22:04

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 :)
The Kamper is always taking my colour!


Return to “Dynamic Scripting”

Who is online

Users browsing this forum: Google [Bot] and 3 guests