Showing posts with label newconverse. Show all posts
Showing posts with label newconverse. Show all posts

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, August 1, 2012

I'll make this one quick

I spent 10-15 minutes working on my WIP today. That gave me some motivation to play around with newconverse a bit more. The good news is:
  1. Got all types of "[ undoing (last command)]" working to my satisfaction.
  2. Moved whatever I could from npc.parse_rank (npc being newconverse's character class object) to ConversePreparse, just because PreParse is only called once per turn while parse_rank is called a lot. In parse_rank, I kept some code that sets a verbroutine value if there isn't one, as that doesn't work in PreParse since the engine clears verbroutine after Parse is finished.
  3. Successfully made newconverse both roodylib and non-roodylib compatible. As much as I'd love everyone to always use roodylib, I'm making an effort for all of my extensions to standalone, if possible. That was too much work for cheaplib and colorlib (which now comprise part of the "roodylib suite"), but when I can, I will. Anyhow, it was very satisfying to get it working for both, as I originally thought I had to use some of roodylib's special features for optimal performance.
I think I'm done with newconverse and roodylib for a bit. The latest versions can be downloaded at http://roody.gerynarsabode.org/JC/newconverse.zip and http://roody.gerynarsabode.org/notdead/roodylib_suite.zip, respectively.

Newconverse could use a lot of documentation, and now that I think about it, I probably broke the peaceful coexistence of ASK/TELL characters and menu-driven-conversation characters (which I had purposefully supported with earlier versions of newconverse). I'll probably patch that up down the road, even if I might be the only person who thinks having both kinds of conversations in one game might be kind of cool.

Tuesday, July 31, 2012

more stuff

Ha, I had already forgotten what my last post was about, but having read it, I can say, my latest problem was a continuation of the old one.

So, newconverse has this feature where >UNDO can remind the player what is being undone:

> talk to man
The man's eyes catch the torchlight for a moment as he looks up at
you.

> undo
[ undoing >talk to man ]

Gloomy Hall

>
It exists for a couple different reasons. I think, originally, the point was so that in a game where >UNDO skips over whole conversations, I wanted a mechanism to allow for a "[undoing to before conversation]" (which it does, under the right circumstances). It's also useful because it clarifies what really is being undone. Sometimes, as Hugo game authors, we might accidentally put important game text or code in something that returns false or, just as likely, a player might perceive a turn as counting (when it did not). This feature always keeps the player in the loop as to what's being done. Take, for instance:
 > talk to man
The man's eyes catch the torchlight for a moment as he looks up at
you.

> sleep
Not now--there's work to be done.

> undo
[ undoing >talk to man ]

Gloomy Hall

>

 ANYWAY...

So I noticed that the above feature was having a conflict with the issue from the last post. If you type only an object, the engine runs FindObject. After that, it jumps straight to SpeakTo. Oddly enough, it clears the word array before calling SpeakTo so my undo-command-saving code (which I stuck in an event, as that runs after every successful turn) wasn't saving the command properly (as concise as it was).

Anyhow, lots of futzing later, I got it working, but I also found out that a major culprit to it not working had been that while roodylib uses the word array to save states during UNDOs, RESTOREs, and RESTARTs, I was saving too low on the word array so some of it was actually being written over after a successful UNDO, screwing over the word-array-checking after-UNDO code.

I don't know how well I described all that, but that's what I did today!

Sunday, July 29, 2012

SpeakTo me

Today, I was trying to add some polish to newconverse. At one point, I started trying to modify its SpeakTo replacement so it was more like the one I put in roodylib, as I have become rather fond of most of my roodylib replacements. Eventually, I decided it might be altogether better to just drop the SpeakTo replacement in newconverse and alter newconverse to work with regular old SpeakTo.

This presented some interesting problems. If a player only types <character name> at the prompt, the engine calls SpeakTo directly so there wasn't an obvious way to redirect things with a before property. I ended up putting some code in the parse_rank property (since FindObject is called a couple times before SpeakTo) to change the verbroutine global if need be. Knowing what verbroutines to expect helped me write an order_response for the character class that could interpret things properly.

As if that wasn't all a big enough pain, I wanted to give the player a "You are already speaking." line if you do >TALK TO CHARACTER repeatedly (instead of an endless barrage of "<character> is listening."). While this was easy enough to trap for a regular TALK TO command, this turned out to be a pain for things like CHARACTER, HELLO what with the way that SpeakTo is always clearing or setting the speaking global. I had to change it yet again (hopefully for the last time) so it only clears and sets the speaking global when absolutely necessary (like, it only sets the global after order_response has been run, so your order_response code can correctly gauge whether or not the character was given his first command of the conversation).

Newconverse still has a ways to go before perfection; some of the messages could be tailored for multiple player persons (right now, it's all written in second player person) and just some general polishing, but hey, we're getting there.

Friday, July 27, 2012

one more colorthing...

I forgot, when announcing my colorthing.hex color palette program, to remind everybody that colors are not the same across platforms, so still be aware that what looks cool on Windows might be totally unreadable in Linux (just another good reason to use the roodylib-bundled colorlib extension). Still, sometimes you just want to write a game that looks cool on your system.

Anyhow, I've updated the colorthing.hex file to include that information. Also, that file has the latest version of that HugoFix stuff I was talking about, so typing "$ot 0" should give you an idea of how windows and replaced objects are listed.

So, today was another day where my optimal coding window was in a place where I didn't have much privacy so I didn't work on game-y looking things. I was getting writer's block with the LibreOffice document I've been jotting room descriptions in, so I opened up my update to Christopher Tate's conversation system extension, as I've been meaning to do some things with it.

First, I checked over the properties I created for it and made sure they were alias-able, then proceeded to alias them. Then, I added some more comments to help some of my additions look more organized.

Looking over it now, I think I'm going to routine-ize more of the messages to make more responses configurable. There's a couple other tiny things to do, but overall, I'm thinking it's almost ready for an official release (well, an official upload to HbE, anyway... an ifarchive upload is a long ways off).

To remind people, this conversation system update aspires to give authors several menu conversation options. Conversation options can be at the top of the screen or in game text. Conversations can be topic-based, and players can be informed (or not) of available topics. There's a fair amount of mixing-and-matching available, too. Let's see some screenshots!

Main-text conversation menu, no topic-switching
Top-based conversation menu, topic-switching allowed