Home   Help Search Login Register  

Author Topic: Some tips for addon scripter & mission designer  (Read 9108 times)

0 Members and 1 Guest are viewing this topic.

Offline Killswitch

  • Members
  • *
  • Peace, cheese and ArmA
Re:Some tips for addon scripter & mission designer
« Reply #30 on: 24 Apr 2003, 18:58:19 »
>One more q: when I have like 3 vehicles on a map, and all players start outside these vehicles, will the vehicles then be local to the server only?

Yes. I did some testing on the locality of units and vehicles a while ago. I had me (player),
a Jeep and 2 AI:s not in my group in the mission.

From start, the 2 AI:s are local to the (ded) server and so is the empty Jeep.

IIRC, when I got in the Jeep as driver, it then got local to my machine. It remained local to
my machine even after I got out. At least for a few seconds (didn't wait too long, so I don't know if the "locality" is transferred back to the server after a timeout)

I had a radio trigger that made one of the AIs get in as driver in the jeep, and if I then
entered in the back seat of the jeep, it was still remote (i.e. local to the server). I cant recall
what happened when I (via a trigger) had the AI get out... have to find my test mission again.

I also remember getting in the Jeep as cargo (empty driver seat) but cant recall if the Jeep
remained local to the server or was made local to my machine...

I belive the rule for vehicles is that it is (gets) local to the driver's/pilot's machine. Not sure who
gets locality in a tank (cmdr/driver/gunner), though.


Tactician

  • Guest
Re:Some tips for addon scripter & mission designer
« Reply #31 on: 24 Apr 2003, 19:32:21 »
Tanks seem to always be local to the driver.  This is why the tank driver should always be someone with a good connection :)

I should say that the tank hull is local to the driver.  The gun turret is local to the gunner and the commander turret is local to the commander, naturally.  Interestingly, I've never seen the tank turret skip around the way I've seen the tank hull skip around, probably due to its simplicity; all it has is direction, bank, and pitch or the barrel and turret (most of which can be gleaned from the position of the tank itself) and no messy velocity vectors to be broadcast.

Rubble_Maker

  • Guest
Re:Some tips for addon scripter & mission designer
« Reply #32 on: 24 Apr 2003, 21:26:05 »
Small addition to the command reference; did a 'lil test yesterday:

VehicleRadio
cutRsc

are both local.

animate

is global. Might give synch probs if run from more than one machine, so I guess its best run from the server only.

btw. anybody experienced problems with using the "Time" function in MP?
I cannot confirm this yet, but I experienced some odd problems that might have to do with the Time function. I used it as the condition for scripts triggered by user actions, and none of the actions worked on the clients, only on the server they did. I might be wrong here, but I didn't find any other reason why the actions wouldn't work.

Offline Dinger

  • Contributing Member
  • **
  • where's the ultra-theoretical mega-scripting forum
Re:Some tips for addon scripter & mission designer
« Reply #33 on: 24 Apr 2003, 21:38:50 »
Don't use time for anything critical.
time resets on savegames, which is a problem in SP.
In MP, I'm not sure how well it's synchronized.  I guess that's easy to test.
Dinger/Cfit

Rubble_Maker

  • Guest
Re:Some tips for addon scripter & mission designer
« Reply #34 on: 25 Apr 2003, 09:11:44 »
Allright, I settled all my useraction probs now; it was some wrong conditions in the scripts, and not a general problem :)

The challenge now is to properly replicate all local events on all clients. Therefore I will definetely use the client-server buffer communication I described before. It will allow my to use the same code for SP and MP, as even in SP there will be such a buffer (might introduce a very small latency, but the simplicitiy of it more than makes up for a lil speed penalty). Using the buffer method I no longer have to care about which events/actions trigger which scripts under which condition (i.e. if unit local to the machine). The problem is how to determine when an entry in the buffer (i.e. a command to be called on each client) can be removed. I first thought that having a simple counter would be sufficient, as each client would just increase that counter after dispatching the command, and kick it out once the counter equals the total number of clients. The problem is that the same client could dispatch the command twice this way. So I rather need to tag the command with something that says wether or not a specific client has already dispatched the command. This of course is where you usually use a bitmask, but ofp doesn't support it.

Offline uiox

  • Contributing Member
  • **
Re:Some tips for addon scripter & mission designer
« Reply #35 on: 25 Apr 2003, 11:19:15 »
I have done something, work and test.
It's a MP dialog for a multi-squad script.

First create array of players and call inits (in init after server side test):

Code: [Select]
[ArrayGroupEastServer ] exec "Init\InitTaskServer.sqs"
Install server loops (select 0 is the group name, select 6 a predifened ID number) :

Code: [Select]
_ArrayOfparam = _this select 0

_i = 0
#Install
_SubArray = _ArrayOfparam select _i
[ _SubArray select 0, _SubArray select 6] exec "Task\ServerTask.sqs"
_i = _i +1
? _i < count _arrayOfParam : goto "Install"


the server side loop install for each player.

Code: [Select]
_Group = _This select 0
_IdGroup = _This select 1

;                  Assemblage de la variable d'attente

Call format ["PubWaitEvent%1%2 = false ; publicvariable {PubWaitEvent%1%2}",side _Group  ,_IdGroup ]
#return

;                   Mise en attente du script d'un appel
@(Call format ["PubWaitEvent%1%2",side _Group  ,_IdGroup ])
;                   Un appel a été demandé par client
;                  On remet la variable d'attente à faux car on boucle dans ce script
Call format ["PubWaitEvent%1%2 = false ; publicvariable {PubWaitEvent%1%2}",side _Group  ,_IdGroup ]

;                  Convention du systéme : lorsque un client appelle il dit aussi ce qu'il veux faire
;                  Donc on récupère l'action demandée
_TypeAction = Call format ["PubTypeOfAction%1%2",side _Group  ,_IdGroup ]

;                  On se branche sur l'action demandée
goto format ["%1",_TypeAction ]
;                  Un petit test au cas ça merde, car si on est ici c que ça pue :)
Goto "end"

;                  Déplacement d'un groupe
#1
_Xmove = Call format ["PubXmove%1%2",side _Group  ,_IdGroup ]
_Ymove = Call format ["PubYmove%1%2",side _Group  ,_IdGroup ]
 _group move [ _Xmove,_Ymove]

Goto "End"
;                  Changer le mode de combat
#2
_Combat = Call format ["PubCombat%1%2",side _Group ,_IdGroup  ]
Leader(_group ) setcombatMode (ArrayCombatGroup select _Combat)
Goto "End"
;                  Changer le mode de formation
#3
_Formation = Call format ["Pubformation%1%2",side _Group ,_IdGroup ]
Leader(_group ) setformation  (ArrayFormationGroup select _Formation)
Goto "End"
;                  Changer la vitesse
#4
_Speed = Call format ["PubSpeed%1%2",side _Group ,_IdGroup ]
Leader(_group ) setspeedMode  (ArraySpeedGroup select _Speed )
Goto "End"
;                  Changer l'engagement au combat
#5
_Behaviour = Call format ["PubBehaviour%1%2",side _Group ,_IdGroup ]
Leader(_group ) setbehaviour  (ArrayBehaviourGroup select _Behaviour)
Goto "End"
;                  Changer la position debout ou allongé
#6
_Unitpos = Call format ["PubUnitpos%1%2",side _Group ,_IdGroup  ]
{_x setUnitPos  (ArrayUnitposGroup select _Unitpos) } foreach units _group
Goto "End"
;                  Changer la direction ou le groupe scrute
#7
_WatchDir = Call format ["PubWatchDir%1%2",side _Group ,_IdGroup  ]
_Group setFormDir _WatchDir
Goto "End"

#End
;                  Se remet en attente
Goto "Return"


Local side caller script


Code: [Select]
;["Move",_Group,_PosMove] exec "eventmanager\ExecOnServer.sqs"         1
;["SetCombat",_whocall ,lbCurSel 1501] exec "eventmanager\ExecOnServer.sqs"      2
;["SetFormation",_whocall ,lbCurSel 1502] exec "eventmanager\ExecOnServer.sqs"      3
;["SetSpeed",_whocall ,lbCurSel 1503] exec "eventmanager\ExecOnServer.sqs"      4
;["SetBehaviour",_whocall ,lbCurSel 1504] exec "eventmanager\ExecOnServer.sqs"      5
;["SetUnitPos",_whocall ,lbCurSel 1505] exec "eventmanager\ExecOnServer.sqs"      6
;["SetFormDir",_whocall ,_TempDir ] exec "eventmanager\ExecOnServer.sqs"      7

_CurrentGroup = _this select 1
_Group = ArrayGroupParameters select _CurrentGroup select 0
_IDGroup = ArrayGroupParameters select _CurrentGroup select 6


_Where = _this select 0

Goto _Where
Goto "End"

#Move
_PosMove = _this select 2

Call format ["PubWaitEvent%1%2 = true ; publicvariable {PubWaitEvent%1%2}",side _Group ,_IdGroup ]
Call format ["PubTypeOfAction%1%2 = 1 ; publicvariable {PubTypeOfAction%1%2}",side _Group ,_IdGroup ]
Call format ["PubXmove%1%2 = %3 ; publicvariable {PubXmove%1%2}",side _Group ,_IdGroup,_PosMove select 0  ]
Call format ["PubYmove%1%2 = %3 ; publicvariable {PubYmove%1%2}",side _Group ,_IdGroup,_PosMove select 1  ]
Goto "End"

#SetCombat
_TypeCombat = _this select 2
Call format ["PubWaitEvent%1%2 = true ; publicvariable {PubWaitEvent%1%2}",side _Group ,_IdGroup ]
Call format ["PubTypeOfAction%1%2 = 2 ; publicvariable {PubTypeOfAction%1%2}",side _Group ,_IdGroup ]
Call format ["PubCombat%1%2 = %3 ; publicvariable {PubCombat%1%2}",side _Group ,_IdGroup,_TypeCombat  ]
Goto "End"

#SetFormation
_TypeFormation = _this select 2
Call format ["PubWaitEvent%1%2 = true ; publicvariable {PubWaitEvent%1%2}",side _Group ,_IdGroup ]
Call format ["PubTypeOfAction%1%2 = 3 ; publicvariable {PubTypeOfAction%1%2}",side _Group ,_IdGroup ]
Call format ["Pubformation%1%2 = %3 ; publicvariable {Pubformation%1%2}",side _Group ,_IdGroup,_Typeformation  ]
Goto "End"

#SetSpeed
_TypeSpeed = _this select 2
Call format ["PubWaitEvent%1%2 = true ; publicvariable {PubWaitEvent%1%2}",side _Group ,_IdGroup ]
Call format ["PubTypeOfAction%1%2 = 4 ; publicvariable {PubTypeOfAction%1%2}",side _Group ,_IdGroup ]
Call format ["PubSpeed%1%2 = %3 ; publicvariable {PubSpeed%1%2}",side _Group ,_IdGroup,_TypeSpeed  ]
Goto "End"

#SetBehaviour
_TypeBehaviour = _this select 2
Call format ["PubWaitEvent%1%2 = true ; publicvariable {PubWaitEvent%1%2}",side _Group ,_IdGroup ]
Call format ["PubTypeOfAction%1%2 = 5 ; publicvariable {PubTypeOfAction%1%2}",side _Group ,_IdGroup ]
Call format ["PubBehaviour%1%2 = %3 ; publicvariable {PubBehaviour%1%2}",side _Group ,_IdGroup,_TypeBehaviour  ]
Goto "End"

#SetUnitPos
_TypeUnitPos = _this select 2
Call format ["PubWaitEvent%1%2 = true ; publicvariable {PubWaitEvent%1%2}",side _Group ,_IdGroup ]
Call format ["PubTypeOfAction%1%2 = 6 ; publicvariable {PubTypeOfAction%1%2}",side _Group ,_IdGroup ]
Call format ["PubUnitpos%1%2 = %3 ; publicvariable {PubUnitpos%1%2}",side _Group ,_IdGroup,_TypeUnitpos  ]
Goto "End"

#SetFormDir
_WatchDir = _this select 2
Call format ["PubWaitEvent%1%2 = true ; publicvariable {PubWaitEvent%1%2}",side _Group ,_IdGroup ]
Call format ["PubTypeOfAction%1%2 = 7 ; publicvariable {PubTypeOfAction%1%2}",side _Group ,_IdGroup ]
Call format ["PubWatchDir%1%2 = %3 ; publicvariable {PubWatchDir%1%2}",side _Group ,_IdGroup,_WatchDir  ]
Goto "End"

#End


And how execute a command in the dialog (not entire script, only main loop and events) :

Code: [Select]
_Update = time +1
;****************************************************************************   Main events loop
         #MainLoop
         ~0.0001
         ? (_OldUnit != lbCurSel 1500)and ( lbCurSel 1500 !=-1) : Goto "1500"
         ? (_OldCombat  != lbCurSel 1501)and ( lbCurSel 1501 !=-1) : Goto "1501"
         ? ( _OldFormation != lbCurSel 1502)and ( lbCurSel 1502 !=-1) : Goto "1502"
         ? ( _OldSpeed != lbCurSel 1503)and ( lbCurSel 1503 !=-1) : Goto "1503"
         ? ( _OldBehaviour != lbCurSel 1504)and ( lbCurSel 1504 !=-1) : Goto "1504"
         ? ( _OldUnitPos != lbCurSel 1505)and ( lbCurSel 1505 !=-1) : Goto "1505"
         ? ( _OldAction != lbCurSel 1508)and ( lbCurSel 1508 !=-1) : Goto "1508"
         ? ( _OldInstantAction != lbCurSel 1510)and ( lbCurSel 1510 !=-1) : Goto "1510"
         

         ? Notaction : goto "Notaction"; Notaction = false
         ? ConfirmAction : goto "ConfirmAction";ConfirmAction  = false

         ? _update > time : goto "skipUpdateInfos"
         _Update = time +1

         goto "UpdateValuesOfgroup"
         #skipUpdateInfos
         
?  !(cancel) and !(validation)and (ctrlVisible 1500) : goto "Mainloop"

;µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ  End of Main event loop

? cancel : goto "cancel"
? !(validation) : goto "cancel"



? validation : goto "store"


#Store

cancel = false
validation = false
closedialog 0

goto "endScript"

#Cancel
cancel = false
validation = false
closedialog 0

#endScript



exit


;******************************************************* Management of events


#1500
? _OneOrderInProgress : lbSetCurSel [1500, _OldUnit ] ; Goto "MainLoop"
_OldUnit =lbCurSel 1500
 _WhoCall = lbCurSel 1500; _Return = "Return1500" ; _OldUnit == lbCurSel 1500; [ lbCurSel 1500] exec "gfolder\Group.sqs";goto "UpdateValuesOfgroup"
#Return1500
Goto "MainLoop"

#1501
_OldCombat =lbCurSel 1501
_CurrentCombat = lbtext [1501,lbCurSel 1501]
["SetCombat",_whocall ,lbCurSel 1501] exec "eventmanager\ExecOnServer.sqs"

#Return1501
Goto "MainLoop"

#1502
_Oldformation =lbCurSel 1502
_Currentformation = lbtext [1502,lbCurSel 1502]
["SetFormation",_whocall ,lbCurSel 1502] exec "eventmanager\ExecOnServer.sqs"


#Return1502
Goto "MainLoop"

#1503
_Oldspeed =lbCurSel 1503
_Currentspeed = lbtext [1503,lbCurSel 1503]
["SetSpeed",_whocall ,lbCurSel 1503] exec "eventmanager\ExecOnServer.sqs"


With this method you create all server side commands.
« Last Edit: 25 Apr 2003, 11:34:06 by uiox »

Offline rom

  • Members
  • *
  • . . .
Re:Some tips for addon scripter & mission designer
« Reply #36 on: 25 Apr 2003, 11:26:51 »
To start stuff on the server and the clients you can exploit the createUnit command.
The init property of this command is treated like the init line of a unit in the mission editor and run on the server and the clients. The only thing you have to take care of is that the variables you use in the init are known and have the same values on the server and every client (just publicVariable them).

Offline uiox

  • Contributing Member
  • **
Re:Some tips for addon scripter & mission designer
« Reply #37 on: 25 Apr 2003, 11:36:47 »
 :D Good tip Rom, never think, thx !

Rubble_Maker

  • Guest
Re:Some tips for addon scripter & mission designer
« Reply #38 on: 25 Apr 2003, 15:04:09 »
Very good idea rom! Some questions:

- will the "createUnit" init string be run on both client and server even if a game logic is created? Coz these normally are simulated only on the client

- can I use global vars to pass the names of other units to the init field? For example, if I want a unit to play a sound on all clients, using your method I'd simply use "createUnit" to make a game logic, and put something like

"globalUnitNameVar say ""sound"""

into the init string. But would all clients recognize the unit stored in
globalUnitNameVar? The unit names are different depending on wether they're local or remote, so maybe the above wouldn't work?

Offline rom

  • Members
  • *
  • . . .
Re:Some tips for addon scripter & mission designer
« Reply #39 on: 25 Apr 2003, 16:49:09 »
I just got the idea of using the createUnit command for this purpose yesterday and I was only playing around with normal soldiers since now so I need to do some further tests to know how exactly this could be used.



[edit:]

I did the following test:

This is the init eventhandler in the config.cpp of a truck:
Code: [Select]
class eventhandlers
{
   init  = "[_this] exec ""\truckca\scripts\unitinit.sqs""";
};


this is the unitinit.sqs:
Code: [Select]
_obj = (_this select 0) select 0
truck1 = _obj
publicVariable "truck1"

~5

_org = group _obj
[_obj] join grpNull
_new = group _obj
truck1 sidechat "init truck1"
"Logic" createUnit [GetPos _obj, _new,"[truck1] exec ""\truckca\scripts\test.sqs""",0.5,"PRIVATE"]
[_obj] join _org

~10
deletevehicle ((units _new) select 0)

and this is the test.sqs:
Code: [Select]
(_this select 0) sidechat "truck1"
hint "test"

The test mission was the following setup:
1 unnamed playable truck
1 unnamed playable soldier
both in different groups

When running the mp mission on a dedicated server and connecting with two clients (one playing the truck and the othe rplaying the soldier), both the sidechat and the hint from the test.sqs worked on both clients. The sidechat in the unitinit.sqs wasn't seen on the clients

So my conclusion of this test would be that it doesn't matter if you createUnit normal soldiers or gamelogics, the init of the command is allways executed on the server and all the clients.
Also it doesn't matter if the objects that are used in the init are local or not as long as they were syncronized with publicVariable before.
« Last Edit: 25 Apr 2003, 18:06:48 by rom »

Rubble_Maker

  • Guest
Re:Some tips for addon scripter & mission designer
« Reply #40 on: 25 Apr 2003, 18:53:17 »
If this really works it'd be incredibly useful. One problem I see is that in oder to run a certain command on all clients, you need to pass a lot of information, e.g. unit name, position, command ID, etc pp.

So all this would have to be broadcast using global variables. Well, this I don't like really because it introduces other synchronization problems. Imagine you don't run that "unitinit" script from the init EH but from, say, the Engine EH. Now it'd be launched with each engine on/off event on each of the vehicles which have the EH installed. So chances are high they will overwrite the global variables that we use to pass the information. For example, the first truck would launch the script with a global variable

truck1= myTruck

then another EH kicks in an overwrites the variable with another vehicle:

truck1= hisTruck

now the same script runs twice and thus duplicates the command!

Thats a very nasty side effect. What I suggest so solve it is passing an init string which is compiled at runtime, not static, using the infamous "call format" trick. For example:

_dummyVector=[_truck]
_initstring=
Format ["[_dummyVector select %1] exec ""\truckca\scripts\test.sqs"""",0]

No idea if this works though; the problem is to paste the unit name into the string, which is quite a PITA in ofp.

Rubble_Maker

  • Guest
Re:Some tips for addon scripter & mission designer
« Reply #41 on: 25 Apr 2003, 19:01:08 »
If it does not work, there might be another possibility:

we don't use the same global variable all the time; instead we use a set of metavaiables. Say we use 10 metavaiabes with indices 0..9, then we compute the index we gotta use for our script call using

index=(index+1)%10

so we toggle the metavariables with each script call, thus minimizing the chance that two scripts overwrite their variables.


Offline rom

  • Members
  • *
  • . . .
Re:Some tips for addon scripter & mission designer
« Reply #42 on: 25 Apr 2003, 20:40:40 »
You will allways have those syncronisation problems when you try to run scripts on all clients. The only thing that makes the createUnit command easier is the way you get the scripts started on all clients.

I guess that the broadcast to the clients is done with a queue by OFP, so that if you execute 2 publicVriable commands on the server after each other the order of the variable changes on the clients and the server is allways the same (the broadcasting doesn't swap the two publicVariables).
So when you allways do the publicVariable on the server then the only syncronisation problem would be if there were two scripts running on the server at the same time.

An easy way to prevent this is the use of semaphore variables.
A simple example for your engine EH:

The engine EH calls a engineEH.sqs:
Code: [Select]
class eventhandlers
{
   Engine  = "[_this] exec ""engineEH.sqs""";
};

 This engineEH.sqs then uses the CreateUnit init to start a globalEngine.sqs script:
Code: [Select]
?!local Server) : exit

;wait until another server engine script is finished then set the semaphore to let other server scripts wait.
@EngineSemaphore==false
EngineSemaphore = true

_obj = (_this select 0) select 0
engineobj= _obj
publicVariable "engineobj"

~5

_before = units tempgroup
"Logic" createUnit [[0,0,0], tempgroup,"[engineobj] exec ""globalEngine.sqs""",0.5,"PRIVATE"]
_after = units tempgroup
_logic = (_after - _before) select 0

; now the sequence of publicVariable and createUnit is in the broadcast queue and nothing gets between those two commands on the clients too. So we reset the semaphore to let other scripts run.
EngineSemaphore = false



~100
deletevehicle _logic
(tempgroup is a group that you need to createUnit something, look at my previous post for an example how to get such a group)

Because of the use of the semaphore it isn't possible that the engineobj is changed by another engineEH.sqs between the publicVariable and the createUnit command.
The globalEngine.sqs then does the work that you want to be done on the server and the clients.

Normally this method should work quite well since even if it seem that two scripts are running at the same time, the CPU only works with one and then switches to the other after some time and back so that it only seems that the two scripts are running at the same time.
There is a really small chance that this switching between two scripts that run at the same time happens exactly between those two commands:
@EngineSemaphore==false
EngineSemaphore = true
This chance is really small and I didn't have any problems so far. But if this appears to be an issue then there are some enhanced semaphore technics that use a wait for a random time to prevent those problems with simultanious starting scripts.
« Last Edit: 25 Apr 2003, 20:45:09 by rom »

Rubble_Maker

  • Guest
Re:Some tips for addon scripter & mission designer
« Reply #43 on: 25 Apr 2003, 21:02:59 »
Yes I agree, the chance is *really small*, but I actually experienced such a situation once. When testing the scripts for my mission "The Prey", I observed a weird error in my voice player script. It occured only 2 or 3 times although I tested the scripts for several month, and it turned out that the problem was several scripts running simultaneously:

_unit say (_voice select _index)
_index=_index+1                                    <==
?_index>MAXINDEX:_index=0

I marked the line which caused the error. I had several instances of this script running, and sometimes ofp would pass control over to one script just when another script's process was at the marked line. I'd then get an overflow error.

So, the chance might be little, but it is still there. Especially in MP there is a certain latency between the server issuing the command from the game logic's init line, and the client running that command. In the meantime the global variable could well be overwritten by another script.

Your semaphore method does indeed solve the problem, but it introduces further latency, as now the scripts have to wait until the other scripts are finished. Also, the semaphore needs to be broadcast to all clients before the script is clear to launch. This latency might cause problems, especially if you do things like install a "Fired" eventhandler to a cannon or rocket launcher.


Rubble_Maker

  • Guest
Re:Some tips for addon scripter & mission designer
« Reply #44 on: 26 Apr 2003, 19:54:57 »
rom, I tested your code today and it crashed all the time. Obviously the problem is in this line:

_org = group _obj
[_obj] join grpNull                          <=====
_new = group _obj
truck1 sidechat "init truck1"
"Logic" createUnit [GetPos _obj, _new, ....]

Basically you're calling createUnit with an empty group, which at least on my machine causes a serious crash (re-boot).