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 19 Apr 2013, 03:09

Re: Dynamic Script Usage

Spawner is a group ID, not a unit ID. UnitHungerSet is expecting a unit ID. You need to use a for-loop and run UnitHungerSet on every unit in the group (using GroupMember)
<<

Ben

User avatar

Former Site Admin

Posts: 3814

Joined: 08 Jan 2009, 23:00

Location: California - Pacific Time (UTC -8/-7 Summer Time)

Post 19 Apr 2013, 18:51

Re: Dynamic Script Usage

Is there a way to automatically detect all units on the map?
I used to spam this forum so much...
<<

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 19 Apr 2013, 22:46

Re: Dynamic Script Usage

Is there a way to automatically detect all units on the map?
That will be added in the next RC (States.PlayerGetAllUnits which returns an array of Unit IDs). The same will be provided for houses and groups.
<<

Ben

User avatar

Former Site Admin

Posts: 3814

Joined: 08 Jan 2009, 23:00

Location: California - Pacific Time (UTC -8/-7 Summer Time)

Post 20 Apr 2013, 04:42

Re: Dynamic Script Usage

Alright, then I will just wait to implement this, because just selecting all units will be easier, and will be better in my case ;)
I used to spam this forum so much...
<<

Tef

User avatar

Lance Carrier

Posts: 64

Joined: 15 Apr 2013, 15:12

KaM Skill Level: Skilled

Post 22 Apr 2013, 12:28

Re: Dynamic Script Usage

I'd like to share my custom procedure to manage overlay text on the screen. I did that because I find that adding and removing texts manually through Actions.SetOverlayText, and manipulating durations to show the text is cumbersome. It gets even more messy when several notifications are overlapping in the time that they need to be shown. My custom ScreenText procedure has five parameters to change the way any new piece of text is treated:

1) show a new piece of text on a new line, at the same line, put it on the top row or clear all current overlay text while adding a new line
2) make any piece of new text visible to a specific player, or all players at once
3) set the duration to show the new piece of text
4) extract the text that needs to be shown through a entry in the mission libx file
5) alternatively, do not use a libx file and feed custom strings to this procedure

Because I am using a global multi-dimensional array and a array of strings to queue the texts, KaM gives a warning. I would be very interested if somebody here could tell me how to change/improve my script to avoid this warning. The script is below. Just Copy-Paste this in any script file, it should work just fine. I welcome any feedback or suggestions for improvement!
  Code:
var {used by ScreenText} LibxTextQueue: array [1..100] of array [1..4] of integer; //100 vertical and 4 horizontal StringTextQueue: array[1..100] of string; procedure ScreenText(DesiredAction: string; PlayerID: integer; Duration: integer; LibxEntry: integer; DirectStringInput: string); {This procedure generates text on the screen through Actions.SetOverLayText. valid input: 1st parameter, DesiredAction: 'OnNewLine, OnSameLine, OnTopLine, ResetAndNewLine, UpdateScreenText' 2nd parameter, PlayerID: -1 for all players, 0..7 for specific player 3rd parameter, Duration: 1..any integer for the duration, in seconds 4th parameter, LibxEntry: index of [missionname].libx file to be shown, or -1 if direct string input in 5th parameter 5th parameter, DirectStringInput: alternatively, use a direct string input here. Note: use -1 for 4th parameter. Example call: ScreenText('OnNewLine',2,5,-1,'This is my custom message');} var VerticalCounter: integer; HorizontalCounter: integer; DownShiftCounter: integer; PlayerCounter: integer; FirstEmptySlot: integer; DeletedRows: integer; TheOverlayText: string; NewLineSpace: string; begin {Start by checking if the input parameters make sense} // check 1st parameter if not((DesiredAction = 'OnNewLine') or (DesiredAction = 'OnSameLine') or (DesiredAction = 'OnTopLine') or (DesiredAction = 'ResetAndNewLine') or (DesiredAction = 'UpdateScreenText')) then begin for PlayerCounter := 0 to 7 do begin Actions.ShowMsg(PlayerCounter,'Error! Procedure ScreenText was called with an invalid first parameter for the intended action (' + DesiredAction + '). Use single quotes and enter either: OnNewLine, OnSameLine, OnTopLine, ResetAndNewLine or UpdateScreenText.'); exit; end; end; // check 2nd parameter if (PlayerID<-1) or (PlayerID>7) then begin Actions.ShowMsg(PlayerCounter,'Error! Procedure ScreenText was called with an invallid second parameter for the intended player ID (' + IntToStr(PlayerID) + '). Use -1 for all players, or 0 up to 7 for a specific player'); exit; end; //check 3rd parameter if (Duration <= 0) and not (DesiredAction = 'UpdateScreenText') then begin Actions.ShowMsg(PlayerCounter,'Error! Procedure ScreenText was called with an invallid third parameter for the intended message duration (' + IntToStr(Duration) + '). Enter any integer larger than 0. Duration is in seconds, not in ticks!'); exit; end; // check 4th paramter if LibxEntry <-1 then begin Actions.ShowMsg(PlayerCounter,'Error! Procedure ScreenText was called with an invallid fourth parameter for the entry in the [missionname].libx file (' + IntToStr(LibxEntry) + '). Usage 1: any integer larger or equal to 0. Usage 2: type in -1, in combination with a custom string in the fifth parameter.'); exit; end; //check 5th parameter if (LibxEntry >= 0) and not (DirectStringInput = '') then begin Actions.ShowMsg(PlayerCounter,'Error! Procedure ScreenText was called with the fourth parameter (' + IntToStr(LibxEntry) + ') and the fifth parameter with the non-empty string (' +DirectStringInput+ '). Please provide: -1 (4th argument) and string (5th parameter). Or provide: 0..any number (4th parameter) and no string (5t parameter).'); exit; end; {if input is OnNewLine, OnSameLine, OnTopLine or UpdateScreenText find length of queued texts} if (DesiredAction = 'OnNewLine') or (DesiredAction = 'OnSameLine') or (DesiredAction = 'OnTopLine') or (DesiredAction = 'UpdateScreenText')then begin for VerticalCounter := 1 to 100 do begin if LibxTextQueue[VerticalCounter][2] = 0 then begin FirstEmptySlot := VerticalCounter; break; end; end; end; {If input is ResetAndNewLine then clear all texts} if (DesiredAction = 'ResetAndNewLine') then begin for VerticalCounter := 1 to 100 do begin for HorizontalCounter := 1 to 4 do begin LibxTextQueue[VerticalCounter][HorizontalCounter] := 0; end; StringTextQueue[VerticalCounter] := ''; end; FirstEmptySlot := 1; DesiredAction := 'OnNewLine'; end; {If OnNewLine, OnSameLine or OnTopLine then add to queue} if (DesiredAction = 'OnNewLine') or (DesiredAction = 'OnSameLine') or (DesiredAction = 'OnTopLine') then begin LibxTextQueue[FirstEmptySlot][1] := PlayerID; LibxTextQueue[FirstEmptySlot][3] := States.GameTime + (Duration*10); if DesiredAction = 'OnNewLine' then LibxTextQueue[FirstEmptySlot][2] := 1; //1 means: OnNewLine if DesiredAction = 'OnSameLine' then LibxTextQueue[FirstEmptySlot][2] := 2; //2 means: OnSameLine if DesiredAction = 'OnTopLine' then LibxTextQueue[FirstEmptySlot][2] := 3; //3 means: OnTopLine LibxTextQueue[FirstEmptySlot][4] := LibxEntry; if LibxEntry >= 0 then begin StringTextQueue[FirstEmptySlot] := ''; end; if LibxEntry = -1 then begin StringTextQueue[FirstEmptySlot] := DirectStringInput; end; end; {If 'UpdateScreenText' then refresh overlay text on screen} if DesiredAction = 'UpdateScreenText' then begin {First, search for strings that need to be deleted} for VerticalCounter := 1 to (FirstEmptySlot-1) do begin // if gametime larger than indicated deletetime then delete corresponding text if (LibxTextQueue[VerticalCounter][3] >0) and (States.GameTime >= LibxTextQueue[VerticalCounter][3]) then begin for DownShiftCounter := VerticalCounter to (FirstEmptySlot-1) do begin for HorizontalCounter := 1 to 4 do begin LibxTextQueue[DownShiftCounter][HorizontalCounter] := LibxTextQueue[DownShiftCounter+1][HorizontalCounter]; end; StringTextQueue[DownShiftCounter] := StringTextQueue[DownShiftCounter+1]; end; VerticalCounter := VerticalCounter - 1; end; end; {Second, process all queued text and actually generate the overlay text} for PlayerCounter := 0 to 7 do begin TheOverlayText := ''; for VerticalCounter := 1 to (FirstEmptySlot - DeletedRows) do begin if (LibxTextQueue[VerticalCounter][1] = PlayerCounter) or (LibxTextQueue[VerticalCounter][1] = -1) then begin // Add newline character if OnNewLine or OnTopLine and not first line if ((LibxTextQueue[VerticalCounter][2] = 1) or (LibxTextQueue[VerticalCounter][2] = 3)) and (VerticalCounter>1) then NewLineSpace := '|'; // if OnNewLine and libx index 0 or bigger provided if (LibxTextQueue[VerticalCounter][2] = 1) and (LibxTextQueue[VerticalCounter][4] >= 0) then TheOverlayText := TheOverlayText + NewLineSpace + States.Text(LibxTextQueue[VerticalCounter][4]) + ' '; // if OnNewLine and libx index -1 provided if (LibxTextQueue[VerticalCounter][2] = 1) and (LibxTextQueue[VerticalCounter][4] = -1) then TheOverlayText := TheOverlayText + NewLineSpace + StringTextQueue[VerticalCounter] + ' '; // if OnSameLine and libx index 0 or bigger provided if (LibxTextQueue[VerticalCounter][2] = 2) and (LibxTextQueue[VerticalCounter][4] >= 0) then TheOverlayText := TheOverlayText + States.Text(LibxTextQueue[VerticalCounter][4]) + ' '; // if OnSameLine and libx index -1 provided if (LibxTextQueue[VerticalCounter][2] = 2) and (LibxTextQueue[VerticalCounter][4] = -1) then TheOverlayText := TheOverlayText + StringTextQueue[VerticalCounter] + ' '; // if OnTopLine and libx index 0 or bigger provided if (LibxTextQueue[VerticalCounter][2] = 3) and (LibxTextQueue[VerticalCounter][4] >= 0) then TheOverlayText := States.Text(LibxTextQueue[VerticalCounter][4]) + NewLineSpace + TheOverlayText + ' '; // if OnTopLine and libx index -1 provided if (LibxTextQueue[VerticalCounter][2] = 3) and (LibxTextQueue[VerticalCounter][4] = -1) then TheOverlayText := StringTextQueue[VerticalCounter] + NewLineSpace + TheOverlayText + ' '; end; Actions.SetOverLayText(PlayerCounter,TheOverlayText); end; end; end; end;
To illustrate how this custom procedure works, paste this:
  Code:
procedure onTick; begin if States.GameTime = 10 then ScreenText('OnNewLine',-1,10,-1,'This is message 1, show me for ten seconds!'); if States.GameTime = 30 then ScreenText('OnNewLine',-1,10,-1,'This is message 2, show me for ten seconds!'); if States.GameTime = 50 then ScreenText('OnSameLine',-1,10,-1,'This is message 3, on the same line!'); if States.GameTime = 70 then ScreenText('OnNewLine',-1,10,-1,'Message 4!'); if States.GameTime = 70 then ScreenText('OnNewLine',-1,2,-1,'Message 5 at the same time! But I am only visible for 2 seconds!'); if States.GameTime = 110 then ScreenText('ResetAndNewLine',-1,10,-1,'I am deleting all previous messages!'); if States.GameTime = 130 then ScreenText('OnTopLine',-1,10,-1,'I am later but get to be on top!'); if States.GameTime mod 5 = 0 then ScreenText('UpdateScreenText',-1,-1,-1,''); end;
<<

Krom

User avatar

Knights Province Developer

Posts: 3281

Joined: 09 May 2006, 22:00

KaM Skill Level: Fair

Location: Russia

Post 22 Apr 2013, 13:06

Re: Dynamic Script Usage

That's an awesome idea Tef :)

We plan to implement such a function into Remake scripts, so you could have all this with something like DisplayText(player, timeout, text); and have a list of expiring messages.
Not sure if we could fit that into closest release, but one after it will have it.

We don't want to allow global string variables, because that would make MP saves incompatible if they were saved from different locales.
Knights Province at: http://www.knightsprovince.com
KaM Remake at: http://www.kamremake.com
Original MBWR/WR2/AFC/FVR tools at: http://krom.reveur.de
<<

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 Apr 2013, 13:27

Re: Dynamic Script Usage

Thanks for helping find the bug that allowed scripts to execute even if they had fatal warnings (like unsupported global variables, which can cause inconsistencies/errors after loading a save if the script is allowed to run). In the next release only allowed global variables will work, but we will support a lot more types as global variables such as records, multidimensional and dynamic arrays, enumerators, sets, etc. :)
<<

Tef

User avatar

Lance Carrier

Posts: 64

Joined: 15 Apr 2013, 15:12

KaM Skill Level: Skilled

Post 22 Apr 2013, 15:28

Re: Dynamic Script Usage

We don't want to allow global string variables, because that would make MP saves incompatible if they were saved from different locales.
Because of different languages, right? But I still have no clue how to avoid global strings in some cases. I challenge you:

Suppose there is Team A (0,1,2,3) and Team B (4,5,6,7). Player 0 is doing something that causes a timer to run, which needs the 'playername' of player 0 to be shown only to Team A as overlay text + the status of the timer. But, while the timer runs, also additional notifications need to be shown. So the 'timer-text (=player name, + timer)' and ' other notifications' all need to be shown simultaneously. Dynamic stuff like player names or timers cannot be put into a libx file, so you need dynamic strings. However, local strings won't work, because the procedure 'forgets' any older information when local strings are used. Must I now start coding strings as integers? :? How to solve this case?!
<<

Krom

User avatar

Knights Province Developer

Posts: 3281

Joined: 09 May 2006, 22:00

KaM Skill Level: Fair

Location: Russia

Post 22 Apr 2013, 18:20

Re: Dynamic Script Usage

We realize that essentially you've made an extension to let mapmakers display short messages that auto-remove themselves after some time. We plan to add function similar to those you have made, but directly into the Remake scripting module, so all global strings you have used will be managed by Remake. The script will only have to request ScreenText part.
Knights Province at: http://www.knightsprovince.com
KaM Remake at: http://www.kamremake.com
Original MBWR/WR2/AFC/FVR tools at: http://krom.reveur.de
<<

Philymaster

Peasant

Posts: 4

Joined: 02 Dec 2012, 00:43

KaM Skill Level: Skilled

Post 02 May 2013, 18:23

Re: Dynamic Script Usage

Hey, i encountered a problem while scripting a function which search for soldiers of a player in an area.

if ((GroupID <> -1) and (States.GroupOwner(GroupID) = PlayerID)) then begin
Result := true;
end;

I always use short-circuit evaluation in other languages, but in pascal script it let the game freeze or bring down the performance so the mission doesn't start.
If i however write two "if constructs" it works flawless? Its my fault or does not pascal support this feature?

Just curious, because for me its more readable :)
<<

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 May 2013, 23:16

Re: Dynamic Script Usage

Thanks for bringing that up Philymaster. Short circuit evaluation is enabled by default in Delphi and Lazarus, we use it all the time in the KaM Remake code. However, the PascalScript interpreter has it disabled by default for some reason... I've enabled it now so in the next RC code like you wrote will work :)

The freezing and low performance is possibly because it is generating a lot of errors that are written to the log files. Open up the latest file in the Logs folder and scroll to the bottom, see if there are script errors (in this case it will complain that you used GroupOwner with -1 as the GroupID, because it's not using short circuit evaluation)
<<

Philymaster

Peasant

Posts: 4

Joined: 02 Dec 2012, 00:43

KaM Skill Level: Skilled

Post 03 May 2013, 22:31

Re: Dynamic Script Usage

Thanks for bringing that up Philymaster. Short circuit evaluation is enabled by default in Delphi and Lazarus, we use it all the time in the KaM Remake code. However, the PascalScript interpreter has it disabled by default for some reason... I've enabled it now so in the next RC code like you wrote will work :)

The freezing and low performance is possibly because it is generating a lot of errors that are written to the log files. Open up the latest file in the Logs folder and scroll to the bottom, see if there are script errors (in this case it will complain that you used GroupOwner with -1 as the GroupID, because it's not using short circuit evaluation)
Thanks Lewin, i'm looking forward to the new release. :)
Unfortunately i have two new problems. (Or one problem and one question) :(

I have written a function which returns an array with all group ids of a player and wanted to use the for each loop (for ... in ... do) and your script validator expected a regular for. Is this feature also deactivated?

Also i found a other problem with the procedures GroupOrderStorm and GroupOrderWalk.

My code was:
Actions.GroupOrderStorm( ... ); // Enemy Group 1
Actions.GroupOrderStorm( ... ); // Enemy Group 2
Actions.GroupOrderWalk( ... ); // Enemy Group 3

The first and second group spawn in front of the players groups so they attack after few tiles running. The third group is far away and on the way to the player, but once one of the group start to fight the third group don't walk anymore and just stop. I've tested it with multiple groups and all just stop.

I assume its a bug and not desired? I will script a refresh procedure which will be called every second and will test if they stop multiple times then.
<<

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 May 2013, 00:49

Re: Dynamic Script Usage

The script compiler doesn't seem to support for..in loops. You can use an iterator instead fairly easily: for I:=0 to Length(array)-1 do array

The second problem might be an issue with the AI trying to take control of that group to help in the battle, I'll look into it.
<<

sado1

User avatar

Council Member

Posts: 1430

Joined: 21 May 2012, 19:13

KaM Skill Level: Skilled

Post 14 May 2013, 14:15

Re: Dynamic Script Usage

  Code:
for I := 0 to Length(States.PlayerGetAllHouses(P))-1 do begin ID := States.PlayerGetAllHouses(P)[I]; //rest of the code follows...
returns an error, it expects a semicolon after (P) in the second line here. I'm trying to iterate through all the houses of a player, and each time save ID as an auxilarry variable.

(before you ask, I just compiled 5252 to have all the new needed scripting commands, so I have the PlayerGetAllHouses state)
<<

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 14 May 2013, 14:22

Re: Dynamic Script Usage

Don't use States.PlayerGetAllHouses "inline" like that. For a start that's incredibly inefficient, it's regenerating the list of houses every time you use States.PlayerGetAllHouses. Secondly, it's possible that the PascalScript interpreter can't handle syntax like "States.PlayerGetAllHouses(P)".

Instead, declare an array as a local variable:
var Houses: array of Integer;

Then do:
Houses := States.PlayerGetAllHouses(P);

And the rest of your code becomes:
for I := 0 to Length(Houses)-1 do
ID := Houses;

That will be 100 times more efficient/faster because it's only generating the list of houses once, and should compile fine.

Return to “Dynamic Scripting”

Who is online

Users browsing this forum: No registered users and 1 guest