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.

Messages - llamazing

Pages: [1] 2 3 ... 5
Development / Re: NPC routines
« on: December 07, 2017, 03:02:14 am »
I think there's still some insight that can be gained from my scripts. Really, the only key difference I'm seeing between my implementation and what you seek is to replace my hard-coded path movements with a script that does the path-finding dynamically.

Of course, that is by no means a trivial task. When I was doing the planning for how to implement my script, I was thinking it would be nice to be able to draw the paths for the NPCs to follow in the map editor. You could do the next best thing and place the waypoints on the map using invisible custom entities (for example, place a waypoint at every intersection along a path and at every relevant destination). Then you could make use of either a target movement or path finding movement instead of the path movement I used in my script.

This implementation still isn't a slam dunk because I'm assuming that you'd want to only have to specify the end destination and not all the intermediate waypoints on the route to get there, so you'd still need a script to find the ideal sequence of waypoints to traverse to get to the ultimate destination.

There are a few complications that you'll have to consider. One example is when the player enters a map, you'll need a script that will determine exactly where every NPC should be located on the map based on the time when the player entered the map. This can be tricky because the NPCs could be in the middle of their movements, so you will have to position the NPCs accordingly and then start a movement from their current location.

Another complication is the timing aspect. If an NPC takes too long getting to their destination, then the subsequent movement will either be delayed (a problem that can become compounded over time), or there will be discontinuities in the movement where the NPC instantly skips across a segment of the movement in order to catch up. In my case, having a precisely determined path helps mitigate this issue.

One thing you can do to help with this problem is to add slop by having the NPC wandering around randomly for a brief duration between movements. For example, if it takes 20 seconds for an NPC to move from point A to B, you could allocate 30 seconds before having the NPC move to point C, where the NPC wanders randomly in the vicinity of point B until the 30 seconds has elapsed. So then whether it takes 18 seconds or 24 seconds for the NPC to travel from A to B, it makes no difference because the NPC would be wondering randomly near point B for either 12 or 6 seconds according.

To go into more details about how I implemented my event tables...

I used a map based timer for my game clock ( timer starts on map:on_started() ). The advantage of linking the timer to the map is that the timer automatically gets paused whenever a dialog is displayed or the game is paused. The tricky part is transitioning the timer on changes of the map. What I did in on map:on_finished(), save the current time to the save data file and then kill the timer. Then when the next map loads, start a new timer from the time saved to the save data file. See game_clock.lua map_meta:register_event().

My initial thought was to "register" each NPC's routine when they're created on the map, then have a function that re-arranges this routine in order to place each NPC/waypoint into an hour table.

That is basically the implementation that I used. I have a static table of events for each NPC in my data/events directory. When a map loads (map:on_started()) the relevant table entries corresponding to the current map for any NPCs present are extracted and assembled into a new events_list table with the event start times as keys. (see game_events.lua This is the table structure (keep in mind the table is generated dynamically, hard-coded here for clarity):
Code: Lua
  1. local events_list = {
  2.         ["8:00"] = {
  3.                 { --NPC_1
  4.                         npc_id = "NPC Name #1",
  5.                         location = {x=96, y=157, layer=0, facing=3},
  6.                         path = {4,4,4,2,2,2,2,2}, --hard-coded path
  7.                         target = "to_house", --you could do something like this instead, corresponding to a custom entity with this name
  8.                 },
  9.                 { --NPC_2
  10.                         npc_id = "NPC Name #2",
  11.                         location = {x=160, y=237, layer=1, facing=1},
  12.                         path = {1,2,3,4}, --hard-coded path
  13.                 },
  14.         },
  15.         ["8:23"] = {
  16.                 { --NPC_1
  17.                         npc_id = "NPC Name #1",
  18.                         location = {x=216, y=109, layer=1, facing=3},
  19.                         path = {2,2,2,2,2}, --hard-coded path
  20.                 },
  21.         },
  22.         ["9:00"] = {
  23.                 { --NPC_2
  24.                         npc_id = "NPC Name #2",
  25.                         location = {x=72, y=61, layer=1, facing=3},
  26.                         path = {6,6,8,8,2,2,4,4}, --hard-coded path
  27.                 },
  28.         },
  29. }

Then every in-game minute the event manager script (see game_events.lua can get a list of all the events that need to be started at the current time by calling events_list[current_time].

Development / Re: NPC routines
« on: December 06, 2017, 02:41:40 pm »
You might want to check out what I did for NPC movements in my Cythera sample quest. Specifically you should check out npc.Neoptolemus.lua. Local paths defines the routes from every "waypoint" on the map to every other "waypoint" by listing out the steps taken. My recollection is that I timed my NPC movements such that 1 step is taken per minute in-game (where what I'm calling a step is an 8 pixel movement).

The local locations defines the coordinates of each "waypoint". Note that locations are defined for 2 maps: The town map and the map for inside his house. Basically he gets up out of bed in the morning and walks over to his table, waits briefly then walks outside where he stands for the rest of the day, comes back inside at night waiting at his table again, then walks back to the bed and goes to sleep. I placed an NPC for him on both maps, and it is hidden off-screen during times when he is not present on the current map.

Finally the local schedule defines when he moves from waypoint to waypoint, with an entry for each map the NPC is present in. I specify times to the minute. So for example, he starts walking from his table to his door at 8:00, and it is 24 steps to his door. So the next event begins at 8:23. Events where the NPC is waiting stationary link only to a location waypoint; events where the NPC moves from one waypoint to another link to both a location and a path.

Also note that I defined some movements for the NPC Demodocus as well, but I ended up not using them in the quest since the NPC sprite I used for him in the ALTTP pack did not have movement sprites, and in the end I just made him stand stationary in the bar. My plan was to have him enter the town from the West, walk to the tavern, then exit the town to the East at the end of the day.

Bugs & Feature requests / Re: Straight movement bug with set_max_distance?
« on: September 08, 2017, 02:56:26 pm »
Issue 1075 created

Bugs & Feature requests / Straight movement bug with set_max_distance?
« on: September 08, 2017, 07:31:41 am »
I'm trying to use a straight movement, and the straight_movement:set_max_distance(max_distance) function is not working the way that I'd expect.

Below is simplified code of what I am trying to do. At 4 seconds the table obj is moved 100 pixels to the right, and then 3 seconds after that it is moved 100 pixels to the left to return it to its original position.

The problem is that the set_max_distance function appears to be calculating the distance from (0,0), when I'd expect it to calculate the distance from what the coordinates were at the start of the movement. The result is that the second movement ends up being a 200 pixel movement to the left instead of the 100 pixels that I wanted (the object stops when x=-100, which is 100 pixels from (0,0)).

Code: Lua
  1. local obj = {x=0, y=0}
  2. sol.timer.start(4000, function()
  3.   print(obj.x) --0 at 4 seconds
  5.   local movt1 = sol.movement.create"straight"
  6.   movt1:set_speed(100)
  7.   movt1:set_angle(0)
  8.   movt1:set_max_distance(100)
  9.   movt1:start(obj)
  11.   function movt1:on_finished() print(obj.x) end --obj.x=100 at 5 seconds
  13.   sol.timer.start(3000, function()
  14.     print(obj.x) --100 at 7 seconds
  16.     local movt2 = sol.movement.create"straight"
  17.     movt2:set_speed(100)
  18.     movt2:set_angle(math.pi)
  19.     movt2:set_max_distance(100)
  20.     movt2:start(obj)
  22.     function movt2:on_finished() print(obj.x) end --obj.x=-100 at 9 seconds; expected obj.x=0 at 8 seconds
  23.   end)
  24. end)

Development / Re: Detecting when a pickable is picked up
« on: August 05, 2017, 02:45:18 pm »
I have two pickable items named card and gem and I want to be able to tell when they're picked up and make pointers over each item disappear. I'm using this script to do that:
Code: Lua
  1. function map:on_update()
  2.   if not map.card:exists() then
  3.     cardpoint:remove()
  4.     print("Card removed!")
  5.   else if not map.gem:exists() then
  6.     gempoint:remove()
  7.     print("Gem removed!")
  8.   end
  9.   end
  10. end
However, when I do that, it errors Error: In on_update: [string "maps/street_arspace.lua"]:79: attempt to index field 'card' (a nil value), line 79 being if not map.card:exists() then. Any suggestion for what I'm doing wrong?
It also for some reason doesn't work without that extra end there. I'm not sure what's up with that.

The reason for the two ends is because you did "else if" with a space on line 5. It should be "elseif" with no space.

Try this instead:
Code: Lua
  1. function map:on_update()
  2.   if map.card and not map.card:exists() then
  3.     cardpoint:remove()
  4.     print("Card removed!")
  5.   elseif map.gem and not map.gem:exists() then
  6.     gempoint:remove()
  7.     print("Gem removed!")
  8.   end
  9. end

Development / Re: Trouble porting a script
« on: June 25, 2017, 10:05:02 pm »
Do  you have a sample project available? It's a bit hard to troubleshoot from the forum.

Also double-check that I negated those if statements correctly.

Development / Re: Trouble porting a script
« on: June 25, 2017, 06:41:04 pm »
There isn't an equivalent to next in lua, but you can accomplish the same thing using if statements. Take the two simplified examples (using lua syntax for illustration purposes)

Code: Lua
  1. for i = 1, #self.items do
  2.     if index == i then
  3.         next --not valid lua
  4.     end
  6.     print("not skipped")
  7. end

Code: Lua
  1. --equivalent to the following
  2. for i = 1, #self.items do
  3.     if index ~= i then
  4.         print("not skipped")
  5.     end
  6. end

So in your for block you could do the following in lua (note I have not tested it so there may be errors):
Code: Lua
  1. for i,item in ipairs(self.items) do
  2.     local item_x = item.x
  3.     local item_y = item.y
  4.     local current_item_index = self:find_item(self:find_item_axis(self.cursor_position.x, self.cursor_position.y))
  6.     --  Skip if it is the old item or is in the opposite direction
  7.     if current_item_index ~= i and (command ~= "down" or item_y >= current_y) and (command ~= "left" or item_x <= current_x) and (command ~= "right" or item_x >= current_x) and (command ~= "up" or item_y <= current_y) then
  8.         -- Get the distance of the current item and the new one
  9.         local dist_h = math.abs(item_x - current_x)
  10.         local dist_v = math.abs(item_y - current_y)
  12.         if (wh ~= w1 or w2 * dist_h >= dist_v) and (wv ~= w1 or w2 * dist_v >= dist_h) then
  13.             -- # Get the distance multiplied with their weightings
  14.             local dist = (dist_h * wh + dist_v * wv)
  16.             -- # Set at closest item if appropriate
  17.             if (dist < closest_distance) then
  18.                 closest_distance = dist
  19.                 closest_index = i
  20.             end
  21.         else
  22.             --equivalent to next, do nothing this iteration
  23.         end
  24.     else
  25.         --equivalent to next, do nothing this iteration
  26.     end
  27. end

Also note that instead of doing
Code: Lua
  1. for i = 1, #self.items do
  2.     local item = self.items[i]
  3.     ...
  4. end

it is better to use
Code: Lua
  1. for i,item in ipairs(self.items) do
  2.     ...
  3. end

Bugs & Feature requests / Re: "normal" video mode is at 2x
« on: June 04, 2017, 07:19:36 pm »
Ok, I think I'm asking the wrong question. I can set the window size correctly with that call, but it basically scales the same viewport up or down to fit the window size. If I have a bigger window size, I want a bigger viewport as well- is this possible? For example, setting the window size at 320x240 works - but when I set the window to 640x480 instead, I want to see twice as many tiles not the same tiles at twice the size.

In the quest properties you'll want to specify a normal quest size and minimum quest size of 640x480 and at least 640x480 for maximum quest size. That will default to a window 2x the size you want (1280x960), but then you can set it to the correct size with, 480).

Development / Re: Problem with timer
« on: May 29, 2017, 10:41:33 pm »
I think your problem is that you are using map:on_update(), which gets called every cycle. With the way you have things set up, a new timer is getting created every cycle as long as at least one torch is lit. Try adding a print statement at line 5, and you'll probably see a bunch of timers getting created. So what's happening is after 3.5 seconds the "first" timer expires and un-unlights all the torches, but then every cycle all the other timers continue to expire and also un-light all the torches. So then you have to wait another 3.5 seconds for the "last" timer to expire before you can light the torches again.

A better way to do it is to use sprite:on_animation_changed(animation) instead of map:on_update(). That way the function doesn't get called every cycle.

And if you are doing what I think you are doing, you'll probably want three separate timers. It can be done with one timer (that gets restarted each time a torch is lit perhaps?), but that would also mean that all the torches will become unlit simultaneously.

Development / Re: Assign argument variables in function parameter?
« on: May 25, 2017, 01:03:08 am »
In that case you simply make a function that takes a table for the first parameter:
Code: Lua
  1. function test(info)
  2.     if info.a == 5 then
  3.         print("Letter A is",info.a)
  4.     end
  5. end
  7. test{a=5, b="hey", c=true}

Keep in mind that the table passed to the function may not have values defined for every entry in the table. It also might not even be a table that gets passed to the function, so be sure your code handles all the possibilities.

Development / Re: Script working part of the time
« on: May 13, 2017, 03:03:56 am »
I'm not following the steps you are doing for the scenario where it does not work. You say that it works the first time, but it should only work once period. After one successful run, are you opening the console to set the value of open_walk back to false? And then you leave the map and come back?

It's not clear to me which lines of the script are working as expected and which are not. The script does everything correctly except for initiating the "girl.down_here" dialog? It may be helpful to add some debug print statements to see if any lines aren't executing (for example, does the movement finish and execute the callback function you defined?).

Development / Re: On_interaction error?
« on: May 09, 2017, 05:22:24 am »
how big is your custom entity if you do entity:get_size()?

Development / Re: Speed depending on a fixed duration ?
« on: May 05, 2017, 01:35:11 am »
The equation you want to keep in mind is the following:
distance = rate * time -> rate = distance / time

So first calculate the distance to the hero from both cameras (find hypothenuse of right angle triangle made up of the delta x and delta y distances from camera to hero)

Code: Lua
  1. local hero = {x=100, y=200}
  2. local camera1 = {x=300, y=200}
  3. local camera2 = {x=800, y=150}
  5. local distance_1 = math.sqrt( (hero.x - camera1.x)^2 + (hero.y - camera1.y)^2 )
  6. --> distance_1 = math.sqrt( (100 - 300)^2 + (200 - 200)^2 )
  7. --> distance_1 = math.sqrt( -200^2 + 0^2)
  8. --> distance_1 = 200 pixels
  10. local distance_2 =  math.sqrt( (hero.x - camera2.x)^2 + (hero.y - camera2.y)^2 )
  11. --> distance_2 = math.sqrt( (100 - 800)^2 + (200 - 150)^2 )
  12. --> distance_2 = math.sqrt( -700^2 + 50^2 )
  13. --> distance_2 = math.sqrt( 490000 + 2500)
  14. --> distance_2 = 701.78 pixels

Then to find the speed of the camera (rate), simply divide the distance in pixels by time in seconds:

Code: Lua
  1. local rate_1 = 200 / 2 --this is the camera_1 speed
  2. --> rate_1 = 100 pixels per second
  4. local rate_2 = 701.78 / 2 --this is the camera_2 speed
  5. --> rate_2 = 350.89 pixels per second

Another way to think about it is that since the distance of the 2nd camera is 3.5x the distance of the first camera, the speed of the second camera should be 3.5 times the speed of the first camera.

Development / Re: Bring hero back?
« on: May 02, 2017, 12:02:58 am »
Could you cut off the tops of the bookshelves and draw the tops on a higher layer? That way the hero would naturally be behind them without having to change the layer of the hero.

Development / Re: Trying to make an NPC disappear
« on: April 30, 2017, 04:00:16 am »
To get the NPC to disappear when the map reloads, you should use the map:on_started() event.

I think you want something like this:
Code: Lua
  1. function map:on_started(destination)
  2.    if game:get_value("meeting_quest_1") then
  3.       worker_npc:remove()
  4.    end
  5. end
  7. function meeting_sensor:on_activated()
  8.    if not game:get_value("meeting_quest_1") then
  9.       game:start_dialog("meeting.1")
  10.       hero:freeze()
  11.       local path_movement = sol.movement.create("path")
  12.       path_movement:set_path({ 2, 2, 2, 2, 2, 1, 1, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0 })
  13.       path_movement:set_ignore_obstacles(true)
  14.       worker_npc:is_traversable()
  15.       path_movement:set_speed(64)
  16.       path_movement:start(worker_npc, function()
  17.          worker_npc:remove()
  18.          game:set_value("meeting_quest_1", true)
  19.          hero:unfreeze()  
  20.       end)
  21.    end
  22. end

you can probably delete line 1 as well because that doesn't look like it is doing anything.

Pages: [1] 2 3 ... 5