Hello, this is my first tutorial about scripting custom logic with lua. In this tutorial we will create simple mod - monster spawner. *I may mistake in some moments, feel free to correct me. There also may be some text mistakes since i'm not a native speaker. Any feedback/questions appreciated!* 1. Basic mod structure Since the game loads files from every directory in 'asset' folder i recommend you to create special folder for you mode, where you can store all files. I've used Starbound/assets/mods/apx_beacondefence/ (you may need to create 'mods' folder too) 2. Creating spawner object Most of text-based assets are represented in JSON. It has very simple format, but if you not sure you can always check yourself with validator like http://jsonlint.com/ There are nubmer of types like 'object', 'item', 'recipe' etc, specified by file extension. For now only wired objects can be used with lua script so we will create this one. Create object file in mod folder and open it with any text editor. I've created alienbeacon.object file (so full path looks like assets/mods/apx_beacondefence/alienbeacon.object now). I've used Iron Beacon (assets/objects/boss/ironbeacon.object) as a source for my object, after a short editing it looks like Code: { "objectName" : "alienbeacon", "rarity" : "Common", "objectType" : "wire", "description" : "Prepare for repair!", "shortdescription" : "Alien Beacon", "race" : "generic", "category" : "tools", "printable" : "false", "inventoryIcon" : "/objects/boss/ironbeaconicon.png", "orientations" : [ { "dualImage" : "/objects/boss/ironbeacon.png:<color>.<frame>", "imagePosition" : [-16, 0], "frames" : 12, "animationCycle" : 2, "spaceScan" : 0.1, "anchors" : [ "bottom" ] } ], "animation" : "/objects/wired/ironbeacon/ironbeacon.animation", "animationParts" : { "beacon" : "/objects/boss/ironbeacon.png" }, "animationPosition" : [-16, 0], "scripts" : [ "/mods/apx_beacondefence/alienbeacon_logic.lua" ], "scriptDelta" : 5 } Now let's explain every property: objectName - this is an internal name of this object. This name should be uniq through all items and object so don't enter obvious names. Later we will use this name in the recipe rarity - rarity of object. For example you can write 'Legendary' here objectType - since lua works only with 'wire' objects now, this is what we place here. description - craft description shortdescription - this is the name which shown in the tooltip race - race-based restrictions. Leave 'generic' for no restrictions category - category of object. Just leave a 'tool' printable - set "true" if you want this item to be printable with 3D printer (note - you also should specify price then) inventoryIcon, orientations, animation, animationParts, animationPosition - this attributes specify all graphics and animation of this object. I've just copied this values from the Iron Beacon scripts - path to lua script (note - always specify absolute path here) scriptDelta - i'm not sure what it is actually, but i think it is update time (in millisec). 5 is default for most scripts *Some notes about paths* If you have image file in the same directory with object file, you can specify only it's name, without path. But this doesn't works with lua scripts - you should always specify absolute path for them. 3. Creating recipe Ok now we have spawner object, but we need to get it ingame somehow. If you want to use other methods like quest rewards etc you can go to next section. Let's create alienbeacon.recipe file (assets/mods/apx_beacondefence/alienbeacon.recipe) Code: { "input" : [ { "item" : "ironbar", "count" : 10 } ], "output" : { "item" : "alienbeacon", "count" : 1 }, "groups" : [ "plain", "all", "other" ] } Recipe definition is pretty easy. input - craft cost. any item resource can be specified here by it's objectName (you can find items in the assets/items/ folder) output - what we receive. Here we place objectName of our Alien Beacon. groups - craft gorup. Use 'plain' if you want this recipe in the default players craft menu, or for example 'anvil'. Also we should add this recipe as default blueprint to allow crafting. Edit player.config in the asset folder, search defaultBlueprints / tier1 (line 26 currently) and add alienbeacon there, so the code will looks like Code: "defaultBlueprints" : { "tier1" : [ { "item" : "alienbeacon" }, { "item" : "copperarmorhead" }, { "item" : "copperarmorchest" }, .... Now you can craft this object ingame. 4. Lua scripting Ok now we are ready for the most interesting part of this tutorial This game use LUA as a scripting language. It's syntax is pretty easy, and you don't need much knowledge since most of default packages (like io) are removed. Create lua file. For me it is alienbeacon_logic.lua. Don't forget to change script path in .object file if you will change script name. Now add some default methods into it Code: function main() end function init(args) end There are some default function wich are called by the game. init() - this function is called by game evey X millisec while you are placing this object (init phase) main() - this function is called by game evey X millisec after you've placed this object. (main phase) There are also some more functions, but this two are default. You can also use default Iron Beacon's lua script as a sample (you can find it here: assets/objects/wired/ironbeacon/ironbeacon.lua) *Lua supports comment using '--', so you can see some information tooltips inside code* *You can see differences between my code and original one. I write in a way i think is right, you may choose other ways* 4.1 Hello world! Let's start with a Hello world script of course! Code: function init(args) world.logInfo("Hello world!"); end We use world.logInfo() to print text into log file (Starbound/starbound.log). You can use this method to debug state of your scripts, or print any info. Now you can start the game, craft Alien Beacon, select it, and watch log file. You should see lot's of Hello world! strings at the end of it (remember that init function executes every X milisecs!) 4.2 Object Initialization First of all let's add 'rotation' animation to our beacon when it's placed (like original iron beacon) and make it usable. To make this let's modify main method: Code: function main() -- Check for the single execution if self.initialized == nil then -- Init object initializeObject(); -- Set flag self.initialized = true; end end function initializeObject() -- Make our object interactive (we can interract by 'Use') object.setInteractive(true); -- Change animation for state "active" object.setAnimationState("beaconState", "active"); end I use default global self variable to store information about this beacon. self is a global metatable, uniq for this instance of object. For example if you have 2 beacons, they have different self tables. I check self.initialized variable, and if it is nil (undefinded), i initialize object and define this variable. It guarantees signle execution of initializeObject() function. Inside initializeObject() function we use another global variable - object. object is a link to current entity. There are number of functions, availiable for such global variables like object, world, entity (for monster behavior only). You can find full function list here, i will try to make docs with arguments and explanation later. object.setInteractive(true); - obviously this function allows interaction with our object. object.setAnimationState(); - this function set current animation state of the object. You can see all animation states in the .animation file (for us it is assets/objects/wired/ironbeacon/ironbeacon.animation) 4.3 Interaction response Ok in the previous part we've set our object interactive. Now we can define onInteraction() function. This function is called by the game every time you interact with object (press 'E') Code: function onInteraction(args) return { "ShowPopup", { message = "Hello world 2! :)" } }; end This code will create popup window each time you interact with object. If you don't want to create popups you can just remove return, or return nil 4.4 Give me something real now! Okay okay, now time to create something real! Monster spawning is actually very easy. Code: function onInteraction(args) -- Spawn monster world.spawnMonster("serpentdroid", object.toAbsolutePosition({ 0.0, 5.0 }), { level = 10 }); end First argument of world.spawnMonster() function is monster name. serpentdroid is a mosnter from assets/monsters/unique/. Second argument is position, object.toAbsolutePosition({ 0.0, 5.0 }) creates new position, relative to position of object (beacon for us) but with offset (0, 5). Note that game use left-bottom-corner coordinate system, so (0, 5) will spawn monter above the beacon. Third argument is a table that specifies some parameters of new monster (for us it is level) Now our monster spawner works fine. You can download sample here: http://puu.sh/5H5QZ/04dd08fac1.zip. Simply extract it into assets/mods/ (create mod folder if needed) to make full path like assets/mods/apx_beacondefence/ (Don't forget to add alienbeacon in the player.config - watch stage #3) In next lesson i will tell about scripting monster behavior and some other features. Stay tuned
Awesome! thank you, very easy to understand.. will keep an eye out for your next one.. also is there any where that i can find all the commands/arguments/ functions that starbound Lua is compatible with?
There's no official lua API yet, but you can find some information here, or in the native lua scripts.
Does this lua API support arrays? Is it possible to spawn a random monster from said array? Can I have the object move around erratically and spawn monsters on its own? These are important questions. For science. Edit: also, can I apply a velocity to the spawned monster, and can I track the monster after spawn with a thingID?
to correct your quote about Main() being run every so milliseconds... Its actually ever number of ticks. The game runs at 60 ticks a second, so a deltaScript of 5 means every 5 ticks, or ever 5/60ths of a second. also, instead of having an initializeObject, just put that code in the init(args) function and let that handle it.
The problem is that it will be done multiple times (for objects at least). I know there's no difference, because it will be done before main cycle, but if i want code to be done once, i prefer to do it once. Yes, tables are equal to arrays in lua. Yes it is possible to spawn random monster. You cannot move object atm (only if it is tech...), but you can spawn monsters on it's own. Yes, you can set velocity and track target. I will make next tutorial for monsters soon, just testing some things atm.
Yes you can, simply change the name of monster in the Lua code to any monster types. Some of that monster type is : smallbiped, tallbiped, quadruped, and many others ... To find what monstertypes are available to be put into the code, you have to look in the Starbound/assets/monsters, open any ".monstertype" files inside the in a notepad -or other software- and look the 'type' name there, it usually in the top line. So the code is going to be like this world.spawnMonster("smallbiped", object.toAbsolutePosition({ 0.0, 5.0 }), { level = 10 }); test it , and it gonna spawn random small bipeds monsters sorry for my bad english though.. >.<
apexolog, where did you find the scripting reference for world.spawnMonster() ? edit: https://gist.github.com/devcutter/8094601 is one place for reference, another is \assets\scripts\API
Really great tutorial. Thanks so much for making it. The only problem I ran in to was I had to change all of the "object" variables to "entity" in the lua script.
Sorry for the necro, but I'm having some problems trying to make an item printable. When I place the item on the 3d printer, it reconizes it's price, but when I press 'scan', nothing happens, could you tell what am I doing wrong here?