To be honest, returning to game design hasn't been going all that well, so recently, I thought I could distract myself by looking over an old Roodylib "to do" list to see if there was anything I still wanted to add.
The first thing that sounded appealing was trying to come up with a solution to Hugo's default handling of >FOLLOW, as a game seems kind of dumb when you have just seen a character leave a room and >FOLLOW responds with "Which way did he go?"
I wrote a system that relies on using CharMove to move your NPCs. The code takes note of the direction the NPC left in, and unless the NPC returns at some point, >FOLLOW will result in going in the same direction. There was no ultra-clean method that didn't involve replacing some routines, but this was the most elegant solution that I could come up with. Beyond including this code, authors would just need to remember to give their roaming NPCs the "last_dir" property that I have defined.
!:: | |
! Smarter >FOLLOW SO-AND-SO behavior | |
!:: | |
! If your game has more than 3 characters that roam and can be followed, | |
! define this constant as something higher | |
#if undefined MAX_FOLLOWABLE_CHARACTERS | |
constant MAX_FOLLOWABLE_CHARACTERS 3 | |
#endif | |
array followable_characters[MAX_FOLLOWABLE_CHARACTERS] | |
! It is important that you add a last_dir property to any character | |
! that can be followed | |
property last_dir | |
#ifclear _VERBSTUB_H | |
routine DoFollow | |
{} | |
#endif | |
replace DoFollow | |
{ | |
if object = player | |
{ | |
if speaking | |
SpeakTo(speaking) | |
else | |
{ | |
if player_person = 2 | |
"Who are you talking to?" | |
else | |
{ | |
"It's not obvious who you want "; | |
print player.pronoun #2; | |
" to talk to." | |
} | |
} | |
} | |
elseif object in location | |
print CThe(object); IsorAre(object, true); " right here." | |
elseif object.last_dir | |
return Perform(&DoGo, object.last_dir) | |
else | |
print "Which way did "; object.pronoun; " go?" | |
} | |
replace CharMove(char, dir) | |
{ | |
#ifclear NO_OBJLIB | |
local newroom, a | |
general = 1 | |
if dir.type ~= direction | |
return | |
if char in location | |
{ | |
AddArrayValue(followable_characters,char) | |
char.last_dir = dir | |
} | |
newroom = parent(char).(dir.dir_to) | |
if newroom.type = door | |
{ | |
a = newroom | |
newroom = a.between #((parent(char) = \ | |
a.between #1) + 1) | |
if a is not open | |
{ | |
if char in location or newroom = location | |
{ | |
self = a | |
RlibOMessage(door, 3) | |
} | |
} | |
elseif newroom = location or char in location | |
a = 0 | |
} | |
if char in location and not a and general = 1 | |
{ | |
Message(&CharMove, 1, char, dir) | |
event_flag = true | |
} | |
move char to newroom | |
if newroom = location | |
char.last_dir = 0 | |
#ifset DEBUG | |
if debug_flags & D_SCRIPTS | |
{ | |
print "["; CThe(char); IsorAre(char, true); " now in: "; | |
print capital parent(char).name; ".]" | |
} | |
#endif | |
if char in location and not a and general = 1 | |
{ | |
Message(&CharMove, 2, char, dir) | |
event_flag = true | |
} | |
elseif char in location | |
event_flag = true | |
#endif ! ifclear NO_OBJLIB | |
general = 0 ! always reset it | |
run parent(char).after | |
return true | |
} | |
routine AddArrayValue(arr, val) | |
{ | |
local i | |
for (i=0; i< array arr[]; i++) | |
{ | |
if array arr[i] = val | |
return i | |
elseif array arr[i] = 0 | |
{ | |
array arr[i] = val | |
return i | |
} | |
} | |
return false | |
} | |
replace BeforeRoutines(queue) | |
{ | |
local r, i | |
if verbroutine ~= &MovePlayer | |
r = player.react_before | |
if r | |
{ | |
#ifset DEBUG | |
if debug_flags & D_PARSE | |
{ | |
print "\B["; player.name; | |
if debug_flags & D_OBJNUM | |
print " ["; number player; "]"; | |
print ".react_before returned "; number r; "]\b" | |
} | |
#endif | |
return r | |
} | |
r = player.before | |
if r | |
{ | |
#ifset DEBUG | |
if debug_flags & D_PARSE | |
{ | |
print "\B["; player.name; | |
if debug_flags & D_OBJNUM | |
print " ["; number player; "]"; | |
print ".before returned "; number r; "]\b" | |
} | |
#endif | |
return r | |
} | |
if verbroutine ~= &MovePlayer | |
r = location.react_before | |
if r | |
{ | |
#ifset DEBUG | |
if debug_flags & D_PARSE | |
{ | |
print "\B["; location.name; | |
if debug_flags & D_OBJNUM | |
print " ["; number location; "]"; | |
print ".react_before returned "; number r; "]\b" | |
} | |
#endif | |
return r | |
} | |
r = location.before | |
if r | |
{ | |
#ifset DEBUG | |
if debug_flags & D_PARSE | |
{ | |
print "\B["; location.name; | |
if debug_flags & D_OBJNUM | |
print " ["; number location; "]"; | |
print "before returned "; number r; "]\b" | |
} | |
#endif | |
return r | |
} | |
if verbroutine = &MovePlayer | |
r = object.before | |
if r | |
{ | |
#ifset DEBUG | |
if debug_flags & D_PARSE | |
{ | |
print "\B["; object.name; | |
if debug_flags & D_OBJNUM | |
print " ["; number object; "]"; | |
print "before returned "; number r; "]\b" | |
} | |
#endif | |
return r | |
} | |
for i in location | |
{ | |
if i ~= player | |
r = i.react_before | |
if r | |
{ | |
#ifset DEBUG | |
if debug_flags & D_PARSE | |
{ | |
print "\B["; i.name; | |
if debug_flags & D_OBJNUM | |
print " ["; number i; "]"; | |
print ".react_before returned "; number r; "]\b" | |
} | |
#endif | |
return r | |
} | |
} | |
if verbroutine = &MovePlayer | |
{ | |
ClearTrail | |
return | |
} | |
! queue is -1 if the object was a number (i.e., a literal digit) | |
if queue ~= -1 and xobject > display | |
{ | |
r = xobject.before | |
if r | |
{ | |
#ifset DEBUG | |
if debug_flags & D_PARSE | |
{ | |
print "\B["; xobject.name; | |
if debug_flags & D_OBJNUM | |
print " ["; number xobject; "]"; | |
print ".before returned "; number r; "]\b" | |
} | |
#endif | |
return r | |
} | |
} | |
if queue ~= -1 and object > display | |
{ | |
r = object.before | |
if r | |
{ | |
#ifset DEBUG | |
if debug_flags & D_PARSE | |
{ | |
print "\B["; object.name; | |
if debug_flags & D_OBJNUM | |
print " ["; number object; "]"; | |
print ".before returned "; number r; "]\b" | |
} | |
#endif | |
return r | |
} | |
} | |
} | |
routine ClearTrail | |
{ | |
local n,i | |
while followable_characters[n] and n<MAX_FOLLOWABLE_CHARACTERS | |
{ | |
i = followable_characters[n] | |
i.last_dir = 0 | |
n++ | |
} | |
} |
After writing this, I wanted to test it out in a game. I first tried "Guilty Bastards" because I incorrectly remembered being able to follow someone at some point (although it definitely has a character following you). I then tried "Spur," which I was reminded was actually the game that inspired this whole better-following thing in the first place, but I couldn't even use my code with it as it completely substitutes another character script routine for CharMove. I mean, sure, I could have rewritten it all so my code would still have worked, but in my sandbox version of "Spur", I had already written a >FOLLOW SO-AND-SO workaround anyway.
So I just had to test it with my own code, and hey, everything seems to be working fine. I'll probably just throw it in the "extensions" folder in my Roodylib distribution at some point.
No comments:
Post a Comment