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.
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:
"extends SqRootScript"Code:class MyScript 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:
The { and } are used to define the start and end of each thing (the script, the actions of each of its functions, etc).Code:class MyScript extends SqRootScript { function OnTurnOn() { something... } }
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:
"Thing" is a class nameCode:Thing { something_1 something_2 ... }
each of the "somethings" is a function you can call. Example:
Inside Object {...} there is
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.Code:ObjID Create(object archetype_or_clone);
In this case, there is only one parameter, whose type is object.
What is an object?
Open up API-reference.txt.
We are told that an ObjID is just an integer, and that there some other types, e.g.LinkID, that are also just integers.an ObjID or an object name string
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'.
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.Code:class MakeWoodCrate extends SqRootScript { function OnTurnOn() { local crateObj = Object.Create("WoodCrateSealed"); Object.Teleport(crateObj, vector(), vector(), self); }
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.
It would probably be useful to create a "Squirrel Cookbook" sort of document that described how to script various common tasks.
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 05:21.
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.
"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.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 ... } } }
Last edited by R Soul; 29th Apr 2017 at 14:41.
This is where I'm disappointed with Squirrel.
Otherwise you could just create new functions on the fly to handle custom messages.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]
In fact, it doesn't look like the OSM lets you change functions after script creation. I tried
Doesn't work. But this does.Code:class Test extends SqRootScript { constructor() { Test["OnTurnOn"] <- Test.doCustomMessage } function doCustomMessage() { print("Custom Message") } }
hmm... what aboutCode:class Test extends SqRootScript { constructor() { } function doCustomMessage() { print("Custom Message") } } Test["OnTurnOn"] <- Test.doCustomMessage
Prints "Another message". So you can change functions, just not create new slots. Too bad.Code:class Test extends SqRootScript { constructor() { Test["OnTurnOn"] <- Test.doCustomMessage2 } function doCustomMessage() { print("Custom Message") } function doCustomMessage2() { print("Another Message") } } Test["OnTurnOn"] <- Test.doCustomMessage
-- tom
The Dromesday Book
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?
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.
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.
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); } }
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.
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.
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.
Sorry Mr. Pedant, I meant the steepness and length of the curve shouldn't be too bad given my background.![]()
That doesn't seem either naively confident or discouragingly realistic. Just where do you think you are on the curve right now, hmm?![]()
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.
Here's a demonstration with the script being triggered by a button instead of sim start.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); } } } } }
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.
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.
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?
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.
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).
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.
installer no, just a few files inside.
I'm slowly getting a headache. How does the string() work
From the documeentation:
The str variable remains empty even when using something simple like Version.GetGame(str).Code:local str = string(); if ( Engine.ConfigGetRaw("somevar", str) ) print("the config var was: " + str); #Result: the config var was:
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 10:53.