TTLG|Thief|Bioshock|System Shock|Deus Ex|Mobile
Page 1 of 2 12 LastLast
Results 1 to 25 of 31

Thread: Understanding Squirrel Scripts

  1. #1
    Member
    Registered: Jan 2001
    Location: Formby, NW England

    Understanding Squirrel Scripts

    Squirrel script has some documentation, but for people who don't do programming some things may be confusing. The purpose of this thread is to explain things so more people can have a go and know what they're doing, rather than just copying and pasting code samples. It isn't my intention to explain everything myself; other people are welcome to explain other things.

    The readme explains the basic structure of a script, but I'd like to explain one thing:
    Code:
    class MyScript extends SqRootScript
    {
    }
    "extends SqRootScript"
    Extending another script allows MyScript to access all of the functions of that script. You can think of it like the Object Hierarchy, where Door10 inherits the properties of door4x8, which inherits from spinny_door and each of its other parent archetypes.

    Getting a script to do something.
    Scripts respond to messages. Some messages are sent to all objects by default, such as Sim and Create. Other messages are sent by some trigger, e.g. a button sends a TurnOn message to all objects on the other end of a ControlDevice link.

    For a Squirrel script, we can set up functions that respond to these standard messages. E.g:

    Code:
    class MyScript extends SqRootScript
    {
        function OnTurnOn()
        {
            something...
        }
    }
    The { and } are used to define the start and end of each thing (the script, the actions of each of its functions, etc).

    Within a function you can call other functions. As well as functions you've written yourself, there are plenty of existing functions. Those ones are listed in API-reference_services.txt

    In that file is a set of blocks laid out like this:
    Code:
    Thing
    {
        something_1
        something_2
        ...
    }
    "Thing" is a class name
    each of the "somethings" is a function you can call. Example:
    Inside Object {...} there is
    Code:
    ObjID Create(object archetype_or_clone);
    The fist part (ObjID) is the type of value the function generates, the second part (Create) is the function's name. Inside the brackets are the required parameters. Each parameter starts with the type of thing and a name which the function uses as an alias.
    In this case, there is only one parameter, whose type is object.

    What is an object?
    Open up API-reference.txt.
    an ObjID or an object name string
    We are told that an ObjID is just an integer, and that there some other types, e.g.LinkID, that are also just integers.

    To explain how to use parameters, a useful example is creating an object at a specific location. There isn't a single function for this, so two have to be used. Object.Create and Object.Teleport.

    Object.Create("WoodCrateSealed"); will create that object at 0,0,0.

    Object.Teleport requires an object (ID or name), two vector types, which allow the location and orientation of the object to be set, and another object. You'll notice the parameter list has this: object ref_frame = 0. The use of = means the parameter is optional, and if no value is supplied, in this case a value of 0 will be used. This object can be used to have the crate teleported somewhere relative to the ref_frame object.

    Object.Create returns an object, so that can be used to generate the first parameter for Object.Teleport.

    Do something like this:
    local crateObj = Object.Create("WoodCrateSealed");
    Object.Teleport(crateObj, ...)

    local creates a variable which can only be accessed from within the current pair of { and }, and any pairs inside them, but not outside. Its type is determined by the value it's given, so in this example crateObj is of the type ObjID.

    The next parameter, position, has to be a vector, which can be created by calling the vector function. In API-reference.txt, go to the GLOBAL FUNCTIONS section. Here you'll see that there are three ways to create a vector:
    vector() which generates a vector type with values of (0,0,0). The other two versions of this function allow other values to be used, so a specific location can be set.

    The next parameter, facing, can be set in the same way. The three values refer to the object's axes, not heading, pitch and bank, so vector(0,0,45) will rotate it about the Z axis.

    As mentioned, the ref_frame parameter is optional, but it can be useful, as this shows:

    The self object.

    When a script requires an object parameter, you can use the keyword self (which does not use double quotes) to mean 'the object that has this script'.

    Code:
    class MakeWoodCrate extends SqRootScript
    {
        function OnTurnOn()
        {
            local crateObj = Object.Create("WoodCrateSealed");
            Object.Teleport(crateObj, vector(), vector(), self);
        }
    This will create a WoodCrateSealed at the location of the object with the script. It's a very basic script but hopefully it shows you how to use the documentation to work out what you can use, and how you can use it.

  2. #2
    Member
    Registered: Mar 2001
    Location: Ireland
    I also recommend that anyone interested in Squirrel scripting read An Introduction to Scripts .
    Most of it should also apply to scripts written in Squirrel.

  3. #3
    ZylonBane
    Registered: Sep 2000
    Location: ZylonBane
    It would probably be useful to create a "Squirrel Cookbook" sort of document that described how to script various common tasks.

  4. #4
    Member
    Registered: Jan 2012
    Location: Gèrmany
    I would like to link to the documentation which helped me to learn squirrel:

    Official documentation:
    http://www.squirrel-lang.org/doc/squirrel3.html Not the most reason version but it's a big html website and you can find stuff with ctrl+f. Most recent documentation: (Online or PDF)

    Additional:
    The official documentation is lacking examples here comes a real bushiness website which does the necessary additions:
    https://electricimp.com/docs/squirrel/

    On the right you can click and learn about the different datatypes/libraries which are available for squirrel. Also you will find find some better explanation+examples of the so called built-in functions like string.slice(start,end). Which are only roughly documented in the official documentation.


    Last but not least the site provides a 2-side "Cheat sheet" with all the basic knowledge you need for programming: datatypes, operators, syntax...

    https://electricimp.com/docs/attachments/images/squirrelrefsheet.pdf
    Last edited by Daraan; 10th Apr 2017 at 06:21.

  5. #5
    Member
    Registered: Jan 2001
    Location: Formby, NW England
    I had a go at doing a script that allows a custom [script name]On parameter, specified in a base class which can be derived from.

    Here's an example, but the script itself doesn't do anything.

    Code:
    class AllYourBase extends SqRootScript
    {
    	function getOnMessage()
    	{
    		local onMsg = "TurnOn"; //default
    		if (HasProperty("DesignNote"))
    		{
    			local scrNameOn = GetClassName() + "On";
    			if(scrNameOn in userparams())
    			{
    				onMsg = userparams()[scrNameOn];
    			}
    		}
    		return onMsg;
    	}
    }
    
    class UselessScript extends AllYourBase
    {
    	function OnMessage()
    	{
    		if(MessageIs(getOnMessage()))
    		{
    			... do things here ...
    		}
    	}
    }
    "UselessScript" goes on the object. When it receives any message (which magically triggers the OnMessage() function), it asks if the message is the value generated by the getOnMessage() function, which is inherited from the base script. This starts by setting the on message to TurnOn to create a default value. If the object has a Design Note parameter called [script name]On, it changes the value of the on message to the value of the parameter.
    Last edited by R Soul; 29th Apr 2017 at 15:41.

  6. #6
    Member
    Registered: Aug 2001
    Location: Virginia, USA
    This is where I'm disappointed with Squirrel.

    Code:
    SQUIRREL ERROR> D:\Games\T2\FM\tnhdemo\sq_scripts\test.nut(18) [in constructor()]:
    AN ERROR HAS OCCURED [class instances do not support the new slot operator]
    Otherwise you could just create new functions on the fly to handle custom messages.

    In fact, it doesn't look like the OSM lets you change functions after script creation. I tried

    Code:
    class Test extends SqRootScript {
        constructor() {
    	Test["OnTurnOn"] <- Test.doCustomMessage
        }
        function doCustomMessage() {
            print("Custom Message")
        }
    }
    Doesn't work. But this does.

    Code:
    class Test extends SqRootScript {
        constructor() {
        }
        function doCustomMessage() {
            print("Custom Message")
        }
    }
    Test["OnTurnOn"] <- Test.doCustomMessage
    hmm... what about

    Code:
    class Test extends SqRootScript {
        constructor() {
    	Test["OnTurnOn"] <- Test.doCustomMessage2
        }
        function doCustomMessage() {
            print("Custom Message")
        }
        function doCustomMessage2() {
            print("Another Message")
        }
    
    }
    Test["OnTurnOn"] <- Test.doCustomMessage
    Prints "Another message". So you can change functions, just not create new slots. Too bad.

  7. #7
    ZylonBane
    Registered: Sep 2000
    Location: ZylonBane
    I have some questions about the script data services, setData/getData/etc.

    Does every instance of every script get its own namespace for variable names? So I could just save something under, say, "x" and not worry about it conflicting with any other script.

    And, is this data persisted into savegames? So my OnBeginScript should check to see if it's resuming from an already initialized state?

  8. #8
    Member
    Registered: Mar 2001
    Location: Ireland
    IIRC, yes to both.

    Fairly sure they don't survive level transitions, though, so you're still better off using properties / params to store data if your object can travel between levels.

  9. #9
    Member
    Registered: Jan 2012
    Location: Gèrmany
    SetData is savegame persistant and to confirm NV it is as you said linked to the script instance.

    if you enter edit_scriptdata you can see which data is stored by which script on which object and also some other information.

  10. #10
    ZylonBane
    Registered: Sep 2000
    Location: ZylonBane
    Fun with dynamic lights.



    Code:
    class colorcycle extends SqRootScript {
    
    	function OnBeginScript() {
    		SetOneShotTimer("ColorChange", 0.05);
    		SetData("hue", 0.0);
    	}
    
    	function OnTimer() {
    		if (message().name != "ColorChange") {
    			return;
    		}
    		local color = GetData("hue");
    		hue += 0.01;
    		if (hue > 1) {
    			hue = 0;
    		}
    		SetData("hue", hue);
    		SetProperty("LightColor", "hue", hue);
    		SetOneShotTimer("ColorChange", 0.05);
    	}
    }

  11. #11
    ZylonBane
    Registered: Sep 2000
    Location: ZylonBane
    I have to say, once you get the hang of the script service API, Squirrel scripting is addictive. Tasks that I used to puzzle hours over trying accomplish, usually by lashing together existing scripts with NVRelayTrap, can now simply be done directly. Want to fling the player in a specific direction then bring him to a dead stop, flash the screen a certain color, or execute a series of actions at precisely timed intervals? Just code it up!

    On the one hand, it's amazingly empowering. On the other hand, it's so empowering it almost feels like cheating. Like going from cursive handwriting to using a word processor. I can feel hard-won skills already starting to atrophy... but I can't turn back.

    It's kind of sad there isn't more activity in this thread. I'd assumed DromEd'ers would be all over Squirrel.

  12. #12
    Member
    Registered: Mar 2001
    Location: Ireland
    It's like scripting in general, the old ways of flowerpots falling down chutes and hitting banners are gone, along with the spinning emittertraps shooting a stream of arrows at carefully arranged circles of paintings.

  13. #13
    Desperately dodgy geezer
    Registered: Nov 2001
    Location: The Wailing Keep
    I am planning to deep dive into it now that the 800 lb gorilla is off my back. With my programming background I think it won't be that hard to pick up, just the learning curve.

  14. #14
    Member
    Registered: Aug 2007
    Location: LosAngeles: Between Amusements
    Ah hem, the learning curve is how hard it is to pick up!




    or, perhaps more realistically,

    Click image for larger version. 

Name:	1-kSdzNtl2QtbkpmIwXeAYVA.jpeg 
Views:	5 
Size:	93.3 KB 
ID:	2422
    Last edited by LarryG; 30th Oct 2017 at 10:21.

  15. #15
    Desperately dodgy geezer
    Registered: Nov 2001
    Location: The Wailing Keep
    Sorry Mr. Pedant, I meant the steepness and length of the curve shouldn't be too bad given my background.

  16. #16
    Member
    Registered: Aug 2007
    Location: LosAngeles: Between Amusements
    That doesn't seem either naively confident or discouragingly realistic. Just where do you think you are on the curve right now, hmm?

  17. #17
    ZylonBane
    Registered: Sep 2000
    Location: ZylonBane
    This is a script for randomizing the positions of objects on level start. Works with both free-standing and contained objects. I'm not aware of any way to "floor" objects using the script services, so it's best to either only shuffle identically sized objects, or start everything in midair and give them physics so they fall into place.

    Instead of shuffling a group of objects, you could also use it to randomize the position of a single object by placing markers at every possible location. Or a mix of both. For additional randomization groups you just place another instance of the script on another object, etc.

    Use by placing the script on any object and linking to all the objects to be shuffled. This was written for SS2 so it looks for SwitchLink flavor links. For Thief I suppose you'd change it to ControlDevice.

    Code:
    class scpShuffle extends SqRootScript {
    	function OnSim() {
    		local t, m, lnk, obj;
    		local order = [];
    		local objRef = [];
    		local objPos = [];
    		local objFac = [];
    		local objDest = [];
    		local i = 0;
    		// cache pos/loc/container of every linked object
    		foreach (lnk in Link.GetAll("SwitchLink", self, 0)) {
    			order.push(i++);
    			obj = sLink(lnk).dest;
    			objRef.push(obj);
    			objPos.push(Object.Position(obj));
    			objFac.push(Object.Facing(obj));
    			objDest.push(sLink(Link.GetOne("~Contains", obj)).dest);
    		}
    		// do the Fisher-Yates Shuffle
    		m = order.len();
    		while (m) {
    			i = floor(Data.RandFlt0to1() * m--);
    			t = order[m];
    			order[m] = order[i];
    			order[i] = t;
    		}
    		// process objects
    		for (i = 0; i < order.len(); i++) {
    			t = order[i];
    			if (t != i) {
    				obj = objRef[i];
    				Object.Teleport(obj, objPos[t], objFac[t])
    				lnk = Link.GetOne("~Contains", obj);
    				if (lnk) {
    					Link.Destroy(lnk);
    				}
    				if (objDest[t]) {
    					Link.Create("~Contains", obj, objDest[t]);
    					Property.SetSimple(obj, "HasRefs", FALSE);
    				}
    				else {
    					Property.SetSimple(obj, "HasRefs", TRUE);
    				}
    			}
    		}
    	}
    }
    Here's a demonstration with the script being triggered by a button instead of sim start.


  18. #18
    Desperately dodgy geezer
    Registered: Nov 2001
    Location: The Wailing Keep
    Nice work, thanks! If I ever get around to making that Clue FM, I could use that to randomize the locations of the suspects and murder weapons.

  19. #19
    Desperately dodgy geezer
    Registered: Nov 2001
    Location: The Wailing Keep
    Warning:

    If you release a mission which utilizes squirrel script, I highly recommend you include squirrel.osm in your ZIP in the root, along with other script modules. The reason is that GoG.com's Thief2 installer puts it into the /doc folder instead for some reason, which doesn't make much sense to me. Some of their users had issues with DCE because of this, and they have asked me to include it in the next update of DCE, so I would recommend you all do the same. I did suggest they modify their installer to put it in the root, or in wherever the script_module_path is pointing to in their darkinst.cfg file, but I don't know if they will do that.

  20. #20
    Member
    Registered: Jan 2012
    Location: Gèrmany
    Quote Originally Posted by ZylonBane View Post
    This is a script for randomizing the positions of objects on level start. Works with both free-standing and contained objects. I'm not aware of any way to "floor" objects using the script services, so it's best to either only shuffle identically sized objects, or start everything in midair and give them physics so they fall into place.
    Giving them physics seams like the best method, but might not work in every situation.

    The idea to flooring and how I assume it's similar done in the native Editor Function is via PortalRaycast(Object.Position(),vector(0,0,-1),returnVec), which gives back the floor position below the object.
    Lil' problem is how to get the RedBoundingBox values?

    Workarround is would be OBB.Z/2 or Radius or via a OBB dummy with the same model, which really gives back the RedBox size.
    But still the question remains: Is there are direct methoe to access the RedBoundingBox values?

  21. #21
    Member
    Registered: Mar 2001
    Location: Ireland
    Quote Originally Posted by Yandros View Post
    Warning:

    If you release a mission which utilizes squirrel script, I highly recommend you include squirrel.osm in your ZIP in the root, along with other script modules. The reason is that GoG.com's Thief2 installer puts it into the /doc folder instead for some reason, which doesn't make much sense to me. Some of their users had issues with DCE because of this, and they have asked me to include it in the next update of DCE, so I would recommend you all do the same. I did suggest they modify their installer to put it in the root, or in wherever the script_module_path is pointing to in their darkinst.cfg file, but I don't know if they will do that.
    That sounds like a really bad idea.
    You are including a version of a NewDark library that may change if there are future versions.
    It's not even beyond reason that the version of the library you've included in your FM may not be compatible with future versions, depending on how interrelated the two are. That's not something that you really have to worry about with normal custom scripts (you just wouldn't get bugfixes), but we don't know how the Squirrel osm actually works or if there are special functions for it to use inside the engine itself.

    If people have broken installs, then the proper solution is to get them to fix their installs, and/or to get the patcher tools to fix the problem during patching up. Anything else is a hack that might come back to bite you later.

  22. #22
    Zombified
    Registered: Sep 2004
    to make the GOG T2 story short, I wasn't quite sure what to do with the osm when putting together the GOG T2 package, so I've moved it away for the time being (not having it active wasn't really a problem at that point). was aware that some future missions may need it, but didn't quite expect that there would be people that will try to run FMs without patching up with TP (which installs all the scripts that exist, squirrel included).

    it will be active again in the next update, in the meantime, all the people running manual/default T2 installs should just move it back to the root (and also check all the other scripts, as they may be missing more than one).

  23. #23
    Desperately dodgy geezer
    Registered: Nov 2001
    Location: The Wailing Keep
    LOL I didn't realize you did the GoG installer for them, Voodoo. They did reply to me last night and said they had passed my suggestion on to their technical folks for consideration, sound like that might end up coming back to you.

  24. #24
    Zombified
    Registered: Sep 2004
    installer no, just a few files inside.

  25. #25
    Member
    Registered: Jan 2012
    Location: Gèrmany
    I'm slowly getting a headache. How does the string() work

    From the documeentation:
    Code:
           local str = string();
           if ( Engine.ConfigGetRaw("somevar", str) )
                print("the config var was: " + str);
           #Result: the config var was:
    The str variable remains empty even when using something simple like Version.GetGame(str).
    if I use str=string("value"). I just get "value" back after the function call, as if the function did nothing.

    I somehow fear that string() is broken.
    Last edited by Daraan; 6th Dec 2017 at 11:53.

Page 1 of 2 12 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •