(done) Directional trigger

This forum is for the Lua scriptable clone of DM/CSB called Dungeon Strikes Back by Sophia. Use DSB to build your own highly customised games.

Moderator: Sophia

Forum rules
Please read the Forum rules and policies before posting. You may Image to help finance the hosting costs of this forum.
Post Reply
User avatar
Parallax
DMwiki contributor
Posts: 424
Joined: Mon Aug 28, 2006 7:56 pm
Location: Back in New Jersey

(done) Directional trigger

Post by Parallax »

I am probably missing something obvious, but I can't seem to make a floor trigger operated by the party only when facing a given direction (say, north.)

There are two problems to solve. 1) The party must not set the trigger when it steps on it facing anything but north. 2) If, while standing on the obj.trigger, the party turns to face north, the trigger should get, well, triggered. And of course, turning away from north should release the trigger. A basic obj.trigger instance triggers when stepped on, regardless of direction, and does nothing when the party turns while standing on it.

- Giving the obj.trigger instance a 'face' exvar doesn't prevent the trigger from acting when stepped on while facing the wrong direction.

- Giving it an on_turn exvar set to 'on_turn = floor_trigger' does not do anything. floor_trigger is the function that gets called when the trigger is stepped on, but clearly whatever happens on party turning, it doesn't involve checking the exvars of triggers.

- Modifying the obj.trigger archetype to add one line to it 'on_turn = floor_trigger,' does nothing most of the time and crashes DSB sometimes (when facing north (0)?)

- I have tried leaving the trigger as is and making it activate a function_caller object, but how do these even work? I do not know. And how would I get the activation to occur when the party turns? Can the function caller deactivate the trigger? would that be enough?

_ I have thought about putting a telporter on that tile as well, with a delay of one tick for its activation, that would teleport the party to the same tile, in the hopes of deactivating-reactivating the trigger, but I suspect that might lead to infinite telportation and, frankly, it shouldn't be that complicated.

I have spend several hours on this problem now, and I am no closer to a working solution. Any help would be appreciated.
User avatar
Sophia
Concise and Honest
Posts: 4307
Joined: Thu Sep 12, 2002 9:50 pm
Location: Nowhere in particular
Contact:

Re: Directional trigger

Post by Sophia »

Parallax wrote:I am probably missing something obvious, but I can't seem to make a floor trigger operated by the party only when facing a given direction (say, north.)
Don't feel bad. It's actually a bit tricky, because there are two distinct events to concern ourselves with.
Parallax wrote:The party must not set the trigger when it steps on it facing anything but north.
To solve this problem, you could simply put the the trigger itself on the NORTH instead of CENTER of the tile. This is as far as the problem needs to go with objects.

However, it won't yet completely solve our problems with the party, because there's still the second issue:
Parallax wrote:If, while standing on the obj.trigger, the party turns to face north, the trigger should get, well, triggered. And of course, turning away from north should release the trigger
Normally, the on_turn event on the trigger sitting on the NORTH will be fired when the party turns north, but nothing will happen when the party turns away. This will thus not work with the standard triggers that need to support things like trigger counts and constant weight. However, this is how things like stairs that don't need such fancy features work. If a simple trigger is enough, something like this will suffice:

Code: Select all

function party_trigger(self, id, what)
	-- If there is no triggering inst, it must be the party
	if (what == nil) then 
		got_triggered(id, nil)
	end
end

function party_turn(self, id)
	got_triggered(id, nil)
end
Then simply make a new arch with on_trigger=party_trigger and on_turn=party_turn and assign targets, msg, and so on as normal. (Except opby, as it's assumed to operated by the party only)

If you want full-fledged triggers, things get (quite a bit) more complicated.
Parallax wrote:Giving it an on_turn exvar set to 'on_turn = floor_trigger' does not do anything.
on_turn events (along with all other events) are associated with object archs, not exvars. So, this is just the wrong place for this kind of thing.
Parallax wrote:Modifying the obj.trigger archetype to add one line to it 'on_turn = floor_trigger,' does nothing most of the time and crashes DSB sometimes (when facing north (0)?)
This is because on_turn takes a different parameter list than on_trigger. Its parameters are on_turn(self, id, dir); it does not take a triggering object because DSB only handles this by default when the party turns. On the other hand, on_trigger takes (self, id, what). So, the engine was taking the direction passed as an inst id of a triggering object. (inst id 0 is illegal, hence the crash on NORTH)

So, how to handle the more complicated triggers? Well, first, we'd better put the trigger back in the CENTER of the tile, because we'll handle things in our custom code rather than letting the engine do it.

Around line 362 of base/triggers.lua you'll find code like this:

Code: Select all

if (exvar[id].opby_party == true) then
	return true
end
This will need to be expanded to:

Code: Select all

if (exvar[id].opby_party == true) then
	if (not exvar[id].opby_party_face) then 
		return true
	end
	local lev, x, y, face = dsb_party_coords()
	if (face == exvar[id].opby_party_face) then 
		return true
	end
end
We'll then have to write a custom on_turn function:

Code: Select all

function trigger_turn(self, id, dir)
	if (not exvar[id] or not exvar[id].opby_party_face) then
		return nil
	end

	if (exvar[id].opby_party_face == dir) then
		floor_trigger(self, id, nil)
	elseif (exvar[id].opby_party_face == g_old_dir) then
		local store_face = exvar[id].opby_party_face
		exvar[id].opby_party_face = dir
		floor_trigger_off(self, id, nil)
		exvar[id].opby_party_face = store_face
	end
end
Associate this with our new arch the same as before, with on_turn=trigger_turn in the arch definition.

You'll notice a couple of hacks in order to emulate the functionality of an off_trigger when we don't really have one. In order to make one of these hacks work we'll have to override sys_party_turn, too.

Code: Select all

function sys_party_turn(dir)
	g_old_dir = dir
end
Finally, don't forget to dsb_export("g_old_dir") or strange things might happen when you save and reload.

If this last chunk of code seems very complicated, well... that's because it is. Depending on how well it works, I may merge this into the base code, but I'm not sure, because I'm sure there are some issues I haven't spotted yet.

EDIT: After looking at this code a bit more, I've determined that if I'm going to be adding this to a new version, I could modify the core engine a bit to make this less hackish... I am probably going to add an off_turn event to eliminate most of the strangeness with the turn events. However, the idea behind being able to use a either a trigger on a specific direction (NORTH, SOUTH, etc. instead of CENTER) or an opby_party_face exvar on a centered trigger should remain the same.
User avatar
Parallax
DMwiki contributor
Posts: 424
Joined: Mon Aug 28, 2006 7:56 pm
Location: Back in New Jersey

Post by Parallax »

Thank you for the in-depth explanation. I think I understand most of the lua code you wrote, although it would have taken me a very long time to figure it out and write it right on my own. I was really expecting "here's what you missed" followed by a 10-second solution involving the proper exvar.
I'm very much in favor of incorporating these changes, along with the new exvar opby_party_face, for the next version of DSB.

I am also not quite certain what you're doing here:

Code: Select all

   elseif (exvar[id].opby_party_face == g_old_dir) then
      local store_face = exvar[id].opby_party_face
      exvar[id].opby_party_face = dir
      floor_trigger_off(self, id, nil)
      exvar[id].opby_party_face = store_face
   end
Why store opby_party_face, then override it, then call a function that cannot possibly know about it, then restore it? And even if it is, for some reason, necessary to overwrite opby_party_face before calling floor_trigger_off, can't we restore it simply by assigning it the value of g_old_dir, since that's where it started the loop? Surely floor_trigger_off is not going to turn the party, right?

Another thing is, in the beginning, you say to put the trigger on the NORTH (=0) side of the cell. But the only way for a floor trigger to get walked on is if the cell is open, in which case 0 is the direction of a corner (the northwestern one?) of the cell. I was under the impression that this was how you made triggers that got activated only if a character was standing on the proper subtile, but regardless of what direction the party is facing. I have not looked into subtile triggers at all so I could be completely wrong about that though, but I'm curious.
User avatar
Sophia
Concise and Honest
Posts: 4307
Joined: Thu Sep 12, 2002 9:50 pm
Location: Nowhere in particular
Contact:

Post by Sophia »

Parallax wrote:I'm very much in favor of incorporating these changes, along with the new exvar opby_party_face, for the next version of DSB.
I am too. :)
I've got an early version working and if I may say so it's quite nice. I've determined it's not that much additional work to allow the triggers to work in both ways, so that's what I'm going to allow-- you can either put the trigger on one side of the tile, or put it in the CENTER and set an opby_party_face, and in both cases, it'll do what you expect.
Parallax wrote:Why store opby_party_face, then override it, then call a function that cannot possibly know about it, then restore it?
This is one of those ugly hacks. ;)
The function does know about it, because it calls is_opby, and we modified is_opby up there. However, that version of is_opby tries to match the opby direction with the party's current direction: when they're moving off a trigger, that's the wrong direction. So, we have to tweak the opby direction.
Parallax wrote:And even if it is, for some reason, necessary to overwrite opby_party_face before calling floor_trigger_off, can't we restore it simply by assigning it the value of g_old_dir, since that's where it started the loop? Surely floor_trigger_off is not going to turn the party, right?
I don't know. Truthfully, after all of the weird things you people have done to DSB, I try to not make assumptions like that. ;)
Parallax wrote:Another thing is, in the beginning, you say to put the trigger on the NORTH (=0) side of the cell. But the only way for a floor trigger to get walked on is if the cell is open, in which case 0 is the direction of a corner (the northwestern one?) of the cell.
Well, actually, it's both. DSB tiles have 5 subtile positions, numbered 0-4. Position 4 is always the CENTER. However, what exactly positions 0-3 mean varies depending on what code is handling it. For objects lying on an open tile, it's the corners. For objects on walls, it is the cardinal directions. However, it's really just a number-- 0-3. So, when you're putting a trigger on the NORTH side of a tile, yes, you're actually putting it in the NORTHWEST corner. When the party walks over the tile, though, the triggers executed are chosen based on their facing, which is based on the cardinal directions. This means that something on the NORTHWEST (corner 0) will be processed when the party is facing NORTH (direction 0).

In many cases, it's ok, as directional triggers will be opby either the party or an object, not both. You can use the system of corners for objects and the system of cardinal directions for the party, things in alcoves, and so on. Still, the weirdness this system can create is why I decided to support both directional triggers and the opby_party_face exvar.
Parallax wrote:I was under the impression that this was how you made triggers that got activated only if a character was standing on the proper subtile, but regardless of what direction the party is facing. I have not looked into subtile triggers at all so I could be completely wrong about that though, but I'm curious.
No, though that would be pretty great, and could be done by some custom code.
I'll have to think about that one! :)
Right now, though, the game doesn't care about the content of the party when a trigger is operated by the party. The "direction" is determined by the party's facing.
User avatar
Parallax
DMwiki contributor
Posts: 424
Joined: Mon Aug 28, 2006 7:56 pm
Location: Back in New Jersey

Post by Parallax »

Sophia wrote:I've got an early version working and if I may say so it's quite nice. I've determined it's not that much additional work to allow the triggers to work in both ways, so that's what I'm going to allow-- you can either put the trigger on one side of the tile, or put it in the CENTER and set an opby_party_face, and in both cases, it'll do what you expect.
That's nice, and it will keep it working somewhat smoothly with DDM, since I don't think there's going to be a DDM update anytime soon.
Sophia wrote:Truthfully, after all of the weird things you people have done to DSB, I try to not make assumptions like that. ;)
We have done nothing that you have not allowed. :P
Parallax wrote:I was under the impression that this was how you made triggers that got activated only if a character was standing on the proper subtile, but regardless of what direction the party is facing. I have not looked into subtile triggers at all so I could be completely wrong about that though, but I'm curious.
Sophia wrote:No, though that would be pretty great, and could be done by some custom code.
I'll have to think about that one! :)
Right now, though, the game doesn't care about the content of the party when a trigger is operated by the party. The "direction" is determined by the party's facing.
Well, I went and serched for that topic in the DSB subforum, and I cannot find it. I found Remy's discussion of the directional trigger problem (shame on me, tsk tsk :oops: ) but it seems I have hallucinated the discussion about subtile triggers operated by individual party members stepping on them. That's weird, because I remember reading about that and everything. Oh well, I guess you're right, it's nothing some custom code couldn't do.

This leaves me with only one question: What is the function_caller object and how do you use it? OK, I guess that's two questions.
User avatar
Sophia
Concise and Honest
Posts: 4307
Joined: Thu Sep 12, 2002 9:50 pm
Location: Nowhere in particular
Contact:

Post by Sophia »

Parallax wrote:What is the function_caller object and how do you use it? OK, I guess that's two questions.
It calls Lua functions. (Ok, that's not very helpful)

Truthfully, it has been somewhat deprecated by the "func" exvar, but if you want something to call functions in response to messages, it still may be useful. I think it could stand to be a bit better documented, though, as the interface isn't all that clear without reading through the code.

If you want to call the Lua function activate_func in response to a M_ACTIVATE, set it up like this:

Code: Select all

exvar[func_caller_id].m_a = "activate_func"
You can also use m_d (for M_DEACTIVATE), m_t (for M_TOGGLE) and m_n (for M_NEXTTICK). The function call is of the form func(id, level, x, y, tile_pos, msg_data). If you want to see the actual implementation, it's the function func_caller in base/msg_handlers.lua.
User avatar
Parallax
DMwiki contributor
Posts: 424
Joined: Mon Aug 28, 2006 7:56 pm
Location: Back in New Jersey

Post by Parallax »

Thanks for the explanation. It would be great to have a manual, or at least some tutorials, on DSB.* Right now looking at the code is the best way to learn how DSB works, and that's not a very user-friendly way.**

*: No, I am not volunteering to write them.

**: I know you never claimed DSB was user-friendly. I can dream, can't I? :)
Post Reply