R Soul on 9/4/2017 at 17:21
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.
Quote:
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.
ZylonBane on 9/4/2017 at 22:50
It would probably be useful to create a "Squirrel Cookbook" sort of document that described how to script various common tasks.
R Soul on 29/4/2017 at 18:26
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.
Telliamed on 30/4/2017 at 00:56
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.
ZylonBane on 8/7/2017 at 17:56
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?
Nameless Voice on 8/7/2017 at 21:46
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.
Daraan on 8/7/2017 at 21:58
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.
ZylonBane on 15/7/2017 at 19:21
Fun with dynamic lights.
[video=youtube;s2dlYdcShm8]https://www.youtube.com/watch?v=s2dlYdcShm8[/video]
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);
}
}