Wednesday, October 16, 2013

new old bug! (DoExit)

We've had some new eyes looking at Hugo code lately, and sometimes that's all it takes to discover a long-existing bug.  Don't really know how it didn't turn up before, but this person noticed that when the player is in a closed, openable, and enterable container, typing "OUT" would result in "Nothing is closed."

Initially, I was worried that this was due to the SetUpDirectionObjects routine not properly setting the object global to the out_obj object (since I still don't entirely trust how Perform uses that routine), but the problem turned out to be in DoExit itsellf (in fact, the out_obj doesn't really matter when DoExit is called since almost all applicable error messages will mention the parent of the player, if anything).

The problem turned out to be that the "so-and-so is closed" VMessage text was expecting the object global to exist, and DoExit wasn't properly defining it in that instance.  So, here's a version of DoExit where that is fixed:

replace DoExit
{
local p

#ifclear NO_OBJLIB
! >GO OUT winds up calling DoExit with object = out_obj, thanks to
! the direction-parsing code in Perform().  English ambiguities being
! what they are, we correct that interpretation of "out" here, and
! treat the command as a generic instruction to exit whatever
! container context we may be in.
if object = out_obj
object = nothing

if object = nothing or object = location
{
if player in location and out_obj in direction
{
word[1] = out_obj.noun
word[2] = ""
return Perform(&DoGo)
}
}
elseif object = d_obj and player in location
{
return Perform(&DoGo, object)
}
#endif

p = parent(player)

#ifclear NO_OBJLIB
if object and player not in object
#else
if object and player not in object
#endif
VMessage(&DoExit, 1)             ! "You aren't in that."
elseif p is openable, not open
{
object = p
VMessage(&DoLookIn, 1)           ! "X is closed."
}
else
{
if object = nothing
object = p
move player to location
if not object.after
VMessage(&DoExit, 2)     ! "You get out.."
}
return true
}

Of course, this will be added to future versions of Roodylib.

Friday, July 12, 2013

Dead Laptop

So, a couple nights ago, my laptop died.  Now, as far as my Hugo stuff goes, that isn't extremely dire.  First off, I was pretty good about uploading Roodylib and other extension stuff to my bitbucket account as I worked on it, so, at most, the bitbucket version might be missing a day or two's amount of work.  Also, I backed up all of my game work in progress code before a trip some months ago, and both fortunately and unfortunately, I've been so bad about working on my games in progress the last several months that there isn't probably a whole lot changed between what-I-most-recently-had and what-I-have-backed-up (I did recently have my games open and did a whole lot of code-cleaning and optimizing, but no real new content was added).

Beyond that, I'm pretty sure the hard drive in my defunct laptop is ok, so I should be able to retrieve all of my old data once I get an external dock or something.  Between that and the fact that I was in a midsummer coding slump anyhow, I'm not in any real hurry to work on anything in the near future.

Mainly, I'm just kicking myself for not making better use of today's free cloudspace so I had more of my data still at my fingerprints.

As far as music goes, while I don't have my ~100 GB mp3 collection uploaded anywhere, all of my purchased-from-Amazon mp3s are always available in the Amazon Cloud Player (plus the extra 240 songs I could fit into my remaining free space).  My Amazon music might make up for a fraction of my overall collection, but it's still 4000 some songs so it's not shabby, either.  Plus, some of my music is on last.fm, too, so I can just sign into that and do the playlist where it only plays songs it has already played previously in my music library. Lastly, spotify can be good for individual album listening.

My bought games can all be re-downloaded, but I have to say that I'm especially glad that Steam has added cloud saving.  It is really nice that I don't have to restart on the few Steam games I really cared about.

My coding projects are mostly in various states of "backed-up".  The most backed up projects I have are the ones that I share with Robb Sherwin and are repo'd.  It's a really nice thing that with a repository, you always know you have the working copy of something.  On the other hand, I'm currently borrowing time on somebody else's laptop, so I'm not sure if I'm actually going to install repo software on it.

Robb Sherwin also set me up with a linode server account, so maybe I should have kept all of my stuff on there.  Also, maybe I could just bitbucket all of my game code, besides just library stuff.

There's also just being better about backing up to USB devices and stuff, but that's such an easy thing to fall out of habit with.  Just the same, I wish I had been better about stocking a USB stick with as many of my favorite apps that support running from portable drives.

I'll also say that I was happily surprised that since I had used the multiple-user feature in Chrome on my dead laptop, when I added accounts to this temporary one, all of my bookmarks and passwords were instantly accessible.  Besides that, of course, all of my Google Docs stuff is already all save in the cloud.  On my old laptop, I pretty much used Chrome for just e-mail and productivity stuff (Google Docs, Google Tasks, etc.), but with how easy it is to have your stuff follow you everywhere, maybe I'll get in the habit of using it for more things.

I can understand why some people are distrustful of the cloud and warn people to not put all of their eggs in that basket. I agree with that, too, but there are still plenty of ways of using it to your advantage.  I'll try to be smarter about it in the future.

Tuesday, July 2, 2013

recent updates

So, first off, updating Roodylib to support non-container, transparent objects did take quite as many changes as I thought it would.  I just need to replace DoLookIn and add some more code to CheckReach and DoLook but that's basically it.  Now it's just a waiting game to see if I made more problems for myself.

Anyhow, let's get back to that situation the other day, where I was helping code a wearable coat with a pocket.  The problem was that its contents weren't showing up in inventory listings.  First, this required a change to WhatsIn to fix some spacing issues.  After that, I realized that SpecialDesc was only listing children-of-children if the object had more than one child, so I added some code to check for that when there is one child.

You can see the evolution of my code over at this thread: http://www.joltcountry.com/phpBB2/viewtopic.php?t=8938

While testing all of these compiled coats with their various pockets, I would often take the keychain from the coat pocket just to make sure inventory was still printing correctly even when the coat has no children.  At one point, I noticed that while >REMOVE KEYCHAIN FROM POCKET worked, >REMOVE KEYCHAIN alone did not.  I thought, huh, well, it really should be smart enough that if the object has a parent other than the player, it should be directed to DoGet.

So, I added some code to fix that, but in doing so, I noticed that DoTakeOff and DoWear both had multi grammar tokens, so out of curiosity (and since my test game had both a coat and the player wearing some shoes), I started testing the commands >WEAR ALL and >REMOVE ALL.

Oddly, I found that the multi grammar token defaulted to not-held items, so both of these commands often would respond with things like "Nothing to wear." I thought, huh, I'll fix that!

So, besides playing around with grammar, I tracked down the problem to hugolib's VerbHeldMode routine, which redirects the parser to held or not-held behavior based on the verb being used.  I also turned on the checkheld system and added some code to FindObject.

By this point, >WEAR ALL and >REMOVE ALL were working correctly, but the results had the player trying to wear or remove everything in his inventory, so I added some code to ExcludeFromAll so non-clothing items would be ignored.

Long story short, Hugo is now "AIF"-ready!


Welcome, AIF Authors!


So yeah, Hugo can now accept >WEAR ALL and >REMOVE ALL.  Still, I wasn't excited about the prospect of default Roodylib behavior making it too easy to disrobe, so I thought, huh, maybe I could do something about that, too.

In the end, I changed ExcludeFromAll so that in the default behavior, it excludes all clothes, too, and in the case of DoTakeOff and DoWear, I changed the ParseError message to "Be specific about what you'd like to <blank>."

So, that means something like >WEAR PANTS AND FUNNY HAT will still work, but >WEAR ALL will get the "Be specific..." message.

Of course, if you want >WEAR ALL to work, all you have to do is set the new "AIF" flag.

My one point of conflict right now is that >REMOVE <object>  gets directed to DoGet even when the object is in your location (and not a child of something).  On one hand, I feel like I should give that instance a "you can't do that." message, but on the other hand, I wonder if there are room descriptions where "remove" will feel like an applicable verb (and I imagine that most players wouldn't notice that it works there otherwise).  Feel free to weigh in!

So yeah, I'll try to get the latest Roodylib uploaded to HbE soon enough so anyone who wants to play around with this stuff can do so.
 

Sunday, June 30, 2013

Container Theory Part II

Ok, so not long after writing that last post, I remembered one of the main reasons I have long associated "transparent" with "container".

In the example of a snowglobe, if we define it as a non-container, transparent object, it'll work fine with DoPutIn. A container, transparent object would need additional before routines to disallow it. This is part of the reason why yesterday's view of containers sounded appealing to me.

Still, that doesn't change the fact that DoLookIn demands that objects be containers. What's the point of a snowglobe that you can't look into? Anyhow, instead of trying to fix what is and isn't a container, I think I'm just going to fix DoLookIn's grammar.

I could try to do it on the grammar level, using Roodylib's "grammar helper" stuff to only allow containers and transparent items to work on the grammar pass, but I don't like forcing Roodylib users to use extensions like that so I'm probably just going to change DoLookIn's container check to an object token and add all necessary additional code to DoLookIn itself.

On the plus side, this should make coding windows (the glass kind) more intuitive to future authors. On the down side, maybe this change will have some ramifications down the road.

I mean, I could go back to my old container assumptions, but since I've already learned that others find other definitions more intuitive, I don't think that's the right path. I mean, the holy grail of an IF language is to make everything as intuitive as possible.

Container Theory -or- How Do You Code a Bucket?

So recently, I've been helping with a contents-listing problem during the >INVENTORY command involving coat pockets. Fixing that involved a change to WhatsIn and SpecialDesc- which I had intended to write up here, if it weren't for the issue that I want to talk about today (in fact, after WhatsIn and SpecialDesc, I have another thing I want to write about, so hey, the next entry will be a nice, multi-part post).

It turns out that my updated code still isn't working for that other person, and my hunch is that it has to do with the fact that my code has been slowly changing how containers are dealt with. Today, I figured I should finally run my designs past Kent. Eventually, we unearthed the point of contention where we disagreed on default behavior.

In Kent's view, a bucket coded in Hugo should have the following attribute definition:

    is container

While in my mind, it should be defined as such:

    is container, open

I think this might also be the difference in the coat objects as defined by me and the fellow I'm trying to help (since my new SpecialDesc code relies on the open attribute being used). To those who prefer the first definition, adding "open" must seem like overkill. As far as whether-contents-can-be-seen, I agree, as far as unopenable containers are concerned, it can be assumed that they are either open or transparent (otherwise, it's useless for them to be a container).

I was going to say that as far as other container behaviors go, I was worried about things like not-open, unopenable, transparent objects, but I had a Hugo-understanding breakthrough just now as I write this. I glanced over at the character class definition and was reminded that characters are transparent without being containers. I think part of my container-understanding problem is that I have so strictly associated "open" and "transparent" with containers (since thinking of transparent examples often make you think of something like a glass jar), when it's better to pretend that "transparent" has nothing to do with containers and remember that "open" only exists to work with "openable".

Anyhow, I feel pretty dumb that I have been so unclear on the concept for this long, but I'll try to update HbE so future coders don't fall into the same traps I did. Also, I still think that some of the hugolib contents-checking conditionals could be more concise, so I'm going to try to do something about that.

While I'll quickly admit being wrong on this issue, it's interesting that something as simple as containers can so easily catch people (maybe most people won't make the same assumptions I did, but I'm sure some do). I imagine the container code I wrote in ZIL recently is just as flawed. Ah, well.

Thursday, June 27, 2013

New newmenu stuff

So, some days ago- I've taken my time getting around to write this blog post about it- I was working on porting John Menichelli's HugoZork to Roodylib. One of the last additions I plan to do involved transferring his menu code over to my newmenu format. This, in turn, got me thinking about newmenu for the first time in a while.

alt_title property


First off, his menu pages often have a different title from the link name that led to them. In the past, with newmenu, I've just kept an extra CenterTitle call in the page text. This has the downside of possibly getting double-titled in a simple terminal interpreter. Converting HugoZork was the straw that made me go, eh, I might as well add another property to menu option objects. Behold, the alt_title property (I actually gave it an alt_name alias in case I forget what I called it along the way)!

(Also, hey, look, I found a better way to share code)

option amusement_option "For Your Amusement/Words To Try"
{
    in main_menu
    alt_title "For Your Amusement"
    menu_text
    {
        "Have you ever:\n"

        "\_  ...opened the grating from beneath while the leaves were still
        on it?"
        "\_  ...tried swearing at ZORK I?"
        "\_  ...waved the sceptre while standing on the rainbow?"
        "\_  ...tried anything nasty with the bodies in Hades?"
        "\_  ...burned the black book?"
        "\_  ...damaged the painting?"
        "\_  ...lit the candles with the torch?"
        "\_  ...read the matchbook?"
        "\_  ...tried to take yourself (or the Thief, Troll, or Cyclops)?"
        "\_  ...tried cutting things with the knife or sword?"
        "\_  ...poured water on something burning?"
        "\_  ...said WAIT or SCORE while dead (as a spirit)?\n"

       ! "\nPress any key for more..."

       ! pause
            CoolPause(1)
        CenterTitle("Words To Try")

        "Words you may not have tried:\n"

        "\_  HELLO (to Troll, Thief, Cyclops)"
        "\_  ZORK"
        "\_  OIL (lubricate)"
        "\_  XYZZY"
        "\_  WALK AROUND (in forest or outside house)"
        "\_  PLUGH"
        "\_  FIND (especially with house, hands, teeth, me)"
        "\_  CHOMP (or BARF)"
        "\_  COUNT (candles, leaves, matches, blessings)"
        "\_  WIN"
        "\_  MUMBLE (or SIGH)"
        "\_  LISTEN (especially to the Troll, Thief, or Cyclops)"
        "\_  REPENT"
        "\_  WHAT IS (grue, zorkmid, ...)"
        "\_  YELL (or SCREAM)"
        "\_  SMELL\n"

        CoolPause(1)
    }
}

So, the name of the object represents how the choice will be listed in the menu itself, and the alt_title property is how the page is titled when you are reading it (if you don't provide an alt_title property, it just uses the link name).

TopPageMargin routine


I also added a routine called TopPageMargin. It is to newmenu what LinesFromTop is to Roodylib. I give it its own routine for two reasons- 1) newmenu does not require Roodylib and 2) I find that I sometimes like menu pages to have a slightly different margin than the game itself.

Right now, TopPageMargin defaults to returning 2 so page text automatically starts 2 lines down from the top. If your menu page text already does its own carriage returns for proper spacing, you can always replace TopPageMargin to return 0.

Glk menus


I also tweaked the glk behavior for better detecting when there are too many options and the menu has to be drawn by the "cheap" non-windowed method. Right now, it only draws the cheap menus as needed, so it's entirely possible that mid-menu, it'll jump from one kind to the other. I'm considering having it decide before it even gets started if it'd ever possibly use the cheap menu (and then just use that throughout), but that leaves the window open for game crashes if the player resizes the window mid-menu.

Other thoughts


Looking at the code again, I was reminded how newmenu is currently a mix of array and object hierarchy design. Given that I've done much more object-hierarchy organized design since I initially wrote newmenu, I started wondering, hmm, maybe I should just switch newmenu to doing everything by object hierarchy.

After thinking about it, my initial conclusion was that all of the necessary object-organization routines would complicate things more than necessary, and the end result would probably be more memory-intensive than the existing system so I've given up on that idea for now. Still, it seems like it might be possible to design something that only rearranges itself as necessary and largely works without writing values to anything.

I'll probably get back to this as some point when I have something else I really should be doing instead.

Thursday, June 20, 2013

SpeakTo stuff

So, yesterday, I was adapting John Menichelli's Hugozork to Roodylib, just because I'm always curious to see how well Roodylib plays with already-written games. Initially, there was some ParseError replacement code for the Loud Room that I was able to move to Roodylib's PreParseError routine, but I noticed that some of it didn't do the Loud Room's echo behavior quite right so I spent some time fixing that.

While testing that code, I realized that the engine-called SpeakTo was skipping all of my glorious code completely (SpeakTo is called directly both when you give characters commands, like: >CLOWN, HELLO or just when you type an object with no verb, like >ROCK).

I decided that SpeakTo should have to do location and player before checks, to better allow response configurations for situations just like the Loud Room. Here is my current SpeakTo:
replace SpeakTo(char)
{
local a, v, TryOrder, IgnoreResponse, retval, stay, same, different
#ifset USE_CHECKHELD
if verbroutine = &DoDrop_CheckHeld
verbroutine = &DoDrop
elseif verbroutine = &DoPutIn_CheckHeld
verbroutine = &DoPutIn
#endif

#ifset VERBSTUBS
if verbroutine = &DoHelpChar and object = player
{
verbroutine = &DoHelp
object = nothing
}
#endif

#ifset USE_CHECKHELD
ResetCheckHeld
#endif

#ifset DEBUG
if debug_flags & D_PARSE
{
print "\B[Speakto("; char.name;
if (debug_flags & D_OBJNUM)
print " ["; number char; "]";
print ") verbroutine="; number verbroutine;
print ", object="; object.name;
if (debug_flags & D_OBJNUM)
print " ["; number object; "]";
print ", xobject="; xobject.name;
if (debug_flags & D_OBJNUM)
print " ["; number xobject; "]";
print "]\b"
}
#endif

v = verbroutine
a = actor
actor = player
verbroutine = &SpeakTo
retval = player.before
actor = a

if retval
{
#ifset DEBUG
if debug_flags & D_PARSE
{
print "\B["; player.name;
if debug_flags & D_OBJNUM
print " ["; number player; "]";
print ".before returned "; number retval; "]\b"
}
#endif
return retval
}

retval = location.before
if retval
{
#ifset DEBUG
if debug_flags & D_PARSE
{
print "\B["; location.name;
if debug_flags & D_OBJNUM
print " ["; number location; "]";
print "before returned "; number retval; "]\b"
}
#endif
return retval
}

verbroutine = v

if char is not living
{
ParseError(6)  ! "That doesn't make any sense."
return
}

AssignPronoun(char)

! Handle player/typist-related ParseError messages:
if char = player
Message(&Speakto, 1)    ! "Stop talking to yourself..."
elseif not ObjectisKnown(object) and not FindObject(object, location)
ParseError(10, object)
else
   stay = true

if not stay
  {
  speaking = 0
  return
  }

if char is unfriendly
IgnoreResponse = true
else
{
! In the event of:  >CHARACTER, GO NORTH.  GET THE THING.  GO WEST., etc.
if not FindObject(char, location)
{
speaking = char
run char.order_response
return true
}

same = (char = speaking)

select verbroutine
case 0                  ! Just the character name is given,
! so just "X is listening."
{
if not char.order_response
Message(&Speakto, 2, char)
retval = true
}

#ifclear NO_VERBS
case &DoHello           ! Note the ampersands ('&')--or else
{                       ! the routines themselves would run
if not char.order_response
{
if char is not unfriendly
{
! "X nods hello."
Message(&Speakto, 3, char)
retval = true
}
else
{
IgnoreResponse = true
}
}
else
retval = true
}

case &DoAskQuestion
return Perform(&DoAsk, char, object)

case &DoTalk
{
if xobject
ParseError(6)
else
return Perform(&DoAsk, char, object)
}

case &DoTell
{
if object = player
return Perform(&DoAsk, char, xobject)
else
TryOrder = true
}
#endif  ! ifclear NO_VERBS

case else
{

! If the character can respond to a request, this should be dealt with by
! an order_response property routine; order_response--if it exists--should
! return false if there is no response for the given verbroutine

TryOrder = true
}
}

if TryOrder
{
if (not char.order_response)
IgnoreResponse = true
else
retval = true
}

different = (speaking ~= char)

! This same/different local variable stuff allows for certain
! orders to end conversations. If your order_response code clears
! the speaking global, this code prevents it being reset.

if retval and not (same and different)
speaking = char

if IgnoreResponse
{
if not char.ignore_response
Message(&Speakto, 4, char)      ! "X ignores you."
speaking = 0  ! clear the speaking global
}
return retval
}
Now, there are a couple problems. One, currently, both the location and player code have no idea what command the player is trying to order done, as I had to change the verbroutine to &SpeakTo for easy catching. Further complicating things, is that for the player object, I had to change the SpeakTo-determined actor global, too.

Ideally, I'll be able to figure out a way to organize all of these values so authors can have as clear an idea as possible about what was typed, and hopefully I'll be able to do it without the creation of new globals (although that isn't a big deal and I'll probably end up doing it anyway).

Also, let it be said that this new code is very lightly tested so there might be a gaping bug I haven't found yet.