Multiple key objects

This is a discussion / support forum for the Hugo programming language by Kent Tessman. Hugo is a powerful programming language for making text games / interactive fiction with multimedia support.

Hugo download links: https://www.generalcoffee.com/hugo
Roody Yogurt's Hugo Blog: https://notdeadhugo.blogspot.com
The Hugor interpreter by RealNC: http://ifwiki.org/index.php/Hugor

Moderators: Ice Cream Jonsey, joltcountry

loafingcoyote
Posts: 89
Joined: Wed Jan 04, 2012 2:57 pm
Location: Texas

Multiple key objects

Post by loafingcoyote »

I don't know how useful this is, but here is a modification of DoLock and DoUnLock that allows for doors to have multiple key objects. I've tested it with up to four different key objects without trouble. You may notice that certain things are checked in a different order that with the original routine. I would like to know if this different order presents any problems or if I've forgotten anything important.

Also included are some new verb messages that had to be replaced and one global.

Code: Select all

global correct_key	! This is only used to print a couple of messages correctly


replace DoLock
{
	local a, test_key, flag, not_xobject, not_in_player	

	if not CheckReach(object): return false

	if object is open
	{
		VMessage(&DoLock, 2)             ! "Have to close it first..."
		return true
	}

	if object is locked
	{
		VMessage(&DoLock, 1)             ! already locked
		return true
	}

	while object.#key_object > a and flag = false
	{
		a++
		test_key = object.key_object #a

		if xobject
		{
			if xobject = test_key
			{
				flag = true
				not_xobject = false
			}
			else: not_xobject = true
		}

		if not xobject
		{
			if Contains(player, test_key)
			{
				xobject = test_key
				flag = true
				not_in_player = false
			}
			else: not_in_player = true
		}
	}

	if not_xobject = true
	{
			VMessage(&DoUnlock, 1)  ! "Doesn't seem to work..."
			return true
	}
	if not_in_player = true
	{
		VMessage(&DoUnlock, 2)           ! no key that fits
		return true
	}

	correct_key = xobject
	object is locked
	if not object.after
	{
		if not xobject.after
			VMessage(&DoLock, 3)     ! "Locked."
	}
	return true
}

replace DoUnLock
{
	local a, test_key, flag, not_xobject, not_in_player	

	if not CheckReach(object): return false

	if object is open
	{
		VMessage(&DoUnLock, 5)             ! "The door is open."
		return true
	}

	if object is not locked
	{
		VMessage(&DoUnLock, 3)             ! already unlocked
		return true
	}

	while object.#key_object > a and flag = false
	{
		a++
		test_key = object.key_object #a

		if xobject
		{
			if xobject = test_key
			{
				flag = true
				not_xobject = false
			}
			else: not_xobject = true
		}

		if not xobject
		{
			if Contains(player, test_key)
			{
				xobject = test_key
				flag = true
				not_in_player = false
			}
			else: not_in_player = true
		}
	}

	if not_xobject = true
	{
			VMessage(&DoUnlock, 1)  ! "Doesn't seem to do the trick..."
			return true
	}
	if not_in_player = true
	{
		VMessage(&DoUnlock, 2)           ! no key that fits
		return true
	}

	correct_key = xobject
	object is not locked
	if not object.after
	{
		if not xobject.after
			VMessage(&DoUnLock, 4)     ! "Unlocked."
	}
	return true
}

replace NewVMessages(r, num, a, b)
{
	select r

	case &DoLock
	{
		select num
		case 3
		{
			if correct_key ~= 0 and object.key_object
				print "(with "; The(correct_key); ")"
			print "Locked."
			return true
		}
	}

	case &DoUnLock
	{
		select num
		case 4
		{
			if correct_key ~= 0 and object.key_object
				print "(with "; The(correct_key); ")"
			print "Unlocked."
			return true
		}
		case 5
		{
			print CThe(object); IsorAre(object); " open."
			return true
		}
	}
}
I've wanted to write something like this ever since I started Npcmove.h. Having npc's move logically in relation to locked doors, when the player is in possession of the only key object for that door, can be a problem. That's why npcmove.h doesn't stop npc's from moving through locked doors.

I've realized, however, that this doesn't make sense and should be changed; a locked door should work properly for both the player and characters.

If the above code passes the test, then a character can have a key to a door that the player or other npc's have a key to also. This should make coding logical behavior in npc's that much easier.

Roody_Yogurt
Posts: 2256
Joined: Mon Apr 29, 2002 6:23 pm
Location: Milwaukee

Post by Roody_Yogurt »

I never got around to writing a replacement myself, but I also saw the usefulness of writing one (I can easily imagine a game where a door has the "correct" key but then also has a "lockpick" object the player can use to unlock a door). Since I didn't have a pressing need to write it, I just had it as a "Hugo Homework" challenge on the HbE key_object page.

Good work!

Roody_Yogurt
Posts: 2256
Joined: Mon Apr 29, 2002 6:23 pm
Location: Milwaukee

Post by Roody_Yogurt »

Looking at it more closely, I do have one thing to say about the global variable. Of course, using the global variable will allow you to reference it in object.after messages, which is nice, but if you created it just for the sake of the NewVMessage responses, you could also do something like this:

Code: Select all

      if not xobject.after
         VMessage(&DoUnLock, 4, xobject)     ! "Unlocked."
And then update case 4 to something like this:

Code: Select all

      case 4
      {
         if a
            print "(with "; The(a); ")"
         print "Unlocked."
         return true
      } 

loafingcoyote
Posts: 89
Joined: Wed Jan 04, 2012 2:57 pm
Location: Texas

Post by loafingcoyote »

Roody_Yogurt wrote:Looking at it more closely, I do have one thing to say about the global variable. Of course, using the global variable will allow you to reference it in object.after messages, which is nice, but if you created it just for the sake of the NewVMessage responses, you could also do something like this:.....
Thank you so much for fixing this! The global was the one thing that I didn't like. I knew that it was inefficient but didn't know how to fix it. I sometimes forget that routines often have built-in locals that can make life much easier.

Here's another thing that you may have noticed already, but it's worth pointing out. In the original library routines, if the player types "unlock/lock door" without specifying an xobject, then that xobject's "after" property will not be run. The replacements above allow an xobject's after property to be run even if it is not directly referenced by the player.
Roody_Yogurt wrote:Since I didn't have a pressing need to write it, I just had it as a "Hugo Homework" challenge on the HbE key_object page.
Hey, I like the idea of Hugo Homework Challenges. I'm going to find some more of those!

Roody_Yogurt
Posts: 2256
Joined: Mon Apr 29, 2002 6:23 pm
Location: Milwaukee

Post by Roody_Yogurt »

loafingcoyote wrote:Hey, I like the idea of Hugo Homework Challenges. I'm going to find some more of those!
Yeah, I'm not sure how many I've done so far. There may be one other one. There are also a couple instances where I ask something and have a spoiler space showing the "answer", but I don't think I've been consistent in calling them all "Hugo Homework".
loafingcoyote wrote:Here's another thing that you may have noticed already, but it's worth pointing out. In the original library routines, if the player types "unlock/lock door" without specifying an xobject, then that xobject's "after" property will not be run.
I didn't notice this, but I did run into the same thing myself last night when I wondered, huh, I wonder what my original vision for this routine would look like? My version got around it by just setting the xobject global to the object-we-have-determined-to-be-a-key. The whole thing ended up like this:

Code: Select all

routine DoUnlock
{
	local a
	if not CheckReach(object):  return false

	if xobject ~= 0
	{
		a = InList(object,key_object,xobject)
		if not a
		{
			VMessage(&DoUnlock, 1)  ! "Doesn't seem to work..."
			return true
		}
	}
	elseif object.key_object
	{
		for &#40;a=1; a <= object.#key_object ; a++&#41;
		&#123;
		if Contains&#40;player, object.key_object #a&#41;
			&#123;
			xobject = object.key_object #a
			break
			&#125;
		&#125;
		if not xobject
		&#123;
			VMessage&#40;&DoUnlock, 2&#41;           ! no key that fits
			return true
		&#125;
	&#125;

	if object is not locked
		VMessage&#40;&DoUnlock, 3&#41;           ! already unlocked
	else
	&#123;
		object is not locked
		if not object.after
		&#123;
			if not xobject.after
				VMessage&#40;&DoUnlock, 4, xobject&#41;   ! "Unlocked."
		&#125;
	&#125;
	return true
&#125;


routine DoLock
&#123;
	local a
	if not CheckReach&#40;object&#41;&#58;  return false

	if xobject ~= 0
	&#123;
		a = InList&#40;object,key_object,xobject&#41;
		if not a
		&#123;
			VMessage&#40;&DoUnlock, 1&#41;  ! "Doesn't seem to work..."
			return true
		&#125;
	&#125;
	elseif object.key_object
	&#123;
		for &#40;a=1; a <= object.#key_object ; a++&#41;
		&#123;
		if Contains&#40;player, object.key_object #a&#41;
			&#123;
			xobject = object.key_object #a
			break
			&#125;
		&#125;
		if not xobject
		&#123;
			VMessage&#40;&DoUnlock, 2&#41;           ! no key that fits
			return true
		&#125;
	&#125;

	if object is locked
		VMessage&#40;&DoLock, 1&#41;             ! already locked
	elseif object is open
		VMessage&#40;&DoLock, 2&#41;             ! "Have to close it first..."
	else
	&#123;
		object is locked
		if not object.after
		&#123;
			if not xobject.after
				VMessage&#40;&DoLock, 3, xobject&#41;     ! "Locked."
		&#125;
	&#125;
	return true
&#125;
I haven't tested it, so there very well could be some typos somewhere, but in theory, it should work. I think the main thing it doesn't do that your routines do is it doesn't check for open doors as early as your code does as it is basically a souped-up version of the original routines. Your take sounds appealing, but I didn't really want to think on it long enough to decide what's best for me.


loafingcoyote
Posts: 89
Joined: Wed Jan 04, 2012 2:57 pm
Location: Texas

Post by loafingcoyote »

Roody_Yogurt wrote:I haven't tested it, so there very well could be some typos somewhere, but in theory, it should work.
Yes, your code works great! I learned a couple of things too.

First the routine, "InList", looks like another one of those obscure yet extremely useful library routines. I had seen you use it before and meant to look it up. I'm going to start using it regularly. Also, I noticed that you used "break" to exit a loop. I had mistakenly thought that it would always cause a routine to exit immediately. HbE states clearly: "If the game is in a loop (for, while, etc.), break immediately exits the current loop."

Roody_Yogurt wrote:I think the main thing it doesn't do that your routines do is it doesn't check for open doors as early as your code does as it is basically a souped-up version of the original routines. Your take sounds appealing, but I didn't really want to think on it long enough to decide what's best for me.

You know, I'm not sure that one approach is necessarily better than the other. Your approach has the advantage of being closer, in form, to the original so that It might be easier for someone to modify. It also doesn't require any modification to the messages and I can imagine that being a plus for some people.

The main advantage to my approach is that it keeps the message: "(With the [key]) / Unlocked," message intact. It also checks if a door is open before trying to unlock it. But, really, the player should know that an open door is unlocked already, so this isn't a huge plus. Also, if the player tries to lock a door with a coat, for instance, the routine will tell him he needs to shut the door first without letting him know that the coat isn't a valid key object.

Thinking about this caused me to wonder if the best solution might be to inform the player of all conditions that need to be modified before the door can be locked/unlocked. So if the player tries to unlock a door that is open and unlocked, he would see:
Hugo Interpreter wrote: The closet door is open.
(It's already unlocked anyway.)
I coded up a fairly crude example of this that can be found here:

http://dl.dropbox.com/u/11102009/newkey.h

It gives the player all of the conditions that have to be met to lock/unlock a door(open/shut, locked/unlocked, no key, wrong key). I also replaced the "NewVMessages" routine with my own "ExMessages". I did this with a mind toward a possible future extension, so that it doesn't interfere with any preexisting replaced verb messages.

I admit that this whole idea could be misguided. The player should be well aware of what needs to happen to lock/unlock a door. But I would like to get someone elses opinion before I abandon the idea. Even if it doesn't work here, it might be nice for other situations.


Thanks for finding these! They look pretty tough. They mostly cover areas that I haven't dealt with yet. I especially like the idea of configuration files. That has serious potential!

Roody_Yogurt
Posts: 2256
Joined: Mon Apr 29, 2002 6:23 pm
Location: Milwaukee

Post by Roody_Yogurt »

loafingcoyote wrote:First the routine, "InList", looks like another one of those obscure yet extremely useful library routines. I had seen you use it before and meant to look it up. I'm going to start using it regularly.
To be honest, I've been putting off writing a HbE page for InList partly because I've taken for granted its usefulness, but I can't remember exactly when I realized that. Looking at Kent's code, one can see how much of things are handled with property arrays instead of regular arrays, so it really is helpful to be familiar with propery-array-interaction routines.

Somewhat unofficially, Kent released selected bits of source to his game Future Boy! at some point (if you don't have it, I can e-mail it to you). Two particularly useful routines in there are AddPropValue and RemovePropValue, which do for any property what AddToScope and RemoveToScope do for scope. For someone who gets in the habit of using properties to keep track of things, it is invaluable (and probably should have been in the standard library in the first place, heh).
loafingcoyote wrote: Thinking about this caused me to wonder if the best solution might be to inform the player of all conditions that need to be modified before the door can be locked/unlocked. So if the player tries to unlock a door that is open and unlocked, he would see:
Hugo Interpreter wrote: The closet door is open.
(It's already unlocked anyway.)
This sounds pretty intriguing. I'm not exactly wild about standard door messages, so maybe extra text like that in some places would help make them feel less flimsy.
loafingcoyote wrote:I coded up a fairly crude example of this that can be found here:

http://dl.dropbox.com/u/11102009/newkey.h
Looks like it'd be a good library extension and/or HbE page!

loafingcoyote
Posts: 89
Joined: Wed Jan 04, 2012 2:57 pm
Location: Texas

Post by loafingcoyote »

Roody_Yogurt wrote:Somewhat unofficially, Kent released selected bits of source to his game Future Boy! at some point (if you don't have it, I can e-mail it to you).
Yes, please! The snippets of "Future Boy!" code that I've seen so far are impressive and make me believe that the next update to the Hugo library will be worth the wait.

Roody_Yogurt wrote:Looks like it'd be a good library extension and/or HbE page!
I'm glad you like the idea. The code needs to be cleaned up and probably most of the messages rewritten. I didn't put as much effort into it as I could have since I didn't know if it was a good idea or overkill. Sometimes it's hard to tell the difference!

Roody_Yogurt
Posts: 2256
Joined: Mon Apr 29, 2002 6:23 pm
Location: Milwaukee

Post by Roody_Yogurt »

loafingcoyote wrote:I'm glad you like the idea. The code needs to be cleaned up and probably most of the messages rewritten. I didn't put as much effort into it as I could have since I didn't know if it was a good idea or overkill. Sometimes it's hard to tell the difference!
It is true that one doesn't want to push the lbrary into being too specialized. As far as I understand, one of the biggest obstacles to learning TADS 3 is learning the many facets of its extensive library, so yeah, there seems to be a fine line between improving the library and adding code that will just obfuscate purpose to a newcomer. This is why I present all of my code as optional and try to explain the reasoning behind it. Making Hugo seem too complicated seems detrimental to the cause.

But yeah, back to the original issue, yeah, I think it's a nice idea.

Roody_Yogurt
Posts: 2256
Joined: Mon Apr 29, 2002 6:23 pm
Location: Milwaukee

Post by Roody_Yogurt »

loafingcoyote wrote:Hey, I like the idea of Hugo Homework Challenges. I'm going to find some more of those!
As far as coding challenges go, the other day, I noticed that the IF Wiki has some pages specifically for example code, for comparing the various systems (it's their take on "Cloak of Darkness").

Anyhow, I did a couple, but there are still a couple left (one involving a combat system and one involving coffee disambiguation), in case anyone wants to try it: http://ifwiki.org/index.php/Category:Examples_of_code

(Or make up some new code scenarios if you want.)

Post Reply