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!)