Page 1 of 2

Passing variable number of params from AlcScript to Python?

PostPosted: Mon Jun 27, 2011 8:53 am
by tachzusamm
I wrote a PythonFileMod which can "handle" multiple objects. With "handling" I mean move them, but this does not matter here.

The problem I ran into is, how can it be done to make the Python file mod accept a variable number of objects?

Example AlcScript snippet:
Code: Select all
                parameters:
                  - type: activator
                    ref: logicmod:logicMod_WarpEnter
                  - type: activator
                    ref: logicmod:logicMod_WarpExit
                 
                  - type: int
                    value: 12    # Number of warp objects
                 
                  - type: sceneobject
                    ref: scnobj:WarpObj_1
                  - type: sceneobject
                    ref: scnobj:WarpObj_2
                  - type: sceneobject
                    ref: scnobj:WarpObj_3
            ...
                  - type: sceneobject
                    ref: scnobj:WarpObj_n


Well, sounds easy, but getting the attributes must (AFAIK) be done in the "beginning" (the "global" part) of the Python file, like this:
Code: Select all
from Plasma import *
from PlasmaTypes import *
actEnter = ptAttribActivator(1, 'Enter region activator')
actExit = ptAttribActivator(2, 'Exit region activator')

numWarps = ptAttribInt(3)   # Number ob objects to warp
warpOb_1 = ptAttribSceneobject(4)
warpOb_2 = ptAttribSceneobject(5)
...
warpOb_n = ptAttribSceneobject(n)
...
(class definition starts here)

because trying to fetch them later, e.g. in "OnServerInitComplete()" in the class does not work.

But, in the global section, the *values* of the passed parameters are not yet available.
So an attempt like this:
Code: Select all
obList = []
for obIndex in range(numWarps.value):
    warpOb = ptAttribSceneobject(obIndex+4)
    obList.append((warpOb))

failed (obList is empty); numWarps.value is just 0 in the global section. The values become available later in the class methods.

Just omitting the numWarps int and checking the warpOb_x instead, e.g. in a try: except: block checking if it has a getName() property for example, did not work too.


How would this be handled normally? (IF it's possible, that is)

tach

Re: Passing variable number of params from AlcScript to Pyth

PostPosted: Mon Jun 27, 2011 9:50 am
by diafero
Nexus passes a SceneObjectList or similar to the Python file, but I have no clue how to write that in AlcScript:

Code: Select all
objlistLinkPanels = ptAttribSceneobjectList(17, 'Objct: Link Panels')
...
for objPanel in objlistLinkPanels.value:
    objPanel.doSomething()

Re: Passing variable number of params from AlcScript to Pyth

PostPosted: Mon Jun 27, 2011 10:03 am
by D'Lanor
I would use a scene object list. The AlcScript notation for lists is a bit tricky but I found that this works (At least for activator- and responder lists. I haven't used object lists yet).
Code: Select all
                  - type: sceneobjectlist
                    refs: ['scnobj:WarpObj_1',
                           'scnobj:WarpObj_2',
                           'scnobj:WarpObj_3']

Then this goes into the global section of your Python code:
Code: Select all
warpObjs = ptAttribSceneobjectList(3, 'list of warp objects')
objList = []


And you populate your Python list during OnFirstUpdate.
Code: Select all
    def OnFirstUpdate(self):
        for warpOb in warpObjs.value:
            objList.append(warpOb)

Re: Passing variable number of params from AlcScript to Pyth

PostPosted: Tue Jun 28, 2011 5:32 am
by tachzusamm
First, thank you again.

I tried it, but... well, I would not say it did not work, but... yeah, its really weird.

The modified script did not do anything in the first place; the Python0.log gave the error:
"AttributeError: 'NoneType' object has no attribute 'isInitialStateLoaded'"

Trying to find what's wrong, I removed more and more from the Python code, until just basic stuff remained - and STILL the error exists.
Nothing left from the sceneobjectlist and warping action stuff, and even removed the object parameters from the AlcScript (only keeping the logicmod params); still "no attribute 'isInitialStateLoaded'".

The strange thing is, that my original script worked (the one which only warped one object), and this has the SAME framework; the only difference
is that it includes the warping stuff.

Totally confused, I just added the original script under a different name into the age.pak as well (it now contains a paraWarpObject.py and a paraWarpObList.py), so there are two absolutely identical scripts, just with a different name.
Calling the first one from AlcScript works, the other gives the error mentioned.

Again confused, but thinking "hey, maybe having two similar scripts in the PAK causes problems", so I removed the first one, keeping just "paraWarpObList.py" - but, you won't believe it, this did not work either. Same error.

What the hell...

Any ideas?


Code: Select all
(06/28 12:22:44) We're linking out...
(06/28 12:22:44) UamEvents.RegisterForOnServerInitComplete: <function OnServerInitComplete at 0x251C5D60>
(06/28 12:22:44) _UamVars.Reset
(06/28 12:22:44) _UamTimer.Reset
(06/28 12:22:47) _UamEvents._OnPageLoad what=2 room='Personal_District_psnlMYSTII'
(06/28 12:22:47) _UamEvents._OnPageLoad what=2 room='Personal_psnlDustAdditions'
(06/28 12:22:50) Traceback (most recent call last):
(06/28 12:22:50)   File "C:\DOKUME~1\Riker\LOKALE~1\Temp\tmp3D4.tmp\paraWarpObList.py", line 283, in glue_setParam
(06/28 12:22:50)   File "C:\DOKUME~1\Riker\LOKALE~1\Temp\tmp3D4.tmp\paraWarpObList.py", line 223, in glue_getParamDict
(06/28 12:22:50)   File "C:\DOKUME~1\Riker\LOKALE~1\Temp\tmp3D4.tmp\paraWarpObList.py", line 201, in glue_findAndAddAttribs
(06/28 12:22:50) AttributeError: 'NoneType' object has no attribute 'isInitialStateLoaded'
(06/28 12:22:50) __init__paraFogFade v.1
(06/28 12:22:50) __init__paraFogChange v.1
(06/28 12:22:51) _UamEvents._OnPageLoad what=1 room='Ani_District_mainRoom'
(06/28 12:22:51) _UamEvents._OnServerInitComplete
(06/28 12:22:51) _UamModReset.UamOnNewAgeLoaded
(06/28 12:22:51) _UamVars.OnServerInitComplete
(06/28 12:22:51) xKI.OnServerInitComplete(): age =  Ani
(06/28 12:22:51) paraFogFade.OnServerInitComplete:
(06/28 12:22:51) Traceback (most recent call last):
(06/28 12:22:51)   File "C:\DOKUME~1\Riker\LOKALE~1\Temp\tmp2C1.tmp\paraFogFade.py", line 49, in OnServerInitComplete
(06/28 12:22:51) AttributeError: 'NoneType' object has no attribute 'isInitialStateLoaded'
(06/28 12:22:51) _UamEvents._OnAvatarSpawn
(06/28 12:22:51) _UamModReltopages.UamOnNewAgeLoaded

(Sidenode: "paraFogFade.py" is a different, older script and works well (as long before) unless I include paraWarpObList.py too; but even removing it from the PAK does not change anything. And the really, REALLY, additional strange thing is: URU STILL complains about "no attribute 'isInitialStateLoaded' in the paraFogFade.py (!!) script, which is not even there any longer! Although it's included in another age's PAK...)
Maybe we can't have identical scripts in different ages? (But, this did not matter in the first place, with my original script...)
Something really weird is going on here.


By the way: What is the self.id and self.version under __init__ for?
And what does the Python glue do?

Re: Passing variable number of params from AlcScript to Pyth

PostPosted: Tue Jun 28, 2011 11:35 am
by diafero
I have no clue what this weird error is caused by, but usually if I have weird Python errors, I forgot to change the class name to match the file name. This is absolutely crucial for Uru Python to work.
Glue Python code allows the C++ code to mess with the runtime state of this Python module... Cyan, to no surprise, chose a very strange way to integrate Python into their engine (instead of instantiating classes, the whole module is somehow instantiated - different PythonFileMods will have different values for global vriables, but modules imported are shared with other PythonFileMods).

Re: Passing variable number of params from AlcScript to Pyth

PostPosted: Tue Jun 28, 2011 1:25 pm
by D'Lanor
tachzusamm wrote:By the way: What is the self.id and self.version under __init__ for?

They have no use within the game itself. They exists because the Max plugin needs them.

tachzusamm wrote:And the really, REALLY, additional strange thing is: URU STILL complains about "no attribute 'isInitialStateLoaded' in the paraFogFade.py (!!) script, which is not even there any longer! Although it's included in another age's PAK...)

Yes, each module is loaded from the pak file with the latest time stamp, regardless what age it is from. Duplicates should be avoided. That is why it is the custom to add an age prefix to Python file names/classes.
This can get tricky with global files. For example if a writer heavily modifies a global module and distributes it with his own age then any other age using that module will break.

Re: Passing variable number of params from AlcScript to Pyth

PostPosted: Wed Jun 29, 2011 12:00 am
by diafero
Yes, all the pak file's contents are merged by Uru, the originating pak file does not play any role during module load. Which is the reason why fan-ages should never overwrite global files (which my scripts actually automatically check for, all the fan-ages currently in UAM are fine). Offline KI can do that since it is explicitly incompatible with all other global modifications.

Re: Passing variable number of params from AlcScript to Pyth

PostPosted: Wed Jun 29, 2011 1:53 am
by tachzusamm
diafero wrote:[..], but usually if I have weird Python errors, I forgot to change the class name to match the file name.

LOL !!
Diafero, you hit the nail on its head here!
Actually, I really forgot to name the class as the file in this case. Funny thing is, I already knew that it's important, but, well, sometimes I've got "Ein Brett vor dem Kopf". :lol:
To bring it to the point: Removing this mistake, suddenly everything worked fine. Thank you so much for mentioning the obvious.

diafero wrote:Glue Python code allows the C++ code to mess with the runtime state of this Python module...

Aha, so this is the Python part of the engine to Python interface; now I understand why it's called glue.


D'Lanor wrote:
tachzusamm wrote:By the way: What is the self.id and self.version under __init__ for?

They have no use within the game itself. They exists because the Max plugin needs them.

Good to know; so I can stop thinking about numbers which are not used elsewhere.

D'Lanor wrote:Yes, each module is loaded from the pak file with the latest time stamp, regardless what age it is from. Duplicates should be avoided. That is why it is the custom to add an age prefix to Python file names/classes.
This can get tricky with global files. For example if a writer heavily modifies a global module and distributes it with his own age then any other age using that module will break.

This is really useful information; I did not know that. It explains why I got this strange effects.

As said, it already works now, like a charm, and totally as expected.
Thanks again to both of you, D'Lanor and diafero.


===================================================================

Okay, now let me reveal why I did all this (warping multiple objects around):

I love to have vegetation in an age. Huge vegetation, grass, bushes, flowers and trees all around the places.
But - there's a problem with it: the number of faces of all objects in total will grow, the more you copy plants around, slowing down the engine's speed. Yes, there's the option to make use of VisRegions, which can help a lot, but putting duplicates of plants in various regions of an age increases the pak's file size and loading time.
So I came up with an idea: What if I just warp objects around between regions, depending on the player's location, instead of just hiding/unhiding them? This could keep the file size lower, while keeping the facility to have objects shown to the player everywhere.

Imagine there are 3 regions A, B and C; some plants are normally located at the (link-in) region A. The player now enters a region B, from where region A can't be seen - now we warp the plants to region B, giving the illusion there is again plenty of vegetation. Same with region C, and back to region A.
Actually dozens of regions could "hold" a vast amount of plants.
True, this is "faking", or let me call it doing illusion - but hey, isn't the whole URU game mostly working this way?

To simplify the handling, I implemented making use of "hooks" in the script. This means, you can put empties (as hooks) in the Age, working as placeholders for the destinations for the real objects. This is much easier than having to type co-ordinates into AlcScript as destinations.
(Next step could be to apply rotation as well in addition to location, if this is possible. If you know how, please tell.)

The currently used Python script:
Code: Select all
from Plasma import *
from PlasmaTypes import *

actEnter = ptAttribActivator(1, 'Enter region activator')
actExit = ptAttribActivator(2, 'Exit region activator')
doNetForce = ptAttribInt(3, 'netForce flag')
warpObjs = ptAttribSceneobjectList(4, 'list of warp objects')

obList = []

# Warp to a destination object
def WarpObjectToDestOb(warpOb, destOb):
    pos = warpOb.position()
    dest = destOb.position()
    matrix = warpOb.getLocalToWorld()
    matrix.translate(ptVector3((dest.getX() - pos.getX()), (dest.getY() - pos.getY()), (dest.getZ() - pos.getZ())))
    warpOb.netForce(doNetForce.value)
    warpOb.physics.warp(matrix)
    return pos

# Warp to a known location
def WarpObjectToPos(warpOb, dest):
    pos = warpOb.position()
    matrix = warpOb.getLocalToWorld()
    matrix.translate(ptVector3((dest.getX() - pos.getX()), (dest.getY() - pos.getY()), (dest.getZ() - pos.getZ())))
    warpOb.netForce(doNetForce.value)
    warpOb.physics.warp(matrix)


# Note: Change all "ptResponder" to "ptModifier" for a Modifier class
class paraWarpObList(ptModifier,):
    __module__ = __name__

    def __init__(self):
        ptModifier.__init__(self)
        self.id = 16005     # (the value does not really matter)
        self.version = 1
        print ('__init__%s v.%s' % (self.__class__.__name__, self.version))
       
        self.oriPosList = None


    def OnFirstUpdate(self):
        #print ("---OnFirstUpdate---")
        tog = False
        for obj in warpObjs.value:
            if not tog:      # its the warp object
                warpOb = obj
            else:         # its the destination hook
                obList.append((warpOb, obj))
            tog = not tog
        print ("numWarps=%d" % len(obList))


    def OnServerInitComplete(self):
        #print ("---OnServerInitComplete---")
        pass


    def OnNotify(self, state, id, events):
        #print ("---OnNotify---")
        if (not PtWasLocallyNotified(self.key)):
            return
        if ((id == actEnter.id) and state):
            self.MyWarpObject_onEnter()
        elif ((id == actExit.id) and state):
            self.MyWarpObject_onExit()



    def OnSDLNotify(self, VARname, SDLname, playerID, tag):
        #print ("---OnSDLNotify---")
        pass


    def MyWarpObject_onEnter(self):
        print 'Warp Trigger Region entered'
       
        bStorePos = False
        if self.oriPosList is None:
            self.oriPosList = []
            bStorePos = True
       
        #print ("ListLen:%d" % len(obList))
        for obIndex in range(len(obList)):
            #print ("obIndex=%d" % obIndex)
            warpOb = obList[obIndex][0]
            destOb = obList[obIndex][1]
            #print warpOb.getName()
           
            oriPos = WarpObjectToDestOb(warpOb, destOb)
           
            if bStorePos:
                self.oriPosList.append(oriPos)


    def MyWarpObject_onExit(self):
        print 'Warp Trigger Region left'
       
        if self.oriPosList is not None:
            for obIndex in range(len(obList)):
                warpOb = obList[obIndex][0]
                oriPos = self.oriPosList[obIndex]
                WarpObjectToPos(warpOb, oriPos)



### Python Glue starts here ###


and an example AlcScript:
Code: Select all
Region_WarpObject_B:
    logic:
        modifiers:
          - name: logicMod_WarpEnter
            cursor: up
            flags:
              - multitrigger
            conditions:
              - type: volumesensor
                satisfied: true
                direction: enter
                trignum: -1
            activators:
              - type: objectinvolume
                remote: Region_WarpObject_B
            actions:
              - type: pythonfile
                ref: :WarpObjectPythonFileMod
          - name: logicMod_WarpExit
            cursor: up
            flags:
              - multitrigger
            conditions:
              - type: volumesensor
                satisfied: true
                direction: exit
                trignum: -1
            activators:
              - type: objectinvolume
                remote: Region_WarpObject_B
            actions:
              - type: pythonfile
                ref: :WarpObjectPythonFileMod
        actions:
          - type: pythonfile
            name: WarpObjectPythonFileMod
            pythonfile:
                file: paraWarpObList
                parameters:
                  - type: activator
                    ref: logicmod:logicMod_WarpEnter
                  - type: activator
                    ref: logicmod:logicMod_WarpExit
                  - type: int
                    value: 0     # netForce(0)
                   
                  - type: sceneobjectlist
                    refs: ['scnobj:Plant_1', 'scnobj:Plant_1_Dest_B',
                           'scnobj:Plant_2', 'scnobj:Plant_2_Dest_B',
                           'scnobj:Plant_3', 'scnobj:Plant_3_Dest_B',
                           'scnobj:Plant_4', 'scnobj:Plant_4_Dest_B']



Sidenote: Currently, the scripts store the object's original location of the objects before warping is applied, and restore the location if the region is left; I made this for testing. For the behaviour described above, the storing part, restoring location parts (in Python code) and leaving region part (in Python and AlcScript) should be omitted.

Oh, and as a final but important note: Both the object(s) and the destination hook(s) have to be set as Actor in Blender to get it working (otherwise the objects don't get a coordinate system applied, to mention the technical internals).

Thanks to Sirius as well for providing code examples.

Original thread for the warping part:
viewtopic.php?f=59&t=5251

Re: Passing variable number of params from AlcScript to Pyth

PostPosted: Wed Jun 29, 2011 3:48 am
by dendwaler
So I came up with an idea: What if I just warp objects around between regions, depending on the player's location, instead of just hiding/unhiding them? This could keep the file size lower, while keeping the facility to have objects shown to the player everywhere.

Really a good idea!
That way an age can be made much more visionable atractive.
Not only for vegetation, i think you can use it for complete buildings as well.
To simulate a long street would not cost to much effort in that way.
A sort of "dynamic array modifier"

good work!

Re: Passing variable number of params from AlcScript to Pyth

PostPosted: Wed Jun 29, 2011 5:57 am
by Wamduskasapa
dendwaler wrote:
So I came up with an idea: What if I just warp objects around between regions, depending on the player's location, instead of just hiding/unhiding them? This could keep the file size lower, while keeping the facility to have objects shown to the player everywhere.

Really a good idea!
That way an age can be made much more visionable atractive.
Not only for vegetation, i think you can use it for complete buildings as well.
To simulate a long street would not cost to much effort in that way.
A sort of "dynamic array modifier"

good work!

Excellent work, not only can this be used for plants and decorations, I can also see it being used for darts, spears and daggers coming from the walls, floor and ceiling. Stopping us from going down one passage and thus blocking access to that area until the puzzle was solved.