Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - llamazing

#196
Development / Re: How do I do Mouse inputs?
October 21, 2016, 08:13:36 AM
I think you are confused about how the coordinates work. x & y are the coordinates of where the player clicked, not where they have to click for the action to occur.

You probably want something more like the following:
Code (lua) Select

function sol.main:on_mouse_released(button,x,y)

    if button=="right" and x==10 and y==100 then
        print("right pressed")
    end
end


Although that code requires the player to click exactly on one pixel, which could be tricky to do. In all likelihood, you'd be specifying a range for x and y (i.e. x>=10 and x<=20).
#197
Development / Re: How to require from a map?
October 15, 2016, 07:43:07 AM
Your problem is that your script defines sol.main:on_key_pressed(key), which can only be defined once. When your game_manager calls the pick_name.lua script, it defines sol.main:on_key_pressed(key). Then when your map loads the calls the pick_name.lua script, a new function is assigned to sol.main:on_key_pressed(key), overwriting the old one.

Using sol.main:on_key_pressed(key) is not the correct way to do things. You really only need that for handling key presses that are handled all the time, including when there isn't a game running (an example would be alt+f4 to exit). And then only define sol.main:on_key_pressed(key) exactly one time. Likewise for sol.main:on_draw()

What you should do instead is make your pick_name.lua script be a menu. So in your pick_name.lua script:
Code (lua) Select

--function sol.main:on_draw(screen) --replace this line with the following
function name_listing:on_draw(screen)

--function sol.main:on_key_pressed(key) --replace this line with the following
function name_listing:on_key_pressed(key)


And then to "run" your script, do the following:
Code (lua) Select

--from game_manager.lua:
local name_listing = require("scripts/pick_name.lua")

function game_manager:start_game()
local exists = sol.game.exists("save1.dat")
local game = sol.game.load("save1.dat") --note this line must come before starting the menu

sol.menu.start(game, name_listing) --assuming you want to start the menu as soon as your game starts

--additional code...
end

--from first_map.lua:
local name_listing = require("scripts/pick_name.lua")
local map = ...

--additional code...

--function sol.main:on_draw(screen) --replace this line with the following
function map:on_draw(screen)

--additional code...

end

--function sol.main:on_key_pressed(key) --replace this line with the following
function map:on_key_pressed(key)

--additional code...

if key == "p" then
sol.menu.start(self, name_listing)
end
end


When you no longer want the pick_name.lua script to be drawing to the screen or handling key press events, call the following:
Code (lua) Select

sol.menu.stop(name_listing)


And by the way, you should delete the following line from your pick_name.lua script. It doesn't work the way you think it works for scripts loaded using require.
Code (lua) Select

local game = ... --delete this line (line 2)
#198
Development / Re: How to require from a map?
October 15, 2016, 05:36:57 AM
It should totally be possible to have your game manager and your map running the same script. Although I'm not sure why you'd want the map and the game manager to both be processing key presses for the same script. You're going to have to provide more detail on what your script does and what you are trying to accomplish.

If one of your on_key_pressed() functions returns true, it prevents the on_key_pressed() function from getting called elsewhere for that key press that was already handled.
#199
Again, you probably want to use a menu for this task, but without knowing more about what you are trying to do, it is hard to say.

Assuming "WHAT" is a sequence of characters entered by the player, you'd want to use the menu:on_character_pressed(character) function. It could look something like this:
Code (lua) Select

local chars_pressed = {}
function my_menu:on_character_pressed(character)
table.insert(chars_pressed, character)
end

function my_menu:print()
local str = table.concat(chars_pressed)
print(str)
chars_pressed = {} --clear table
end


Note that this is compatible with utf-8 (international) characters, but be warned that str:len() will return the number of bytes in the string, not necessarily the number of characters. i.e. if the player enters 4 characters that are each multi-byte characters of 2 bytes, then str:len() would return 8 even though there are only 4 characters. Instead, #chars_pressed can be used to get the number of characters entered.
#200
Quote from: Zefk on October 04, 2016, 05:01:01 AM
I normally use arrays when things are related to the same thing.

Lets say I have a image with a "1" on it. I might want to draw many at once and manipulate each one to a different position.

I would use an array like this...
Ouch! Why are you drawing each number individually when you can make your life easier and use a text_surface to do the same thing?
#201
Quote from: Diarandor on September 29, 2016, 02:57:58 PM
Another solution is to use the function defined in the multi_events.lua script that Christopho made a few weeks ago:
https://github.com/christopho/solarus-alttp-pack/blob/dev/data/scripts/multi_events.lua
How does the multi_events.lua script work in regard to map:on_started()? Correct me if I'm wrong, but you wouldn't want to do the following in a map script...
Code (lua) Select

local map = ...

map:register_event("on_started", function()
--blah blah
end)


...because the map script gets executed every time the player enters the map, each time registering a new on_started function to the multi_events, and the on_started events registered from all the previous times the player entered the map would be undesirable.
#202
Since your module has an on_key_pressed() function and image function (I'm assuming you mean on_draw()?), you probably want to use a menu. It works as just a simple table too if you don't need a menu.

Something like the following:
Code (lua) Select

--mymodule.lua
local mymodule = {}
--variables

--called on sol.menu.start()
function mymodule:on_started() --only needed if menu
end

--called on sol.menu.stop()
function mymodule:on_finished() --only needed if menu
end

function mymodule:on_draw(dst_surface)
--blah blah
end

function mymodule:on_key_pressed(key, modifiers)
--blah blah
end

function mymodule.myfunction()
--blah blah
end

return mymodule


--map.lua
local mymodule = require "mymodule" --mymodule is a table

function map:on_started()
sol.menu.start(self, mymodule) --only needed if menu
end

function map:on_finished()
sol.menu.stop(mymodule) --only needed if menu
end

mymodule.myfunction() --normal function call


If you want the menu to be run for all maps you can use a metatable:
Code (lua) Select

--you could put this code in mymodule.lua
local map_meta = sol.main.get_metatable"map"
function map_meta:on_started()
sol.menu.start(self, mymodule)
end

function map_meta:on_finished()
sol.menu.stop(mymodule)
end


If using the metatable method, having map:on_started() in an individual map overrides the metatable function.

Also see Christopho's require() tutorial:
https://www.youtube.com/watch?v=CUcfPYMlVs8
#203
Quote from: Christopho on September 27, 2016, 08:55:46 AM
Also, in your timer function, don't forget that you can return true if you want the timer to be repeated.

Ah yes, I did forget that timers can be repeated by returning true. That makes for a cleaner implementation; I like it. In fact, I'm going to have to go back and apply that to some of my other scripts that use timers as well.
#204
Quote from: Christopho on September 27, 2016, 07:34:47 AM
You can store the timer and cancel it explicitly.

I didn't think that would work because there isn't just one timer. The timer expires and generates a new timer, so the stored reference to the original timer would have to be updated with the new timer each time. And then I wasn't sure if there could be a race condition where two different sets of timers running simultaneously would both be vying to overwrite the same variable at the same time.

I don't know, my method seemed cleaner and safer.
#205
FYI-- I've come across a bug with the code I posted for the timer implementation.

Description of the bug:

  • Say an npc is assigned a path movement. The movement starts and the NPC is set to traversable.
  • Now the hero waits stationary at the location on the map where the NPC will stop its movement.
  • When the npc movement ends, since the hero overlaps the npc, the repeating timer begins and periodically checks if the hero is still overlapping the npc, and if not then makes the npc non-traversable.
  • While the timer loop is still active, the npc is given a new movement path and set to traversable (even though it already is traversable).
  • The hero can follow along with the npc since it is traversable. Note that the original timer is still looping.
  • As soon as the hero moves away from the npc, the timer will end and make the (currently in motion) npc non-traversable.
  • Now if the hero moves to where the npc's second movement ends, the hero will get stuck.

My solution is to assign an empty table to the npc entity when the timer is started. Whenever the npc is given a movement, I remove that table from the npc. The timer will only restart as long as that table is present. If a second timer were to be started while the first is still active, then the npc would get assigned a new empty table, and since the new empty table is a different instance than the original, the first timer would stop looping while the second timer continues to loop.

Here is the revised code (line 3 is new):
Code (lua) Select
npc:set_enabled(true) --always enable when giving movement (overrides above)
npc.is_sleeping = nil --no sleepwalking!
npc.collision_check_context = nil
npc:set_traversable(true)

local mov = sol.movement.create"path"
mov:set_path(event.path)
mov:set_speed(16)
mov:set_ignore_obstacles(true)
mov:set_loop(false)

mov:start(npc)
function mov:on_finished()
collision_check(npc)
if event.end_facing then npc:get_sprite():set_direction(event.end_facing) end
end


And the revised collision_check() function (lines 8-10,14 & 18 are new):
Code (lua) Select

--// Checks if entity1 & entity2 are overlapping and if not makes entity1 non-traversable
--// If they are overlapping then starts a timer to re-test again later (indefinite loop)
--if entity2 is not specified then the hero is used
local function collision_check(entity1, entity2)
if not entity2 then entity2 = game:get_hero() end
if not entity1 or not entity2 then return end

--only restart the timer while the context remains unchanged
local context = {} --newly created empty table is unique identifier
entity1.collision_check_context = context --replaces previous context

local do_check
do_check = function() --tests if entities overlap
if entity1.collision_check_context==context then
if entity2:overlaps(entity1, "overlapping") then
sol.timer.start(entity1, 200, do_check)
else entity1:set_traversable(false) end
end
end

do_check() --kick off for first time
end

#206
Development / Re: Getting the sprite of a Dynmaic Tile
September 25, 2016, 10:27:54 PM
Quote from: Christopho on September 25, 2016, 08:43:04 PM
Dynamic tiles don't use sprites, they have their own displaying system in the engine. Maybe this is a mistake :p
Okay, thanks for the info. I made the flickering effect a little more subtle so if my timer gets out-of-sync with the internal timer then hopefully it won't be as noticeable.
#207
Development / Getting the sprite of a Dynmaic Tile
September 25, 2016, 08:31:09 PM
I have a tile that I converted to a dynamic tile (pattern torch_big.top from the ALTTP resource pack), but when I call get_sprite() for the dynamic tile from in the map:on_draw() function, it returns nil. What am I doing wrong?

What I want to do is read the frame number so that I can synchronize an overlay with the animation.
#208
The easiest way to do it would be to convert soda to a string and then get each place value as a substring like Diarandor suggests.

For example:
Code (lua) Select

local soda = 9478
local soda_string = string.format("%04d", soda) --soda_string = "9478"

local soda_split = {}
for i = 0,3 do
local index = soda_string:len()-i
local num = tonumber(soda_string:sub(index,index))
table.insert(soda_split, num)
end

--soda_split[1] = 8
--soda_split[2] = 7
--soda_split[3] = 4
--soda_split[4] = 9


The "%04d" adds leading zeroes to the number converted to a string, so 1 becomes "0001" and 12345 becomes"12345". That ensures you'll get a value of zero rather than nil if soda is less than 4 digits.
#209
I changed the implementation to use a repeating timer. In case anyone is interested, here's the revised code:
Code (lua) Select
npc:set_enabled(true) --always enable when giving movement (overrides above)
npc.is_sleeping = nil --no sleepwalking!
npc:set_traversable(true)

local mov = sol.movement.create"path"
mov:set_path(event.path)
mov:set_speed(16)
mov:set_ignore_obstacles(true)
mov:set_loop(false)

mov:start(npc)
function mov:on_finished()
collision_check(npc)
if event.end_facing then npc:get_sprite():set_direction(event.end_facing) end
end


And here is the function collision_check():
Code (lua) Select

--// Checks if entity1 & entity2 are overlapping and if not makes entity1 non-traversable
--// If they are overlapping then starts a timer to re-test again later (indefinite loop)
--if entity2 is not specified then the hero is used
local function collision_check(entity1, entity2)
if not entity2 then entity2 = game:get_hero() end
if not entity1 or not entity2 then return end

local do_check
do_check = function() --tests if entities overlap
if entity2:overlaps(entity1, "overlapping") then
sol.timer.start(entity1, 200, do_check)
else entity1:set_traversable(false) end
end

do_check() --kick off for first time
end


If you are wondering why I changed from using the movement callback function to using the on_finished() event, it's because sometimes I stop the movement a little early to get the NPC to line up with the next segment when the timing is slightly off. When I was stopping the movement early, the callback wasn't getting called. Now I can just call on_finished() manually whenever I stop the movement.
#210
Quote from: Diarandor on September 24, 2016, 06:49:32 PM
I think it is a very bad idea using hero.on_position_changed for this. If the hero changes map that code may give you problems. Also, that event may be used for other things and you may be overriding it. You should use a function of the NPC instead, because that is more natural and it is something related only to the NPC.
Ah, you're right. I was thinking that the code associated with the hero would get wiped out on map transitions like it does for other entities. Since the hero persists outside of an individual map it could be problematic. I could use map:on_finished() to tidy things up on map transitions, but that would most likely become too complicated.

I don't think that there's a function of the NPC I can use since I only want to check for overlapping anytime that the player moves, and the NPC would be stationary.

I don't like the idea of using a timer, but it's probably what I'll end up doing.

Quote from: Christopho on September 24, 2016, 07:54:26 PM
Other remark: you forgot to declare your hero variable local.
Good catch! Fixed.