Lua:Tutorial GettingStarted: Difference between revisions

From Spring
Jump to navigationJump to search
Knorke (talk | contribs)
copy over from User:Knorke/Sandbox1‎ into real page
 
m <syntaxhighlight> doesn't appear to be working, so for the time being I've subbed it out with <pre><code>
 
(10 intermediate revisions by 4 users not shown)
Line 1: Line 1:
[[Engine_Development|Development]] < [[Lua_Scripting|Lua Scripting]] < {{FULLPAGENAME}}
[[Engine_Development|Development]] < [[Lua_Scripting|Lua Scripting]] < {{FULLPAGENAME}}


==What this is==
Here are some examples of small widgets, with useful code to get you started!
This page explains how to get started making scripts for spring.


==What this is not==
To play with each example, place the code into a "my_new_widget_name.lua" file in your Spring/LuaUI/widgets dir (create it if not already present) and run Spring with any game. Your widget should then appear ingame!
This page does not explains how to make scripts for spring.


This page is not a Lua tutorial.
To see many more examples, check out the LuaUI/widgets directory of the games in [[Gamedev:PublicRepos|PublicRepos]].  


==Requirements==
Note that every widget (and gadget) must include a GetInfo() callin, but all other callins are optional.
* having played spring game for some days
* text editor with synthax coloring
* copy of [[SpringTutorialGame]]
* [http://springfiles.com/spring/spring-maps/aliendesert some small map] because those load faster
* Spring set to [[Springsettings.cfg#Fullscreen|windowed mode]] for easier switching between editor/game
* a very bit familiar with programing (variables, functions, loops, arrays = enough)
* read through [[TestingYourGame|this tips page]] once


==Wupget and friends==
==Example 1==
In Spring games there are 3 kinds of Lua scripts.
The "hello world" example, with a ticking clock.
Crude and simple explaination:
# gadgets: these are the "gameplay logic code."
# widgets: these are for interface.
# unit scripts: animate units & controll their weapons.


Making those 3 is not very different but widgets are the easiest to start with.
There is also 4) functionality provided by the engine.
'''Also look at this:'''
http://springrts.com/mediawiki/images/1/13/Gadgets_widgets_and_friends.png
All 4 are in a constant fight against each other.
==My first wupdget==
The [[SpringTutorialGame]] is simple and good for testing:
You can use any other game but some block player widgets or are very confusing.
Create a txt file like this and there:
Spring\LuaUI\Widgets\myfirstwidget.lua
and copypaste this text into it:
-----
<code><pre>
<code><pre>
function widget:GetInfo()
function widget:GetInfo()
  return {
  return {
    name     = "my first widget",
      name         = "Name as shown in widget list",
    desc     = "this is so awesome",
      desc         = "Description as normally shown in tooltip",
    author   = "horse 3000",
      author       = "It could be you!",
    date     = "2009",
      date         = "now",
    license   = "GNU GPL, v2 or later",
      license     = "PD", -- should be compatible with Spring
    layer     = -10,
      layer       = 0,
    enabled   = true
      enabled     = true
  }
  }
end
end
</pre></code>
-----
For now it does not matter what excactly this does but here you can put informations about your widget.


What matters is that without such GetInfo() function Spring will not load the widget correctly.
local helloWorld = "I STARTED LOOK AT ME JIM!"
(Since it takes up screen-space it will be left out when code is posted. Of course it must still be there.)
local toggle


Start spring.exe and press F11. The "widget menu" opens:
function widget:Initialize()
 
  Spring.Echo(helloWorld)
http://springrts.com/mediawiki/images/8/80/Widget_F11menu.png
end
 
It is likely already filled with some other widgets contained in the game.
Find your new widget in the list. Click on it to turn it on and off: The name in list should go green or red accordingly.
 
If that works - congratulation: You made a new widget that does excactly nothing.
 
="Hello unit" wupdget=
Add the following at the end of myfirstwidget.lua:


<code><pre>
function widget:Update()
function widget:UnitFinished(unitID, unitDefID, teamID)
  if (toggle == true) then
Spring.Echo ("a new unit was finished! It belongs to team " .. teamID)
      Spring.Echo("TOCK")
      toggle = false
  else
      Spring.Echo("TICK")
      toggle = true
  end
end
end
</pre></code>
</pre></code>


UnitFinished(...) is a [[Lua:Callins|callin function]] : Those are functions that the engine calls when certain events happen.
==Example 2==
Captain Obvious has already figured out: This function gets called when a new unit is finished.
How to make stuff happen when units do stuff.
 
Spring.Echo is used to output text to the chat/console.
So this widget should tell when a new unit is made.
 
4)
Open the widget menu and disable then re-enable your widget: This will reload the widget.
Now build some units to test it.
 
===Finding errors and infolog.txt===
For test add an error like this:
Spring.Echo (100 + nil)
and reload the widget.
The widgets name is green but turns yellow as soon a unit is build: "nil" can not be used in math like that.
 
The error is also displayed in chat and [[infolog.txt]]
In that logfile you can look up all errors even if they have disappeared from chat.
Fix the error, reload the widget and it is green again. Oha.
 
FIXME: expand this
 
===widget sees you and only you and your allies===
Even if there is a bot/other player building stuff, the widget will not notify on all  their new units if they are outside line of sight.
That is because widgets are limited in what they can "see."
The widget only sees what what the player who is running the widget can see, too. This is to prevent cheating.
 
=="Hello unit" 2 - with markers and advancement==
change the widget to this and guess what it does:
 
<code><pre>
<code><pre>
function widget:UnitFinished(unitID, unitDefID, teamID)
function widget:GetInfo()
local x,y,z = Spring.GetUnitPosition (unitID)
  return {
Spring.MarkerAddPoint (x,y,z, "a new unit! id=" .. unitID)
      name        = "Unit Announcer",
      desc        = "Units are proud to be alive",
      author      = "Some guy",
      date        = "I don't know why we include dates",
      license      = "GPLv2 or later",
      layer        = 0,
      enabled      = true
  }
end
end
</pre></code>


It adds a marker point on every new unit!
local machoUnits = {}
Note how the unitID parameter from UnitFinished() is passed to GetUnitPosition()


===unitIDs===
function widget:UnitCreated(unitID, unitDefID, unitTeam, builderID)
Previous paragraph featured ''unitID'' variable.
  Spring.Echo("HELLO")
Every unit, feature or weapon projectile in spring has an ID number:


http://springrts.com/mediawiki/images/3/3c/Units_features_projectiles.png
  local humanName = UnitDefs[unitDefID].humanName
  local numWeapons = #UnitDefs[unitDefID].weapons
  local plural = numWeapons~=1 and "s" or ""


There are never two units with the same unitID at the same time.  
  Spring.Echo("I am unit " .. unitID .. ". I am a " .. humanName .. ".")
  Spring.Echo("I have " .. numWeapons .. " weapon" .. plural .. ".")
  if numWeapons>0 then
    machoUnits[unitID] = true
  end
end


<pre>#YOLO?</pre>
function widget:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam)
No YOLO: when a unit is destroyed its unitID might be reused later for a different unit.
  machoUnits[unitID] = nil
end


=a boring counter widget=
function widget:UnitDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponDefID, projectileID, attackerID, attackerDefID, attackerTeam)
A counter that is increased when units are made:
  if machoUnits[unitID] then return end


<code><pre>
  local n = math.random(3,10)
local counter = 0
  local ow = "O"
function widget:UnitFinished (unitID, unitDefID, teamID)
  for i=1,n do
counter = counter +1
    ow = ow .. "W"
Spring.Echo ("now there are " .. counter .. " units")
  end
if counter > 10 then
  Spring.Echo(ow)
Spring.Echo ("wow, more than 10 units!")
end
end
end
</pre></code>
</pre></code>


[[TestingYourGame#Lua|Reload the widget]] and note how the counter gets reseted.
==Example 3==
That is how widgets roll: Their variables get cleared on reload.
Something actually useful.
 
===boring counter widget with tables===
Imagine you want a counter that stores for each factory how many units it has already produced. One variable will not be enought here but an array can store multiple things.
 
Arrays in Lua are tables. Read about tables: http://lua-users.org/wiki/TablesTutorial
 
<code><pre>
<code><pre>
local buildCount = {}
function widget:GetInfo()
function widget:UnitFromFactory(unitID, unitDefID, unitTeam, factoryID, factoryDefID, userOrders)
  return {
--read from table how many units this factory already built
    name      = "Stop means Stop",
local n = buildCount[factoryID]
    desc      = "Cancels Self D orders when unit is given a stop command",
 
    author    = "enotseulB",
--was the entry empty?
    date      = "GPL v2 or later",
if n == nil then
    license  = "Feb 2015",
n = 0 --if yes, start counting from 0
    layer    = 0,
end
    enabled  = true 
n = n +1 --increase n by 1 (best comment ever)
  }
Spring.Echo ("This was unit number " .. n .. " from factory " .. factoryID)
--store new buildCount for this factory
buildCount[factoryID] = n
end
end
</pre></code>
UnitFromFactory is basically the same as UnitFinished, but has the usefull ''factoryID'' parameter.
'''Why the if n == nil?'''
In Lua everything not initialized is nil (=nothing) and nil can not be used in any math.
(n = n +1  is a math)
Btw a shorter version could be:
<code><pre>buildCount[factoryID] = (buildCount[factoryID] or 0) + 1</pre></code>
==I made enough "hello world" widgets. What now?==
On [[Lua_Scripting]] the most important are the pages under '''GameState''' and '''Client'''
See these [[TestingYourGame|pro-tips]] to make testing less frustrating.
While widgets are initially easiest to make, soon there is a problem:
widgets are for interface stuff.
That means widgets usually need to either...
* ...draw something on the screen. (for example the health of Commander)
and/or
* ...give orders to units. (for example make Commander automatically run away from danger)
Both graphics and the whole order-system can be bit tricky for starters.
Without graphics and orders, the awesomeness potential of a widget is small.
'''Pro tip:''' gadgets can be awesome without either!
==go go gadgets time!==
We learned widgets can only "look at" the gamestate or do things that players can do, for example ordering units to attack another unit.
On the other hand gadgets are scripts that are able to alter the state of the game.
With gadgets it is possible to implent new game logic like jumpjets, drop pods, tech tree systems, etc.
Unlike widgets, gadgets must be [[Gamedev:Structure#LuaRules.5C|included in a game or in a map.]]
To do so it is best to unpack the game file into an [[Gamedev:Archives|.sdd archive]]
Writing gadgets is similiar to widgets, except the getInfo() header and callin functions are prefixed with <code>gadget:xxx</code> instead of <code>widget:xxx</code>
===synced & unsynced===
Gadgets are split into a synced and into an unsynced part.
In simple terms that means the synced part is usually for controlling the game logic.
The unsynced part is for drawig graphics.
For example look at this lava tide gadget:
http://springrts.com/mediawiki/images/3/33/Beachbuggysuicidecult_lava_sync_unsync.gif
The '''synced''' part is responsible for ''damaging/destroying the units'' in the lava. It actually alters the gamestate.


The '''unsynced''' part is only for ''drawing'' the graphic of the lava. The graphic itself does not influence the gamestate in any way.
local CMD_STOP = CMD.STOP


In code it looks like this:
function widget:UnitCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions)
    if cmdID ~= CMD_STOP then return end
    if not unitID then return end
    if teamID ~= Spring.GetMyTeamID() then return end


<code><pre>
    if (Spring.GetUnitSelfDTime(unitID) > 0) then
if (gadgetHandler:IsSyncedCode()) then
        Spring.GiveOrderToUnit(unitID, CMD.SELFD, {}, {})
  --synced code
    end
  ..
  damage all units that are underneath the lava tide level
  ..
else
  --unsynced code
  ..
  drawing the lava with GL-api functions
  ..
end
end
</pre></code>
</pre></code>
Of course that also means that some functions or callins can only be used in synced or unsynced part. For example the synced part can not draw anything and the unsynced part can not spawn units.
Sometimes it is nessecary to transfer some data/information "across the sync-unsynced border" - but the scope of variables is only in the part they were declared in.
In lava-exampe that might be the current height of lava.
How this still works is explained here: [[LuaTutorials::InterCommunications]]
==Look at more examples==
Common hint for learning spring Lua is: ''"Look at some examples in other games."''
But a spring game usually has dozen of wupdgets and only a few are useful examples for beginners. Many scripts deal with some very specifique problem or are just way too long.
Or one has to be familiar with the game to even understand what the script even does.
Or if one wants to use the wupdget in ones own game, suddendly nothing works anymore because it depends on so many configs or other files.
Two simple examples are:
"Linked units" (hub dies, child-units explode too)
http://springrts.com/phpbb/viewtopic.php?f=23&t=25435
"Moveable metal extractor"
http://springrts.com/phpbb/viewtopic.php?f=14&t=26877
---
==Feedback? Questions?==
This page is open for feedback {{forumlink|topic=32047|title=in the forum.}}
[[category: Lua]]
[[category: Lua]]

Latest revision as of 04:30, 21 June 2023

Development < Lua Scripting < Lua:Tutorial GettingStarted

Here are some examples of small widgets, with useful code to get you started!

To play with each example, place the code into a "my_new_widget_name.lua" file in your Spring/LuaUI/widgets dir (create it if not already present) and run Spring with any game. Your widget should then appear ingame!

To see many more examples, check out the LuaUI/widgets directory of the games in PublicRepos.

Note that every widget (and gadget) must include a GetInfo() callin, but all other callins are optional.

Example 1

The "hello world" example, with a ticking clock.

function widget:GetInfo()
   return {
      name         = "Name as shown in widget list",
      desc         = "Description as normally shown in tooltip",
      author       = "It could be you!",
      date         = "now",
      license      = "PD", -- should be compatible with Spring
      layer        = 0,
      enabled      = true
   }
end

local helloWorld = "I STARTED LOOK AT ME JIM!"
local toggle

function widget:Initialize()
   Spring.Echo(helloWorld)
end

function widget:Update()
   if (toggle == true) then
      Spring.Echo("TOCK")
      toggle = false
   else
      Spring.Echo("TICK")
      toggle = true
   end
end

Example 2

How to make stuff happen when units do stuff.

function widget:GetInfo()
   return {
      name         = "Unit Announcer",
      desc         = "Units are proud to be alive",
      author       = "Some guy",
      date         = "I don't know why we include dates",
      license      = "GPLv2 or later",
      layer        = 0,
      enabled      = true
   }
end

local machoUnits = {}

function widget:UnitCreated(unitID, unitDefID, unitTeam, builderID)
  Spring.Echo("HELLO")

  local humanName = UnitDefs[unitDefID].humanName
  local numWeapons = #UnitDefs[unitDefID].weapons
  local plural = numWeapons~=1 and "s" or ""

  Spring.Echo("I am unit " .. unitID .. ". I am a " .. humanName .. ".")
  Spring.Echo("I have " .. numWeapons .. " weapon" .. plural .. ".")
  if numWeapons>0 then
    machoUnits[unitID] = true
  end
end

function widget:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam)
  machoUnits[unitID] = nil
end 

function widget:UnitDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponDefID, projectileID, attackerID, attackerDefID, attackerTeam)
  if machoUnits[unitID] then return end

  local n = math.random(3,10)
  local ow = "O"
  for i=1,n do
    ow = ow .. "W"
  end
  Spring.Echo(ow)
end

Example 3

Something actually useful.

function widget:GetInfo()
  return {
    name      = "Stop means Stop",
    desc      = "Cancels Self D orders when unit is given a stop command",
    author    = "enotseulB",
    date      = "GPL v2 or later",
    license   = "Feb 2015",
    layer     = 0,
    enabled   = true  
  }
end

local CMD_STOP = CMD.STOP

function widget:UnitCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions)
    if cmdID ~= CMD_STOP then return end
    if not unitID then return end
    if teamID ~= Spring.GetMyTeamID() then return end

    if (Spring.GetUnitSelfDTime(unitID) > 0) then
        Spring.GiveOrderToUnit(unitID, CMD.SELFD, {}, {})
    end 
end