Tutorial LUA Part 1: Terms and Strings v1.2

Discussion in 'Starbound Modding' started by The | Suit, Oct 1, 2014.

  1. The | Suit

    The | Suit Agent S. Forum Moderator


    [​IMG]

    This multipart guide was made in collaboration with @severedskullz .
    It is to give you a basic overview of LUA and to allow more unique mods to flourish in the community. Fully utilizing starbound's capabilities. If you notice any mistakes, please point them out so they can be corrected.



    SERIES LINKS
    1. PART 1: Terminology and Strings
    2. PART 2: Tables - Functions - Operators
    3. PART 3: Practicals


    TABLE OF CONTENTS
    [ Clickable inpage Links ]


    .


    CHAPTER - 1 STARTING OFF



    Just like JSON - you are going to need a code based text editor.
    My Recommendation are

    The second most important tool is your interpreter to practice the scripts you learn.
    Interpreter [lua 5.1]: http://repl.it/languages/Lua
    This interpreter will let you test your code online. Simply press the play button at the top right of the left code input window to run your code.

    REFERENCES:

    Good places to further your education of LUA and clarify issues not covered here.
    1. http://www.lua.org/pil/contents.html
    2. http://www.phailed.me/2011/02/learn-lua-the-hard-way-1/
    3. http://lua-users.org/wiki/TutorialDirectory
    4. http://www.tutorialspoint.com/lua/
    5. http://luatut.com/crash_course.html
    6. http://www.dev-hq.net/lua/1--introduction-and-setup
    7. http://wiki.visionaire-tracker.net/wiki/Basic_lua:_Introduction
    8. http://lua.gts-stolberg.de/en/Programmieren.php
    STARBOUND LUA DOCUMENTATION
    .



    CHAPTER - 2 TERMINOLOGY



    IDENTIFIER -
    is a fancy word for "name". It can be in reference to a variable, function, table, array. What is important to note is identifiers are case sensitive so bob, Bob, BOb, BOB are all different. It is recommended you avoid identifiers starting with an underscore followed by one or more upper-case letters. As they are reserved for special uses.

    Speaking of reserved there are a few words which are reserved
    Code:
      and  break  do  else  elseif
      end  false  for  function  if
      in  local  nil  not  or
      repeat  return  then  true  until
      while

    TYPES -
    The basic types in Lua: nil, boolean, number, string, function, and table.

    By using the type function you can find type of data.
    By using the interpreter try each line one at a time and see the result.
    Code:
      print(type("Hello world"))
      print(type(10.4*3))
      print(type(print))
      print(type(type))
      print(type(true))
      print(type(nil))
      print(type(type(X)))
    The last example will result in "string" no matter the value of X, because the result of type is always a string.

    CHUNKS -
    Chunks are akin to sentences, a complete statement by it self. Chunks can either be separated by a space, semi colon or line break. Ideally you should use a line break as it makes reading your code easier, as well as making it much less troublesome if an issue arises later.
    Code:
      a = 10 b = 11 print(a,b)
    or
    Code:
      a = 11; b = 12; print(a,b)
    or
    Code:
      a = 11
      b = 11
      print(a,b)
    VARIABLE-
    A variable is something that holds a value. Akin to what you learned in ALgebra. But in this case it holds more than a numerical value or problem. Variables in LUA can hold strings, tables and even functions.

    Variables do not have to be defined ahead of time. Also variables are divided into 2 major groups. Local and Global.
    Code:
    local a = 45 - local variable
    a = 45 -- global variable
    Now lets look at the scope of variables.
    Code:
    local global = 10 -- LOCAL variable, but has global scope!
    function randomFunc()
         global = 20 -- SAME variable as above
         local global -- DIFFERENT than above. Both inside this function and above
         end
    
    A local variable can only be called within a function, and will remove it self once that function finishes. While a global variable will remain until it is manually assigned a nil value. Only use global variables as necessary, an keep it unique enough as not to cause conflict with others for example modinitials_varname. Where mod initials is the initials for your mod and varname is the variable name you wish to assign.

    variables can also inherit values of other variables.
    For example
    Code:
      a = 10
      b = 43
      a = b
      print(a)
    you can also join evaluate variables together
    Code:
      a = 10
      b = 43
      print(a+b) - we will cover operators in a later section.
    COMMENTS -
    Comment tags let you write in comments into your code to help others understand. Comments will be ignored by the interpreter. In lua the comment tags are done by using --[[ to start --]] and on a separate line to end. So everything written between these will be ignored.
    You can also use -- to comment out a single line instead of a block.

    Comments help immensely in terms of readability. So other people looking at your code knows the purpose of the functions and variables.

    NIL -
    Nil is the default value to a space where nothing is mapped.

    BOOLEAN -
    True or False value. empty string and 0 are considered true.

    STRINGS -
    Are a collection of text encompassed in single or double quotes. You should stick to one or the other. Certain special characters cannot be used in LUA which can be used through escape sequences.


    Code:
      \a  bell
      \b  back space
      \f  form feed
      \n  newline
      \r  carriage return
      \t  horizontal tab
      \v  vertical tab
      \\  backslash    -->  print('a backslash inside quotes: \'\\\'')
      \"  double quote
      \'  single quote
      \[  left square bracket
      \]  right square bracket
    One important thing to note is "10" does not equal 10. One is a number and the other is a string. You have to use the functions
    Code:
      tostring -->  tostring(10) --> will turn value inside into a string
      tonumber -->  tonumber("10") --> will turn value to a number

    to convert between one and the other.

    TABLE -
    Is an object which holds varying amounts of data.

    FUNCTIONS -
    Are the brains of all programming languages. Essentially it is a chunk of code which contains a condition and an action and a result.
    .



    BREAK TIME


    Too much at once is not ideal for learning. So it is time to take a break. After the break time to do a small review on what you have learned. Remember do not look up at the answer. Try and recall everything from memory. Mistakes are fine, answers are provided below.

    TRUE OR FALSE QUESTIONS
    • Question 1. If x = 10 and y = "10" does x = y?
    • Question 2. If x = 11 y = 11 does X = y?
    • Question 3. boolean value of " " = false
    • Question 4. boolean value of nil = true
    COPY FOLLOWING PROBLEMS INTO AN INTERPRETER.

    Question 5.
    Modify print function to make the statement true.
    Code:
      a = 10
      print(a == "10")
    Question 6.
    Change only the number in the print function to make statement true.
    Code:
      a = 25; b = 35; c = 10;
      a = b
      print(a-15 == c)
    Question 7.
    Would this be a true statement yes or no and why?
    Code:
      a = 25; b = 35; c = 10;
      a = b ; b = 25
      print(a-15 == c)
    Question 8.
    Correct the following statement so it results in a true statement, by only modifying the comment tags.
    Code:
      --[[ a = 45
      b = a
      b = 16
      --]] b = b - 20
      -- b = b + 9
      print(b == 25)
    Question 9.
    Explain the mistake.
    Code:
    "My name is Bob" = b
    print(b)
    ANSWERS
    Answer 1: False - one is a string one is a number
    Answer 2: False - - x is not the same as X
    Answer 3: True - empty strings result in true
    Answer 4: False - nil value has no value so it is false. Do not confuse with 0
    Answer 5: print(tostring(a) == "10") - variable replaced with value in function.
    Answer 6: print(a-25 == c) - the value of a gets replaced with variable b.
    Answer 7: No - values are passed sequentially, so A inherits the original value of B, so it will remain 35. Even though the value of B is changed later. Hence making the statement false.
    Answer 8:
    One possible answer
    Code:
      --[[ a = 45
      b = a
      --]]
      b = 16
      --b = b - 20
      b = b + 9
      print(b == 25)
    Answer 9: The statement tried to assign a variable to a string. Instead of a string to a variable.
    .



    CHAPTER - 3 STRINGS



    Now we have a rough idea of the basics. Lets go into a bit more detail about strings.

    What we are going to learn now is the mastery of political speech. So what does politics have to do with programming you ask?............[ pause for effect ]..............................
    Text manipulation! That is right we are going to learn how to manipulate words as you see fit. Just as the millions of political manipulators before us.
    .

    STRING CONCATENATION

    What concatenation essentially means is to link something. String Concatenation means exactly that, you are essentially linking two strings together. We use the operator .. (double period) to link the strings together.

    Code:
    a = "hello"
    b = "bob"
    print(a .. b) --> hellobob
    The reason the two strings are fused as one is, essentially no space character was there in the string. We can add a space through concatenation also

    Code:
    print(a .. " " .. b) --> Hello Bob
    .

    STRING.SUB()

    The next thing we are going to look at is string.sub(x, #1,#2)
    Where #1 is the starting number, and #2 is the ending number. So let us see an example - To the interpreter Robin! (obviously I am Batman).

    Code:
      x = "Hi my name is Batman"
      print(string.sub(x, 3))
    Now as you ran the code in your interpreter you would have noticed that "Hi" was no longer there. What happened is you told the function to start the string after the 3rd value which is the letter "m". Now plug in the following code and see what this does.
    Code:
      x = "Hi my name is Batman"
      print(string.sub(x, 0, 14))
    Obviously you already know what is going to happen next. For sake of disclosure the next snippet of code is not possible in starbound. It is simply there for sake of writing your first interactive program.

    Code:
      x = "Hi my name is Batman"
      x = string.sub(x, 0, 14)
      io.write("Enter your name: ")
      name = io.read()
      print(x ..name)
    Now if you are using our online interpreter you will notice on the output window [ right window ] it will ask for your name. Input your name on the right window ( not left) and press enter ( not play button ) to get result.

    Tada you have learned your first lesson in political speech manipulation and wrote your first program to boot! Pat your self on the back slugger, or sluggess ( hmm is slugger asexual? who knows )
    .

    STRING.FIND()

    Now that you know how to truncate strings, lets move on. The next important tool in our disposal is string.find(). String find lets you search for a particular word or pattern in a string. Lets look at an example.

    The first basic format is string.find(x,y, #)
    Where x = the text to search, y = the word to find # = position to start (can be omitted.) A positive number starts the search from the front a negative number starts the count from the back.

    For example: "My name is Bob"
    Code:
        4  would start with [n]ame the "a" in name.
      -4  would start with is[ ]bob the blank space before bob. 
    So lets do an example

    Code:
      i, j = string.find("hello Lua user", "Lua")
      print(i, j)
    i and j are just 2 non specific variables which store the location data. i stores the location of the first character in the search and j stores the last character of the search. So you get 2 variables which each carry one part of the positional data.

    So let us throw some numbers into the mix.

    i, j = string.find("hello Lua user", "Lua", 8)
    In this case you are going to get nil. Because the search starts on the 8th character. The L in lua starts on the 7th. Hence there is nothing after 8th which matches.

    Similarly
    Code:
    i, j = string.find("hello Lua user", "Lua", -8)
    Gives the values 7 and 9. Why? Because it starts counting from the back instead of front. so it starts the count at L 8 spaces from the back.

    One thing when you use string.find(), it finds the first instance of that word or what ever particular thing you are searching for.

    Snippet of code with a practical application.
    I wanted to change change a name from one type to another to spawn a vanilla item base don the name of my modded item. So the basic code I used was below. You can run the code on repl.it to see it in action.
    Code:
    test = "reefpodH_sbno"
    i, j = string.find(test, "H_sbno")
    g = string.sub(test,0,i-1)
    print(g)

    .

    STRING.GSUB()

    The next function are going to look at is the string.gsub(). This function allow us to quickly change one character or a group of characters to another.

    The format follows
    string.gsub(x, y, z, #)
    • Where x = the text to look through
    • Where y = the text to find
    • Where z = the text to replace with
    • Where # - is an optional parameter which controls how many substitutions (starting from the first) can be made. If left out it will replace all.

    Lets see an example
    Code:
      x = string.gsub("I am Bob Bole", "B", "D")
      print(x)
    and to control how many
    Code:
      x = string.gsub("I am Bob Bole", "B", "D", 1)
      print(x)
    As you have noticed, or remembered capitalization is quite important, B does not equal b.

    Character classes are the next best thing since sliced bread. They help refine your search efforts with a bit more prowess by using generic character code to search for a wide swathe of a particular type of information.

    . all characters
    Code:
    %a  letters
    %c  control characters
    %d  digits
    %l  lower case letters
    %p  punctuation characters
    %s  space characters
    %u  upper case letters
    %w  alphanumeric characters
    %x  hexadecimal digits
    %z  the character with representation 0
    An upper case version of any of those classes represents the complement of the class. For instance, '%A' represents all non-letter characters:

    Code:
      ( ) . % + - * ? [ ^ $
    The character `%´ works as an escape for those magic characters. So, '%.' matches a dot; '%%' matches the character `%´ itself. You can use the escape `%´ not only for the magic characters, but also for all other non-alphanumeric characters. When in doubt, play safe and put an escape.

    You can make patterns more useful by using modifiers for repetitions and optional parts. Patterns in Lua offer four modifiers:

    • + 1 or more repetitions
    • * 0 or more repetitions
    • - also 0 or more repetitions
    • ? optional (0 or 1 occurrence)

    Using these with string.find() or string.gsub() it will refine your search to a much more higher degree.

    Lets look at an example.

    Code:
    print(string.gsub("one, and two; and 3e", "%a+", "word"))
    %a looks for a letter to replace. $a+ looks for 1 or more letters in sequence

    By removing the "+" from our search it would replace each letter with the word, word. By keeping it, it would replace each word as well as each individual letter by it self not associated other group of letters with the word, word.

    Now lets add another character class

    Code:
    print(string.gsub("one, and two; and 3e", "%a+%a+", "word"))
    now the search is going to look for at least a group of letters with at least 2 letters .So as you can see 3e remains untouched as "e" is only a single letter.

    A character class is essentially a refined version of the asterisk (*) used as a generic search operator in windows and various other software.
    Let us look at another example.
    Code:
    text = "To make a bit more better example, lets look at a proper piece of text to start mucking around."
    print(string.gsub(text, "be%a+%a+", "word"))
    in this case "better" was replaced with word.
    .

    STRING CAPTURE

    The final part for strings we will be focusing on is captures.
    Captures unlike the previous points is a lot more problem solving oriented.

    So let us go back to our old friend string.find(). Now as you remember find helps you "find" words. But it can also retrieve them. The first 2 variables which are given by find are positional variables. After that it produce results. Let us take a look at the following example

    Code:
      text = "bob is cool 'to cool for school'"
    In this problem we want to find out how cool bob is.
    So we know he is too cool for school.
    So we are going to need reference points. We notice the statement is in single quotes. so lets start with that

    Code:
      text = "bob is cool 'to cool for school'"
      _, _, x= string.find(text, "'(.+)'")
      print(x)
    Now when we place a piece of information within parenthesis inside a find parameter. It returns the first result into the first non positional variable (3rd) in our case "x". If we used the character class %a+ it would have only returned the first word. Since we don't know how many words were in that phrase we instead want to use the single quotes as end points to copy everything in-between.

    If you want to read up more on strings go here
    http://www.lua.org/pil/20.html



    PRACTICAL EXAMPLE FOR STRINGS
    So for sake of example where this would be useful.
    When making my Purchasable Pets Mod - I needed a way for the pet to recognize its own Pet House.
    There were multiple problems involved. The biggest issue was the naming convention used by Chucklefish.
    Some pets were called petcat ( uses the word pet ) some pets were called crasberry ( does not use the word pet )
    an inconsistent naming convention is always a pain to deal with.
    Now my Spawner is called petHouseName, where "Name" is the monster type.
    So not only did I need to remove the word pet in some, but also had to capitalize the first letter.

    My solution

    Code:
        monType = world.monsterType(entity.id())
        if string.find(monType, "pet") then
            monType = string.sub(monType,4)
            monType = string.gsub(monType,"^%l",string.upper)
            monType = "petHouse"..monType
            world.logInfo(monType)
        else
            monType = string.gsub(monType,"^%l",string.upper)
            monType = "petHouse"..monType
            world.logInfo(monType)
        end


    .



    BREAK TIME



    I am sure at this point you are pretty much exhausted. You probably thinking to your self you are about to become a Shaolin lua master. Unfortunately you haven't even gotten in the car let alone started driving to even crash!

    But lets see how well you crash this test!

    Question 1:
    Code:
      x = y
      y = z
      z = 2
    What is the value of x?
    *Hint print the value in the interpreter if you need help.

    Question 2:
    Correct the mistake if any
    Code:
      text = "itemname: bob; rarity: rare; amount: 10"
      i, j = string.find(text, "rarity")
      print(i,J)
    Question 3:
    Using only control characters replace AAAe with apples. Do not change the replacement word apple to apples.
    Code:
    print(string.gsub("one, and AAEes; and three", "%%%%%", "apple"))
    Question 4:
    Find the rarity of the following item
    Since you have not learned functions, you may only use what is taught.
    Code:
    text = "itemname: bob; rarity: rare; amount: 10"

    ANSWERS

    Answer 1: nil - y has no value when x is assigned the value of y.
    Answer 2: print(i,j) j is not the same as J. hence nil value.
    Answer 3:
    Code:
      print(string.gsub("one, and AAEes; and three", "%u+%a", "apple"))
    %u+ will search for instance of capital letters which is more then 1 and which ends in a lower case letter.

    Answer 4:
    Code:
      text = "itemname: bob; rarity: rare; amount: 10"
      i, j = string.find(text, "rarity")
      _, _, rarity = string.find(text, ":.-(%a+)", j)
      print(rarity)
    In the first operation the positional values were kept so we can have a reference to start. In the 2nd operation we no longer needed the positional values so they were instead stored in a dummy variable. Which is why I narrowed our search to the point after rarity. Next we knew a semicolon was after rarity so that is where we begun our search. The command .- meant the least amount of characters to our main subject which is enclosed parenthesis, and since we were looking for an entire word and not letter we used $a+. This result was sent to variable rarity.

    For bonus points you could have saved a step by just using rarity with character classes directly instead of using positional values to narrow the search. "rarity:.-(%a+)"

    Code:
      text = "itemname: bob; rarity: rare; amount: 10"
      _, _, rarity = string.find(text, "rarity:.-(%a+)")
      print(rarity)

    This seems like a good stopping point for the first part. It gives you a general over view of one of the major components of LUA. In the next part we will look at Tables and Functions. Or the Meat and Potatoes of programming.
    Now don't forget to give a big hand to @severedskullz - he helped make the guide possible.
     
    Last edited: Feb 23, 2017
  2. AstralGhost

    AstralGhost Pangalactic Porcupine

    Haha. I skimmed this for fun and it was actually very well written. Nice job. It's a pretty nice rundown for people new to programming who might want to get into it.

    One note:
    I'm sure you already know some of the terminology is missing. You refer to arrays, associative arrays, and userdata but never define them with the rest of your terminology. And I'm sure there are plenty of other terms that would be good for a beginner, as well.
    Going from explaining simple variables to referencing arrays without a definition is a rather stark contrast.
     
  3. The | Suit

    The | Suit Agent S. Forum Moderator

    I figured it would be more helpful to focus on tables and its terms on the 2nd part.
    Since none of them is used in the first part.

    But if you have any advice on what to add and change, I would love to hear it. The better the tutorial the better the mods which come out of it. As for user data - not really sure mods have to worry about memmory management.
     
    Last edited: Oct 1, 2014
  4. Katzeus

    Katzeus Chucklefisherman Chucklefish

    Wow, great work! This will be really helpful for those getting started modding!!
     
    The | Suit likes this.
  5. AstralGhost

    AstralGhost Pangalactic Porcupine

    I didn't mean that you should add the terms because they were useful for learning about variables and other things this tutorial teaches. I meant that they'd simply help the reader follow along and not get confused by terms, which is the opposite of what you want your terminology section to do. Some simple definitions such as: "Arrays are basically just stacks of related variables in one memory space" and "Userdata is a variable for more advanced programmers to manage things in memory and you probably shouldn't use this unless you have a lot of experience" would work to tie the sections together better, I think.
    For example, your definition for 'table' refers to 'associative arrays'. So how will they understand what you're saying when they have not yet learned what a regular array is? Defining an array would probably be a prerequisite to defining a lua-table / associative array.

    You could possibly also leave all of these terms out completely and also remove the current definition for tables and just state you'll explain what those things are later. Sometimes keeping things simple works, too. The point is just to avoid referencing things that the reader might not have any clue what it means unless you can't help it, and then explaining everything that you had to reference.

    Overall these are really minor things to add just a little bit more cohesiveness. Like I said, you did a very nice job on it so far. It's always interesting to see people pick up coding from tutorials like this.
     
    The | Suit likes this.
  6. The | Suit

    The | Suit Agent S. Forum Moderator

    Alright for sake of simplicty I removed references to userdata and threads - since starbound won't use them anyway.
    I also dumbed down the definition of Tables to - objects which hold data. Which would just get more clarification in part 2.

    Part 2 may take a bit of time. I have atrip for a few days. Also Severed is still working on writing up the functions part of the tutorial so - can't start work on that till he is done. So maybe by only the weekend it will be done at the earliest.
     
    Last edited: Oct 2, 2014
  7. AstralGhost

    AstralGhost Pangalactic Porcupine

    Ah yes, skimming over it now it reads even more cleanly than before. :)
    Nice job again. Hope this will help a lot of newbies. (I remember I got into programming by modding games and stuff back in the day. I think these kinds of things really do help.)
     
  8. Peelz

    Peelz Giant Laser Beams

    This tutorial is great! I have some programming background (not a lot, but some) and there were a few helpful things in here that will save me a lot of time! Most notably the notion of a lua code interpreter! I feel kind of dumb, but it doesn't matter now because NOW I don't have to close and re-open starbound after EVERY. SINGLE. EDIT.

    Keep it up!
     
  9. The | Suit

    The | Suit Agent S. Forum Moderator

    I can't remember maybe severed can clear this one up. But I do believe lua changes are live edits. You don't need to restart SB to see it take effect.
    Also if you are running nightlies just type

    /admin
    /reload
    and it will reload starbound assets - so you don't have to quit the game for JSON edits. Just keep in mind already placed objects have to be broken and replaced for changes to occur to them.
     
  10. Akado

    Akado Oxygen Tank

    Just wanted to say, this is awesome. Thanks to you two for doing this!
     
    severedskullz and The | Suit like this.
  11. Peelz

    Peelz Giant Laser Beams

    I'm not running nightlys (waiting for stable) but that is really good to know! And if LUA changes are live (which, being scripts, they may be) then that will make future modding even easier!
     
  12. You just need to go to the main menu for Lua changes to take effect.


    Also im writing a bunch of code snippets for various thing for functions. May take a while but it will be pretty in-depth so you guys will know how to do just about anything... from techs to objects to NPCs.
     
    The | Suit and AstralGhost like this.
  13. AstralGhost

    AstralGhost Pangalactic Porcupine

    Really? That sounds great. Even I could use some examples.
    I've been too busy/lazy to dive into any of the assets to see things for myself so I haven't even really gotten into learning all the lua functions. A nice tutorial going over a bunch of different types would be awesome and save me a lot of time.
     
    The | Suit likes this.
  14. The | Suit

    The | Suit Agent S. Forum Moderator

    You sure about that LUA thing right?
    Because I faintly remember when I made my SkyNet Beacon of doom which summons meteors when you interact with it. All I had to do was break it and replace it - to see the LUA edits.
    But this was a while back - and my memmory gets fudgy when doing things in the middle of the night.
     
  15. Its been a while so things may have changed. But at the very least going to the main menu will work if it isnt instant like you guys claim.
    While Im thinking about it though the interpreter needs to load the file when the object is created so theres no reason it *cant* work like that. Whether or not it does is the question. But Ive always gone to the menu -so atleast a restart of the game isnt required and we know that for sure.
     
  16. The | Suit

    The | Suit Agent S. Forum Moderator

  17. Korvic

    Korvic Void-Bound Voyager

    "$a+ looks for 1 or more letters in sequence" Under the string.gsub() section should be "%a+ looks for 1 or more letters in sequence", no?

    Thank you so much for doing these guides. I enjoy coding, but could never get into any instructional sites as I usually have difficulty learning from text (prefer hands on learning), however you've managed to get across to my brain! I tip my hat to you, good sirs. :proper:

    EDIT: Missed the S key at the end, you both did well. :p

    EDIT 2: Also, quick question, are you planning on making the part 2 and if so, do you have an idea when it'll be ready? I'd really like to keep learning as I have Mondays & Tuesdays off. :p

    Thanks once again!
     
    Last edited: Jan 12, 2015
    severedskullz and The | Suit like this.
  18. DarthTrethon

    DarthTrethon Spaceman Spiff

    Best thread ever.....that "Don't Panic" and "42" at the top made my day!!
     
    The | Suit likes this.
  19. The | Suit

    The | Suit Agent S. Forum Moderator

    Part 2 is actually is mostly done, Just needs one more section and proof reading before its available.
     
  20. DarthTrethon

    DarthTrethon Spaceman Spiff

    These tutorial threads by @xxswatelitexx really deserve to be pinned or something. My current schedule is a bit too hectic to really sit down with them and try some things but I'd like to get back to them later down the road when I have more time on my hands. I get the feeling I'll be playing StarBound for years to come.....especially with the game having a strong modding community keeping the game fresh and new.
     

Share This Page