Sunday, May 29, 2016

"sandbox" modes

After completing Michael Berlyn's Suspended, a player is given a special command that allows configuration of the main game- moving robots here and there, changing the timer, etc.  I heard about this feature many years before I actually beat Suspended.  At the time, it sounded like overkill for a game that I had bashed my head against (although in hindsight, it's really not the Herculean effort I thought it was).

I have to admit that, having beaten the game and finally being familiar with how things fit together, the extra challenge mode seemed like a cool idea.  Wow, a player could really make this game as hard or easy as they want to; I could see a fun optimization puzzle emerging.  I have to admit that I never actually played much with this extra mode, but it was cool to see and finally understand.

I've pondered nice ways to provide a similar experience for players, and the easiest answer I came up with was to give the player a magic word when the game is completed that turns on debugging mode.  They'd be able to look at the object tree, move objects or the player around, or even control how daemons run.  I thought this would be a fun way to share the innards of the game with the player; for some reason, having game source available doesn't provide the same thrill unless it's, say, in Hugo and I'm applying new Roodylib functionality to it or something for my own curiosity.

Today's code sample supplies this magic word debugging system.

! set the following flags (a Roodylib shell file is required)
#set HUGOFIX
#set UNLOCKABLE_DEBUG
! Define your magic word in your grammar
xverb "magicword"
* DoDebugMagicWord
! add the following before routine to your player character object
player_character you "you"
{
before
{
#ifset HUGOFIX
actor DoHugoFix
{
#if UNLOCKABLE_DEBUG
if not debug_on
{
! this fakes a "that wasn't understood" response for debugging
! commands if they haven't been unlocked with the magic word
print "You don't know the word \""; word[1] ; "\"."
return true
}
#endif
return false
}
#endif
}
}
#ifset HUGOFIX
#ifset UNLOCKABLE_DEBUG
global debug_on
#endif
! Roody's note: Only load debugging values for game state changes if the magic
! word has been turned on
replace hugofixlib "hugofixlib"
{
in init_instructions
type settings
did_print true
#ifclear NO_FANCY_STUFF
save_info
{
local b
#ifclear UNLOCKABLE_DEBUG
local debug_on = true
#endif
#ifset UNLOCKABLE_DEBUG
if debug_on
b = true
else
return
#endif
if not (debug_flags or debug_on) ! if absolutely no debug flags are
return ! turned on, just return right away
if debug_flags & D_FUSES
b = SaveWordSetting("fuses",b)
if debug_flags & D_OBJNUM
b = SaveWordSetting("objnum",b)
if debug_flags & D_PARSE
b = SaveWordSetting("parse",b)
if debug_flags & D_SCRIPTS
b = SaveWordSetting("scripts",b)
if debug_flags & D_FINDOBJECT
b = SaveWordSetting("findobject",b)
if debug_flags & D_PARSE_RANK
b = SaveWordSetting("parserank",b)
if debug_flags & D_PLAYBACK_HELPER
b = SaveWordSetting("playbackhelper",b)
if debug_flags & D_OPCODE_MONITOR
b = SaveWordSetting("opcodemonitor",b)
return true
}
#endif
execute
{
if word[LAST_TURN] ~= "undo","restore"
{
#ifclear NO_FANCY_STUFF
local a,b
a = CheckWordSetting(self.name)
while a
{
#ifset UNLOCKABLE_DEBUG
debug_on = true
#endif
select word[(a+1)]
case "fuses" : b = D_FUSES
case "objnum" : b = D_OBJNUM
case "parse" : b = D_PARSE
case "scripts" : b = D_SCRIPTS
case "findobject" : b = D_FINDOBJECT
case "parserank" : b = D_PARSE_RANK
case "playbackhelper" : b = D_PLAYBACK_HELPER
case "opcodemonitor" : b = D_OPCODE_MONITOR
case else : break
debug_flags = debug_flags | b
a++
}
#endif
OrganizeTree
#ifset UNLOCKABLE_DEBUG
if debug_on
#endif
HugoFixInit
}
}
}
#endif
! We replace GameTitle so the game banner doesn't show debugging mode unless
! the magic word has turned it on
replace GameTitle
{
color TITLECOLOR
Font(BOLD_ON | ITALIC_OFF)
print GAME_TITLE;
Font(BOLD_OFF | ITALIC_OFF)
color TEXTCOLOR
#ifset DEMO_VERSION
print "\B (demo version)\b";
#endif
#ifset HUGOFIX
#ifset UNLOCKABLE_DEBUG
if debug_on
#endif
print "\I (HugoFix Debugging Suite Enabled)\i";
#endif
}
! turns on debugging mode
routine DoDebugMagicWord
{
if not debug_on
{
"Debugging verbs turned on. Type \"$?\" for full list."
debug_on = true
}
else
{
"Debugging verbs turned off."
debug_on = false
}
}
view raw sandbox.h hosted with ❤ by GitHub


Now I just need to write a game that players would want to dig into!

Saturday, May 28, 2016

general update and "bags of holding"

Some interesting news in Hugoville.  Nikos Chantziaras has added an opcode system to Hugor, allowing for some special behavior here and there.  Unfortunately, along with posting here about stuff more often, I've been meaning to update the Roodylib documentation to cover all of the new stuff and put out a new release.  So, more news down the road!

In the meantime, I thought I'd share some code examples I put together the other month; well, one now, one next time I write here.

Today's bit of code will be for what IF lingo calls a "bag of holding."  I'd say they were first popularized in late era commercial IF although they first showed up a bit earlier than that.  Players still had set carrying capacities so object-management was required, but one object in the game- usually a bag or knapsack- could carry a limitless number of objects.  As soon as you couldn't pick up anything more, you'd stick stuff in the bag of holding and, yay, looting can recommence!

Bags of holding got even cooler when the game would automatically stick stuff in them for you.  Today's code sample will be an automatic bag of holding system in Hugo.

global bag_of_holding
replace Acquire(newparent, newchild)
{
local p,h
CalculateHolding(newparent)
if newparent.#holding
h = newparent.holding
else
h = holding_global
if (h + newchild.size > newparent.capacity)
{
if not BagOfHolding(newparent, newchild)
return false
}
p = parent(newchild)
move newchild to newparent
CalculateHolding(p)
#ifset MULTI_PCS
MakeMovedVisited(newchild)
#else
newchild is moved
#endif
newchild is not hidden
if newparent.#holding
newparent.holding = newparent.holding + newchild.size
return true
}
routine BagOfHolding(newparent, newchild)
{
local x
if newparent ~= player or bag_of_holding not in player
return false
x = child(newparent)
while x
{
if (x is not worn or (x is not clothing and x is worn)) and
x is not light and
(x.size + newparent.capacity >= newchild.size) and
x ~= bag_of_holding
{
if Acquire(bag_of_holding,x)
{
print "(Putting "; The(x); " into "; The(bag_of_holding) ; \
" to make room)"
return true
}
}
x = younger(x)
}
return false
}


Just set the bag_of_holding global to your bag of holding object, and there you go!  Now, admittedly, this system is pretty simple. It only allows for swapping one object that, when moved, will create enough space for the new object. I considered writing another pass if the first one didn't find a suitable most, but I figured that this behavior would fit most games.  If a game has large variance in object sizes, the large objects probably should get special treatment through before routines and such.  Plus, it's always kind of ugly when you try to pick something up and then half of your inventory is moved to the bag of holding so I didn't want to encourage that.