Saturday, December 29, 2012

Are You Sure You'd Like To Read This Post?

I've recently been playing around with a version of the ProcessKey routine, part of my compartmentalized-version of EndGame that tries to make it easier to add new choices to the "The game has ended. Do you want to (R)ESTART, R(E)STORE a saved game, (U)NDO your last turn, or (Q)UIT?" text.

In this latest version, opting to restart or quit brings up a "Are you sure?" prompt just as it is in-game. My thinking is that, hey, even though it's the end of the game, we still want to make sure the player really wants to stop the party before we let him.

Of course, some of the appeal is that while the original version of ProcessKey rehashed a lot of the code from DoRestart, DoUndo, and DoQuit (with the exception of the prompt text, of course), the new version is really simple and looks like this:
replace ProcessKey(entry,end_type)
{
    local r

    if not entry
        return -1
    select entry
        case "restart", "r" : r = &DoRestart
        case "restore", "e" : r = &DoRestore
#ifclear NO_UNDO
        case "undo", "u" : r = &DoUndo
#endif
        case SpecialKey(end_type) : r = &SpecialRoutine
        case "quit", "q" : r = &DoQuit

    if not (call r) : return -1 : else : return true
}
The bonus of this is, if your game does anything special with any of these routines, ProcessKey automatically inherits the (hopefully) wanted behavior.

Anyhow, for now, I think I'm only going to keep this code in my "undolib.h" library extension because I can see how the new behavior might be controversial. If a couple other people like it (I figure 2 other people is almost a Hugo-users-majority), I'll probably end up adding it to roodylib.

Saturday, December 8, 2012

roodylib listening

Okay, this post is mainly just because I feel guilty when I go too long without writing something here. The excuse I'm using for writing this is just a recent decision I made concerning roodylib's handling of listening.

Now, for those that remember, Hugo's old listening behavior was to direct players to object-listening when they do location-listening (listening without an object). I thought location-listening was pretty important so I made it possible in roodylib.

Lately, I've been working on a WIP that tries to be realistic and arty and all that crap, so I've had to pay special attention to sensory verbs (not that I've done much about it yet). Anyhow, pretty early on, I changed the default object-listening behavior to a variation of "Why do that?" since really, inanimate objects are generally not worth listening to.

Anyhow, I recently decided that, hmm, maybe something like that should be the default object-listening response. Let's kill those dreams before they become our nightmares!

In the end, I'm going with "That would serve no purpose."

Saturday, December 1, 2012

IF, the harsh mistress

Anyone who knows me as an IF enthusiast has probably heard me speculate that maybe we writer fans are all suckers, as implementing a game to satisfactory levels can be suck both time and creative energy. Here I am, about 15 years past the point in my life where I realized hey, *I* could write these text games! Only within the last couple years have my coding and writing improved to the point where writing games is not a horribly slow process; now it's just a somewhat horribly slow process. Still, from time to time, I am reminded of all of the game ideas I've had over the years, many that I will likely never get around to writing. I have to wonder, have I dedicated myself to the wrong medium? Is there an easier/better way? Every time I design a game with only 1 or 2 really important verbs, I start to think, hmm, probably!

There have been several attempts to solve this problem. Most promisingly, there's the BloomEngine guy's stuff, where he has made a couple games that tell IF-esque stories with the use of hyperlink-esque links. The main drawback is that it seems like it might best be limited to tightly looping stories. The first game, Vicious Circles, had a just-large-enough loop to be only barely tolerable. Still, maybe this kind of interface could be saved by a good method for saving games.

There's also the option of creating a game that recalls IF tropes with a CYOA system like Twine or Undum. The drawback to this is that you are responsible for the whole feel of the world model yourself. It's the CYOA version of having to write your own parser for each game. That isn't very appealing to me.

Ideally, as far as these types of interfaces go, I think it'd be cool if such a system had a regular IF world model underneath where, as an author, you control which command buttons are available at any given point (and yeah, commands would be buttons, not hyperlinks).

Of course, the other approach of simplifying IF keeps the parser intact but limits commands. Interestingly, Phoenix games don't allow for examining objects. On one hand, this goes against deeply rooted IF expectations. On the other hand, while playing some Phoenix games, I found that, in the end, it didn't bother me as much as I would have guessed (the author just needs to make sure that all necessary information is within the object's name or short_desc). Other old games have a world model where just holding an object is interpreted as using or wearing it.

These kinds of things might seem ridiculous, but writing a game is probably much easier when you only have to write an average of 2-3 descriptions and important responses for each object, instead of 6 or 7. I hope to write at least one Phoenix-style game in the future just to see how the style feels on me.

So, the question is, can the IF interface be more perfect with less wasted effort? Time will tell, I guess.

Friday, November 16, 2012

space layout, the final frontier

When I released my first work of IF, I think the concept I was most lost with was how to do proper spacing in my game. There really aren't any guides on the different styles you can use for your game, and I found my game was really inconsistent and wished one existed. I eventually would learn that it boils down to "try to figure out the assumptions your language already makes and copy that." Still, even after all of these years, I can imagine some merit in such a guide.

Hugo, in particular, is especially confusing, as it introduces indented text, and there isn't really a large body of work out there to fully get how it's to be used, especially since it clashes with the Infocom games we grew up with. Over the years, I have come to respect the indentation system and appreciate the look it brings to the system. Just the same, if only to better understand the different styles, I've made it a quest to better support Infocom-style spacing in Hugo games.

Out of the box, Hugo can do some Infocom-style spacing things if you give the FORMAT global the DESCFORM_F flag. When that is set, the text in the status line is offset by a space, so it is not right up to the edge of the screen. Also, there's an extra line between the room description and the room contents listing.  My DescribePlace  replacement has furthered this, by including an extra line between every type of object in the room content listing.

A few days ago, I continued refining this "Infocom style Hugo mode", mainly because one of my WIPs is an homage to Infocom. The big change is that there is no longer an automatic empty line before a room listing. Coupled with the NOINDENT_F flag, it looks quite Infocom-y.

At first, I tied this behavior into DESCFORM_F, but I then decided that that was too much Infocom all of the time as I have come to appreciate the semi-Infocom look in some of my other games. I decided to give that behavior to a new flag that I called DESCFORM_I.

Anyhow, the new changes have been making me tweak this and that, as now it's up to any code that prints something before DescribePlace to also print an extra line. I'm liking the new behavior, but figure it'll be some time before this has settled in right among all extensions that call DescribePlace.

I think I'm due to upload several updates. I noticed some horrible behavior in the default menu for newmenu.h. I've since fixed it, but it's one of those things that makes you scratch your head and wonder how it got out the door in the first place. I think newautomap.h had some kind of update, too.

Anyhow, the work on this extension stuff has been kind of disheartening, what with all of the leaks that have popped up. I hope to get some good WIP progress done soon.

Monday, November 12, 2012

just WIP it

I spent a fair amount of time yesterday updating on older WIP to be roodylib-compatible. Initially, this meant more deleting than anything else, as it had a lot of the routine replacements and various code that were eventually incorporated into roodylib.

The first thing I learned from the process was that I was not happy with how newautomap.h interacts with newconverse.h, statusline-wise. I found myself wishing I had documented it better when I wrote my new PrintStatusLine replacements system. Eventually, I remembered that its current behavior has the map still show up when conversation options are present. Sure, the two windows were drawing fine, but in my false memory, I thought I had created a system to block the map from showing up at all under certain circumstances. In the end, I spent several hours tweaking newautomap.h to work better and do just that. I'm not sure it's the best-it-can-be yet, but it's better.

The other thing my old code reminded me about was how it had some code to allow clumping items-in-a-room's short_desc all to one paragraph, like:

      A giant spoon lies on the ground, as if discarded by some humongous spoon musician. Your friend, the giant raisin, is here, lying on its side.
 (the above is the clumping of two objects)

Traditionally, nice looking room prose like that has to be hardcoded into the room's long_desc property, but this little venture has me thinking that maybe I'll take this task on again. The way it's handled in my WIP right now is kind of ugly, but I'm thinking I might be able to handle the challenge a bit better now. We'll see.

Thursday, November 8, 2012

Thousands of Golf Balls

In my game, there's a part where you are in a room (street) where newspapers are being delivered. It occurred to me that if I used one object for both singular ("GET PAPER") and plural ("GET PAPERS") cases, there was a possibility of incorrect responses. Instead of checking this assumption or deciding to bulk up my newspaper object with parsing code that checks how it was referred to or splitting it into two hidden objects, I thought, hey, maybe I could actually do this with objlib.h's plural class.

Initially, this worked great, but I found that I could trick the game into showing me that there were only 4 newspaper objects with a command like >GET 5 NEWSPAPERS.

This also reminded me of an older WIP where, in a room full of golf balls, a tester tried the command >GET 1000 GOLF BALLS. Now, I'm not going to go code a thousand golf ball objects, but ever since then, I was a little disappointed that I couldn't do more to give the illusion that the game could handle such a command.

Anyhow, now that I have roodylib and a better understanding of how things work, I thought I'd take a swing at the problem. I tracked down the code in ParsePluralObjects that interprets the number in the command line. It calls WordIsNumber, which matches the word with a simple select-case that handles numbers "1"/"one" through "10"/"ten". I added code so it also calls StringToNumber, so it can now also handle 11-32767 (it can't handle the word version of these numbers but I figured that was a lost cause).

My first hurdle was that this section of code checks the word array for numbers twice, supposedly so that Hugo can understand commands like >GET TWO OUT OF THE THREE CHICKENS. My StringToNumber-enhanced routine can't be called twice like that, so I had to throw the "<blank> out of <blank>" code out.

It was easy enough to find the code in plural_class object where it prints a "There are only X objects here."-type command when you refer to too many, and it was also easy enough to change it to disregard the number of actual-objects-there.

Still, I thought there was another important number in the equation. There's the number you want the player to think is there. In the above example, I *don't* want >GET 1000 PAPERS to have the same reply as >GET 5 PAPERS. To handle this, I added a couple new properties to the plural_class object. One is called imaginary_plurals. It holds the number the plural class is supposed to have*.

* The type of plural object that uses these properties are going to be largely scenery. They aren't actually going to be picked up or interacted with to any large degree. We're only doing this to make responses sound smarter.

The other one is called over_max. It just holds the response the player will see when he refers to more objects than the imaginary plurals number.

Let's take a look at my finished newspaper object:
identical_class newspapers "newspapers"
{
    plural_of paper1, paper2, paper3, paper4
    noun "newspapers" "papers"
    single_noun "newspaper" "paper"
     imaginary_plurals 12
     over_max
        "Even with all of the apartments and homes on your street, you'd
     be lucky to find more than a dozen papers in the near vicinity."
}
 Hopefully, that'll all make sense in the end. There's a good chance I'm breaking more than I realized, as plural class stuff seems to juggle a lot. Anyhow, I think this *will* call for a new Roodylib upload. People can expect it soon enough.

Monday, November 5, 2012

"extra" extra_scenery

In the game-I've-most-recently-worked-on (I don't want to call it my "WIP" as I really should be starting on a HugoComp game soon), I got a little frustrated with the default handling of extra_scenery. The game in question has 2 or 3 words that should always result in "You don't need to refer to that.", on top of each room's additional list of words. I really didn't want to add those 2 or 3 words to every room's extra_scenery property, so I eventually just threw the entire list of words into the room class definition.

This was a pretty inelegant solution so I intended to try to come up with a better solution eventually. Well, that day ended up being yesterday, and here's what happened.

Originally, I tried to declare my own extra_scenery_words array and replace the room class's property array with a property routine that checked it. This was problematic.

The main thing was, Parse's extra_scenery checking code already checks every word in the word array against the extra_scenery property, so to find a match myself, I'd have to check every word array word against every extra_scenery_words word, so there's some wasted looping right there.

In the end, I thought the simplest solution was just to edit Parse and have it check the player object for extra_scenery words, too, so you can put your always-on extra_scenery there (this is also appropriate as I often use extra_scenery for player body parts that aren't implemented and such). The Roodylib  Parse routine now has this:
    for (a=2; a<=words and word[a]~="" and word[a]~="then"; a++)
    {
        if Inlist(player, extra_scenery, word[a])
        {
            Message(&Parse, 1)
            word[1] = ""            ! force ParseError(0)
            words = 0
            customerror_flag = true
            return true
        }
        elseif Inlist(location, extra_scenery, word[a])
        {
            Message(&Parse, 1)
            word[1] = ""            ! force ParseError(0)
            words = 0
            customerror_flag = true
            return true
        }
    }
(I'm not going to upload a new release of Roodylib just for this, but it'll be in the next version, of course)

I figure this'll take care of the majority of extra_scenery needs. If you find that you really need to tie extra_scenery behavior to an object, I also came up with this work around:
  1. Have your init routine set one of your player.extra_scenery property elements to a call to a routine:
    player.extra_scenery #3 = call &CheckStuff
  2. Write a routine for the above that returns a dictionary word under the right circumstances:
    routine CheckStuff
    {
        if FindObject(mess, location)
            return "stuff"
    }
That may be a little ugly, but hopefully your game doesn't have a lot of words like that!



Saturday, November 3, 2012

DoVerbTestAll update

In looking up my old post on DoVerbTest, I noticed that I had already posted about it when I last wrote about it. In any case, I recently added some code to DoVerbTestAll, the version that tests every object in a game. Since code-posting looks a bit better over there, I put it at Royce Odle's Hugo forum.

This new version tests some no-object verbs before it goes on to object-test everything.

Thursday, November 1, 2012

ASCII Errors

So, the last couple weeks, I have been distracted by the annual IF Competition. No Hugo games this year, but I decided it was a good year to make an honest effort at playing and reviewing the games. I got through them, but I tell you, playing other people's games is nowhere near as satisfying as making progress in one's own game or coding ability.

I tried to jump back into the Hugo swing of things yesterday. It went about as well as could be expected. There was a moment where I tried to remember why I thought my PrintStatusLine redesign the other week was a good idea (I mean, I still think it is, but it sure looks kind of a monstrosity at first glance). I moved on to my WIP, where I spent an ungodly amount of time trying to come up with a good, unused name for a bowling alley. I made some progress but I'm not sure I'm there yet.

I switched my attention to another part of the code where I had a random ASCII-key-printing thing. In fixing some problems, I learned a couple things. One, my ASCII "clip text" file that I made for EditPlus had some issues. For one thing, I made no notation for when it skipped numbers, which made it possible for me to forget that in such cases like my randomizer, I couldn't use every value between a and b (like my code was doing). When the official Hugo interpreter gets a non-valid number, it doesn't really print anything. Hugor, on the other hand, does one of those "hey-we-couldn't-print-this-character" characters.

So, I went back over my EditPlus clip text file and fixed the misleading entries (and added a BAD token for unaccounted values). On one hand, the lower range of ASCII values, where all of the normal letters are, have none of those troublesome gaps. Still, some fun ASCII symbols, like yen symbols and what not, are in the higher ranges. I also updated the Hugo by Example ASCII page so it now links to the best ASCII value chart I found in my limited searches.

Part of the reason I was attacking this ASCII thing was because Hugor had an unexpected gap at the end of a line that should have printed to the end. Eventually, I figured it out that just calling system(61) (which checks if the interpreter is a simple port) makes Hugor think we have progressed a character position, so commands like 'print to display.linelength;' always come up short.

Anyhow, I'm pretty happy to have found this one, as I think there's a good chance it's responsible for the weird behavior I wrote about in this post.

So, don't know if I'm entirely in Hugo mode yet, but that can't be a bad start. Anyhow, the HugoComp officially starts tomorrow (I'll make a post to intfiction.org), so it'd be really nice to wrap up one of these projects real soon like. Fingers crossed.

Thursday, October 18, 2012

The fruits of my labor

I think the latest versions of the library contributions discussed in the last couple posts are ready for the public:

NewConverse 2.0
Opportune 1.3
MultiOpportune 1.3
PastTense 1.2
Roodylib 2.0

The new UNDOlib extension is part of the roodylib suite (among several other added features). Opportune.h actually has decreased functionality compared to the earlier version; MultiOpportune.h is the version which does multiple opportunities, but like I said, fuses are probably better in that case. PastTense.h just has some bug fixes, but I've tested that one so little that I'm sure there are several more out there.

Wednesday, October 17, 2012

array utility routines

In the previous post, I talked about how, for a while, I tried to do a "multiple opportunity" version of my "windows of opportunity" opportune.h extension. The final version ended up using property arrays, but originally, I approached the problem with regular arrays. I kept on wanting to do something and would think, huh, is there a routine for that? I'd then remember there wasn't and then would write it. I had mixed success.

The Good:
routine ClearArray(array_to_be_cleared)
{
    local n
    for (n=0;n< array array_to_be_cleared[] ; n++ )
        {
        array array_to_be_cleared[n] = ""
        }

}
Sometimes, hey, I like to clear arrays. Nothing wrong with a routine that saves me some time.

The Bad:
routine AddArrayValue(arr, val)
{
    local i
    for (i=0; i< array arr[]; i++)
    {
        if array arr[i] = 0, ""
        {
            array arr[i] = val
            return i
        }
    }
}
The problem with this one is that it doesn't distinguish from successfully placing the value in element 0 or being unable to place the value in an empty slot at all. It'd be slightly more successful if I just changed it to a return-true-on-success/return-false-on-failure thing.

The Ugly:
routine InDisArray(arr, val)
{
    local i

    for (i=0; i< array arr[] ; i++)
    {
        if array arr[i] = val:  return i
    }
}
Bad pun aside ("in this array", get it?), this array-based version of InList has the same problem as AddArrayValue but is even more hampered by it, as returning-the-element-number is pretty important in such a routine. I could get around this by having element 0 return something else (like a constant called ZERO or the string "zero"), but it seems kind of dumb to make people remember this hack.

If anything, I think this all is just a good reminder why property arrays can be favorable to regular arrays in these kinds of instances, as property arrays start at element 1 and therefore avoid the element-0 problem.

Tuesday, October 16, 2012

more SpeakTo speculation

I've been working again on my update to Christopher Tate's converse.h extension. I forget what the original issue was to get the ball rolling, but somewhere near the beginning was a point where I noticed >CHARACTER, GOODBYE commands (which, in my extension, is a viable way to end a conversation), on their success, was resetting the speaking global to the npc. I updated the extension's (and roodylib's) SpeakTo replacement to use local variables to check for certain changes of the speaking global during the execution of the routine. This is probably a useful-to-only-me feature, but it seems feasible to me that certain successful orders should end conversations (without sending the NPC to another room).

Like the previous paragraph implied, SpeakTo, if you don't remember, handles commands to characters, like >CHARACTER, HELLO. If you just type >CHARACTER_NAME at a prompt, SpeakTo is also called directly.

I noticed another thing, too. The original SpeakTo has:
 if not FindObject(char, location)
 {
  actor = player
  ParseError(11, char)
  return
 }
And then later in the routine:
 ! In the event of:  >CHARACTER, GO NORTH.  GET THE THING.  GO WEST., etc.
 if not FindObject(char, location)
 {
  run char.order_response
  return true
 }
For a while, this tricked me into thinking that the engine, while smart enough to interpret  ">CHARACTER, GO NORTH. GET THE THING. GO WEST., etc.", was *also* smart enough to recognize orders even when the character isn't in scope, which would allow for smart-sounding error messages like "You're trying to command someone who isn't here." Instead, my attempts to replicate this kept on giving me "Better start with a verb."

Eventually I realized it was just a mistake in SpeakTo, so I ended up taking out that first bit of extra code (since hey, we want cool order support right?).

The current state of SpeakTo:
replace SpeakTo(char)
{
    local 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

    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
}
Oddly enough, though, the new code wasn't working with my newconverse.h extension. Multi-command orders were getting odd responses for the second action. This prompted me to scrutinize newconverse.h even further. Eventually, I tracked down the problem to my fancy undo shenanigans (where I have the option to show the player which command is being undo'ed). It turns out writing over the word array all willy nilly can have some drawbacks! I changed the code to not clear the word array (which I'm not sure why I did that in the first place).

Then, I thought I had broken this nifty feature where, in some circumstances, it was possible to undo and skip over an entire conversation. In the end, it'd turn out that I had forgotten that the feature only worked under very specific settings, but long before that, my solution was to strip newconverse.h of its undo code, making a standalone "undolib.h" library extension. The extension, besides supporting what's-being-undo'ed reminders, also has the ability to attempt to undo multiple times at once (say, an unwinnable game that can undo back to when it was winnable).

The main disappointment is that I was too lazy to make undolib.h not roodylib-dependent. newconverse.h, while supporting roodylib, doesn't require it. It doesn't require undolib.h, either, but if somebody wants it, they'll have to use roodylib for now.

Another thing wrong with newconverse.h was that I was over-writing several arrays (like, writing six elements to a five-element-array). Hopefully, the experience will make me more aware of the bad code that caused such problems, but it was a good lesson nonetheless. If your game is acting nonsensically wonky, there's a good chance you overwrote an array somewhere!

For a little while, I thought newconverse.h would use my opportune.h extension, too. It's an extension for starting little daemons where a global variable's value dictates special responses to actions. I call them "windows of opportunity."

My opportune.h was kind of limited, though, since it could only handle one opportunity at a time (and it really preferred that those opportunities lasted for one turn). So, I rewrote it to make it property-array-based instead of global-variable-based. I got it working to satisfaction, but in the end, I decided, you know, in this case, I really do just want a fuse. Now, in retrospect, I'm not sure if there's really much call for a multiple-opportunity, multiple-turn opportune.h at all, so I'll probably change it back to its original form.

So yeah, there's a new fuse in newconverse.h. The positive side of all of my tinkering is that newconverse is a lot more consistent overall. Disallowing UNDO and skipping conversations and allowing normal undo works pretty much the same between most newconverse.h-supported conversation menus, whether they're on the top of the screen or at the bottom.

Undolib.h and newconverse.h need a little more polishing before I upload the latest versions, but things are looking good.

Wednesday, September 26, 2012

More PrintStatusLine stuff

Today, I got around to applying my new PrintStatusLine design to status-line-changing library extensions like newautomap.h and newconverse.h. It took some tweaking, but now with the current version of RoodyLib, it involves no finessing at all to have glk-enabled automap working in a game along with top-screen conversation menus. Just because I feel like my PrintStatusLine core code has changed just enough, I'm going to share the entire thing again. It might make some more sense if you are looking at a status-window-drawing extension along with this (like newautomap.h in http://roody.gerynarsabode.org/hbe/newautomap.zip), but maybe you can get some idea through the comments below:

! find_height - property that points to routines for determining status window height
property find_height alias u_to

! draw_window - routines with instructions for drawing the status window
property draw_window alias e_to

!\ bottom_justified - have this return true for status windows where regular
status information shares a window with other information and you want the
regular status information to be printed at the bottom of the window \!
property bottom_justified alias d_to

! terp_type - this property gets set to the current interpreter type automatically
property terp_type alias w_to

!\ status_override - normally, the status window object with the highest
find_height number gets drawn (so have those properties return 0 when not in
use), but sometimes more than one extension *could* be drawn, so we use
status_override to have one override the other (have it return true to initiate
such override) \!
property status_override alias nw_to

!\ chosen_window - set to the window instructions object whose draw_window
property will be executed. authors can ignore this. \!
property chosen_window alias nw_to

! "terp_type" values  0, 2, 4
enumerate step * 2
{
    NORMAL_TERP, GLK_TERP = 2, SIMPLE_TERP
}

!\ a PrintStatusLine object in which we will put our instruction objects in (by
inclusion of extensions and what not. PrintStatusLine will call this directly).
\!
object printstatuslib
{
    find_height
        {
        local highest, i, a
        for i in self
            {
                a = i.find_height
                if i.status_override
                    {
                    self.chosen_window = i
                    highest = i.find_height
                    break
                    }
            if higher(highest,a) = a
                    {
                    self.chosen_window = i
                    highest = i.find_height
                    }
            }
        return highest
        }
    draw_window
        {
        run (self.chosen_window).draw_window
        }
    chosen_window 0
    terp_type NORMAL_TERP
    bottom_justified 1
}

replace PrintStatusline
{
local newstatusheight

! set the "terp_type" value
if IsGlk
    printstatuslib.terp_type = GLK_TERP
elseif system(61) ! minimal port
        printstatuslib.terp_type = SIMPLE_TERP
else
    printstatuslib.terp_type = NORMAL_TERP

#ifset CHEAP
        if cheap and printstatuslib.terp_type ~= SIMPLE_TERP
            {
#if defined GAME_TITLE
            CenterTitle(GAME_TITLE,0,1)
#endif
#if undefined GAME_TITLE
            CenterTitle(CheapTitle,0,1)
#endif
            display.needs_repaint = false
            return
            }
        elseif cheap
            return
#endif ! CHEAP

! figure out the size our window will be
newstatusheight = printstatuslib.find_height

! clear/remove the window if the status window height has changed
if (newstatusheight < display.statusline_height) and not system(61)
    {
    window display.statusline_height
    {cls} ! clear whatever's there
    window 0
    }

display.statusline_height = newstatusheight

Font(BOLD_OFF | ITALIC_OFF | UNDERLINE_OFF | PROP_OFF)
window display.statusline_height
    {
    if printstatuslib.terp_type ~= SIMPLE_TERP
        {
          color SL_TEXTCOLOR, SL_BGCOLOR
        cls
        locate 1,1
        }
    run printstatuslib.draw_window
    }
color TEXTCOLOR, BGCOLOR, INPUTCOLOR
Font(DEFAULT_FONT)
}

! Here is an example status window instruction object. It and its routines
! draw a regular status window
object statuswindow
{
    in printstatuslib
    find_height
        {
        return (call &FindStatusHeight)
        }
    draw_window
        {
        return (call &WriteStatus)
        }
}
!\ Note: These properties *could* just say "return FindStatus". I just used
the above syntax to give a clue as to how one would change a value "mid-game".
If you want statuswindow.find_height to point to *another* routine, you could
have a line like this:
    statuswindow.find_height = call &FindNewStatusHeight  \!


! routine for finding the height of the regular status info
routine FindStatusHeight
{
local a, b
text to _temp_string
! can't start off a string with a space, it seems
!if not location     ! so we'll save this space-writing code for the
!    print "\_";     ! "status-writing" routine
!else
if not light_source
    print "In the dark";
else
    {
        print capital location.name;
         if FORMAT & DESCFORM_F
        print "\_";
    }
text to 0
a = StringLength(_temp_string)

text to _temp_string
select STATUSTYPE
    case 1 : print number score; " / "; number counter;
    case 2 : print HoursMinutes(counter);
    case 3 : print "Score: "; number score; "\_ "; "Moves: "; number counter;
    ! STATUSTYPE case 3 is the "Infocom"-style status
    case 4 : StatusType4 ! routine for configurable statusline
if (FORMAT & DESCFORM_F) and (printstatuslib.terp_type ~= GLK_TERP)
    print "\_";
text to 0
if STATUSTYPE
    b = StringLength(_temp_string)

if (b + a + 4)<display.screenwidth ! let's force a 4 character gap between
    {                              ! the two fields
    return 1
    }
elseif (b + a - 4 ) < display.screenwidth and STATUSTYPE = 3
    {
    text to _temp_string
    print "S: "; number score; "\_ "; "M: "; number counter;
    if (FORMAT & DESCFORM_F) and (printstatuslib.terp_type ~= GLK_TERP)
        print "\_";
    text to 0
    return 1
    }
else
    return 2
}

! Roody's note: Replace this if you want to use the top right area
! for something else ("HUNGRY", "TIRED", or whatever)
routine STATUSTYPE4
{}

! routine for drawing the regular status
routine WriteStatus
{
    if printstatuslib.bottom_justified and
        printstatuslib.terp_type ~= SIMPLE_TERP
        {
        if statuswindow.find_height = 2
            {
            locate 1, (display.windowlines - 1)
            }
        else
            locate 1, display.windowlines
        }
    if not location
        print "\_";
    elseif not light_source
        print "In the dark";
    else
        {
            if FORMAT & DESCFORM_F or (printstatuslib.terp_type = GLK_TERP)
                    print "\_";
            print capital location.name;
        }

    if statuswindow.find_height = 1 and STATUSTYPE
        {
            print to (display.linelength - \
            (StringLength(_temp_string) + \
            ((printstatuslib.terp_type = SIMPLE_TERP)*2) ));
            StringPrint(_temp_string)
        }
    elseif STATUSTYPE and statuswindow.find_height = 2
        {
            if printstatuslib.terp_type ~= SIMPLE_TERP and
            not printstatuslib.bottom_justified
                locate 1, 2
            else
                ""
            if (FORMAT & DESCFORM_F) or (printstatuslib.terp_type = GLK_TERP)
    print "\_";
            StringPrint(_temp_string)
        }
}

Monday, September 17, 2012

new debugging verb

The last time I was looking over roodylib's comments and documentation, I was reminded that I had added a new debugging verb (and had failed to mention it). Anyhow, it's called "verbtest", and it's pretty much a ripoff of something I saw that Juhana Leinonen wrote for Inform 7 the other year. Basically, you use it on an object, and it shows you the response to a ton of default verbs.

Here is an example transcript from a game I'm working on:
>verbtest house
Getting object:
You can't take that.

Wearing object:
You can't wear the house.

Listening to object:
The house is not making a sound.

Eating object:
You can't eat the house.

Drinking object:
You can't drink the house.

Hitting object:
Venting your frustrations on the house won't accomplish much.

Hello-ing object:
That doesn't make any sense.

Examining object:
Dark and silent.

Looking through object:
You can't see through that.

Looking under object:
You don't find anything under the house.

Go-ing object:
No, it's time to go home.

Entering object:
No, it's time to go home.

Sitting on object:
No, it's time to go home.

Exiting object:
You're not in the house.

Moving object:
You can't move the house.

Searching object:
You don't find anything new.

Smelling object:
You don't smell anything unusual.

>
The intent is that it'll remind authors of obvious commands that should have better responses.

Maybe I'll incorporate it into HugoFix at some point (giving it a $vt command or something), but that seems a bit presumptuous for now.

What's my PrintStatusLine?

I've been pretty good lately about coming up with roodylib improvements instead of working on my game. The last idea I had was especially challenging, and I have to admit, it took a couple days of pecking to truly get up the steam to finish it.

In a nutshell..

I've been annoyed that some of my library extensions have to replace PrintStatusLine. What do you do when two of your extensions replace PrintStatusLine? Of course, you hack together a version that works this way in some circumstances and that way in others. Like other parts of roodylib, I wondered if there wasn't a way to make it more modular.

Now, I probably could have done it all with multiple routines and maybe some global variables and arrays, but like my other "settings" in roodylib, I decided to make it object based. Extension PrintStatusLine objects have properties that point to routines that determine the height they need and instructions for drawing their portion of the status window.

Even when you aren't using fancy library extension stuff, you could make the status window bigger than usual and draw whatever you want in the extra space (and you can make the normal status part of the window "bottom justified" if you want).

I just finished this thing so hopefully I'll be able to pretty it up a little so it's easier to understand at some point, but it's uncertain that this version of PrintStatusLine will be readable to anyone but me, unfortunately. Let's take a look at what I wrote:
property find_height alias u_to
property draw_window alias e_to
property bottom_justified alias d_to
property terp_type alias w_to

! "terp_type" values  0, 2, 4
enumerate step * 2
{
    NORMAL_TERP, GLK_TERP = 2, SIMPLE_TERP
}

object printstatuslib
{
    find_height
        {
        local sum, i
        for i in self
            {
            sum += i.find_height
            }
        return sum
        }
    draw_window
        {
        local i
        for i in self
            {
            run i.draw_window
            }
        }
    terp_type NORMAL_TERP
    bottom_justified 0
}

! this object and its properties draw the status window as we normally know it
object statuswindow
{
    in printstatuslib
    find_height
        {
        return (call &FindStatusHeight)
        }
    draw_window
        {
        return (call &WriteStatus)
        }
}

replace PrintStatusline
{
local newstatusheight
#ifset CHEAP
    if cheap
        return
#endif

! set the "terp_type" value
if IsGlk
    printstatuslib.terp_type = GLK_TERP
elseif system(61) ! minimal port
        printstatuslib.terp_type = SIMPLE_TERP
else
    printstatuslib.terp_type = NORMAL_TERP

! figure out the size our window will be
newstatusheight = printstatuslib.find_height

! remove the windows if the status window height has changed
if (newstatusheight < display.statusline_height) and not system(61)
    window 0

display.statusline_height = newstatusheight

Font(BOLD_OFF | ITALIC_OFF | UNDERLINE_OFF | PROP_OFF)
window display.statusline_height
    {
    color SL_TEXTCOLOR, SL_BGCOLOR
    if printstatuslib.terp_type ~= SIMPLE_TERP
        {
        cls
        locate 1,1
        }
    run printstatuslib.draw_window
    }
color TEXTCOLOR, BGCOLOR, INPUTCOLOR
Font(DEFAULT_FONT)
}

! routine for finding the height of the regular status info
replace FindStatusHeight
{
local a, b
text to _temp_string
if not location
    print "\_";
elseif not light_source
    print "In the dark";
else
    {
        if FORMAT & DESCFORM_F:  print "\_";
        print capital location.name;
        print "\_";
    }
text to 0
a = StringLength(_temp_string)

if STATUSTYPE = 1
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F)
        print "\_";
    print number score; " / "; number counter;
    if (FORMAT & DESCFORM_F)
        print "\_";
    text to 0
    }
elseif STATUSTYPE = 3
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F) : print "\_";
    print "Score: "; number score; "\_ "; "Moves: "; number counter;
    if (FORMAT & DESCFORM_F) : print "\_";
    text to 0
    b = StringLength(_temp_string)
    }
elseif STATUSTYPE = 2
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F) : print "\_";
    print HoursMinutes(counter);
    if (FORMAT & DESCFORM_F) : print "\_";
    text to 0
    }
elseif STATUSTYPE = 4
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F) : print "\_";
    STATUSTYPE4 ! routine for configurable statusline
    if (FORMAT & DESCFORM_F) : print "\_";
    text to 0
    }

if (b + a + 4)<display.screenwidth ! let's force a 4 character gap between
    {                               ! the two fields
    return 1
    }
elseif (b + a - 4 ) < display.screenwidth and STATUSTYPE = 3
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F) : print "\_";
    print "S: "; number score; "\_ "; "M: "; number counter;
    if (FORMAT & DESCFORM_F) : print "\_";
    text to 0
    return 1
    }
else
    return 2
}

! Roody's note: Replace this if you want to use the top right area
! for something else ("HUNGRY", "TIRED", or whatever)
replace STATUSTYPE4
{}

! routine for drawing the regular status
routine WriteStatus
{
    if printstatuslib.bottom_justified and
        printstatuslib.terp_type ~= SIMPLE_TERP
        {
        if statuswindow.find_height = 2
            {
            locate 1, (display.windowlines - 1)
            }
        else
            locate 1, display.windowlines
        }
    if not location
        print "\_";
    elseif not light_source
        print "In the dark";
    else
        {
            if FORMAT & DESCFORM_F:  print "\_";
            print capital location.name;
        }

    if statuswindow.find_height = 1 and STATUSTYPE
        {
            print to (display.screenwidth - \
            (StringLength(_temp_string) + \
            ((printstatuslib.terp_type = SIMPLE_TERP)*2)));
            StringPrint(_temp_string)
        }
    elseif STATUSTYPE and statuswindow.find_height = 2
        {
            if printstatuslib.terp_type ~= SIMPLE_TERP and
            not printstatuslib.bottom_justified
                locate 1, 2
            else
                ""
            StringPrint(_temp_string)
        }
}

So, say, if you're using newconverse's version of this (after I update it, that is), it'd point printstatuslib find_height and draw_window properties to either these routines or newconverse's status window instructions, depending on whether the player is in a conversation.

 Hopefully, this isn't too complicated.

One other thing...

I also threw together an extension for score notification, as I never have these things around when I need them. I'll add proper documentation and upload it somewhere at some point:

!::
! Hugo Score Notification extension
!::

!\
Provides text like "You score has gone up by [x] points!"
\!

#ifclear _SCORENOTIFY_H
#SET _SCORENOTIFY_H

#ifset VERSIONS
#message "ScoreNotify.h Version 0.5"
#endif

property score_notify alias d_to

property points alias e_to

object scorenotifylib "scorenotify"
{
    score_notify true
    points 0
#ifset _ROODYLIB_H
    save_info
        {
        select self.score_notify
            case 0 : SaveWordSetting("score_off")
            case 1 : SaveWordSetting("score_on")
        return true
        }
    type settings
    in init_instructions
    execute
        {
        local a
        a = CheckWordSetting("scorenotify")
        if a
            {
            select word[(a-1)]
                case "score_off": self.score_notify = 0
                case "score_on": self.score_notify = 1
            }
        }
#endif
#ifset _NEWMENU_H
    usage_desc
        {
        "\BSCORE NOTIFICATION ON\b- Be notified when you score points."
        Indent
        "\BSCORE NOTIFICATION OFF\b- Play without score notifications."
        }
#endif ! NEWMENU
}

#ifset _ROODYLIB_H
object scorenotifymain
{
    type settings
    in main_instructions
    execute
        {
        ScoreNotify
        }
}
#endif  ! _ROODYLIB_H

routine ScoreNotify
{
      if scorenotifylib.points and scorenotifylib.score_notify
           {
              ""
              Font(BOLD_ON)
           ScoreNotificationMessage(&ScoreNotify, 1, scorenotifylib.points ) ! "[Your score has gone up.]"
              Font(BOLD_OFF
           }
      score += scorenotifylib.points   ! add the points to the score
      scorenotifylib.points = 0    ! reset the point counter
}

! routine to call for the last score of a game (after the winning move), as
! main is not called again
routine LastScore(a)
{
    score += a
}

! otherwise, call this routine to add to the game score
routine AddScore(a)
{
    scorenotifylib.points += a
}

routine DoNotifyOnOff
{
      if scorenotifylib.score_notify
            Perform(&DoNotifyOff)
      else
            Perform(&DoNotifyOn)
}

routine DoNotifyOn
{
      if scorenotifylib.score_notify
           ScoreNotificationMessage(&DoNotifyOn, 1 ) ! "[Score notification already on.]"
      else
           {
           ScoreNotificationMessage(&DoNotifyOn, 2 ) ! "[Score notification on.]"
           scorenotifylib.score_notify = 1
           }
}

routine DoNotifyOff
{
      if not scorenotifylib.score_notify
           ScoreNotificationMessage(&DoNotifyOff, 1 ) ! "[Score notification already off.]"
      else
           {
           ScoreNotificationMessage(&DoNotifyOff, 2 ) ! "[Score notification off.]"
           scorenotifylib.score_notify = 0
           }
}

routine ScoreNotificationMessage(r, num, a, b)
{
    if NewScoreNotificationMessages(r, num, a, b):  return

    select r
        case &DoNotifyOn
            {
            select num
                case 1:  "[Score notification already on.]"
                case 2: "[Score notification on.]"
            }
        case &DoNotifyOff
            {
            select num
                case 1:  "[Score notification already off.]"
                case 2: "[Score notification off.]"
            }
        case &ScoreNotify
            {
            select num
                case 1 : "[Your score has gone up.]"
            }
}

!\ The NewScoreNotificationMessages routine may be REPLACED and should return
true if a replacement message exists for routine <r> \!

routine NewScoreNotificationMessages(r, num, a, b)
{
   select r
!    case &ScoreNotify
!        {
!        select num
!            case 1
!                {
!                print "[Your score has gone up by "; number a; " points.]"
!                }
!        }
   case else : return false
   return true ! this line is only reached if we replaced something
}

#endif _SCORENOTIFY_H
Actually, I haven't even compiled that code yet, so there might be a typo or other kind of bug in there.

Friday, September 14, 2012

library suggestions

So far, roodylib replaces 54 standard library routines or objects (that number might be off by some; I sort of counted carelessly by hand). Some of these cover justified inadequacies or bugs uncovered by others; some are only replaced since an object something inherits from was also replaced. Still, yet, there are those that I replace because I feel there is something wrong with the design of something, and as it is clear that much of Hugo is the result of fine reasoning, it is likely that my changes have flaws I have not yet considered.

Anyway, let's talk about some of these kinds of changes. For starters, I replaced the female_character class. In the standard library, that class gets its own type ('female_character"), so if you have a game where you are checking if objects are NPCs, you have to check for both "character" and "female_character". I changed it so female_character objects are of type "character", as you can always check for the female attribute if you are truly looking for a female NPC.

More recently, I was writing some code that assumed the player_character object was of type "player_character". Funnily enough, this was not the case (it is actually of type "character")! Now, maybe people won't agree with me on this one, but I feel the player_character should get its own type, so in roodylib, it currently does so.

Much further back, I changed up some of the vehicle class code so it'd be (hopefully) easier to change which directions can exit a vehicle:
replace vehicle
{
    type vehicle
    vehicle_verb "drive"            ! default verb
    prep "in", "out"                !   "     prepositions
    vehicle_move true               ! by default, always ready to move

#ifclear NO_VERBS
    before
    {
    parent(player) DoGo
        {
        if word[2] = "out" and object = self
            {
            object = out_obj
            return false
            }
        if (object ~= u_obj, out_obj) and object.type = direction
            {
            ! "To walk, you'll have to get out..."
            OMessage(vehicle, 1, self)
            return true
            }
        else
            return object
        }
    }
#endif
    is enterable, static
}
(DoGo also has some slightly different code, I believe)

Lately, I also was looking at something whose design I disagree with but will do nothing about. I was taking a look at GetInput, which is a short and simple routine:
routine GetInput(p)
{
    print p;
    input
}
Personally, I'd think that you'd only call GetInput when you want to also provide a prompt, so it should be something like this:

routine GetInput(p)
{
    if p
        print p;
    else
        print prompt;
    input
}
 My logic is, hey, only call GetInput when you want a prompt of some point. Otherwise, why not just call input directly? Thing is, the standard library (and other existing code) call GetInput without a prompt argument a lot. I could either change all of those library routines (breaking lots of game source, too, in the process, probably) or just ignore it. I'll just do the latter. I could add a slightly-similar-sounding routine to roodylib that behaves like I feel GetInput should, but hey, I admit this isn't an important problem...

So yeah, that.

In other news, I updated Activate, the routine for starting daemons, so it complains if it is called before the player global has been set (I've run into that problem once or twice):
replace Activate(a, set)                ! <set> is for fuses only
{
    local err
    if not player
        {
        Font(BOLD_ON)
        print "[WARNING:  The player global must be set before
        daemon (object "; number a;") can be activated.]"
        err = true
        }
    a.in_scope = player
    a is active
    if a.type = fuse and not err
    {
        if set
            a.timer = set

        run a.activate_event
    }
    elseif a.type = daemon and not err
    {
        if set and not a.#timer
        {
            Font(BOLD_ON)
            print "[WARNING:  Attempt to set nonexistent timer
                property on daemon (object "; number a; ")]"
            err = true
        }
        else
            a.timer = set

        run a.activate_event
    }
    elseif not err
    {
        Font(BOLD_ON)
        print "[WARNING:  Attempt to activate non-fuse/\
        daemon (object "; number a; ")]"
        err = true
    }

#ifset DEBUG
    if debug_flags & D_FUSES and not err
    {
        print "[Activating "; a.name; " "; number a;
        if a.type = fuse
            print " (timer = "; number a.timer; ")";
        print "]"
    }
#endif
    if err
        {
        Font(BOLD_OFF)
        "\npress a key to continue..."
        HiddenPause
        }
    return (not err)
}
So yeah, that's all some stuff.

Wednesday, September 5, 2012

just saying hey

I don't have any inspired motivation behind this post, but I want this blog to feel active so I'll just throw together a post on recent developments.

First off, I found my first in-Hugo's-innards bug. Like, I've found library bugs or interpreter inconsistencies before, but this was the first bug that goes back to the engine code. Anyhow, it involves the dictionary-word total not being correctly reset between game restarts. It's not a huge bug, so it likely won't prompt a new Hugo release very soon but it's nice to add another reason to have a new official release (still, considering some projects I know of will use adding-to-the-dictionary-table intensively, a sooner fix will be appreciated regardless).

So, other than that, I figured I'd pad this post out with some utility routines I am considering adding to roodylib. I wrote them for my WIP, which should hopefully be ready for testing within a week or so.

The first routine would fall in the category of "auxiliary math routines." When I am doing games with windows and/or graphics, I find myself saying, well, I'd like it to do this if the screen has this aspect ratio and I'd like to do that if it has that aspect ratio. So, I'm like, ok, do this if the screenwidth is between this and that number. Anyhow, after wanting some approximation code for a while, I finally threw together a simple routine for such things:
routine IsNear(fir,sec,range)
{
    if (abs(fir - sec) <= range)
        return true
    else
        return false
}

So you'd do something like IsNear(display.linelength, 40, 10) to find out if the line length is within 10 spots of 40. Anyhow, I figure it'd be useful beyond screen stuff, sometimes.

The other routine I'm considering adding is a line-only version of CenterTitle, that only draws the text given to it center-justified. If the text is too long for the screen width, it just does a normal indentation to show that the text is special.

routine CenterLine(a)
{
    print newline ! make sure we are at the start of a line
    local l
    l = string(_temp_string, a)
    if l < display.linelength
        print to (display.linelength / 2 - l/2);
    else    ! if we don't have enough line width to center it, we'll show the
        Indent    ! line's importance by indenting
    print a
}

Centering when you don't have proportional text off is an uncertain gamble, but still, I thought there might be a demand for such a routine.

When roodylib is much more mature, I'll get around to adding switches or whatever so a lot of these routines are only compiled when they are wanted, but that's for another day.

Friday, August 24, 2012

finishing touches

Ok, I did those things I mentioned in the previous post, as far as newmenu.h goes. I'm pretty much happy with everything. I did notice that there is some odd behavior in Hugor where the following code:
                    print "[N]ext item"; to 24; "[Q]uit menu"
                    print "[P]revious item"; to 24; "[Enter] to select"
 Occasionally results in:
[N]ext item             [Q]uit menu
[P]revious item          [Enter] to select
(Where [Q]uit is not aligned with [Enter])

Now, the font is PROP_OFF at the point in question. The only way I've found to fix it is to print a blank line beforehand (""). Somehow, I guess, Hugor is getting confused about cursor position or something. Maybe it's a result of the newly-supported realtime transcripting; I haven't checked it in older versions yet. Anyhow, it seems like it might be a tricky bug to track down so I'm going to see how simply I can replicate it before I send it on to Nikos.

Anyhow, my point is, other than that, I'm pretty happy with the current state of newmenu.h. I think all I really need to do now is perfect all of the default messages. I'm still not sure what the best "press a key to continue" text should be, and my "what is IF?"/"how to play IF" stuff in the default menu could use some polishing.

Again, the latest version is at http://roody.gerynarsabode.org/hbe/newmenu.zip. Everybody is invited to weigh in!

updates

A lot has changed since I uploaded "newmenu.h" to the IF archive, and I'd like to make sure it's in a good place before I upload it again. After working on the transcript stuff, I doublechecked that it worked ok with and without roodylib.h. This brought me to the auto-generating help menu thing that I set up some months ago. I was reminded that there should be a way for game-specific menu options to take precedent over the generic ones; in fact, the author should have complete control over the order of all of the options, right?

So yeah, I made a "priority" property for menu options- the higher the number, the earlier an option is listed. Coming up with a solution was trickier than I had predicted as I originally wanted to do it with regular arrays (and even modified my SortArray routine to allow for starting at an element other than 0), but since newmenu currently keeps track of its menu choices using two arrays, it was easier to just write the choices to a property array and use SortProp to re-order them.

As far as newmenu goes, I seem to be running out of hugolib-defined properties to alias to. Not aliasing is no big deal, but I try to do it when possible. Still, you don't want to accidentally alias something with a property that might be interpreted another way or otherwise interfere. I found one such overlap today, when checking to make sure that the roodylib-enhanced version of newmenu didn't run the MenuInit routine after UNDOs and RESTOREs.

So, the main thing is, the latest versions of newmenu and roodylib can be found at http://roody.gerynarsabode.org/hbe/newmenu.zip and roody.gerynarsabode.org/notdead/roodylib_suite.zip, respectively.

I've got some things to do with newmenu before I upload it to IF archive, yet. I have to make sure that the different-color-scheme-per-menu-page code still works and that the "cheap"-mode codes still works. Plus, just now, it occurs to me that maybe I should cut it down to use one array, so yeah, maybe I'll do that.