Phantom door message

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

Phantom door message

Post by loafingcoyote »

I recently tried to implement the extension cango.h by Cardinal Teulbachs and noticed some odd behavior from a door object. Whenever the YouCanGo() routine was called in a room with a door object called "pantry door", the following message would be displayed:

You open the pantry door and leave, closing it behind yourself.

I had neither opened, closed or interacted with the door in any way, nor had I left the room.

I experimented a little and discovered that the following bits of code display that same message:

Code: Select all

	if object.door_to: "Yes, it's a door.\n"	! The object is a door.

Code: Select all

	if location.s_to: "You can go south.\n"		! The south object is a door.

Code: Select all

	newroom = location.(s_obj.dir_to)		! the south object is a door.
It seems that any time a door object's door_to property is called, in any way, the message "you open the [door] and leave, closing it behind yourself." is displayed.


I think that the problem is found in the door_to property itself. Here is the code that displays the offending message:

Code: Select all

		if self is not open and (actor in location or
			location = self.between #((parent(actor) = \
				self.between #1) + 1))
		{
			! Notify the player that the player or a character
			! has gone through the door:
			!
			OMessage(door, 5)
			general = 2
		}
If a door's "door_to" property is called, then it appears that the only way this code isn't executed is if the actor is the player and he either:
(1)needs to get out of something, or
(2)the door is closed and the player is attempting to go through it.

Therefore, if the door is closed and the player is on either side of the door, then this message will be displayed if the previous conditions are not true and the door_to property is called, no matter what.

I honestly don't know how to fix this and I need it to work properly for my current WIP.
I'm still learning Hugo so I could be wrong about where the problem lies. I would appreciate anyone's input.

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

Post by Roody_Yogurt »

I imagine you are defining your door-direction like this:

Code: Select all

room roomplace "Room Place Name"
{
    e_to 
       {
       return kitchen_door.door_to
       }
}
As that is what the Hugo manual recommends. Looking at the code, I think cango.h is expecting this:

Code: Select all

room roomplace "Room Place Name"
{
    e_to 
       {
       return kitchen_door
       }
}
Whiich still seems to work, from a getting-from-one-room-to-another perspective.

In any case, yeah, exit-checking routines can get you into a lot of trouble when it comes to doors and stuff. I ran into similar problems when writing my CanGoDir routine (which just checks if a direction/door is valid without printing anything).

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

Post by loafingcoyote »

Roody_Yogurt wrote: Looking at the code, I think cango.h is expecting this:

Code: Select all

room roomplace "Room Place Name"
{
    e_to 
       {
       return kitchen_door
       }
}
Whiich still seems to work, from a getting-from-one-room-to-another perspective.

In any case, yeah, exit-checking routines can get you into a lot of trouble when it comes to doors and stuff. I ran into similar problems when writing my CanGoDir routine (which just checks if a direction/door is valid without printing anything).
It's late, so I haven't been able to test it fully, but I think this will work. Thank you! I've been beating my head against a wall here.

It would have been easy enough to make my own door class with minor modifications to avoid any unwanted messages, but I'm trying to write an extension where exit checking is vital.

The idea behind it is to make implementing independently moving npc's easier. It's inspired by the random move routine written by Cardinal Teulbachs. When ready, it will not only be able to make randomly wandering npc's easy to implement, but goal driven ones as well; where an npc in any one room can find any object or room by calling a single routine. Each room that you want an npc to be able to move through will need an extra property, but I think that's a lot easier than trying to hard-code complex movement behavior into a game for one or more npc's.

This door message thing was a major hang up. Thanks again!

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

Post by Roody_Yogurt »

That is a cool endeavor. Not to be a copycat, but you got me wondering if it was within my own abilities, too. I started by looking at the path-finding routines for TADS on the IF Archive but eventually admitted to myself that it was beyond my understanding. I decided I'd try to design something- however inefficient- myself.

I envisioned a system where each room had two values. One would be the proximity to the start and one would be the proximity to the end. As long as you disallow going backwards, I figured that would be a decent way of figuring out the optimal path. Unfortunately, it was beyond my programming capabilities to design some loops to first do the start room, then do the rooms that is connected to, then do each of the rooms THOSE are connected to, all with decreasing values.

I ended up giving up and just giving all rooms connected to the first room the lesser value, then doing a search through ALL rooms, looking for that lesser value, and seeing what is connected to those (and so on).

I started off making a one-character, one-path system, but then thought, why don't I write each step to a character script? This ended up being more complicated than I realized, especially since I had to break down character scripting in a way I've never seen before.

Then I realized that while my code required the same "e_to door_object" code mentioned above, CharMove hates that, so I had to replace CharMove.

Anyhow, here is my take on path-finding:
( There was code here but it turns out that the forum code was interpreting my less-than-or-equals sign as some kind of tag and therefore making segments of my code disappear. Instead, you can download it here: http://roody.gerynarsabode.org/JC/findpath.h

Edit: fixed the door message for when a door is open
And off they go!

Anyhow, there are probably still more bugs in there, but if you have trouble with your system, feel free to use this one.
Last edited by Roody_Yogurt on Thu Feb 02, 2012 2:32 pm, edited 2 times in total.

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

Post by loafingcoyote »

Roody_Yogurt wrote:I started off making a one-character, one-path system, but then thought, why don't I write each step to a character script? This ended up being more complicated than I realized, especially since I had to break down character scripting in a way I've never seen before.
This is why I haven't used scripts much to this point. They don't seem terribly flexible and I just don't understand them very well yet.
Roody_Yogurt wrote: Then I realized that while my code required the same "e_to door_object" code mentioned above, CharMove hates that, so I had to replace CharMove.
Nice catch. I didn't notice this until you mentioned it. It turns out my character moved into the previously mentioned pantry door when I moved him with CharMove. Your replacement of CharMove seems to work though; he now moves through the door properly.

Also, there is an error near the top of the FindPath routine that doesn't allow it to compile. I don't quite understand the code so I wasn't able to fix it myself.
Roody_Yogurt wrote: Anyhow, there are probably still more bugs in there, but if you have trouble with your system, feel free to use this one.
Even though I haven't been able to test this out yet, I have a good idea of what it does. I'm very impressed that you figured this out so quickly.

My project is progressing well but I'm running into some of the same difficulties you encountered. My method, so far, is creative use of arrays to track paths through the rooms that can be traversed by the character. It maps out all possible routes by the character, then counts how many steps there are between the character and his goal. If multiple paths exist, then the routine should pick the shortest route.

Mine is still very much a work in progress so I'm glad you came up with an alternative. I can't wait to try it!

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

Post by Roody_Yogurt »

Oops, it seems that part of my code got clipped or something when I pasted it. Anyhow, I added the missing lines back.

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

Post by Roody_Yogurt »

loafingcoyote wrote: This is why I haven't used scripts much to this point. They don't seem terribly flexible and I just don't understand them very well yet.
Kent himself thinks of character scripts as pretty inflexible, and I'm pretty sure they are a legacy concept from "early IF system" design. Just the same, I don't feel like it has to be limiting, considering that in some ways, you are mainly limited to versatility of the character routines you write. Character scripting can still be a good way to set up good multiple-step actions. Sure, it doesn't support xobjects, but in the rare cases you need an NPC to do something complicated like that, hell, you can write a character routine just for that action! Also, letting any character script run unwatched is surely a recipe for disaster, but if you put in some code to watch game-changing events, CancelScript can save the day (and then you can start the next appropriate script, possibly)!

Anyhow, I don't have any game ideas that especially lend themselves to character scripting, but as you can tell, I hope to test their limits one day.
loafingcoyote wrote: Even though I haven't been able to test this out yet, I have a good idea of what it does. I'm very impressed that you figured this out so quickly.
Well, I've been kind of wondering how I would do it ever since playing "Hugo Clock". I only thought of that numbers-thing last night, though. My initial idea was to break a game's layout into different areas or regions, where each one would have a "main track" where I could set longer characte scripts that'd take NPCs to other areas. Then I'd just need a bunch of smaller instructions to get from side rooms to the track. All of the specialization that idea implied eventually killed that idea.
loafingcoyote wrote: My project is progressing well but I'm running into some of the same difficulties you encountered. My method, so far, is creative use of arrays to track paths through the rooms that can be traversed by the character. It maps out all possible routes by the character, then counts how many steps there are between the character and his goal. If multiple paths exist, then the routine should pick the shortest route.
Interesting! I look forward to seeing it!

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

Post by Roody_Yogurt »

First off, I realized that the php base or something was interpreting my less-than-or-equals sign as some kind of tag, which was ruining the code. You can now download the code at: http://roody.gerynarsabode.org/JC/findpath.h

Also, I forgot to mention that it is nice that you can get the gist of it even though I didn't comment it very well. In any case, I thought I'd go over some of my tricks for those who don't see what's going on.

forward property- proximity to start room; the closer it is, the higher the number is
backward property- same thing with end room
dirsteps property - this is just property array used to hold each of the steps that will eventually be fed into the character script array (number of elements determined by MAXDISTANCE constant)

Checking-every-object code-
When I run out of better ways to do things, I check every object "by hand", using a for loop. What makes this convenient is the objects global variable, which the compiler sets to the total number of objects in the game. That'll obviously be the last value of our for loop. To begin with, you'll notice that if you compile with HugoFix on and look at the object tree, the 29th object is a last_library_object defined by HugoFix. This means that non-lbrary objects start at their earliest at object number 29, which is where our for loop should start.

In your own games, you could create a "firstobject" object after hugolib.h is included, allowing a for loop like this:

Code: Select all

for ( i = firstobject ; i (is less than or equal to) objects ; i++)
Using properties-as-numbers- In one way, I guess I've known that properties are basically number values for a while, especially when it comes to the idea that aliases are a-different-name-with-the-same-value. I recently read Kent's reply in this thread, though: http://www.joltcountry.com/phpBB2/viewt ... f0c98c4ade

So, in findpath.h, I wanted to check every exit of the room using a for loop. First, I tried using the order defined values listed on the HbE properties page. These turned out to be way off from actual values, so I went and figured out each value by writing a program with a loop and wrote them all down by hand. Of course, aliases explained some discrepancies, but I also hadn't taken into account that numbering started at 0, not 1.

So, now I knew I could refer to n_to as 24, but I still wasn't happy with that since, as unlikely as it is that properties would be reordered in a future iteration of the library, a change would definitely break my system. Then it finally occurred to me that I didn't need to use an actual number at all, I could just do this:

Code: Select all

for ( i = n_to ; i (is less than or equal to) out_to ; i++)
(n_to is the first defined property; out_to is the last)
Now, the engine will always get that right.

character script-
The syntax for setting a character script looks like a mess. I'd say that character scripting is one of Hugo's worst offenders in terms of do-it-this-way-without-really-understanding-what's-going-on. It doesn't help that when I look at the Script routine, Kent uses an "o" as the local variable, and it always takes me a minute or so to remember that those aren't all zeros.

When it comes down to it, though, Script(char,steps) does some important stuff, but in terms of that "setscript[Script(character object,number of steps in script)]" command, it just basically returns a number so you can build a character routine like this:

Code: Select all

setscript[0] = &CharMove, e_obj , &CharWait, 0, &LoopScript, 0
(where setscript[0] = &CharMove, setscript[1] = e_obj , setscript[2] = &CharWait, etc.)

My code just first ran Script(char,steps) to get the initial return number, then fills out the setscript array in a more leisurely fashion with a while loop:

Code: Select all

	b = 1
	a = Script(char, PropertyCount(char,dirsteps))

	while b <= PropertyCount&#40;char,dirsteps&#41;
		&#123;
		setscript&#91;a++&#93; = &CharMove
		setscript&#91;a++&#93; = char.dirsteps #&#40;b++&#41;
		&#125;
Ok, so I think those are the most interesting things that my code does, but if anyone has any other questions, feel free to ask!

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

Post by loafingcoyote »

Roody_Yogurt wrote: So, now I knew I could refer to n_to as 24, but I still wasn't happy with that since, as unlikely as it is that properties would be reordered in a future iteration of the library, a change would definitely break my system. Then it finally occurred to me that I didn't need to use an actual number at all, I could just do this:

Code: Select all

for &#40; i = n_to ; i &#40;is less than or equal to&#41; out_to ; i++&#41;
(n_to is the first defined property; out_to is the last)
Now, the engine will always get that right.

Thank you for explaining this. At first it threw me for a loop!

Roody_Yogurt wrote: When it comes down to it, though, Script(char,steps) does some important stuff, but in terms of that "setscript[Script(character object,number of steps in script)]" command, it just basically returns a number so you can build a character routine like this:

Code: Select all

setscript&#91;0&#93; = &CharMove, e_obj , &CharWait, 0, &LoopScript, 0
(where setscript[0] = &CharMove, setscript[1] = e_obj , setscript[2] = &CharWait, etc.)

My code just first ran Script(char,steps) to get the initial return number, then fills out the setscript array in a more leisurely fashion with a while loop:

Code: Select all

	b = 1
	a = Script&#40;char, PropertyCount&#40;char,dirsteps&#41;&#41;

	while b <= PropertyCount&#40;char,dirsteps&#41;
		&#123;
		setscript&#91;a++&#93; = &CharMove
		setscript&#91;a++&#93; = char.dirsteps #&#40;b++&#41;
		&#125;
This helps illustrate what you said earlier about scripts not having to be limiting and inflexible. My own method, which doesn't use scripts, involves calling a single routine per moving character every turn. That can easily be handled with daemons, but I can see the advantage to calling a couple of routines and then not having to worry about it again.

I got to play around with "findpath.h" a little tonight. I made three characters and eleven rooms. Some of the rooms make a loop and in this loop is a lockable door.
When the door is unlocked the characters move through it normally. When it's locked, they have no problem finding their way around the loop to their intended destination, even though it is a longer path than the one through the locked door.
The only thing I found that it couldn't handle is if the destination room is behind a locked door and there is no alternate path. My interpreter froze. I haven't tried it yet, but I suspect this would also be true of a room that is inaccessible some other way.

It needs to be tested fully, but I think this is already a great addition to Hugo and an amazing accomplishment in such a short time. Very nice!

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

Post by Roody_Yogurt »

loafingcoyote wrote:This helps illustrate what you said earlier about scripts not having to be limiting and inflexible. My own method, which doesn't use scripts, involves calling a single routine per moving character every turn. That can easily be handled with daemons, but I can see the advantage to calling a couple of routines and then not having to worry about it again.
Well, it's also true that as one gets closer to having a super-adaptive character script, in functionality, it'll end up being more and more like such a daemon anyway. I guess I'm just curious about scripts since they are there.
loafingcoyote wrote:I got to play around with "findpath.h" a little tonight. I made three characters and eleven rooms. Some of the rooms make a loop and in this loop is a lockable door.
When the door is unlocked the characters move through it normally. When it's locked, they have no problem finding their way around the loop to their intended destination, even though it is a longer path than the one through the locked door.
The only thing I found that it couldn't handle is if the destination room is behind a locked door and there is no alternate path. My interpreter froze. I haven't tried it yet, but I suspect this would also be true of a room that is inaccessible some other way.
Right on. Thanks for looking at this! I think when I wrote it, I figured it'd be within the responsibility of the game author to first make sure a route was possible, but still, hanging the interpreter under any circumstances is no good. I have uploaded a version 1.1 (untested) that should hopefully quit out in such a case. In fact, SetPath should return false in such a circumstance so a game could have some sort of "if (not) SetPath(char)" code or something.
loafingcoyote wrote:It needs to be tested fully, but I think this is already a great addition to Hugo and an amazing accomplishment in such a short time. Very nice!
Thanks! Now I just need to psych myself up to get back into one of my WIPs!

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

Post by loafingcoyote »

Roody_Yogurt wrote: I have uploaded a version 1.1 (untested) that should hopefully quit out in such a case.

Here is the good news. An inaccessible destination no longer locks up the interpreter. Also, you cleared up some other interesting behavior I forgot to mention the other night. In version 1.0, If the character was in the destination room when you called Makepath and SetPath, he would go to his intended start room and then visit each adjacent room before making his way back to the destination room. I honestly considered this more a feature than a bug, but it's always good to clear up any strange behavior.

Now the bad news; if a door exists anywhere along the possible path between a character and his destination then he refuses to move. This is true whether the door is open or closed. Also, the characters now don't necessarily chose the shortest route in a loop. Using the exact same room layout as 1.0, the characters often went the long way around. I couldn't find a definite pattern to this behavior although I think they prefer to travel through rooms that are defined earlier in the code.

Roody_Yogurt wrote: In fact, SetPath should return false in such a circumstance so a game could have some sort of "if (not) SetPath(char)" code or something.

You might take a look at this too. I wrote something along the lines of "if not SetPath([char]): "[char] can't find his destination.\n", but it didn't work. In fact, it printed the message whether the character could find his path or not.

Oh, this reminds me, in my own project I put this at the top of my FindGoal(npc, goal) routine.

Code: Select all

	if parent&#40;goal&#41; = nothing	! The goal object isn't anywhere
	&#123;				       ! that the the npc can find it.
#ifset BETA
	"Goal object in nothing for ";
	print npc.name;
	"!\n"
#ENDIF
		return false
	&#125;
I haven't gotten there yet, but I also plan on putting in something similar if the goal object is in the object tree but inaccessible to the npc. I thought you might find this helpful.

Roody_Yogurt wrote: Thanks! Now I just need to psych myself up to get back into one of my WIPs!

Yeah, I'm struggling mightily with my intended Spring Thing entry. I've made almost no progress. If I don't make serious headway this week I'll admit defeat and work on a one-room game I've been eager to do.

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

Post by Roody_Yogurt »

Thanks for the beta-testing! This thing gets ever-nearer to being a decent library contribution!

As far as the breaking-down goes, in some cases, I was checking doors for forward properties (and not the rooms they led to). That's fixed now.

As far as "if SetPath" goes, yeah, I had forgotten to have it return true when things go correctly. That has been added now.

I also noticed that it complained that the player_character object was inheriting from my later-replaced character class, so now findpath.h replaces the player_character object, too, giving it the things it would have inherited from the character class.

I haven't tested it with a loop. I imagine my fix should send it back to working-correctly, but I can't say for certain.

Post Reply