Author Topic: Wait, I don't understand how "require" works  (Read 193 times)

Max

  • Full Member
  • ***
  • Posts: 162
    • View Profile
Wait, I don't understand how "require" works
« on: October 04, 2018, 04:10:21 am »
So, I've got a few towns in my games and I hate copying/pasting the blacksmiths' and shopkeepers' code from one place to another, because they all function the same. I think this is a situation I can make a script which all will use, but I'm not sure how the syntax for this works. Does the "require" syntax (is this an operator?) return... anything? I'm not quite sure how to use require properly.

But anyway, here's my (third) attempt:

In a town's map:
Code: Lua
  1. function blacksmith:on_interaction()
  2.   local shop = require("scripts/shops/blacksmith")
  3.   shop:blacksmith()
  4. end
  5.  

The blacksmith() function in the script is pretty simple and tested, just dialogues and choices which will take money and increase attack power, basically, but here is it anyway. I don't think this is where the problem is.
Code: Lua
  1. function blacksmith()
  2.  
  3.  game:start_dialog("_yarrowmouth.npcs.blacksmith", function(answer)
  4.     --sword
  5.     if answer == 2 then
  6.       --have required items
  7.       if game:has_item("sword") == true and game:get_item("coral_ore"):get_amount() >= 1 and game:get_money() >= 50 then
  8.         game:set_value("sword_damage", game:get_value("sword_damage") + 1)
  9.         game:remove_money(50)
  10.         game:get_item("coral_ore"):remove_amount(1)
  11.         game:start_dialog("_goatshead.npcs.palladio.sword_improved")
  12.       else --don't have required items
  13.         game:start_dialog("_game.insufficient_items")
  14.       end
  15.  
  16.     --bow
  17.     elseif answer == 3 then
  18.       --have required items
  19.       if game:has_item("bow") == true and game:get_item("coral_ore"):get_amount() >= 1 and game:get_money() >= 50 then
  20.         game:set_value("bow_damage", game:get_value("bow_damage") + 1)
  21.         game:remove_money(50)
  22.         game:get_item("coral_ore"):remove_amount(1)
  23.         game:start_dialog("_goatshead.npcs.palladio.bow_improved")
  24.       else --don't have required items
  25.         game:start_dialog("_game.insufficient_items")
  26.       end
  27.  
  28.     end -- which answer end
  29.  
  30.   end) --dialog end
  31.  
  32. end
  33.  

llamazing

  • Full Member
  • ***
  • Posts: 122
    • View Profile
Re: Wait, I don't understand how "require" works
« Reply #1 on: October 04, 2018, 07:02:56 am »
local shop in your map is a table. backsmith() is a function in that table.

When using require, you need to have the script being required return a table. Anywhere that that script is required, it will get a reference to the table, the advantage being that the script only has to be loaded once.
EDIT: the script that gets required does not have to return a table if instead it simply defines global variables/functions

You'll want to do something like the following for your blacksmith script:
Code: Lua
  1. local blacksmith = {}
  2.  
  3. function blacksmith:start_dialog()
  4.   --do stuff
  5. end
  6.  
  7. function blacksmith:function2()
  8.   --some other function
  9. end
  10.  
  11. return blacksmith

The problem with this, though, is that the blacksmith script here won't be able to use any references to game since it doesn't know what that is. If the same game instance will be used everywhere you use the script then you can get away with something like the following: (be warned that every time you require the script the game will get set, and only the last one will stick, but in the case of a map it will be set every time the map loads)
Code: Lua
  1. local blacksmith = {}
  2.  
  3. local game
  4.  
  5. function blacksmith:set_game(current_game)
  6.   game = current_game
  7. end
  8.  
  9. function blacksmith:start_dialog()
  10.   game:start_dialog("_yarrowmouth.npcs.blacksmith", function(answer)
  11.     --blah blah
  12.   end)
  13. end
  14.  
  15. function blacksmith:function2()
  16.   --some other function
  17. end
  18.  
  19. return blacksmith

Then your map script becomes:
Code: Lua
  1. local blacksmith_funcs = require("scripts/shops/blacksmith")
  2. blacksmith_funcs:set_game(game)
  3.  
  4. function blacksmith:on_interaction()
  5.   blacksmith_funcs:start_dialog()
  6. end

But if you're going to have multiple game instances then this solution is no good and you'll want to do something like the following instead:
Code: Lua
  1. local blacksmith = {}
  2.  
  3. function blacksmith:start_dialog(game)
  4.   game:start_dialog("_yarrowmouth.npcs.blacksmith", function(answer)
  5.     --blah blah
  6.   end)
  7. end
  8.  
  9. function blacksmith:function2(game)
  10.   --some other function
  11. end
  12.  
  13. return blacksmith

And map script:
Code: Lua
  1. local blacksmith_funcs = require("scripts/shops/blacksmith")
  2.  
  3. function blacksmith:on_interaction()
  4.   blacksmith_funcs:start_dialog(game)
  5. end
« Last Edit: October 04, 2018, 07:04:44 am by llamazing »

Max

  • Full Member
  • ***
  • Posts: 162
    • View Profile
Re: Wait, I don't understand how "require" works
« Reply #2 on: October 07, 2018, 08:49:37 pm »
I think I understand it all now! The require command will just run the script, which might return a table, if you tell it to. In this case, you should write the script to return a table. Got it! I messed around with this for a long time before I realized I wasn't returning the table, hahaha. All working now!

Thanks, llamazing!

llamazing

  • Full Member
  • ***
  • Posts: 122
    • View Profile
Re: Wait, I don't understand how "require" works
« Reply #3 on: October 07, 2018, 11:10:50 pm »
The require command will just run the script, which might return a table, if you tell it to.
There's one subtlety that I'm not sure if you picked up on, and that's that the require command will only run the script once. So if the script is also required somewhere else, then instead of running the script a second time, the return values saved from the first time the require command was used will simply be passed along to wherever else the script gets required.

To see an example of this, imagine a script (print_script.lua) to be required that simply contains the line print("some text").

Now if you add require("/scripts/print_script.lua") to both main.lua and game_manager.lua then you'll only see one print statement when the game is loaded.

If instead you change it to sol.main.do_file("scripts/print_script.lua"), then this time the script will actually be run twice and you'll see two print statements. Put the sol.main.do_file statement in a map script and you'll actually see the line get printed every time the map loads, which is not the case if using require.

If the script were to return a table, then using require would result in the same table reference being returned at both places. If using do_file, then two separate table instances (that probably look identical) would be returned. Generally, using require is what you are going to want to use the majority of the time, but there may be instances where sol.main.do_file is better for what you want to accomplish.


Diarandor

  • Hero Member
  • *****
  • Posts: 1018
  • Cats are cool! (ΦωΦ)
    • View Profile
Re: Wait, I don't understand how "require" works
« Reply #4 on: October 08, 2018, 02:06:48 am »
As far as I know, requires are usually written at the top of your script, and I recommend to do so (so that others can see the dependencies and avoid errors if some required file is missing). Think on them as "imports", since they are just that.
“If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you.”

Max

  • Full Member
  • ***
  • Posts: 162
    • View Profile
Re: Wait, I don't understand how "require" works
« Reply #5 on: October 08, 2018, 02:52:49 am »
Awesome, great advice from the both of you : )
I hadn't quite picked up on that subtlty, llamazing, and I can easily imagine myself trying to clean things up by exporting duplicate code to scripts and then being lost when it didn't work as expected. Thanks!

alexgleason

  • Newbie
  • *
  • Posts: 29
    • View Profile
    • Vegan on a Desert Island
Re: Wait, I don't understand how "require" works
« Reply #6 on: October 11, 2018, 07:56:02 pm »
So if the script is also required somewhere else, then instead of running the script a second time, the return values saved from the first time the require command was used will simply be passed along to wherever else the script gets required.

Thank you for clarifying this! I found this thread very helpful.

Variable scope still trips me up. For instance, in this menu script I don't understand why this code doesn't work:

Code: Lua
  1. local menu = {}
  2. local game = sol.main.game -- Use `game` as a shorthand for `sol.main.game`
  3.  
  4. function menu:on_started()
  5.   print(game:get_item_assigned(1):get_name())
  6. end
  7.  
  8. return menu

Error: In on_started: [string "scripts/menus/my_menu.lua"]:5: attempt to index upvalue 'game' (a nil value)

Yet this code works just fine:

Code: Lua
  1. local menu = {}
  2.  
  3. function menu:on_started()
  4.   -- Use sol.main.game directly
  5.   print(sol.main.game:get_item_assigned(1):get_name())
  6. end
  7.  
  8. return menu

Using sol.main.game works great. 👍 (I'm using the boilerplate code which sets sol.main.game within main.lua)

More confusingly, this code also doesn't work.

Code: Lua
  1. local menu = {}
  2. game = sol.main.game -- Use a global variable this time
  3.  
  4. function menu:on_started()
  5.   print(game:get_item_assigned(1):get_name())
  6. end
  7.  
  8. return menu

Error: In on_started: [string "scripts/menus/item_menu.lua"]:5: attempt to index global 'game' (a nil value)

I thought when you require() a Lua script, any global variables created by the script persist throughout the whole codebase. I'm super confused why these don't work. I thought variable scope was the problem, but maybe not?
« Last Edit: October 11, 2018, 07:59:36 pm by alexgleason »

Christopho

  • Administrator
  • Hero Member
  • *****
  • Posts: 1137
    • View Profile
Re: Wait, I don't understand how "require" works
« Reply #7 on: October 11, 2018, 08:27:38 pm »
sol.main.game does not exist yet (i.e. is nil) at the time your script is executed. And your script is executed the first time you require() it, so most likely long before you assign something to sol.main.game.

alexgleason

  • Newbie
  • *
  • Posts: 29
    • View Profile
    • Vegan on a Desert Island
Re: Wait, I don't understand how "require" works
« Reply #8 on: October 11, 2018, 08:36:59 pm »
sol.main.game does not exist yet (i.e. is nil) at the time your script is executed. And your script is executed the first time you require() it, so most likely long before you assign something to sol.main.game.

OMG thank you! It took me a minute to understand what you meant. The menu script itself is executed before sol.main.game is set, but the function call happens after, so it has access to it. Thank you for bringing me clarity. ;D

llamazing

  • Full Member
  • ***
  • Posts: 122
    • View Profile
Re: Wait, I don't understand how "require" works
« Reply #9 on: October 12, 2018, 12:31:56 am »
The other thing to consider is that even if assigning sol.main.game to local game did work, you'd still have problems for the case where the player exits the game to the title screen and starts a different game, in which case the local game would have a reference to the wrong instance of the game.