Team Spartan Cookies & Milk Forums

Full Version: Starfall Tutorial
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
This tutorial assumes a basic understanding of Lua's syntax. I highly advise learning the basics of the Lua language first before attempting to use Starfall.

You can access Starfall's documentation in-game by clicking the ? button at the top-right hand corner of the editor.

Starfall Tutorials
Last updated: 10/04/2016 - Added section for permissions.

Contents:
  1. Including and requiring
  2. Creating and using wire inputs and outputs
  3. Meta-tables and meta-methods (objects)
  4. Hooks and timers
  5. Server and client environments
  6. Starfall emitters and 3D2D cameras
  7. 3D Models in screens/HUDs
  8. Render Targets
  9. VGUI Elements
  10. Channels Library
  11. Coroutines
  12. General Lua Tips
  13. Permissions

The parts are not in any special order, go through the first two parts if you're new to Starfall then look at the parts that interest you after.

1. Including and requiring
The way Starfall handles inclusion of other files is slightly different to that of Expression 2. Instead of just putting #include, you need to do two things.

Setting up the file
At the top of your Starfall file, you will need to add one or more lines for each of the files you wish to include. This tells Starfall to package these files alongside the main script file.
--@name Include some stuff
--@author Person8880
--@include lib/test.txt
--@include lib/chatcmd.txt

This will add two files to the chip, "test.txt" and "chatcmd.txt". Note the full path is required, including the .txt extension.

Require vs dofile
Once you have added the files in include directives, you need to tell Starfall when to execute them. You have two ways of doing this, using require() or using dofile().

Essentially, require() will execute your included file and return the result. It will also cache this result, meaning if you were to run it again, the same result as the first require is returned. On the other hand, dofile() will only return the result of the execution, it will not cache the result anywhere. Therefore, dofile()'s return value can change, but require()'s cannot.

In practise, you usually don't care about the return value resulting from the included file, you just want to ensure its contents are executed. Therefore, dofile() should be how you execute your included files if you only plan on running them once. Otherwise, use require() (unless you have a changing return value in a file for whatever reason).

For example:
--@name Include some stuff
--@author Person8880
--@include lib/test.txt
--@include lib/chatcmd.txt

dofile( "lib/test.txt" )
dofile( "lib/chatcmd.txt" )
will run the two included files immediately, but not cache the return value from each file. If the file test.txt was set to return time.curTime() when executed, then dofile() would update this value, but require() would return only the time it was first executed.

The behaviour of require() is based on its use in standard Lua. It is used to load modules, which generally return a table containing all their functions. Rather than have Lua go through and define everything again every time a file requests the module, Lua caches the result. In Starfall, its usefulness is diminished as each Starfall chip is in its own instance.

Automatic requiring
It seems tedious to include and require files. You have to put them in the --@include sections and then run require on them too. Enter the --@require statement. With this, the file is both included AND require()'d before your main script runs so you don't have to put require()s everywhere.

--@name Auto require
--@author Person8880
--@require lib/somesharedlib.txt
--@requiresv lib/someserverlib.txt
--@requirecl lib/someclientlib.txt
--@shared

--Out here, lib/somesharedlib.txt has been required already.

if SERVER then
    --In here, lib/someserverlib.txt has been required already.
else
    --In here, lib/someclientlib.txt has been required already.
end

Note the three variations. Using --@require require()s the script on both the server and the client. Using --@requiresv only require()s on the server, and using --@requirecl only require()s on the client.

Including a folder
You may want to structure your work so that you have folders of useful things. Rather than having to --@include every single file individually, you can use --@includedir to include the entire directory.

Note, that this only includes the files directly inside the given folder, it won't go into sub-folders.

--@name Include directory
--@author Person8880
--@includedir lib

--This requires every included script, you could instead come up
--with a per-folder loader using the result of getScripts().
requireAll()

--All the files in the lib/ folder have now loaded.

Back to contents

2. Creating and using wire inputs and outputs
Starfall has a significantly different method of creating inputs and outputs and using them. If you are new to Lua, this may be difficult to understand at first.

Creating inputs and outputs
In order to create inputs and outputs, make sure you are running server-side. Starfall processors are server-side, screens will need the --@shared directive and an if SERVER then block. Then make your inputs and outputs as follows:
--@name Wire example
--@author Person8880

wire.createInputs( { "A", "B", "C" }, { "normal", "string", "vector" } ) --A is a number, B is a string and C is a vector.
wire.createOutputs( { "D", "E", "F", "G" }, { "entity", "string", "normal", "vector" } ) --D is an entity, E is a string, F is a number and G is a vector.
Both functions take two arguments, a table of input/output names and a table of input/output types. You can define them inline, or if you prefer, you can create two tables before and then insert them into the functions. This will allow you to have variable amounts of wire inputs and outputs set up.

Accessing inputs and outputs
Once created, the inputs and outputs are available in the wire.ports table. Reading from it will get the value of an input, writing to it will set the value of an output. For example, with the above code:
local AwesomeNumber = wire.ports.A
local AwesomeString = wire.ports.B

wire.ports.E = "Stuff!"
this shows how to get the values of inputs and how to set the value of an output. Make sure you match types correctly, if you have a number output, pass it a number when you set its value.

Hooking into input changes
Unlike E2, where the entire chip executes on input change, Starfall works with the concept of hooking. This allows you to define functions to run when certain events occur rather than having to use ugly if(first()), if(clk()) etc. statements. To run code when an input changes, you need to hook into the "input" hook. This is achieved as follows:
hook( "input", "UniqueHookNameHere", function( Input, Value )
    if Input == "A" then
      wire.ports.F = Value ^ 2
    end
end )
This will set the output 'F' to the value of the input A, squared. The function you pass this hook needs the two parameters as above, as it is passed the input's name first as a string, then the value that the input has been set to.

Wirelinks
Wirelinks are handled in a nice way in Starfall. You create them like any other input, then you can use them as a table directly from the wire.ports table. For example:
wire.createInputs( { "Stargate" }, { "wirelink" } )

local Active = wire.ports.Stargate.Active

wire.ports.Stargate.Close = 1
Assuming you've wired the wirelink input to a Stargate, the Active value will be the Active output on the Stargate. Setting values, like I've done with the Close value above, sends that value to the given input on the wirelink. So I would send the Close input 1 with the above code and close the gate if it's open.

Back to contents

3. Meta-tables and meta-methods (objects)
This section is meant for people with a firm grasp of Lua's basic ideas. If you are just beginning with Starfall and Lua, do not attempt to go through this section without first understanding tables and methods.

With that said, Lua's main form of object manipulation is through the use of meta-tables. These are, as the name implies, tables that describe a table. In Starfall, you are free to create your own new objects and meta-tables but you cannot edit the existing objects.

Creating a new object
Lets start by creating a 'colour' object. In this tutorial we're going to make it so we can add colours together, multiply colours by another colour or a number and divide colours by a number.

We begin with the colour creation function.
local Clamp = math.clamp

local ColourMeta = {} --We create an empty table, this will be our meta-table.

function Colour( R, G, B, A ) --Make a function that takes R, G, B and A as arguments.
    local Col = {
        r = Clamp( R or 0, 0, 255 ),
        g = Clamp( G or 0, 0, 255 ),
        b = Clamp( B or 0, 0, 255 ),
        a = Clamp( A or 0, 0, 255 )
    }

    return setmetatable( Col, ColourMeta ) --This is the important part.
end
Now when you use the 'Colour' function, it will return a table with 4 number values indexed at 'r', 'g', 'b' and 'a'. Unlike an ordinary table however, this table will have our 'ColourMeta' table as its meta-table, which lets us define what happens when we add, multiply and divide colours and other things too.

The function setmetatable( Table, Meta-Table ) is the important part. It returns the modified table so we return it directly.

Setting up our meta-methods
Now, we can define what happens when we perform various operations on this colour object. This is done through meta-methods.

To string
The first meta-method we're going to look at is the :__tostring() method. This lets us tell Lua what to output when we use tostring() on our object. In this case, we want it to tell us we have a colour and what its r, g, b and a values are. Note the use of the colon, it's syntactic sugar for ColourMeta.__tostring( self ).
function ColourMeta:__tostring() --What happens when we use tostring() or print() on a colour?
    return string.format( "Colour. R = %s. G = %s. B = %s. A = %s.", self.r, self.g, self.b, self.a )
end
If we now do:
local Test = Colour( 255, 255, 255, 255 )
print( Test )
this will output: "Colour. R = 255. G = 255. B = 255. A = 255.".

Addition
The addition meta-method is the :__add( Value ) function.
function ColourMeta:__add( Col ) --What happens when we add something to the colour?
    if type( Col ) ~= "table" or getmetatable( Col ) ~= ColourMeta then --We only want to add colours to colours.
        error( "Attempted to add a "..type( Col ).." to a colour!" )
    end

    return Colour( self.r + Col.r, self.g + Col.g, self.b + Col.b, self.a + Col.a )
end
Now if you try something like:
local Test = Colour( 255, 0, 0 ) + Colour( 0, 255, 255 )
you should find 'Test' will be 255, 255, 255.

Multiplication
The multiplication meta-method is the :__mul( Value ) function.
function ColourMeta:__mul( Value ) --What happens when we multiply our colour with a value?
    if type( Value ) == "table" then --If it is a table, then make sure it is a colour.
        if getmetatable( Value ) ~= ColourMeta then
            error( "Attempted to multiply a colour with a table!" )
        end

        return Colour( self.r * Value.r, self.g * Value.g, self.b * Value.b, self.a * Value.a )
    elseif type( Value ) == "number" then --Only other allowed multiplier is a scalar value.
        return Colour( self.r * Value, self.g * Value, self.b * Value, self.a * Value )
    else --All other types are not defined for multiplication.
        error( "Attempted to multiply a colour with a "..type( Value ).."!" )
    end
end
From this if you try something like:
local Test = Colour( 100, 100, 100 ) * 2
will give you the colour 200, 200, 200. Also,
local Test = Colour( 100, 100, 100 ) * Colour( 2, 2, 2 )
will give you the same result.

Division
Finally, let's set up our colour objects with division. The division meta-method is the :__div( Value ) function.
function ColourMeta:__div( Value )
    if type( Value ) == "table" then
        if getmetatable( Value ) ~= ColourMeta then
            error( "Attempted to divide a colour by a table!" )
        end
        return Colour( self.r / Value.r, self.g / Value.g, self.b / Value.b, self.a / Value.a )
    elseif type( Value ) == "number" then
        return Colour( self.r / Value, self.g / Value, self.b / Value, self.a / Value )
    else
        error( "Attempted to divide a colour by a "..type( Value ).."!" )
    end
end
As you can see, it is very similar to the multiplication method. Now, if you were to do:
local Test = Colour( 100, 100, 100 ) / 2
you should find 'Test' will be 50, 50, 50. Similarly for division by another colour.

Hopefully you have seen enough methods to understand how to add other operators such as equivalence and subtraction.

It is important to note that the methods I have given above work only if the colour is the first element of the operation. I leave it to the reader to make them error handle if the first element of the operation is not a colour. (HINT: Check the value of the 'self' variable.)

Setting up methods
Now we have our meta-methods set up, we can also define standard methods. We do this using one last meta-method, the __index meta-method. This allows us to redirect index calls to a different table so we can return things that aren't actually present in the colour object. Note that this won't override the values we have in our colour, so we'll still get the r, g, b and a values correctly.
ColourMeta.__index = ColourMeta --We redirect all index calls to the meta-table itself. You can make a new methods table if you wish.

function ColourMeta:Zero() --A method to reset the colour to its default values.
    self.r = 0
    self.g = 0
    self.b = 0
    self.a = 0
end
Now if we were to do the following:
local Test = Colour( 255, 255, 255, 100 )

Test:Zero()

print( Test )
you should find it prints that the colour is now 0, 0, 0, 0.

That's all for this section. There's plenty of meta-methods available that let you create all kinds of classes in Lua, hopefully you can see how much more powerful Starfall is compared to Expression 2.

Back to contents

4. Hooks and timers
The main way to get things done in Starfall is through the use of hooks and timers. They both share similar ideas but do slightly different things.

A hook is something that is called on a certain event, for instance, the think hook is called when the server processes a new tick. A timer is called in a fixed interval of time and you can set how many of those intervals after starting should call the timer's function.

Both hooks and timers will need a function to call up when they're ran. This is where Starfall is significantly different to E2. In E2, timers would execute the entire chip at once, here they only execute the function you give them.

Hooks
List of all available hooks:
http://teamscm.co.uk/sfdoc/hooks.html

To set up a hook, you use the hook function.
local Count = 0
hook( "think", "ServerTickCounter", function()
    Count = Count + 1
end )
This code will increase the count value by 1 every tick.
hook( "starfall_used", "ScreenUsed", ScreenUsed )
This code will run the function 'ScreenUsed()' (if you have defined a function with that name) with the player that pressed use on the screen as the argument.

Removing a hook
To remove a hook, use the hook function again with the same hook name, but pass nil as the function. For example:
hook( "think", "ServerTickCounter", nil )
would remove the tick counter hook from above.

Timers
Starfall has two kinds of timers available, the simple timer and the normal timer. The simple timer is called only once and is useful to delay an action. The normal timer can be called as many times as you want, even indefinitely.

Simple timer
Simple timers are like a delay, you give them a time in seconds to wait, then after that time they'll run the function you've given them.
timer.simple( 5, function()
    print( "5 seconds have passed!" )
end )
This code will print "5 seconds have passed!" after 5 seconds. It will not run the timer again.

Normal timer
Normal timers are more flexible than simple timers and can be repeated automatically at a given rate.
timer.create( "5SecondCount", 5, 0, function()
    print( "Another 5 seconds have passed!" )
end )
This code will print "Another 5 seconds have passed!" every 5 seconds for as long as the chip is spawned and the timer exists.

The first value of the function is a unique name you give the timer so you can identify it later on if you want to remove it. The second value is the delay time in seconds, here we have a 5 second delay. The third value is the number of times the timer should repeat. A value of 0 like we have means it repeats indefinitely until the timer is destroyed or the chip is removed. The last value is the function to run when the timer is called.

Removing a timer
You can remove a normal timer by using
timer.remove( TimerName )
where 'TimerName' matches the name you gave the timer you want to stop.

Chat listening
For whatever reason, the event of chat being said is not a hook. Instead, you must set up a listen function with chat.listen() in the chat library.
local function ParseChat( Message, Player )
    print( Player:name().." said: "..Message )
end

chat.listen( ParseChat )
This will print the player's name and the message they entered into your chat whenever someone says something.

You can add a second parameter to chat.listen() to specify a specific player to listen to. For instance:
local Owner = ents:owner()

local function ParseChat( Message, Player )
    print( Player:name().." said: "..Message )
end

chat.listen( ParseChat, Owner )
would only print when you say something.

Removing a chat listener
To remove a chat listener, use chat.stop().
chat.stop( ParseChat, Owner )
would remove the above chat listener.

Back to contents

5. Server and client environments
Starfall screens have access to run on both the server and the client environments, unlike E2 and Starfall processors which are fixed to run on the server only.

This presents a problem. How do you tell the client instance about something like "a Stargate just opened" or "a wire input changed"? This part of the tutorial will cover basic networking principles and what each instance of your Starfall screen has access to.

The server environment
The server environment is where almost everything other than rendering a screen is done. This environment can get information about entities and manipulate them. By default, a Starfall screen will not run on the server at all, meaning it is restricted purely to rendering and gathering basic entity information.

You can tell the server to run your Starfall screen's code by adding the "--@shared" pre-processor directive.
--@name Shared screen test
--@author Person8880
--@shared

if SERVER then
    print( "Hi, I'm the server!" )
else
    print( "Hi, I'm the client!" )
end
Notice how we can get code to run on a certain instance by the use of "if SERVER then". You can also use "if CLIENT then", but "CLIENT" is just "not SERVER" and vice-versa.

Running on the server environment, we can now create wire inputs and interact with entities accordingly.
--@name Shared screen test
--@author Person8880
--@shared

if SERVER then
    wire.createInputs( { "Text" }, { "string" } )
else
    print( "Hi, I'm the client!" )
end
If you were to try and use the wire library in the client block or outside the if statement, you'd end up with an error when you try to use it as the wire library doesn't exist on the client.

The client environment
The client environment is where we can render to the screen. It can get basic information about entities on its own, but things like my Stargate library and lots of entity functions are not available to it.
--@name Shared screen test
--@author Person8880
--@shared

if SERVER then
    wire.createInputs( { "Text" }, { "string" } )
else
    local Text = "Bleh."

    hook( "render", "RenderHook", function()
        render.clear()
        render.setColor( 255, 255, 255, 255 )
        render.drawText( "Default", 256, 256, Text, render.TEXT_ALIGN_CENTER )
    end )
end
The shared environment
Though it may not be immediately obvious, there is a third environment, known as the shared environment. Code here is executed on both the server and the client instances.

The shared environment in our case is anywhere outside our if SERVER then else end block.
--@name Shared screen test
--@author Person8880
--@shared

--Shared environment is out here.

if SERVER then --In here is the server environment.
    wire.createInputs( { "Text" }, { "string" } )
else --In here is the client environment.
    local Text = "Bleh."

    hook( "render", "RenderHook", function()
        render.clear()
        render.setColor( 255, 255, 255, 255 )
        render.drawText( "Default", 256, 256, Text, render.TEXT_ALIGN_CENTER )
    end )
end
Networking data between environments
Ok, maybe you can see where this example is going. We now want to change the text that's being rendered using our wire input on the server side. The problem is, how do we tell the client instance that the wire input's changed, and how do we tell it what it changed to?

This is solved using my net library.
--@name Shared screen test
--@author Person8880
--@shared

if SERVER then
    wire.createInputs( { "Text" }, { "string" } )

    hook( "input", "TextChanged", function( Input, Value )
        if Input == "Text" then
            if net.start() then
                net.writeString( Value )
                net.broadcast()
            end
        end
    end )
else
    local Text = "Bleh."

    hook( "render", "RenderHook", function()
        render.clear()
        render.setColor( 255, 255, 255, 255 )
        render.drawText( "Default", 256, 256, Text, render.TEXT_ALIGN_CENTER )
    end )

    hook( "net", "ReceiveString", function( Length )
        Text = net.readString()
    end )
end
This code will update the text in the centre of the screen with whatever the wire input is changed to. Essentially, this is now a wire text screen (except you can customise the font, colours etc. on the fly and more).

Let's go over exactly what I did with net.
if net.start() then --This starts the net message. If it returns false, we can't write a net message so we check to make sure.
    net.writeString( Value ) --We write the string the input changed to.
    net.broadcast() --Now we end the message and send it to everyone so their screen instance is updated.
end

hook( "net", "ReceiveString", function( Length ) --We hook into the net hook, which is ran when a message is received.
    Text = net.readString() --We read the string we were sent by the server instance which will now get rendered.
end )
As you can see, it's pretty easy to send net messages, and the best thing is they work both ways! You can send data from the client to the server in the same way, but end it with net.sendToServer() instead of net.broadcast().

Also, the server's net hook receives an extra argument, which is the player entity that sent the message.
hook( "net", "ReceiveOnServer", function( Length, Ply )
    print( Ply:nick() )
end )
Remember that the client side is running on each client individually. Thus if you set up a network message to send to the server and don't check the local client, you can end up with a net message from every single client to the server instance. Use the ents.player() entity to determine which client an instance is running for.

That's it for this part about the three environments and the net library, hopefully you can see how powerful a Starfall screen can become with some networking magic.

Back to contents

6. Starfall emitters and 3D2D cameras
A recent addition to Starfall on the server is emitters. These, like screens and HUDs, are a primarily client side entity that uses the render library to draw stuff. Emitters however, will not draw anything by simply calling render functions in the render hook. This is because they are used to draw anywhere in the world by projecting a 2D plane into 3D space.

Starting a 3D2D camera context
In order to begin rendering, we need to define a 3D2D camera context. This requires 3 variables.
  1. Position of the plane's top left corner.
  2. World angles of the plane, these define its orientation in the world.
  3. Scale factor. Scales less than 1 will scale down what is rendered inside so you can render text nicer, but it will need a higher font size. A scale of 1 is 1:1 with units in the game.

With these three variables, we can start a 3D2D context by using:
render.start3D2D( Position, Angles, Scale )
This must be called inside your render hook, and once you have drawn everything you want to draw in this plane, you must end the camera with:
render.end3D2D()
Dealing with angles
When using 3D2D cameras, it is often necessary to rotate the angles around an entity's local co-ordinate axis, for instance, you want the camera to be in front of a prop and you want the text to be aligned with it in its upright position. In order to get the plane in the right orientation, you need to use the entity's angles and rotate them around its axis.

To rotate an angle, use:
Angle:RotateAroundAxis( Axis, Amount )
where the amount is in degrees.

For example, to get the camera to show aligned to a Stargate:
local Font = render.createFont( "Default", 36, 750, true )

hook( "render", "Stargate3D2D", function()
    local Angles = Ent:ang()

    --[[
        The Stargate's world angles will position the text above it and parallel to the ground.
        These rotations ensure it is instead parallel to the Stargate's up direction and the text is not upside down.
    ]]
    Angles:RotateAroundAxis( Ent:right(), 90 )
    Angles:RotateAroundAxis( Ent:forward(), 90 )
    Angles:RotateAroundAxis( Ent:up(), 180 )

    --All rendering for emitters MUST take place inside a 3D2D camera, otherwise you won't be drawing anything.
    render.start3D2D( Ent:pos(), Angles, 1 )
        render.drawText( Font, 0, 0, "Hello there!", render.TEXT_ALIGN_CENTER )
    render.end3D2D()
end )
This will show the text "Hello there!" in the centre of the Stargate, assuming 'Ent' is the Stargate's entity. You will need to either find the entity clientside, or network it from a server side instance.

Back to contents

7. 3D Models in screens/HUDs
Drawing 3D models in 2D is a slightly complicated affair, however, it sets Starfall significantly above all other forms of wire screen rendering such as EGP and GPUs.

This method ONLY WORKS ON SCREENS AND HUDS.

The 3D camera context
The foundation of drawing 3D models on screens and HUDs is the 3D camera context. Much like the 3D2D context, this camera takes numerous input values that determine where it is placed, but unlike the 3D2D camera, it is drawing a 3D world in a 2D rectangle.

The procedure for setting up a 3D camera is different depending on whether you are using a screen or a HUD. The code below will work on both.

local Owner = ents.owner()
local Centre = Vector( 0, 0, 0 )

hook( "render", "DrawStuff", function()
    if not render.isHUD() then
        render.clear( 0, 0, 0, 255 )

        render.setColor( 0, 0, 0, 255 )
        render.drawRect( 0, 0, 512, 512 )
    end

    local AimVec = Owner:aimVector()

    render.start3D( Centre + AimVec * -128, AimVec:Angle(), 70, 0, 0, 512, 512, 1, 2048 )
        --Here is the 3D world in the 3D camera context!
    render.end3D()
end )
The arguments to start3D are:
render.start3D( Origin, Angles, FOV, X, Y, W, H, NearZ, FarZ )
  • Origin is the position of the camera relative to the centre of the 3D world in its 3D space.
  • Angles is the angle of the camera in 3D space.
  • FOV is the field of view of the camera.
  • X and Y are the 2D position of the 3D camera.
  • W and H are the width and height of the 3D camera on the screen.
  • NearZ and FarZ are the distance to the near and far clipping planes.

Clientside Models
Now we move on to the actual process of drawing a 3D model. To do so, you need to create a clientside model. There is a limit on these (not enforced directly, but if you hit it your console will spam that it can create no more), so use them sparingly.

Note also: you only need to create these once. Think of them like holograms, but each client sees their own version rather than being synced by the server.

local Owner = ents.owner()
local Centre = Vector( 0, 0, 0 )

local CSModel = render.createClientModel( Owner:model(), render.RENDERGROUP_OPAQUE )

hook( "render", "DrawStuff", function()
    if not render.isHUD() then
        render.clear( 0, 0, 0, 255 )

        render.setColor( 0, 0, 0, 255 )
        render.drawRect( 0, 0, 512, 512 )
    end

    local AimVec = Owner:aimVector()

    --Vitally important, without this, nothing will draw!
    render.clearDepth()

    render.start3D( Centre + AimVec * -128, AimVec:Angle(), 70, 0, 0, 512, 512, 1, 2048 )
        --Start by suppressing the engine lighting.
        render.suppressEngineLighting( true )
       
        --Set the light to come from the centre of the world.
        render.setLightingOrigin( Centre )
        --Set the light to be white (values are 0-1)
        render.resetModelLighting( 1, 1, 1 )
       
        --Set the model's colour to white, and alpha to 0.5 (again, values are 0-1)
        render.setColorModulation( 1, 1, 1 )
        render.setBlend( 0.5 )
       
        --Add directional lights.
        for i = 0, 5 do
            render.setModelLighting( i, 1, 1, 1 )
        end
        --Draw the model.
        CSModel:drawModel()
       
        --Stop suppressing engine lighting.
        render.suppressEngineLighting( false )
    render.end3D()
end )
With this code, you should see your playmodel in the centre of the screen, and as you turn your view, it rotates with it.

There's a few important things this code does.
  • We start by clearing the depth buffer with render.clearDepth(). Without this, nothing draws.
  • We then suppress the standard lighting and set up our own light in the world. In screens, the light will only work on models with an alpha value less than 1. This is a garry bug. HUDs can draw opaque models with lighting fine.
  • After setting up the light, we draw the model itself.
  • After drawing, we stop suppressing the engine light.

Now you know how to draw one model. To draw more, and to alter their position in the world, use the standard entity functions. For example:
CSModel:setPos( Vector( 0, 0, 100 ) )
will move the model 100 units above the centre in the 3D camera's world. You can also animate clientside models with CSModel:setAnim() which functions identically to holograms.

You do not have to set up lighting for every model unless you want to change how they are lit. Setup the light first, then draw all your models in the scene together.

To remove a clientside model you are no longer using, use:
CSModel:remove()

Back to contents

8. Render Targets
Render targets provide a way to draw stuff to a screen without wasting ops. A render target is a texture that you can draw into that remembers what you've drawn until you draw over it or clear it.

Getting a render target
To get a render target for your screen/HUD/emitter, simply call create() or createWithSize() from the render target library.

For a screen:
-- Creates a rendertarget that matches the screen's size.
local RT = rendertarget.createWithSize( render.getConfiguredScreenRes() )
For a HUD/Emitter:
-- Creates a rendertarget with width 1024 and height 512. Note that width and height must be powers of 2.
local RT = rendertarget.createWithSize( 1024, 512 )

Drawing to the render target
All drawing to the render target is done asynchronously. You pass it a function which is executed inside a drawing context that writes to the render target. Once the function has executed, the render target will retain the pixel data until you clear it or draw to it again.

You can call drawToTexture() at any time, including within the function you pass to it to queue another render for the next frame, but remember that it does not execute immediately. Thus you cannot write to local variables and expect to see the changes after drawToTexture().

For example:

local function DrawingFunction()
    render.clear( 0, 0, 0, 255 )
    render.setColor( 255, 255, 255 )
    render.drawRect( 0, 0, 512, 256 )
end
RT:drawToTexture( DrawingFunction )

Here we only draw to the texture once with a white rectangle that takes up the top half of the texture.

Drawing the render target texture
Once you have drawn to the texture, you can draw the texture itself on a screen, HUD or emitter plane using draw().

hook( "render", "DrawRT", function()
    RT:draw( 0, 0, 512, 512 )
end )

This draws the RT at 0, 0 with width and height 512. Increasing/decreasing the size compared to the RT's size when drawing will stretch the texture.

Note that you can also pass the render target object to render.setTexture() or render.setMaterial() and use other drawing functions for more advanced rendering use cases.

Back to contents

9. VGUI Elements
VGUI is a simple but useful set of UI controls that let you create interactive interfaces in Garry's Mod.

Starfall has VGUI bound for use in HUDs. A few important points regarding the implementation:
  1. VGUI elements are only created when the local player is linked to the HUD. When they unlink, all VGUI controls are destroyed.
  2. There are 2 very useful hooks, "hudlink" and "hudunlink". Both of these are passed the player linking/unlinking. Use these to determine whether your VGUI elements are showing and can be created or not.
  3. If you find elements stuck on your screen from other HUDs that have gone out of view, then you can use the command "starfall_hud_removevgui" to force them to be removed. This may break the chip that is using them though!
With that said, let's dive into how they work.

Creating a VGUI control
This is done with the "vgui" library.

--A player has linked to us.
hook( "hudlink", "bleh", function( Ply )
    print( Ply, "linked to us" )
    --This will make a frame, size 800x600 in the centre of the screen.
    local Frame = vgui.create( "DFrame" )
    Frame:SetSize( 800, 600 )
    Frame:Center()
    Frame:MakePopup()
end )

--A player has unlinked from us. We don't need to remove the VGUI stuff, it's done automatically.
hook( "hudunlink", "bleh", function( Ply )
    print( "Goodbye", Ply )
end )

All methods and fields are accessible with the same names as for GLua. Note that you cannot write fields directly to the object, but you can override methods.

For example, to make the frame show as a white rectangle instead of the default look:
function Frame:Paint( W, H )
    render.setColor( 255, 255, 255 )
    render.drawRect( 0, 0, W, H )
end

Back to contents

10. Channels Library
The channels library allows for communication between server instances. It is an event driven system, one chip broadcasts a message on a channel, and subscribed chips will receive the message.

Channel types
There are two types of channel.
  1. Public channels, which have a string name and broadcast to anyone's chip that is listening out for them.
  2. Private channels, which only send to chips you own.
Listening to a channel
To listen to a channel, use the channels.listen* functions.
--Listen out for a public channel
channels.listen( Name, ID, Callback )

--Listen out for a private channel
channels.listenPrivate( ID, Callback )
Here the ID value is a unique identifier that lets you remove the callback later if you want to.

The callback takes first the entity that sent the message, and then the message's values.
channels.listen( "Cake", "MyAwesomeListener", function( Chip, ... )
    print( "We received stuff:", ... )
end )

channels.listenPrivate( "MyAwesomeListener", function( Chip, ... )
    print( "Super secret stuff:", ... )
end )

Sending data to a channel
To send data to a channel, call the channel.send* functions.
--Send data to a public channel called "Cake"
channels.send( "Cake", "Is Great" )
With the listener registered above, we'd get a print of:
Code:
We received stuff:    Is Great
as the listener function received the string "Is Great".

For private channels,
--Send data to your private channel
channels.sendPrivate( "Super secret information here" )
with the listener we added above, we'd get a print of:
Code:
Super secret stuff:    Super secret information here
as the listener function received the string "Super secret information here".

Data you send can be anything, not just strings. Do note however, if you send a table then the other instance can directly modify that table's values and it will affect the table in your instance too. Also, you can send functions, but it will run in your chip's instance and use up your own ops/time quota.

Back to contents

11. Coroutines
Coroutines are a form of co-operative threading available to Lua. They are not separate threads, so do not benefit from multiple processor cores, but their idea is similar. Their main usage is to stagger workloads, processing large amounts of data steadily over time. They can also be used for queuing tasks.

Creating a coroutine
In Starfall, creating a coroutine is identical to creating one in Lua.
local function ILikeCake()
    print( "Cake is great!" )

    coroutine.yield( "Mmmm." ) --Halt the function here, we'll start from here again next.

    print( "But don't forget cookies and milk!" )

    return "Thanks for asking."
end

local Thread = coroutine.create( ILikeCake ) --Creates the coroutine object.

print( coroutine.resume( Thread ) ) --Runs the coroutine through, will stop at the yield.

print( coroutine.resume( Thread ) ) --Runs it again, this time starting from our yield.
Here, we create a coroutine or 'thread' object. We have to pass it the function it should run.

Yielding
Now, unlike ordinary functions, we can stop coroutines part way through what they are doing. In the above:
coroutine.yield( "Mmmm." )
this ends the function at this point, and returns "Mmmm." to coroutine.resume.

Thus, the above code will print this in sequence:

Cake is great!
true    Mmmm.
But don't forget cookies and milk!
true    Thanks for asking!

Note that when we yielded, and when the function returned, it passed these values as the second return value of coroutine.resume(). The first value will always be either true or false, and tells you whether the coroutine ran correctly or whether it errored.

A more involved example
Let's look at a more useful example that shows us the real usefulness of coroutines. Say we have a table of data, and we want to apply an action to every entry. However, this action is expensive, and we want to only process part of the table at a time. This is a perfect job for coroutines.

Let's assume our data is stored in a table called "Data", and we want to process only 5 entries every tick to limit its performance impact. Also assume the function we're using to process the elements is called "Process", and it takes the value as its argument.
--Keep track of how many items we've processed.
local Iterations = 0

--This is our coroutine function, it will go through the table 5 elements at a time.
local function IterateTable()
    for Key, Value in pairs( Data ) do
        Process( Value )

        --We've performed another iteration, add to our count.
        Iterations = Iterations + 1

        --We need to stop the thread here, as we've done our 5 iterations for now.
        if Iterations == 5 then
            coroutine.yield()
        end
    end
end

--This is our thread object we're going to use.
local Worker = coroutine.create( IterateTable )

--We want to process 5 per tick, so we hook into "think".
hook( "think", "Process 5 at a time!", function()
    --Reset the iterations for this tick.
    Iterations = 0

    local Status = coroutine.status( Worker )

    --If the status is "dead", then we're finished, so we can remove our hook.
    if Status == "dead" then
        hook( "think", "Process 5 at a time!" )
        return
    end

    --We're not dead, so we still have work to do!
    coroutine.resume( Worker )
end )

Back to contents

12. General Lua Tips
There's quite a few things about Lua that aren't obvious or well documented. I'll go over a few good tips I've picked up and use while I code Lua.

The bad table.insert habit
For all intents and purposes, using table.insert to insert something into the end of a table is BAD. For starters, it's slow, and secondly, there's a much quicker way to do it.

When you are building a table once from scratch:
--Keep track of the table index we need to use.
local Table = {}
local Count = 0

--Here Condition() is some function used to filter.
for i = 1, 10 do
    if Condition( i ) then
        Count = Count + 1
        Table[ Count ] = i
    end
end

--Conveniently, we now have the populated table, and its size.
print( "Populated with", Count, "elements" )

When you are appending to a table you do not know the size of:
Table[ #Table + 1 ] = Value

When to use table.insert
The only time you need table.insert is if you want to add an element to the table at a specific index that isn't the last. For example, if you wanted to add a value to the start (though you could just reverse the table's order and then add new values to the end and avoid this).

The bad ipairs habit
Straight off table.insert's heels, is ipairs. This innocent iterator is entirely pointless. It does nothing but add overhead to what you're doing.

Its replacement? The simple numeric for loop.
for i = 1, #Table do
    local Value = Table[ i ]

    --To replicate ipairs exactly, we should break if Value is nil.
    if not Value then break end

    --Do something with i and Value here.
end
You can argue that ipairs is easier to write, and nicer to read, but the execution time difference is quite significant if your code is being run often. It's not worth it. Also, the numeric for loop is easier to understand for new coders than ipair's cryptic method.

Function declaration styles
Lua has a nice function defining syntax. Despite that, some people seem to like to declare functions as values instead of, well, functions. This is fine for global values and table values, but for local functions, you're missing out on a nice shortcut.

Let's look at the two ways to define a local function.
local VarFunc = function()
    print( "Variable style declaration" )
end

local function FunkyFunc()
    print( "Function style declaration" )
end

No difference right? Wrong. Try this:

local VarFunc = function( i )
    i = i + 1
    if i > 10 then return i end
    return VarFunc( i )
end

local function FunkyFunc( i )
    i = i + 1
    if i > 10 then return i end
    return FunkyFunc( i )
end

print( FunkyFunc( 0 ) )
print( VarFunc( 0 ) )
You'll get a print of 11 from FunkyFunc, but an error from VarFunc. This is because for VarFunc, the local value isn't assigned until after the function is declared. So VarFunc is called from the global space rather than as an upvalue.

With FunkyFunc, the function syntax has the added advantage that it declares FunkyFunc as a local value before the function is defined, so that FunkyFunc has itself as an upvalue.

Arrays and tables
Many people like to write Lua as if it is C. They will use tables solely as arrays and have a helper function that loops through their array to remove values each time. This is rarely the best method.

The only time you need arrays is when order is important, or you iterate the table far more often than you add/remove elements. Otherwise, you should use tables as what they are, hash tables.

An example:
local Names = {
    Bob = true,
    Bill = true,
    Joe = true
}

if Names[ "Bob" ] then
    --Do something with Bob here.
end

--Bye Bill!
Names[ "Bill" ] = nil

--Hi Alex!
Names[ "Alex" ] = true
By using the important values as keys, we remove the need to loop through the table to find elements, and it makes adding and removing elements extremely easy too.

But it doesn't stop at strings. Lua tables can store ANY value as a key. You can use tables, functions, strings, numbers, userdata and even booleans as keys. This is far more powerful than a simple array structure.

Localise commonly used functions
If you've ever coded Python or similar languages, you know about the import statement. You can import classes from other files and name them how you like, and also restrict what you import to only the parts you use. The equivalent in Lua is localising the functions you want.

This has two advantages:
  1. Functions called from locals are faster, as it avoids reading the global table (and whatever table they're contained in) every time. Most noticeable for code ran often.
  2. You can rename functions to fit your style.
For example:
local Rad = math.rad
local Sin = math.sin

print( Sin( Rad( 90 ) )

You may also see people localise using the same name as a global, for instance:
local Table = Table
This may seem odd and pointless at first, but remember that local variables are faster to access than globals, and that this is a perfectly legal Lua operation.

Back to contents

13. Permissions
Some actions in Starfall require explicit permission from the user running the code to be able to perform actions. For example, on the client, playing sounds or using halos on holograms require the relevant permission to be granted to do it. This helps prevent players from having actions performed that potentially annoy them, or lag their client, while still allowing the features to be used.

Permissions also come into play on the server when you are using a chip loaded directly from the shared area (using the "Load to tool" feature). This helps prevent code that you cannot see from running actions on the server without you knowing, such as removing entities or moving you around. If you are using your own code directly, or code you have the source for, the sever will not require permissions. Only code loaded directly from the shared area using "load to tool" requires server permissions.

For developing, you must be aware of permissions whenever you want to use a feature that requires them.

The first step to your chip being able to perform actions that require a permission is to register the requirement of that permission using permissions.register( permissions.PERMISSION_NAME ).
if SERVER then
    -- On the server, register needing the permission to move the chip owner.
    permissions.register( permissions.SET_PLAYER_POSITION )
else
    -- On the client, register needing the permission to play client sounds, and draw halos.
    permissions.register( permissions.PLAY_CLIENT_SOUNDS )
    permissions.register( permissions.DRAW_HALOS )
end

Once registered, the user of the chip will be able to grant it access to the features you registered. If you want to know if your chip has access to a permission, use permissions.canPerformAction( permissions.PERMISSION_NAME ).
if not permissions.canPerformAction( permissions.DRAW_HALOS ) then
    print( "Unable to draw halos. You must first grant the permission in the permissions tab!" )
end

For a full list of available permissions, check the docs in-game under the permissions library's fields.

Back to contents
Do you think you could add some information about the channels stuff and props lib?
(26-05-2014, 09:32 PM)JoshTheEnder Wrote: [ -> ]Do you think you could add some information about the channels stuff and props lib?
I've added a channels section now.

The props library is literally just the one function for spawning a prop (which returns the prop entity on success) and the canCreate() returns if you can do it (there's a rate limit).
Starfall is very difficult for me, expadv2 Heart is much lighter than ..... Starfall Starfall is just for me very strange and complicated Angry
(28-06-2015, 08:59 PM)lukkypavelka1 Wrote: [ -> ]Starfall is very difficult for me, expadv2 Heart is much lighter than ..... Starfall Starfall is just for me very strange and complicated Angry

Its actually pretty easy after you figure it out.

If you want some help you can ask anyone in ENCOM or just post on the starfall forums.
thanks for making this
[Image: FhP7qpy.png]

Am i doing it right?
Is it possible to add a section about buttons in starfall screen? I know exactly how to make it in E2, but in starfall, that's different.
If you look into T-19's shared file you can find screen and button examples.
(27-08-2017, 12:46 AM)Red The Blue Color Wrote: [ -> ]If you look into T-19's shared file you can find screen and button examples.
Thanks, I will take a look at it.