Thursday, November 27, 2014

Happy Thanksgiving!

So, now that all of my siblings and I are well into our adult years, we've gotten into a tradition of having "SiblingMas," where we are only responsible for the gift of one sibling (hey, I've got five siblings).  Spouses are included, too.

In the past, we've picked names out of a hat, but someone would often pick their own spouse and it was just an imperfect system.  This year, I thought, huh, why don't I write some Hugo code to fix this?

(There is also a "KidsMas," where my nephews and nieces get one cousin to get a gift for- but their own siblings should be disallowed.)

I wrote a program to handle both cases.  It uses Roodylib's object sorting code.


property disallow
object available_jar
{}
object used_jar
{}
object unavailable_jar
{}
object jimmy "Jimmy"
{
in available_jar
misc 0
disallow mikey
}
object mikey "Mikey"
{
nearby
misc 0
disallow jimmy
}
object dan "Dan"
{
nearby
misc 0
disallow meaghan
}
object meaghan "Meaghan"
{
nearby
misc 0
disallow dan
}
object laura "Laura"
{
nearby
misc 0
disallow bob
}
object bob "Bob"
{
nearby
misc 0
disallow laura
}
object jon "Jon"
{
nearby
misc 0
}
object dave "Dave"
{
nearby
misc 0
disallow bridget
}
object bridget "Bridget"
{
nearby
misc 0
disallow dave
}
object anne "Anne"
{
nearby
misc 0
}
routine SiblingMas
{
local x,i, done
while not done
{
while child(used_jar)
move child(used_jar) to available_jar
done = OrderJars(jimmy,anne)
}
i = scripton
print "SiblingMas List!\n"
for (x = jimmy;x <= anne ;x++ )
{
print x.name; " has: "; x.misc
}
""
"KidsMas List!\n"
while child(kids_jar)
{
move child(kids_jar) to available_jar
}
for (i=jimmy;i<=anne;i++)
{
remove i
}
done = false
while not done
{
while child(used_jar)
move child(used_jar) to available_jar
done = OrderJars(declan,piet)
}
for (x = declan;x <= piet ;x++ )
{
print x.name; " has: "; x.misc
}
i = scriptoff
"\nPress a key to exit the program."
HiddenPause
quit
}
object kids_jar
{}
object declan "Declan"
{
in kids_jar
disallow Braden Annelise
misc 0
}
object braden "Braden"
{
nearby
disallow Declan Annelise
misc 0
}
object annelise "Annelise"
{
nearby
disallow Declan braden
misc 0
}
object william "William"
{
nearby
disallow sam
misc 0
}
object sam "Sam"
{
nearby
disallow william
misc 0
}
object elaina "Elaina"
{
nearby
disallow Gillian
misc 0
}
object gillian "Gillian"
{
nearby
disallow elaina
misc 0
}
object wash "Washington"
{
nearby
disallow Dinah Piet
misc 0
}
object dinah "Dinah"
{
nearby
disallow wash piet
misc 0
}
object piet "Piet"
{
nearby
disallow wash dinah
misc 0
}
routine OrderJars(start,end)
{
local x, i, pick
for (x = start;x <= end ;x++ )
{
while child(unavailable_jar)
move child(unavailable_jar) to available_jar
if parent(x) ~= used_jar
move x to unavailable_jar
if x.disallow
{
for (i =1;i<=x.#disallow ;i++ )
{
if parent(x.disallow #i) ~= used_jar
move x.disallow #i to unavailable_jar
}
}
if not child(available_jar)
return
pick = TakeRandom(available_jar)
x.misc = pick.name
move pick to used_jar
}
return true
}
view raw gistfile1.txt hosted with ❤ by GitHub

Wednesday, November 19, 2014

rot13 (string manipulation example)

The other day, I was thinking about how Robb Sherwin's Necrotic Drift paid specific homage to the old Magnetic Scrolls games.  I remembered how the MS games used a letter-substitution code for their hints (actually, the whole point of this article is that I misremembered it as rot13 but looking now, I see it was just one-letter-away substitution) and thought, huh, wouldn't it be neat if something like that was put into a future version of Necrotic Drift, where the game itself would both provide the coded text and decode it?  I thought it'd be fun to code up an example to share here.

First off, if  you're not familiar with ASCII, each letter has its own numerical value.  You can see them in this chart:
String manipulation in Hugo is mainly a matter of checking for these values and changing them to what you want.

For my "rot13" code to work, the game would have to use a "string" grammar token (the one where the string has to be provided in quoted text, like >WRITE "KILROY WAS HERE" ON ELEVATOR WALL)- something I've found is very hard to train the player to do, but, oh well, this post isn't about how to teach players to use quotation marks where we want them.

routine DoHint
{
local x
! write parse$ to an array so we can go through it letter by letter
x = string(_temp_string,parse$)
x = 0
while _temp_string[x] ~= 0
{
! First we'll check if it's a capital letter
if _temp_string[x] > 64 and _temp_string[x] < 91
{
! if the letter is before N, we add 13
if _temp_string[x] < 78
_temp_string[x] += 13
else
! otherwise we subtract 13
_temp_string[x] -= 13
}
! And then we'll check against lowercase letters
elseif _temp_string[x] > 96 and _temp_string[x] < 123
{
if _temp_string[x] < 110
_temp_string[x] += 13
else
_temp_string[x] -= 13
}
x++
}
StringPrint(_temp_string)
""
}
view raw gistfile1.txt hosted with ❤ by GitHub


EDIT:  Ok, I was wrong about the one-letter-off thing, too, even though I guess some of the official hint books were like that.  This actually the Magnetic Scrolls code I remembered:
Of course, I'd have to find out how it worked before I could decide whether I could replicate it in Hugo!

Sunday, November 16, 2014

another DoLook update

So, while testing the new Roodylib code on old released code such as Kent Tessman's Spur, I came across things like Spur's "Little Jimmy" character where the object's/character's inventory is listed in the long_desc.  The problem with that is that verblib.h (and my update to DoLook) still print a new line after the long_desc even if everything has already been listed and no object-content text is going to be printed.

Originally, I was going to make a post here about how the best solve for that is to make a before routine for the object for DoLook, have it run the long_desc and return true so the rest of DoLook is never called.  Just as I was going to write the post here, though, I thought, eh, I'll take another look and see if I can fix DoLook since the before routine method sort of seems like weaseling out.

The good news is, I think I found a solution I'm generally happy with.

replace DoLook
{
local i,skip_ahead, no_fullstop, has_children, count
if not light_source
VMessage(&DoLook, 1) ! "It's too dark to see anything."
else
{
if ( object is transparent or !(object is living, transparent) or
object is platform or (object is container and
(object is open or object is not openable))) and
object is not quiet ! and object is not already_listed
{
for i in object
{
i is not already_listed
if i is not hidden
{
has_children = true
count++
}
}
}
if not object.long_desc
{
#ifclear FORCE_DEFAULT_MESSAGE
if object is container and
object is not quiet and object is not living
{
if (object is openable,open)
print "It's open.";
Perform(&DoLookIn,object) ! so we get "it is closed." if closed
skip_ahead = true
}
elseif has_children
no_fullstop = true
else
#endif
! "Looks just like you'd expect..."
VMessage(&DoLook, 2)
}
if (object is transparent or !(object is living, transparent) or
object is platform or (object is container and
(object is open or object is not openable))) and
object is not quiet ! and object is not already_listed
{
has_children = false
for i in object
{
if i is not hidden and i is not already_listed
{
has_children = true
break
}
}
}
if i and object ~= player and not skip_ahead
{
if count = 1
NEW_PARSE &= ~PRONOUNS_SET
local tempformat
tempformat = FORMAT
FORMAT = FORMAT | NOINDENT_F
list_nest = 0
if not no_fullstop
print ""
WhatsIn(object,has_children)
FORMAT = tempformat
NEW_PARSE |= PRONOUNS_SET
}
run object.after
#ifset AUTOMATIC_EXAMINE ! objects are examined automatically when picked up
if object is not examined
object is examined
#endif
#ifclear NO_LOOK_TURNS
return true
#endif
}
}
view raw gistfile1.txt hosted with ❤ by GitHub


You may notice that the call to WhatsIn has an extra argument.  Normally, WhatsIn goes through every child of the object and clears its already_listed attribute before going on to list things.  I updated it so it does not do that step if the second routine argument is true (which is why DoLook has to clear already_listed before the object's long_desc property is even run).

This is how Little Jimmy's long_desc would look with the new code:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. long_desc
  2. {
  3. "A short, little nuisance, to be frank. He and his
  4. pipsqueak friend have almost bowled you down at least
  5. twice, chasing each other all over town. Jimmy looks ";
  6. if taffy in self
  7. print "just as pleased as punch with the chunk of
  8. candy in his hand."
  9. else
  10. print "considerably more subdued now that you've
  11. relieved him of his taffy."
  12. taffy is known
  13. taffy is already_listed
  14. }

(Basically, the only change is that it marks the taffy as already_listed- of course, I probably should have moved it so it's only marked if Jimmy is holding the taffy, but in this case, it shouldn't affect anything.)

Saturday, November 15, 2014

the Lazy Hobo Riddle

The other week, somebody mentioned this funny coding problem on the ifMUD:
There once were 4 hoboes travelling across the country. During their journey, they ran short on funds, so they stopped at a farm to look for some work. The farmer said there were 200 hours of work that could be done over the next several weeks. The farmer went on to say that how they divided up the work was up to them. The hoboes agreed to start the next day.
The following morning, one of the hoboes - who was markedly smarter and lazier than the other 3 - said there was no reason for them all to do the same amount of work. This hobo went on to suggest the following scheme:
  • The hoboes would all draw straws.
  • A straw would be marked with a number.
  • The number would indicate both the number of days the drawer must work and the number of hours to be worked on each of those days. For example, if the straw was marked with a 3, the hobo who drew it would work 3 hours a day for 3 days.
It goes without saying that the lazy hobo convinced the others to agree to this scheme and that through sleight of hand, the lazy hobo drew the best straw.
The riddle is to determine the possible ways to divide up the work according to the preceding scheme.
I thought it'd be cute to code it up in Hugo.

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. routine HoboPuzzle
  2. {
  3. local i,j,k,l
  4.  
  5. for (i=1;i<=14 ;i++ )
  6. {
  7. for (j=i;j<=14 ;j++ )
  8. {
  9. for (k=j;k<=14 ;k++ )
  10. {
  11. for (l=k;l<=14 ;l++ )
  12. {
  13. if (j*j + i * i + k * k + l * l) = 200
  14. {
  15. print number i ; " " ; number j; " "; number k; " "; number l
  16. }
  17. }
  18. }
  19. }
  20. }
  21. }


Now, that version allows for straws being the same length.  If we can assume that every straw is a different length, we can make each inside loop start at a higher value from the outside one.  We can also break out of the loops if the length on the first straw is incremented to the point that it shares a value with the first possible second straw.

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. routine HoboPuzzle
  2. {
  3. local i,j,k,l, o
  4.  
  5. for (i=1;i<=14 ;i++ )
  6. {
  7. if i = o
  8. break
  9. for (j=(i+1);j<=14 ;j++ )
  10. {
  11. for (k=(j+1);k<=14 ;k++ )
  12. {
  13. for (l=(k+1);l<=14 ;l++ )
  14. {
  15. if (j*j + i * i + k * k + l * l) = 200
  16. {
  17. print number i ; " " ; number j; " "; number k; " "; number l
  18. if not o
  19. o = j
  20. }
  21. }
  22. }
  23. }
  24. }
  25. }

parser conflict checking improvement suggestions?

One of HugoFix's useful-sounding but rarely-used tools is the parser conflict checking command.  If you provide it with an object, it checks against the rest of the game and lists any other objects with shared adjectives or nouns.  If you don't provide an object, it checks every object in the game against every other object.

Anyhow, I like what it does, but here is the current format of the output:


Part of me thinks that it can be improved so it's easier to read, but I haven't yet put too much thought into it (which is why I'm throwing it out to y'all, ha).

Roodylib developments

So, since the last official release of Roodylib, I've somehow managed to get sucked into a ton of tweaking-the-parser type stuff, things that I didn't really even see myself taking a look at just a couple months ago.  Most audaciously, I have moved some of the parsing gruntwork out of the Parse routine and into routines like FindObject and Perform (routines called after the parsing cycle is done).  There has been a lot of attention to the parsing of orders to NPCs.  I've also taken a stab at changing how the pronoun system works.  No longer will authors have to set the object global to "-1" just to make sure AssignPronoun (*) actually works like we want.

some parser responses under the new code
Now, for much of that, the responses are just what you'd expect out of a game, but, of course, that's mainly the point.

Overall, I like where this all is heading, but really, I've tweaked so many things that I could have introduced a ton of small bugs so I've been trying to get more testing of this new code than I usually ask for before an official release.  To do so, I have compiled several games with code available with the latest Roodylib code.  If interested, you can play them here (last updated November 14th with the new pronoun code):
https://drive.google.com/file/d/0B_4ZXs4Z_yoWalJZRGJIVElBbmc/view?usp=sharing

And feel free to take a peek if you are curious about new code.  Like I said earlier, most of the important stuff is in Parse, FindObject, ParseError, or Perform (and SpeakTo).

* My updated AssignPronoun uses a second argument in cases where the author wants to force the pronoun setting.

Friday, September 26, 2014

phones and stuff

The other month, I was taking a look at Cardinal Teulbach's phone code in the Hugo game source directory at the IF Archive, partly because the phone in Cryptozookeeper frustrated me and partly just because I was wondering if I could improve upon it, knowing that people still use whatever they find in that directory for their games today.

I think it might have been easier to just write a new phone class from scratch, so while I made some improvements, I'm not entirely sure how happy I am with the whole thing.  Really, there are just so many options on how to design a phone in a game that it seems kind of like wasted effort to perfectly hone this one approach.

Still, while I don't like the results enough to upload my code to the IF Archive, I figure I should share it somewhere so I'll share it here.

EDIT: misterman83 over at the joltcountry Hugo forum suggested moving answer/dial responses to the phone number object properties, and I agree that it's a good idea.  The code has been updated accordingly.

First off, your game would need the following grammar:

verb "dial", "call"
*                                               DoVague
* object                                        DoDial
* object "on" xobject                           DoDial

verb "answer"
*                                               DoVague
* object                                        DoAnswer

verb "hang"
* "up"                                          DoHangup
* "up" object                                   DoHangup

And then here is the rest of the code:

attribute someone_on_line alias switchedon
property current_number alias misc
property react_dial alias react_before
property react_answer alias react_after
class phone "telephone_class"
{
type phone
is open, platform
nouns "telephone", "phone"
article "a"
parse_rank 3
long_desc
{
"It's just an ordinary telephone.";
if Contains(player, receiver)
{
print AFTER_PERIOD;
"The receiver is off the hook."
}
else : ""
}
before
{
object DoGet,DoListen, DoHangup, DoAnswer
{
Perform(verbroutine, receiver)
}
xobject DoPutIn
{
if object = receiver
Perform(&DoPutIn, receiver, hook)
else : return false
}
xobject DoGet
{
if object = receiver
Perform(&DoGet, receiver, hook)
else : return false
}
}
react_before
{
if verbroutine = &DoGo
{
if object.type = direction and
((location.(object.dir_to)).type = room or
(location.(object.dir_to)).type = door ) and
Contains(player, receiver)
{
"You hang up the phone before leaving the room."
move receiver to hook
receiver is hidden
receiver is not someone_on_line
}
}
elseif verbroutine = &DoListen and not object
{
if receiver is someone_on_line and not Contains(player,receiver)
{
"The phone is ringing."
return true
}
}
return false
}
react_after
{
if (verbroutine = &DoGo, &DoExit, &DoEnter) and location ~= old_location
{
receiver.found_in = location
receiver.attached_to = self
hook.part_of = self
move receiver to hook
}
else
return false
}
}
class phone_number
{
type phone_number
is known
in_scope
{
return player
}
before
{
object DoDial
{
receiver.current_number = self
Perform(&DoDial, receiver)
}
object
{
if verbroutine = &DoLook
return false
print "You can't do that with "; self.name ;"."
}
}
}
!\--------------------------------------------------------------------------
Next come the receiver and hook. Note that the receiver is of the attachable
class. This will prevent the player from moving off to another location
while he's holding it. He'll also get a nice little inventory description
telling him that the receiver he's holding is attached to the telephone,
which impresses the hell out of people from Estonia.
---------------------------------------------------------------------------\!
attachable receiver "telephone receiver"
{
is hidden
found_in 0
current_number 0
adjective "telephone", "phone"
nouns "receiver", "handset"
article "a"
attached_to 0
attached_desc "attached to"
long_desc
{
if not Contains(player,self): "The receiver is on the hook."
else : "The receiver is off the hook."
}
before
{
object DoAnswer, DoGet
{
if Contains(player,self)
"The phone is already off the hook."
elseif receiver is not someone_on_line
{
move receiver to player
receiver is not hidden
if verbroutine = &DoGet
"You pick up the phone."
else
"You pick up the phone, but there's no one on the line."
}
else
{
move receiver to player
receiver is not hidden
run (receiver.current_number).react_answer
}
}
object DoHangup
{
if not Contains(player,self)
"The phone is already hung up."
else
{
move receiver to hook
receiver is hidden
receiver is not someone_on_line
"You hang up the phone."
}
}
object DoDrop
{
Perform(&DoHangup, self)
}
object DoPutIn
{
if xobject.type ~= phone
{
"That shouldn't go there."
}
else
return false
}
}
after
{
object DoListen
{
if not Contains(player,self)
{
if receiver is someone_on_line
"The phone is ringing."
else
"The phone is silent."
}
elseif receiver is someone_on_line
"The other party is speaking to you."
else
"You hear a dial tone."
}
}
}
component hook "phone hook"
{
is open, platform
part_of 0
nouns "hook", "cradle"
article "the"
long_desc
{
if Contains(self,receiver): "There's a receiver on the hook."
else : "The hook is empty."
}
before
{
xobject DoPutIn
{
if object = receiver
Perform(&DoDrop, receiver)
else : "You can't put that on the hook."
}
xobject DoGet
{
if object = receiver
Perform(&DoGet, receiver)
else : "That's not on the hook."
}
object DoMove ! DoPush
{
if not Contains(self, receiver)
{
if receiver is someone_on_line
{
receiver is not someone_on_line
"You press the cradle, breaking the connection."
}
else : "You press the cradle."
}
else : "The receiver is in the way."
}
}
}
routine DoAnswer
{
"Sure. Ok, buddy. Whatever."
}
routine DoDial
{
if not object
"Dial it where?"
elseif (object.type ~= phone,phone_number) and object ~= receiver or
(xobject and xobject.type ~= phone)
"You can't dial that."
elseif receiver.found_in ~= location
"There is no phone here."
else
{
if not Contains(player, receiver)
"You'd better pick up the phone first."
elseif receiver is someone_on_line
"That would be rude."
else
run (receiver.current_number).react_dial
return true
}
return false
}
routine DoHangup
{
if receiver.found_in = location : Perform(&DoHangup, receiver)
elseif not object : "There's nothing here to hang up."
else : "You can't hang that up."
}
#ifset _ROODYLIB_H
object 1st_room_phone_detector
{
in main_instructions
execute
{
local a
a = FindObjectOfType(phone,location)
if a
{
receiver.found_in = location
receiver.attached_to = a
hook.part_of = a
move receiver to hook
}
remove self
}
}
#else
event
{
if counter < 1
{
local a
a = FindObjectOfType(phone,location)
if a
{
receiver.found_in = location
receiver.attached_to = a
hook.part_of = a
move receiver to hook
}
}
}
#endif
#if undefined FindObjectOfType
routine FindObjectOfType(t, loc)
{
local i, obj, suspect
if loc = 0: loc = location
for i in loc
{
if i.type = t
{
if suspect
return nothing
suspect = i
}
elseif children(i) and (i is not container or i is open or i is not openable)
{
obj = FindObjectOfType(t, i)
if obj
{
if suspect
! More than 1
return nothing
else
suspect = obj
}
}
}
! Only do the whole-tree check when loc is a room-level object:
if parent(loc) = nothing and not suspect
{
for (i=1; i<=objects; i++)
{
if i.type = t and i ~= suspect
{
if FindObject(i, location)
{
if suspect
! More than one
return nothing
else
suspect = obj
}
}
}
}
return suspect
}
#endif
phone red_phone "red phone"
{
article "a"
noun "phone"
adjective "red"
in east_room
}
phone black_phone "black phone"
{
article "a"
noun "phone"
adjective "black"
in STARTLOCATION
}
phone_number glorias_number "Gloria's number"
{
is known
in_scope
return player
nouns "555-1212", "5551212", "number"
adjectives "gloria's"
long_desc
"Gloria's number is 555-1212."
react_dial ! what happens when we dial her
"You dial 555-1212."
react_answer ! what happens if we answer the phone when she's calling
{
"You answer the phone. It's your friend, Gloria."
}
}
view raw phoneclass hosted with ❤ by GitHub

Like the original code, there's some example code mixed in there which you'd basically just want to ignore for your own game.  I don't remember everything I did with this and I don't care enough to look, but I think I took some steps to make it more efficient about object and attribute usage and stuff.

Since guns have come up fairly regularly in Robb Sherwin's games, I've been thinking of writing a gun class.  I made a little progress on that but it's complicated enough that I just decided to shelve it for a while.  Besides doing things like making the grammar support "FIRE GUN" and "SHOOT BAD GUY," there's just a bunch of things to implement for a gun class done right (like, checking ammo type and quantity in a multiple-gun-type game and having >RELOAD work appropriately) and making sure SHOOT BAD GUY'S GUN doesn't give you a "You're not holding that." type message.

Anyhow, it's a lot of work considering any game of mine probably wouldn't even have an ammo system, which means I'd also have to code options to turn this or that feature off, and that's even more work (all surmountable, of course, but just annoying enough that I put it off).

Saturday, July 19, 2014

Roodylib 3.9

This was actually released a couple days ago, but I didn't get around to writing this post about it until now.  Here are the new changes!


  • updated ObjectIs and TestObjectIs
  • small fix to CheapTitle
  • updated CenterTitle to work with the NO_MENUS flag
  • changed "skip" local variable in DoGo to "skip_ahead" since newcango.h already uses a SKIP constant
  • added AUTOMATIC_EXAMINE and LIST_CLOTHES_FIRST switches
  • updated code so characters have holding properties automatically and a substitute global is used for containers without them so authors aren't punished for not remembering to supply them
  • added extra supercontainer code to CheckReach, FindObject, DoExit, and DescribePlace
  • fixed mistake in CurrentCommandWords
  • moved ListObject's constant printing (IS_WORD, etc) to RLibMessage for easy replacing
  • better character name listing if there are multiple characters in the room
  • changed HugoFix's object tree listing (now skips replaced objects by default)
  • added "HugoFixInit" menu for setting debugging flags before the game has begun
  • added screen-size-change code to EndGame
  • various changes to grammar, ExcludeFromAll, verbheldmode, ParseError, and DoGet for better handling with clothes
  • added AnythingTokenCheck to FindObject for more control when using anything token grammar
  • changed "rank" local variable in PrintScore to "temp_rank"
  • defined settings objects as type "settings"
  • moved some PrintStatusLine printed text to RLibMessage for easier changing
  • HugoFix, when on, tries to organize the object tree at the start of the game for easier reading
  • lowered the time it takes to redraw the screen in ReDrawScreen and now uses InitScreen for consistancy
  • Changed some of the supercontainer object code itself
  • updated PrepWord to accept multiple words
  • gave names to replaced class objects
  • isolated DoEmptyGround from DoEmpty so authors can write before routines specific to it
  • changed order of checks in DoGet, more parent(object) code and clothing stuff
  • created an AssignPronounsToRoom (defaults to returning true) that allows you to disallow pronouns being set to that room (when it returns false). also created an exit_type global that keeps track of the type of exit used to get to the room (direction, door, non_door_portal). I think my thinking was that if the player types >GO RED DOOR, it'd be jarring when they see the pronoun set to something else. maybe this idea sucks. I don't know!
  • cleaned up children listing for certain instances in DoLook
  • added "object = -1" to things like DoLookIn, DoLook, and DoOpen so pronouns get set nicely
  • added an Infocom-esque "Continuing on." message to canceled DoQuits, DoRestarts, and such.
  • added a default response for trying to remove clothes from other characters. "so-and-so doesn't let you."
  • CoolPause now defaults to bottom-oriented text. put a value in the second routine argument to put it in the statuse line.
  • changed "PauseForKey" to "GetKeyPress". basically just prints and optional prompt and uses HiddenPause to get a keypress value.
  • created a SpeakerCheck routine so you don't have to put too much speaker-checking code in your main routine. if your game needs to do speaker-checking beyond "speaker in location", you can replace SpeakerCheck to fit your needs.
  • Added Kent Tessman's Future Boy! "time.hug" routines to Roodylib. set #USE_TIME_SYSTEM to use.
  • added #FORCE_DEFAULT_MESSAGES flag to keep old DoLook behavior
  • added a default auto-generated DoVersion routine for quick set-up. set #NO_VERSION to turn off.

As always, the latest version can be downloaded at http://roody.gerynarsabode.org/notdead/roodylib_suite.zip

why so much code?

So, when I posted that handful of posts the other day, someone said, "That's a lot of code. Why are you sharing so much code?"  They might have been joking, but it made me instantly self-conscious of how ugly the code looked (I made some changes to the page layout, but there's only so much that can be done).

Admittedly, the last bunch of posts aren't the best advertisement for Hugo.  On the positive side, the vast majority of it is something a new author has no need to be aware of.  I mainly show it now and here just as a snapshot of Roodylib's current development and also to give clues to future authors looking into any of these things.  Still, maybe it couldn't hurt to try to share more game code (you know, stuff that is fun to look at).  We'll see.

Some of these things I've been writing about are difficult in any language.  Some of the code in the last few days handled spacing, and new Inform 7 authors, at least, are often getting spacing wrong.  Still, I know Inform (and probably TADS, too) are a bit more powerful when it comes to grammar, and they probably have code to make some of the other things I've mentioned a lot easier, too.

This all got me thinking about the things I'm proudest about concerning Hugo's current state.  Here are some of them:

  • I really like my PrintStatusLine code.  I'm glad I've set up a system for really-configurable status lines that look great in all sorts of environments.
  • I'm also proud of the work that's gone into "newmenu.h."  Menus look great in the regular terp, glk terps- even simple DOS/Linux terps.
  • There's also my "configlib" extension, which takes various configuration-file-writing extensions and lets them piggyback into one saved file (you can easily add your game-specific configuration thingies to be saved, too), saving the author the headache of figuring out how things be saved when only one part of it changes.
Of course, there's not much I can do to make the actual writing of Hugo rooms and objects easier or more appealing.  It still just comes down to people who want their game code to be code-y but not too code-y (at least, that's one of its appeals for me).

As far as where my code needs the most work, I'd still like to polish "newconverse.h" up.  That's the extension that allows for several types of conversation menus.  When it works, I think it looks great and really has a lot of possibility.  Still, whenever I apply it to a new game, I always have a hard time getting it started so, yeah, that needs to be more user-friendly.

Thursday, July 17, 2014

AnythingTokenCheck (new to Roodylib 3.9)

One of the things I was most excited to take a swing at in Robb Sherwin's CZK was a certain gun.  I had noticed when betatesting the game that >UNLOAD GUN dropped the ammo to the location.  This was the impetus for me to code my "new empty" system for Roodylib, which uses grammar sorcery and object classes to support objects with different emptying behavior- held items that empty to the ground, held items that empty to the player, unheld items that empty to the ground, etc.

So I was really happy to apply this code to CZK and see how it worked.  Unfortunately, it didn't work great!  See, my "grammar helper" stuff uses the "anything" grammar token, so any object is fair game.  Since CZK has several guns, >EMPTY GUN got a "Which gun do you mean, this gun or that gun or... ?"

Nothing else could be done about this at the grammar level so I had to look other places.  Luckily, FindObject determines what objects are available at any point, so it just took some digging and examination to find a good place to test against an AnythingTokenCheck routine.


            if not AnythingTokenCheck(obj)
            {
#ifset DEBUG
                if debug_flags & D_FINDOBJECT
                {
                    print "[FindObject("; obj.name; " ["; number obj; "], "; \
                    objloc.name; " ["; number objloc; "]):  "; \
                    "false (AnythingTokenCheck returned false)]"
                }
#endif
                return false
            }


Now let's look at the AnythingTokenCheck routine:
routine AnythingTokenCheck(obj)
{
local ret = 1
select verbroutine
#ifset NEW_EMPTY
case &DoEmpty
{
if not parent(obj) or not FindObject(parent(obj),location)
ret = 0
}
#endif
case else: return ret
return ret
}

I check for a parent of obj just so we can dismiss any objects not currently in any room or container straight away, and then it can check against the location.  This way, >EMPTY will only be used against objects within scope.

Now, it doesn't seem like people generally use the anything grammar token for much other than ASK/TELL, but I still think AnythingTokenCheck will be useful whenever people do use them for other means, and now it's there to be replaced when people need it.

Tuesday, July 15, 2014

More Roodylib updates

First off, you may have noticed that I've been using pastebin lately for larger pieces of code.  I'm in the process of looking at gist (github's source code sharing thingy) and may switch over to that soon (it seems it does a better job of preserving tabs, although you can still keep pastebin's tabs if you click through to the raw format).  Ideally, eventually I'll have some kind of syntax highlighting on here, too.

Anyhow, let's talk about the things I've added to Roodylib!

New DoOpen Behavior

While taking a look at another look at Cryptozookeeper, that mailbox in the first room reminded me how much I don't like Hugo's default opening behavior.  Funny how my perfect example goes so far back, but I've always liked Zork's "Opening the <blank> reveals a..." text.

So I added this code to do it:

replace DoOpen
{
local tempformat, light_check, skip_ahead
if not CheckReach(object): return false
if object is not openable
{
VMessage(&DoOpen, 1) ! "You can't open that."
return
}
elseif object is open
VMessage(&DoOpen, 2) ! "It's already open."
elseif object is locked
VMessage(&DoOpen, 3) ! "It's locked."
else
{
object is open
object is moved
local i
if not Contains(object,player) and object is not transparent
{
for i in object
{
if i is not hidden
break
}
}
if i
{
if not light_source
light_check = FindLight(location) ! in case the light source
! has been revealed
}
if not object.after
{
if not i or object is quiet
{
VMessage(&DoOpen, 4) ! "Opened."
}
else
{
local x
list_count = 0
for x in object
{
if x is not hidden
list_count++
}
parser_data[PARSER_STATUS] &= ~PRONOUNS_SET
RLibMessage(&DoOpen,1) ! "opening the <blank> reveals"
ListObjects(object)
object = -1 ! so the last pronoun assigned stays
skip_ahead = true
}
}
else
skip_ahead = true ! object.after has to list the contents
! if it exists
if i and object is not quiet and
not skip_ahead
{
print ""
parser_data[PARSER_STATUS] &= ~PRONOUNS_SET
tempformat = FORMAT
FORMAT = FORMAT | NOINDENT_F
list_nest = 0
WhatsIn(object)
FORMAT = tempformat
object = -1 ! so the last pronoun assigned stays
}
}
if light_check
{
Perform(&DoLookAround)
}
return true
}
view raw DoOpen hosted with ❤ by GitHub

See, it even properly sets the pronoun now

Initially, I wanted the contents to be listed even if there was an object.after, so you could have something like:

"The mailbox squeaks open.

Inside the mailbox is..."

But since the original Hugo library didn't do that, I don't want to break all of the existing games out there more than I have to (of course, you can put the content-listing code in your object.after property routine, which is what those old games already do... it's just uglier).

As of right now, I'm thinking this will be a default behavior, but if people hate it, I'll put in some way to turn it off.

Anchorhead-style Auto-Examination of Picked Up Items

The other day, I was looking at the tips section of Hugo By Example, and I was reminded of this neat little thing that Anchorhead does that I wouldn't mind seeing in more games.  In the game, for certain items, if you hadn't examined them before picking them up, picking them up automatically examines them.  I think it's a cute time-saving device, and I figured there's no reason it can't be thrown into Roodylib for convenience.  To use it, all you have to do is set the AUTOMATIC_EXAMINE flag.

Here is the code that makes it work:

#ifset AUTOMATIC_EXAMINE
attribute examined
#endif
! in the player_character object...
#ifset AUTOMATIC_EXAMINE ! objects are examined automatically when picked
react_after ! up
{
if verbroutine = &DoLook and object and word[1] ~= "undo"
{
if object is not examined
object is examined
}
return false
}
#endif
! in DoGet
#ifset AUTOMATIC_EXAMINE ! unexamined objects are automatically examined
if object is not examined and &object.long_desc
{
! "You pick up the <object>.";
RLibMessage(&DoGet,2)
print AFTER_PERIOD;
Perform(&DoLook, object)
}
else
#endif
! "Taken."
VMessage(&DoGet, 8)

Actually, more Zork-style stuff

I also haven't liked how it's always on the author's shoulders to specify whether a container is open. Even though, in this day and age, all objects should have a long_desc anyway, I've updated DoLook so that if an object doesn't have one, openable containers automatically tell you if they are open or closed.

replace DoLook
{
local i,skip_ahead, no_fullstop
if not light_source
VMessage(&DoLook, 1) ! "It's too dark to see anything."
else
{
if ( object is transparent or !(object is living, transparent) or
object is platform or (object is container and
(object is open or object is not openable))) and
object is not quiet ! and object is not already_listed
{
for i in object
{
if i is not hidden
break
}
}
if not object.long_desc
{
#ifclear FORCE_DEFAULT_MESSAGE
! if (object is container or object is platform) and
if object is container and
object is not quiet and object is not living
{
if (object is openable,open)
{
print "It's open.";
if not i
Indent
}
Perform(&DoLookIn,object) ! so we get "it is closed." if closed
object = -1 ! so pronouns are set to any contents
skip_ahead = true
}
elseif i
no_fullstop = true
else
#endif
! "Looks just like you'd expect..."
VMessage(&DoLook, 2)
}
if i and object ~= player and not skip_ahead
{
parser_data[PARSER_STATUS] &= ~PRONOUNS_SET
local tempformat
tempformat = FORMAT
FORMAT = FORMAT | NOINDENT_F
list_nest = 0
if not no_fullstop
print ""
WhatsIn(object)
object = -1
FORMAT = tempformat
}
run object.after
#ifclear NO_LOOK_TURNS
return true
#endif
}
}

mailbox with object.after property routine and the above DoLook code
Pretty sure that's all I wanted to talk about for now! New Roodylib update coming soon!

more parent(player) shenanigans!

So, notice in that last post how, when the player is in an enterable container, DescribePlace doesn't do a good job of specifying which objects are outside the container, what with all of those "this-and-that is here" messages?  Doesn't that bother you?  It bothered me!

So, the next thing I worked on was making that stuff get listed better when the player is in a container.  Here's what the final product looks like:



Currently, my code doesn't do anything with platforms, because while "So-and-so is here on the couch." sounds nice, what do you say for the the things NOT on the couch?  The best I could come up with is "is also in the room." or something, but it'd have to be easily changeable for all of the instances where the room is not a "room" (say, an open space or what have you).

Anyhow, as much as I like the idea of games sounding smarter, this isn't going to go into Roodylib at this point, either.  In the meantime, you can look at the code that I wrote to do it (important stuff is highlighted):


Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. ! made up a couple new constants for the FORMAT mask to be used by the
  2. ! replaced routines
  3. constant OVERRIDEHERE_F 8192
  4. constant ALSO_F 16384
  5.  
  6. replace WhatsIn(obj)
  7. {
  8. local i, totallisted
  9. local initial_list_nest
  10.  
  11. if FORMAT & NORECURSE_F
  12. return
  13.  
  14. for i in obj
  15. {
  16. i is not already_listed
  17. if i is not hidden
  18. totallisted++
  19. }
  20.  
  21. if totallisted = 0
  22. return
  23.  
  24. list_count = totallisted
  25.  
  26. if obj is not container or obj is platform or
  27. (obj is container and (obj is not openable or
  28. (obj is openable and
  29. (obj is open or obj is transparent)))) and
  30. (obj ~= player or FORMAT & INVENTORY_F) and obj is not quiet
  31. {
  32. SpecialDesc(obj)
  33.  
  34. ! If list_count is 0 now, but totallisted was not, something must have been
  35. ! printed by SpecialDesc
  36.  
  37.  
  38. if list_count = 0
  39. {
  40. if FORMAT & INVENTORY_F and not (FORMAT & LIST_F) and
  41. list_nest = 0
  42. {
  43. print newline ! was ' print "" '
  44. }
  45. return totallisted
  46. }
  47.  
  48. if obj.list_contents
  49. return totallisted
  50.  
  51. initial_list_nest = list_nest
  52.  
  53. if FORMAT & LIST_F
  54. {
  55. if list_nest
  56. {
  57. ! Indent the first time so that it lines up with
  58. ! paragraph indentation:
  59. i = list_nest
  60. if list_nest = 1 and not (FORMAT & NOINDENT_F)
  61. {
  62. Indent
  63. i--
  64. }
  65.  
  66. print to (i * 2); ! INDENT_SIZE);
  67. }
  68. }
  69. else
  70. {
  71. Indent
  72. }
  73.  
  74. if obj.contains_desc ! custom heading
  75. {
  76. if FORMAT & LIST_F
  77. RLibMessage(&WhatsIn, 1 ) ! ":"
  78. }
  79. else
  80. {
  81. if obj = player
  82. {
  83. if FORMAT & LIST_F and list_count < totallisted
  84. print "\n";
  85.  
  86. ! "You are carrying..."
  87. Message(&WhatsIn, 1, totallisted)
  88.  
  89. if FORMAT & LIST_F
  90. RLibMessage(&WhatsIn, 1 ) ! ":"
  91. }
  92. elseif obj is living and not obj.prep
  93. {
  94. ! "X has..."
  95. Message(&WhatsIn, 2, obj, totallisted)
  96. if FORMAT & LIST_F
  97. RLibMessage(&WhatsIn, 1 ) ! ":"
  98. }
  99. elseif not (FORMAT & OVERRIDEHERE_F)
  100. {
  101. if list_count < totallisted
  102. ! "Also sitting on/in..."
  103. RLibMessage(&WhatsIn, 2, obj )
  104. else
  105. ! "Sitting on/in..."
  106. RLibMessage(&WhatsIn, 3, obj )
  107. FORMAT = FORMAT | ISORARE_F
  108. }
  109. }
  110.  
  111. ListObjects(obj)
  112.  
  113. list_nest = initial_list_nest
  114. }
  115. return totallisted
  116. }
  117.  
  118. replace ListObjects(thisobj, conjunction)
  119. {
  120. local i, obj, count, pluralcount
  121. local templist_count ! temporary total of unlisted objs.
  122. local id_count ! for identical (or plural) objects
  123. #ifset USE_PLURAL_OBJECTS
  124. local j, this_class
  125. #endif
  126.  
  127. if not conjunction
  128. conjunction = AND_WORD
  129. list_nest++
  130. for obj in thisobj
  131. {
  132. if obj is hidden
  133. {
  134. obj is already_listed
  135. #ifclear NO_OBJLIB
  136. if obj.type = scenery
  137. obj is known
  138. #endif
  139. }
  140. else
  141. obj is known
  142.  
  143. #ifset USE_PLURAL_OBJECTS
  144.  
  145. ! Need to count identical (and possibly plural) objects
  146. ! for grouping in listing
  147.  
  148. if obj.identical_to and obj is not already_listed
  149. {
  150. this_class = obj.identical_to
  151. if this_class.type = identical_class or
  152. FORMAT & GROUPPLURALS_F
  153. {
  154. id_count = 1
  155. for (i=1; i<=this_class.#plural_of; i++)
  156. {
  157. j = this_class.plural_of #i
  158. if j in thisobj and j~=obj and j is not hidden
  159. {
  160. id_count++
  161. pluralcount++
  162. list_count--
  163. j is already_listed
  164. }
  165. }
  166. }
  167. }
  168. #endif
  169. if obj is not already_listed
  170. {
  171. ! May have a leading "is" or "are" that needs to
  172. ! be printed at the head of the list
  173.  
  174. if FORMAT & ISORARE_F
  175. {
  176. if list_count = 1 and id_count <= 1 and
  177. obj is not plural
  178. {
  179. RLibMessage(&ListObjects,1, IS_WORD) ! "is"
  180. ! print " "; IS_WORD;
  181. }
  182. else
  183. {
  184. RLibMessage(&ListObjects,1, ARE_WORD) ! "are"
  185. ! print " "; ARE_WORD;
  186. }
  187. if FORMAT & LIST_F
  188. RLibMessage(&ListObjects,2) ! ":"
  189. FORMAT = FORMAT & ~ISORARE_F ! clear it
  190. }
  191.  
  192. need_newline = false
  193. if obj is plural
  194. pluralcount++
  195.  
  196. AssignPronoun(obj)
  197. if not (FORMAT & LIST_F)
  198. {
  199. if list_count > 2 and count
  200. print ",";
  201. if list_count > 1 and count = list_count - 1
  202. print " "; conjunction;
  203. if not (FORMAT & FIRSTCAPITAL_F)
  204. print " ";
  205. }
  206. else
  207. {
  208. print to (list_nest * 2); ! INDENT_SIZE);
  209. }
  210.  
  211. #ifset USE_PLURAL_OBJECTS
  212.  
  213. ! If a number of identical (or possibly plural)
  214. ! objects are grouped together, they are printed
  215. ! as a single entry in the list
  216. !
  217. if obj.identical_to and
  218. (this_class.type = identical_class or
  219. FORMAT & GROUPPLURALS_F)
  220. {
  221. if id_count = 1
  222. {
  223. if FORMAT & FIRSTCAPITAL_F
  224. CArt(obj)
  225. else
  226. Art(obj)
  227. }
  228. else
  229. {
  230. if FORMAT & FIRSTCAPITAL_F
  231. print NumberWord(id_count, true);
  232. else
  233. print NumberWord(id_count);
  234. print " "; this_class.name;
  235.  
  236. if this_class.type = plural_class
  237. {
  238. local k
  239.  
  240. if FORMAT & LIST_F
  241. print ":";
  242. else
  243. print " (";
  244.  
  245. k = 0
  246. for (i=1; i<=this_class.#plural_of; i++)
  247. {
  248. j = this_class.plural_of #i
  249. if parent(j) = thisobj
  250. {
  251. if not (FORMAT & LIST_F)
  252. {
  253. if id_count > 2 and k
  254. print ",";
  255. if k = id_count - 1
  256. print " "; AND_WORD;
  257. if k
  258. print " ";
  259. }
  260. else
  261. {
  262. print "\n";
  263. print to ((list_nest+1) * 2); ! INDENT_SIZE);
  264. }
  265. Art(j)
  266. if not (FORMAT & NOPARENTHESES_F)
  267. ObjectIs(j)
  268. k++
  269. }
  270. }
  271. if not (FORMAT & LIST_F): print ")";
  272. }
  273. }
  274. }
  275. else
  276. {
  277. #endif
  278. ! Regular old non-plural, non-identical
  279. ! objects get listed here:
  280.  
  281. if FORMAT & FIRSTCAPITAL_F
  282. CArt(obj)
  283. else: Art(obj)
  284. if not (FORMAT & NOPARENTHESES_F)
  285. ObjectIs(obj)
  286. #ifset USE_PLURAL_OBJECTS
  287. }
  288. #endif
  289. FORMAT = FORMAT & ~FIRSTCAPITAL_F ! clear it
  290.  
  291. count++
  292. }
  293.  
  294. ! For itemized listings, list the children of
  295. ! each object immediately after that object (unless
  296. ! it is a SpecialDesc-printed description)
  297.  
  298. if obj is not hidden and FORMAT & LIST_F
  299. {
  300. print newline
  301. if children(obj)
  302. {
  303. if not obj.list_contents
  304. {
  305. templist_count = list_count
  306. WhatsIn(obj)
  307. list_count = templist_count
  308. }
  309. }
  310. }
  311. }
  312.  
  313. ! If not an itemized list, it is necessary to finish off the
  314. ! sentence, adding any necessary words at the end. Then, the
  315. ! children of all previously objects listed at this level are
  316. ! listed.
  317.  
  318. if not (FORMAT & LIST_F)
  319. {
  320. if count
  321. {
  322. if list_nest = 1 and FORMAT & ISORAREHERE_F
  323. {
  324. if count + pluralcount > 1
  325. print " "; ARE_WORD;
  326. else: print " "; IS_WORD;
  327. if not (FORMAT & OVERRIDEHERE_F) or
  328. ((FORMAT & OVERRIDEHERE_F) and thisobj = parent(player))
  329. print " "; HERE_WORD;
  330. if FORMAT & OVERRIDEHERE_F
  331. {
  332. if FORMAT & ALSO_F
  333. {
  334. print " also";
  335. FORMAT = FORMAT & ~ALSO_F
  336. }
  337. print " ";
  338. if thisobj = location and player not in location and
  339. parent(player) is container
  340. {
  341. print "outside ";
  342. }
  343. elseif thisobj is container
  344. print "inside ";
  345. else
  346. print "on ";
  347. The(parent(player))
  348. }
  349. FORMAT = FORMAT & ~ISORAREHERE_F ! clear it
  350. if FORMAT & OVERRIDEHERE_F
  351. FORMAT = FORMAT & ~OVERRIDEHERE_F ! clear it
  352. if not (FORMAT&LIST_F or FORMAT&TEMPLIST_F)
  353. override_indent = true
  354. }
  355.  
  356. if not (FORMAT & NORECURSE_F)
  357. print ".";
  358. }
  359.  
  360. i = 0
  361. local char_count
  362. for obj in thisobj
  363. {
  364. if children(obj) and obj is not hidden and
  365. (obj is not already_listed or
  366. thisobj ~= location) and not ClothingCheck(obj)
  367. char_count++
  368. if char_count = 2
  369. break
  370. }
  371. for obj in thisobj
  372. {
  373. if children(obj) and obj is not hidden and
  374. (obj is not already_listed or
  375. thisobj ~= location) and not ClothingCheck(obj)
  376. {
  377. if FORMAT & TEMPLIST_F
  378. {
  379. FORMAT = FORMAT | LIST_F & ~TEMPLIST_F
  380. i = true
  381. print newline
  382. }
  383.  
  384. if count > 1 and obj.type = character
  385. {
  386. FORMAT = FORMAT | USECHARNAMES_F
  387. if char_count = 2
  388. {
  389. print newline
  390. override_indent = false
  391. }
  392. }
  393. templist_count = list_count
  394. WhatsIn(obj)
  395. list_count = templist_count
  396. }
  397. }
  398. }
  399.  
  400. if --list_nest = 0
  401. {
  402. if not (FORMAT & LIST_F) and not (FORMAT & NORECURSE_F)
  403. {
  404. print newline
  405. override_indent = false
  406. need_newline = false
  407. }
  408. }
  409. }
  410.  
  411. replace Describeplace(place, long)
  412. {
  413. local obj, count, notlisted, tempformat, charcount
  414.  
  415. if not place
  416. place = location
  417.  
  418. if AssignPronounsToRoom
  419. parser_data[PARSER_STATUS] &= ~PRONOUNS_SET
  420.  
  421. ! Since, for example, a room description following entering via
  422. ! DoGo does not trigger before/after properties tied to looking
  423. ! around:
  424. !
  425. #ifclear NO_VERBS
  426. if verbroutine = &MovePlayer
  427. {
  428. if place is not visited and verbosity ~= 1
  429. return Perform(&DoLookAround)
  430. elseif long = true or verbosity = 2
  431. return Perform(&DoLookAround)
  432. }
  433. #endif
  434.  
  435. exit_type = 0 ! clear the exit_type global
  436.  
  437. if not light_source
  438. {
  439. Message(&DescribePlace, 1) ! "It's too dark to see..."
  440. return
  441. }
  442.  
  443. place is known
  444.  
  445. ! Print the name of the location as a heading
  446. RLibMessage(&DescribePlace,1,place)
  447.  
  448. override_indent = false
  449.  
  450. if place is not visited and verbosity ~= 1
  451. {
  452. if &place.initial_desc or &place.long_desc
  453. Indent
  454. if not place.initial_desc
  455. run place.long_desc
  456. }
  457. elseif long = true or verbosity = 2
  458. {
  459. if &place.long_desc
  460. Indent
  461. run place.long_desc
  462. }
  463. elseif place is not visited and verbosity = 1
  464. {
  465. if &place.initial_desc
  466. Indent
  467. run place.initial_desc
  468. }
  469.  
  470. if &place.list_contents and FORMAT & DESCFORM_F
  471. print "" ! for double-space-after-heading formatting
  472.  
  473. ! A location may contain an overriding listing routine, as may any
  474. ! parent, in the list_contents property
  475. !
  476. if not place.list_contents
  477. {
  478. list_nest = 0
  479.  
  480. ! For double-space-after-heading formatting:
  481. if FORMAT & DESCFORM_F
  482. {
  483. for obj in place
  484. {
  485. if obj is not hidden and
  486. (player not in obj or children(obj) > 1)
  487. {
  488. print ""
  489. break
  490. }
  491. }
  492. }
  493.  
  494. ! List contents of chair, vehicle, etc. player is in
  495. if player not in location
  496. {
  497. tempformat = FORMAT
  498. if SmartParents(parent(player))
  499. {
  500. FORMAT = FORMAT | FIRSTCAPITAL_F | ISORAREHERE_F | \
  501. OVERRIDEHERE_F
  502. if FORMAT & LIST_F
  503. {
  504. FORMAT = FORMAT & ~LIST_F ! clear it
  505. FORMAT = FORMAT | TEMPLIST_F
  506. }
  507. list_nest = 0
  508. }
  509. else
  510. list_nest = 1
  511. WhatsIn(Parent(player))
  512. FORMAT = tempformat
  513. }
  514.  
  515. ! List all characters, if any
  516. count = 0
  517. for obj in place
  518. {
  519. if obj is hidden or obj is not living or
  520. player in obj
  521. {
  522. obj is already_listed
  523. }
  524. else
  525. {
  526. obj is not already_listed
  527. }
  528. }
  529. for obj in place
  530. {
  531. if obj is not already_listed
  532. {
  533. print newline
  534. ShortDescribe(obj)
  535. if obj is not already_listed
  536. count++
  537. }
  538. }
  539.  
  540. list_count = count
  541. count = 0
  542.  
  543. if list_count ! if characters are to be listed
  544. {
  545. charcount++
  546. tempformat = FORMAT
  547. FORMAT = FORMAT | FIRSTCAPITAL_F | ISORAREHERE_F
  548. if SmartParents
  549. FORMAT = FORMAT | OVERRIDEHERE_F
  550. if list_count > 1
  551. FORMAT = FORMAT | USECHARNAMES_F
  552. if FORMAT & LIST_F
  553. {
  554. FORMAT = FORMAT & ~LIST_F ! clear it
  555. FORMAT = FORMAT | TEMPLIST_F
  556. }
  557. Indent
  558. list_nest = 0
  559. ListObjects(place)
  560. FORMAT = tempformat
  561. }
  562.  
  563. for obj in place
  564. {
  565. #ifset USE_ATTACHABLES
  566. ! Exclude all attachables for now (and characters)
  567.  
  568. if obj is living or obj.type = attachable or
  569. player in obj
  570. #else
  571. if obj is living or player in obj
  572. #endif
  573. obj is already_listed
  574. else
  575. obj is not already_listed
  576. }
  577.  
  578. for obj in place
  579. {
  580. #ifset USE_PLURAL_OBJECTS
  581. ! ...And don't list identical objects yet, either
  582.  
  583. if (obj.identical_to).type = identical_class
  584. {
  585. if obj is not hidden
  586. count++
  587. }
  588. elseif player not in obj
  589. #else
  590. if player not in obj
  591. #endif
  592. {
  593. if obj is not already_listed and
  594. obj is not hidden
  595. {
  596. ShortDescribe(obj)
  597. if obj is not already_listed
  598. notlisted++
  599. }
  600. }
  601. }
  602.  
  603. if notlisted or count
  604. {
  605. list_count = notlisted + count
  606. tempformat = FORMAT
  607. FORMAT = FORMAT | FIRSTCAPITAL_F | ISORAREHERE_F
  608. Indent
  609. if SmartParents
  610. {
  611. FORMAT = FORMAT | OVERRIDEHERE_F
  612. if charcount
  613. {
  614. FORMAT = FORMAT | ALSO_F
  615. }
  616. }
  617. if FORMAT & LIST_F
  618. {
  619. FORMAT = FORMAT & ~LIST_F ! clear it
  620. FORMAT = FORMAT | TEMPLIST_F
  621. }
  622. list_nest = 0
  623. ListObjects(place)
  624. FORMAT = tempformat
  625. }
  626.  
  627. #ifclear NO_OBJLIB
  628. #ifset USE_ATTACHABLES
  629. for obj in place
  630. {
  631. ! Print attachables last
  632. if obj.type = attachable and obj is not hidden
  633. {
  634. ShortDescribe(obj)
  635. if obj is not already_listed
  636. Message(&DescribePlace, 2, obj)
  637. }
  638. }
  639. #endif
  640.  
  641. print newline
  642. override_indent = false
  643.  
  644. ! Finally, list contents of scenery objects (unless we've
  645. ! already done so as the parent of the player)
  646. !
  647. for obj in place
  648. {
  649. if obj.type = scenery
  650. {
  651. obj is known
  652. if player not in obj and
  653. ! (obj is open or obj is not openable)
  654. ! ((obj is container and (obj is open or obj is transparent)) or
  655. ! obj is platform) and obj is not quiet
  656. (obj is open or obj is not openable or obj is platform or
  657. obj is transparent) and obj is not quiet
  658. {
  659. list_nest = 1
  660. WhatsIn(obj)
  661. }
  662. }
  663.  
  664. ! For scenery-derived objects that may change the type
  665. elseif obj is static, hidden
  666. obj is known
  667. }
  668. #endif ! ifclear NO_OBJLIB
  669.  
  670. print newline
  671. need_newline = false
  672.  
  673. }
  674. }
  675.  
  676. routine SmartParents(obj)
  677. {
  678. if player not in location and parent(player) is container
  679. return true
  680. else
  681. return false
  682. }

Maybe not the easiest stuff in the world to read, but if any of you want any help getting something like this working in your game, just let me know!

Monday, July 14, 2014

Some non-Roodylib parent(player) code

It seems like I've been spending a lot of time this year making sure Roodylib handles parent objects as well as possible, and every so often, I find something to tweak.

The other day, I found myself thinking about the old player-in-a-closed-container situation.  For my purposes, I was coding a coffin in a room.  Once the player is in the coffin and closes it, I wanted only objects within the coffin to be within reach (and seen).

Of course, it doesn't work out of the box, and I went on this epic quest, updating DoOpen, DoClose, FindLight, ObjectIsLight, and FindObject.  I was attempting to code an elaborate system that tried to figure out if the player can be seen from the location and act accordingly.  Working against me was the fact that FindObject likes to take for granted that the player object can always be seen, no matter what room is being used with it.  I tried so hard to tweak it to work for all occasions, but some instance was always breaking.

It's probably for the best that it never worked because who wants to add all of that code, anyway?  Eventually, it occurred to me that the easiest thing an author could do was just to set the coffin as the location when it was closed (and the room to the location when it was opened again).  Here is some example code:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. object coffin "coffin"
  2. {
  3. article "a"
  4. noun "coffin"
  5. is openable open enterable container
  6. before
  7. {
  8. xobject DoPutIn
  9. {
  10. if player in self and self is not open
  11. {
  12. Perform(&DoDrop, object)
  13. }
  14. else
  15. return false
  16. }
  17. object DoLookIn
  18. {
  19. if player in self and self is not open
  20. {
  21. Perform(&DoLookAround)
  22. }
  23. else
  24. return false
  25. }
  26. }
  27. after
  28. {
  29. object DoOpen,DoClose
  30. {
  31. if player in self
  32. {
  33. if verbroutine = &DoClose and location = self
  34. return false ! so location.after doesn't run this again
  35. if self is not open
  36. {
  37. location = self
  38. if not FindLight(location)
  39. return false
  40. "Closed."
  41. }
  42. else
  43. {
  44. "Opened."
  45. location = parent(self)
  46. }
  47. if FindLight(location)
  48. Perform(&DoLookAround)
  49. return true
  50. }
  51. else
  52. return false
  53. }
  54. }
  55. in STARTLOCATION
  56. capacity 100
  57. }

It's possible that I didn't think of every verbroutine instance to be covered when the player is in the container, but that should be a good start!


Closing the coffin without a light source
More coffin antics coming in the next post!!

Clothing Update Part II

So, all of this work with clothing made me go, huh, wonder if my old "AIF" code still works. That's the flag that allows you to take off all of your clothes easily.  I mean, I have no intention of writing any clothes-heavy (or clothes-light har har) games anytime soon, AIF or not, but the world model fetishist in me likes to know these things work.  Anyhow, I found that, no, my old code did not work (I don't think it ever worked perfectly; I just hadn't completely tested it correctly).

See, the big problem is that >REMOVE ALL should default to held items, but you can't give it a multiheld token because you still want REMOVE to work with things like platforms or containers, and if you give it just the multi token, it ignores held items completely (only working for held items when they are used specifically).

Initially, the only way I could get it to work was to use the USE_CHECKHELD flag.  The checkheld system moves everything the player is holding to a hidden container in the room, so everything is considered not held (but it marks them with the checkheld workflag so the library knows it WAS held), allowing all verbs to work as you'd like.

Still, I put in all of this work so that in the instances where REMOVE failed, I made WEAR and TAKE OFF also fail in the same way just so there'd be consistency. I also made some instances of DoGet get directed to DoTakeOff.  Anyhow, I'm sort of embarrassed by how much work went into it, but if you want to check it out for yourself, check out the routines ExcludeFromAll, ParseError (cases 3 and 9), and VerbHeldMode in the next official release of Roodylib (version 3.9).  It is really ugly code; I should probably clean it up, but I just don't want to even look at it for a while.

As it often goes, I figured out a nice, non-checkheld-system workaround the next day. It occurred to me that the following PreParse code would do the trick:


        if word[1] = "remove" and word[2] = "~all"
        {
            word[1] = "take"
            InsertWord(2,1)
            word[2] = "off"
            return true
        }
        return false

So, basically, >REMOVE ALL gets turned into >TAKE OFF ALL (since >TAKE OFF doesn't have the same grammar issues).  Of course, ParseError messages will say TAKE OFF instead of REMOVE, but I don't think most players would even notice it isn't intentional.

Right now, if the AIF flag is not set, all variations of TAKE OFF, REMOVE, or WEAR ALL get a "Please specify which item to <blank>."-type message.  If it's set, those commands work, although they work in slightly different ways, depending on whether USE_CHECKHELD is set.

(Of course, I jokingly call it the "AIF" flag, but an author could easily want such a flag in a roleplaying-flavored game where the PC changes his or her armor a lot.)

Clothing Update Part I

So, it feels like I've been working on Roodylib a lot over the last few weeks.  It'll be interesting when I put up the next official release and go over all of the updates one last time for the changelog.

Robb Sherwin has been looking to get his game Cryptozookeeper sold online at places such as Steam and Desura, so I've been looking over the code to suggest tweaks to make this or that more user-friendly to those not familiar with IF.  Anyhow, looking at some of the things that game does made me go, "oh yeah, that probably should be an option in Roodylib!" on several occasions.

One thing CZK does is list worn clothing items before listing the rest of the inventory.  I actually wrote that code in the first place, but I didn't like how it replaced SpecialDesc in such an awkward way.  I felt, this time around that, hey, Roodylib can do it better!

So, first off, Roodylib has a new LIST_CLOTHES_FIRST flag that you should set if you want to run the new code. Then, for any character you want clothing to be automatically listed for, you add this:


    list_contents
        return ListClothesFirst(self)

Interestingly, ListClothesFirst only returns true if the character is wearing all of their belongings (this keeps an extra space from being printed after the worn-clothing list).  If you have an NPC where you only want clothing and held items to be listed when you examined them (and not in room descriptions), you could do this:


    list_contents
    {
        if verbroutine = &DoLookAround
            return true
        else
            return ListClothesFirst(self)
    }


Here is a screenshots of this system in action:




So, let's take a look at how this works. First off, of course, there's that ListClothesFirst routine:


routine ListClothesFirst(char)
{
    local x,w,c, list_save, v
    v = verbroutine
    verbroutine = &ListClothesFirst
    list_save = list_count
    list_count = 0
    for x in char
    {
        if x is worn and x is clothing
        {
            w++
            list_count++
        }
        elseif x is not hidden
        {
            x is already_listed
            c++
        }
    }
    if w and c
        list_nest = 1
    if list_count
    {
        if v ~= &DoInventory
            Indent
        if v = &DoLook
            FORMAT = FORMAT | USECHARNAMES_F
        RLibMessage(&ListClothesFirst,1,char) ! "You are wearing"
        ListObjects(char)
        if FORMAT & USECHARNAMES_F
            FORMAT = FORMAT & ~USECHARNAMES_F
        else
            FORMAT = FORMAT & ~USECHARNAMES_F
        list_nest = 0
    }
    for x in char
    {
        if x is not worn or (x is not clothing and x is worn)
            x is not already_listed
        else
        {
            AddSpecialDesc(x) ! tags worn items as already_listed
        }
    }
    verbroutine = v
    list_count = list_save - w
    return (not c and w)
}

It checks to see if any objects are worn, lists them, and marks them as already_listed.  I also needed to add this bit to ListObjects:
Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. i = 0
  2. local char_count
  3. for obj in thisobj
  4. {
  5. if children(obj) and obj is not hidden and
  6. (obj is not already_listed or
  7. thisobj ~= location) and not ClothingCheck(obj)
  8. char_count++
  9. if char_count = 2
  10. break
  11. }
  12. for obj in thisobj
  13. {
  14. if children(obj) and obj is not hidden and
  15. (obj is not already_listed or
  16. thisobj ~= location) and not ClothingCheck(obj)
  17. {
  18. if FORMAT & TEMPLIST_F
  19. {
  20. FORMAT = FORMAT | LIST_F & ~TEMPLIST_F
  21. i = true
  22. print newline
  23. }
  24.  
  25. if count > 1 and obj.type = character
  26. {
  27. FORMAT = FORMAT | USECHARNAMES_F
  28. if char_count = 2
  29. {
  30. print newline
  31. override_indent = false
  32. }
  33. }
  34. templist_count = list_count
  35. WhatsIn(obj)
  36. list_count = templist_count
  37. }
  38. }


The ClothingCheck routine makes sure that children of worn clothing items aren't listed a second time (as they were listed by the ListClothesFirst routine).  There's also some code to make sure character names are used when it might be confusing.

Anyhow, here is the ClothingCheck routine:


routine ClothingCheck(a)
{
#ifset LIST_CLOTHES_FIRST
    if (a is worn and a is clothing and verbroutine ~= &ListClothesFirst) or
        (a is not worn and verbroutine = &ListClothesFirst)
        return true
    else
#endif
        return false
}


As you see, it has to check the verbroutine (which ListClothesFirst temporarily sets).

Anyhow, part of the reason I break this all down is because it's very possible you'll want to split up your player's or characters' inventories in other ways, and hopefully this is enough of a blueprint to get you on your way.

(All of this will be in the next official Roodylib release... coming soon!)

Thursday, May 8, 2014

>EXAMINE HACKER'S KEYRING

One of the things I've wanted for Hugo was an automated way to support possessive adjectives when referring to characters' belongings.  Since Hugo doesn't really give you complete control over the grammar, this is pretty hard to do.  Recently, I coded the best solution I could come up with.  Basically, it's a wrapper routine for Acquire that sets or clears adjectives as needed.  The downside is, you'd have to add an extra empty element to any object that might be used with it, like this:

       

object potato "potato"
{
 article "a"
 adjective "hot" 0
 noun "potato"
 in you
}

       
 

Now, I couldn't really decide on an intuitive name for the routine, but since I figured it was for games where objects are passed around between characters a lot, I opted for the name "HotPotato":

       

attribute keep_adjectives
routine HotPotato(obj,new_parent)
{
 local a
 a = parent(obj)
 if a is living and obj is not keep_adjectives
 {
  if CharacterKey(a)
   obj.adjective #(obj.#adjectives) = 0
 }
 if not new_parent
  remove obj
 else
 {
  a = CharacterKey(new_parent)
  if Acquire(new_parent, obj) and obj is not keep_adjectives
  {
   if a
    obj.adjective #(obj.#adjectives) = a
  }
 
}
       
 

If not provided with a "new_parent" object, HotPotato just removes the object after clearing any applicable adjectives.  Any game that uses this would have to provide its own CharacterKey routine, which would look like this:

       
routine CharacterKey(char)
{
 local a
 select char
  case fred: a = "fred's"
  case player: a = "my"
 return a
}
       
 

Now, all of this work is kind of useless since among games with characters, there usually isn't so much object-swapping that most of this couldn't be done by hand.  Plus, you'll notice that HotPotato only checks for one layer of containment in characters and there might be games where you'd want to check for grandchildren (or further).  Still, this is as close to automating this in Hugo as it's going to get, I think.

There's also the issue of words like "your","my","his", and "her" (>ASK FRED ABOUT HIS HAT).  I find the best solution is to just define these words as removals so the parser completely ignores them.

       
removal "his","her"
removal "my","your"
       
 
How many of these you define as removals depends on your game (games like Spur use "your" as a permanent adjective for certain things).  Also, because of games like Spur where some objects keep a certain ownership despite who is carrying them (like, "your gun" compared to "O'Grady's gun"), I added that keep_adjectives attribute to give objects that should be left alone.

Monday, March 24, 2014

Roodylib 3.8

So, this release feels like it adds a lot more than it probably will feel to the end user, but here is the changelog:

* Added an "elevated" attribute for enterable platforms (such as ladders) where "down" is an accepted exit direction
* Changed the vehicle class again (check out roodylibdocs.odt)
* Added USE_SMART_PARENTS flag so if the player tries going into an invalid direction while in an enterable object, they get "You can't go that way." instead of "You'll have to get up/out."
* In doing so, updated DoGo, and the vehicle and door classes to support it
* Added SKIP_DOORS flag so player can breeze through unlocked doors without opening them like NPCs
* Added a HugoFix settings object so debug monitor settings are kept after a restart
* If daemon/fuse monitoring is on (in HugoFix), a prompt asks whether to run the relevant daemon/fuse (to make it easy to skip during testing, if wanted)
* Got rid of an unnecessary AUX_MATH routine call in printstatuslib
* Made sure Roodylib is more NO_OBJLIB, NO_VERBS, and NO_XVERBS compatible
* Added USE_DARK_ROOM flag so dark rooms appear like an actual room when you enter them (check out roodylibdocs.odt)
* Updated flags.h to include new flags
* Update "go" grammar
* Added the coverart extension to Roodylib because of its configlib compatibility
* Updated old files to have UNIX line feeds and fixed indenting on older code

I also touched up some of the documentation files to mention a couple more things.  Still, there is probably lots of stuff that I could describe more.  If there are Roodylib functions that you'd like to see be better described, shoot me an e-mail!

You can download the latest version here: http://roody.gerynarsabode.org/notdead/roodylib_suite.zip

Monday, January 20, 2014

Roodylib v 3.7

Of course, as soon as I submitted that last version, I remembered some other things I had meant to put in.  Luckily, a couple other things popped up along the way so this update isn't quite as minimal as it could be.

Get it here: http://hugo.gerynarsabode.org/index.php?title=Roodylib

v 3.7 -
        * Added CallFinish, MakePlayer, CoolPause, PauseForKey routines
* Updated shell files to make it easier to turn Roodylib off (for tracking errors down to original library code)
* Updated credits text

Monday, January 13, 2014

Roodylib version 3.6 release

Uploaded a new Roodylib.  Mainly, it fixes some things that Juhana Leinonen found in his Hugo dabbling.  Go download it from Hugo By Example or the Hugo Code Lab (up in the links section to the right).

v 3.6 - * Added DoOpen replacement
* Fixed a ParseError bug (caused by me, not original)
* Added DoGet replacement, updated CheckReach for dealing with instances where the player is in a closed enterable
object and is trying to get something else in the same object