Community
SCI Programming => SCI Syntax Help => Topic started by: robbo007 on November 21, 2024, 04:29:16 PM
-
Hi guys,
I'm trying to create a script that mimics the LSL1 pimp door script in Lefty's Bar. I'm trying to understand what is the best approach.
Player types "knock on door", pimp replies "What's the password", player types password and its checked, if correct then opens door.
Would you use a variable to hold the text the player types in "ken sent me"? How do you then check this is the correct text entered against the variable?
When dealing with a script inside the RoomScript do you generally try and keep all the said statements inside that script? Or do you put the said statements in the RoomScript then declare the changeState you want to goto with that said statement ? I'm getting lost how to organise all my said statements and scripts in each room. What's the best approach?
Thanks,
-
What I'd do is, whatever Script is responsible for handling the pimp at the door, you have an enum of states just to make things clear. In that script, have your handleEvent respond to "ken sent me" by checking if state is "waiting for the password". If so, cue the Script. If not, print a joke and don't cue anything.
Or, if you want the pimp to time out, set seconds in the "wait for password" state and have the next one(s) react to a failure to give the password, where the script Just Stops. Have another (enum-named) state after that which handleEvent can respond to by first clearing the Script's seconds and then changeState to the "password accepted" state.
Does that make sense?
As for getting lost organizing your scripts: that's why you define enums for your states. Sierra sure did, and it's an example that helped me a great deal so far.
-
Hi,
Thanks for explaining a little more. I went back through the the LSL3 code to check how they used the enums. It seems a lot clearer when using them. Nice pointer. :)
So I was trying to use the StrCmp command to compare the GetInput text saved in the string @passwd matches but it does not seem to compare it. You can type anything in when prompted and it then moves to the GotPasswd changeState. Is it my syntax that is wrong? Or is there a simpler way to do it?
(enum ;** Pimp state values
OpenHole
AskForPasswd
GotPasswd
)
(local
messageCount
peephole_open = 0
peephole_closed = 1
password = ken sent me
passwd
)
(method (handleEvent pEvent)
(super handleEvent: pEvent)
; handle Said's, etc...
(if (Said 'knock/door')
(if (& (gEgo onControl:) ctlBLUE)
(Print 102 0) ;You rap loudly on the naugahyde door. You wonder how many naugas had to give their all just to decorate this sleazehole.
(aDoorScript changeState: 0)
else
(PrintNotCloseEnough)
)
)
)
(instance aDoorScript of Script ;Peep hole script
(method (handleEvent event)
(if (or (!= (event type:) evSAID) (event claimed:))
(return)
)
)
(method (changeState newState)
(ShowState self newState 1 2)
(switch (= state newState)
(OpenHole ; Peep hole door opens
;(ProgramControl)
(= peephole_open 0)
(aPeepHole cycleSpeed: 1 setLoop: 8 setCycle: End self)
)
(AskForPasswd ;The pimp speaks to ego
(= seconds 5)
(Print 102 1) ;"Yeah. Whatsda passwoid?"
(GetInput @passwd 45 {Whatsda passwoid?})
(if (== STRINGS_EQUAL (StrCmp @passwd "Ken send me"))
)
(self changeState: GotPasswd)
;;; else
;;; (Print {Scram Butthead})
;;; (aPeepHole setCel: 0 stopUpd:)
)
(GotPasswd ;Got password speak friend and enter.
(Print {Testing if reaching new state})
(aPeepHole setCel: 0 stopUpd:)
)
)
)
)
-
See, if I was copying the LSL1 password, I'd not use GetInput but Said, like I, well, said. If only so you can time out or walk away. But also so it becomes case insensitive.
(But not spelling insensitive: it's "Ken sent me".)
-
There are several suspicious bits in the code you posted.
For example:
)
(local
messageCount
peephole_open = 0
peephole_closed = 1
password = ken sent me
passwd
)The "password =" line shouldn't even compile, and the passwd variable doesn't reserve enough space for a 45 character input. Then there is the STRINGS_EQUAL constant which I can't find in my Companion sources. If we're being sensible, it's defined to zero, but my sci.sh has only this:
(define STRING_LESSTHAN -1)
(define STRING_GREATER 1)
I'm not sure I agree with Kawa about using Said. I'd like a GetInput text box, as you have written.
-
SCI StrCmp wraps around C strcmp when given two strings, or strncmp when given two strings and a max length. Therefore, equality is when it returns 0. So yeah, we are being sensible.
-
As for getting lost organizing your scripts: that's why you define enums for your states. Sierra sure did, and it's an example that helped me a great deal so far.
They did, and then they invented the switchto syntax for when the state machine is so straight-forward that no enum is nedded.
-
Right this seems to work. Needs a little more work putting in delays etc.. But as Kawa mentioned its case sensitive. Doh. Is there any way around this using the GetInput command? To capture both Ken and ken when typed?
(method (changeState newState)
(ShowState self newState 1 2)
(switch (= state newState)
(OpenHole ; Peep hole door opens
;(ProgramControl)
(= peephole_open 0)
(aPeepHole cycleSpeed: 1 setLoop: 8 setCycle: End self)
)
(AskForPasswd ;The pimp speaks to ego
(= seconds 20)
(Print 102 1) ;"Yeah. Whatsda passwoid?"
(GetInput @passwd 45 {Whatsda passwoid?})
(if (== STRINGS_EQUAL (StrCmp @passwd "Ken sent me"))
(self changeState: GotPasswd)
else
(Print {Scram Butthead})
(aPeepHole setCel: 0 stopUpd:)
)
)
(GotPasswd ;Got password speak friend and enter.
(Print {Testing if reaching new state})
(aPeepHole setCel: 0 stopUpd:)
)
)
)
)
-
QFG1 EGA uses a StringEquals function in script 802.
;;; Sierra Script 1.0 - (do not remove this comment)
(script# INPUTCOMP) ;INPUTCOMP = 802
(include system.sh) (include sci2.sh) (include game.sh)
(use _User)
(public
ToLower 0
StringEquals 1
EventEqualsString 2
)
(procedure (ToLower param1)
(return
(if (or (< param1 65) (> param1 90))
(return param1)
else
(return (+ (- param1 65) 97))
)
)
)
(procedure (StringEquals param1 param2 &tmp temp0 temp1 temp2 temp3)
(= temp1 (StrLen param2))
(= temp0 (- (= temp0 (StrLen param1)) temp1))
(while (>= temp0 0)
(= temp3 0)
(= temp2 temp0)
(while (< temp3 temp1)
(if
(!=
(ToLower (StrAt param2 temp3))
(ToLower (StrAt param1 temp2))
)
(break)
)
(++ temp3)
(++ temp2)
)
(if (== temp3 temp1) (return (+ temp3 1)))
(-- temp0)
)
(return FALSE)
)
(procedure (EventEqualsString event param2 &tmp i userInputLineAddr)
(if
(and
(User canInput?)
(not (event claimed?))
(== (event type?) keyDown)
(or
(== (event message?) (User echo?))
(and
(<= 32 (event message?))
(<= (event message?) 127)
)
)
)
(event claimed: TRUE)
(if (User getInput: event)
(= userInputLineAddr (User inputLineAddr?))
(= i 1)
(while (< i argc)
(if
(StringEquals userInputLineAddr [param2 (- i 1)])
(return i)
)
(++ i)
)
(event type: speechEvent)
(event claimed: FALSE)
(Parse userInputLineAddr event)
(User said: event)
)
)
(return FALSE)
)
an example of it being used is in script 29.
(if
(or
(StringEquals (User inputLineAddr?) {purple})
(StringEquals (User inputLineAddr?) {pink})
)
(askQuestions changeState: 0)
else
(WrongAnswer)
)
-
Here's how PQ2 converts your input in the copy protection screen to uppercase for comparison:
(procedure (toUpper &tmp i ch)
(for ((= i 0)) (= ch (StrAt @passwd i)) ((++ i))
(if (and (>= ch 97) (<= ch 122))
(StrAt @passwd i (- ch 32))
)
)
)
Noting that 97 to 122 are 'a' to 'z', and 97-32='A'.
-
Amazing. Thanks guys. This seems to do the trick forcing it all to caps. :)
(method (changeState newState)
(ShowState self newState 1 2)
(switch (= state newState)
(OpenHole ; Peep hole door opens
;(ProgramControl)
(= peephole_open 0)
(aPeepHole cycleSpeed: 1 setLoop: 8 setCycle: End self)
)
(AskForPasswd ;The pimp speaks to ego
(= seconds 20)
(Print 102 1) ;"Yeah. Whatsda passwoid?"
(GetInput @passwd 45 {Whatsda passwoid?})(toUpper)
(if (== STRINGS_EQUAL (StrCmp @passwd "KEN SENT ME")
)
(self changeState: GotPasswd)
else
(Print {Scram Butthead})
(aPeepHole setCel: 0 stopUpd:)
)
)
-
After implementing the super-dupper "peephole" script I'm now getting some oddities.
When ego runs the "peephole" script and leaves that room and comes back in, then when trying to exit either to the north (Hall) or south (outside Lefty's) I get the following lock ups. This only seems to happen if the peephole script has initiated. If not initiated both exits work as they should do.
(instance aDoorScript of Script ;Peep hole script
(properties)
(method (handleEvent event)
(if (or (!= (event type:) evSAID) (event claimed:))
(return)
)
)
(method (changeState newState)
(ShowState self newState 1 2)
(switch (= state newState)
(OpenHole ; Peep hole door opens
(ProgramControl)
(aPeepHole cycleSpeed: 1 setLoop: 8 setCycle: End self)
)
(AskForPasswd ;The pimp speaks to ego
(Print 102 1) ;"Yeah. Whatsda passwoid?"
(PlayerControl)
(GetInput @passwd 45 {"Whatsda passwoid?"})(toUpper)
(if (== STRINGS_EQUAL (StrCmp @passwd "KEN SENT ME")
)
(self changeState: GotPasswd)
else
(Print 102 4) ;"Scram, dog breath!"
(aPeepHole setCel: 0 stopUpd:)
(self dispose:)
)
)
(GotPasswd ;Got password speak friend and enter.
(gGame changeScore: 5)
(Print 102 5) ;"Come on in."
(aPeepHole setCel: 0 stopUpd:)
;(aDoor open:)
;(self dispose:)
(gRoom newRoom: 103)
)
)
)
)
-
Have you fixed those things I pointed out with your variable declarations? Because those could result in memory corruption if you got them to compile somehow. If this is not a corruption bug, we'd need to see one or both room init methods.
-
Have you fixed those things I pointed out with your variable declarations? Because those could result in memory corruption if you got them to compile somehow. If this is not a corruption bug, we'd need to see one or both room init methods.
I removed the ones that we not valid and I'm only using:
(local
messageCount
passwd = 1 ;this is the password variable used for the Ken sent me password in the Door_script.
)
The init for Lefty's bar has:
(script# 102)
(include sci.sh)
(include game.sh)
(use main)
(use controls)
(use cycle)
(use game)
(use feature)
(use obj)
(use inv)
(use door)
(use rm797) ;showState debugging changestates script
(public
rm102 0
)
(procedure (GetInput str maxLen prompt &tmp [temp0 4]) ;The GetInput procedure. Does not seem to load form Main.sc. Noting that 97 to 122 are 'a' to 'z', and 97-32='A'.
(if (Print (if (>= argc 3) prompt else {}) #edit str maxLen &rest)
(StrLen str)
)
)
(procedure (toUpper &tmp i ch) ; This procedure turns all text into caps when using toUpper and GetInput statements.
(for ((= i 0)) (= ch (StrAt @passwd i)) ((++ i))
(if (and (>= ch 97) (<= ch 122))
(StrAt @passwd i (- ch 32))
)
)
)
(enum ;** Pimp state values
OpenHole
AskForPasswd
GotPasswd
)
(local
messageCount
passwd = 1 ;this is the password variable used for the Ken sent me password in the Door_script.
)
(define LEFTY_LEFT 150)
(define LEFTY_RIGHT 243)
(define LEFTY_CENTRE 192)
(define LEFTY_Y 109)
(define vRoom 102
)
(define lPicture 0)
(define lDoor 1)
(define lFan 2)
(define lJukebox 3)
(define lPerson 4)
(define lPerson1 5)
(define lPerson2 6)
(define lPerson3 7)
(define lPeepHole 8)
(instance rm102 of Rm
(properties
picture 102
north 0
east 0
south 0
west 0
)
(method (init)
(super init:)
(self setScript: RoomScript)
(switch gPreviousRoomNumber
)
(cond
((== gPreviousRoomNumber 101) (gEgo posn: 86 128 loop: 2)
)
((== gPreviousRoomNumber 103) (gEgo posn: 286 168 loop: 1)
)
(else
(gEgo posn: 155 170 loop: 3)
)
)
(SetUpEgo)
(gEgo init:)
(aPicture init:)
(aFan init:)
(aJukebox init:)
(aPerson init:)
(aPerson1 init:)
(aPerson2 init:)
(aPerson3 init:)
(aLefty init:)
(aPeepHole init:)
(if (== gPreviousRoomNumber 103) (aDoor init: close:) ;this checks to see where ego has come from and if come from room 103 it will then close the door after
else
(aDoor init:)
)
)
)
And outside Lefty's where it crashes:
(script# 106)
(include sci.sh)
(include game.sh)
(use main)
(use controls)
(use cycle)
(use game)
;(use AutoDoor)
(use feature)
(use obj)
(use inv)
(use door)
;(use jump)
;(use dpath)
(use Sound)
(public
rm106 0
)
(define vRoom 106
)
(define lSign 0)
(define lSign2 1)
(define lSign3 2)
(define lDoor 3)
(instance rm106 of Rm
(properties
picture 106
north 102
east 108
south 0
west 107
)
(method (init)
(super init:)
(SetDebug) ;This forced game into debug.
(self setScript: RoomScript)
(switch gPreviousRoomNumber
)
(cond
((== gPreviousRoomNumber 105) (gEgo posn: 283 152 loop: 2)
)
((== gPreviousRoomNumber 102) (gEgo posn: 134 158 loop: 2)
)
((== gPreviousRoomNumber 107) (gEgo posn: 12 166 loop: 0)
)
((== gPreviousRoomNumber 108) (gEgo posn: 309 165 loop: 1)
)
(else
(gEgo posn: 134 162 loop: 2)
)
)
(SetUpEgo)
(gEgo init:)(gEgo view: 705)
;(aSign init:)
(aSign2 init:)
(aSign3 init:)
(aSign4 init:)
(if (== gPreviousRoomNumber 102) (aDoor init: close:)
else
(aDoor init:)
)
)
)
-
OK, so this seems to be connected to the same issue as your earlier ego initialization problem (in the trite phrase thread). The debugger hits at a callb $1 instruction, which is the first instruction after the fault. callb $1 corresponds to (SetUpEgo), so the faulting instruction is one of the sends just before that line, which both send to gEgo. You can double-check by viewing the value of gEgo (global 0): In the debugger, press g then enter the number zero. The next input box after that lets you see the current value of global 0, and optionally to change it. I am guessing that gEgo is 0 at this point.
What did you do about the problem in the trite phrase thread?
-
I think your onto something here. I did not fix the Trite phrase yet but it does the same thing when I add a trite phrase and change rooms.
My main.sc has the following line:
(User alterEgo: (= gEgo ego) blocks: 0 y: 150)
I can't seem to find the equivalent for adding this part: (= ego egoObj) or is it (= gEgo ego) ?
-
OK, so this seems to be connected to the same issue as your earlier ego initialization problem (in the trite phrase thread). The debugger hits at a callb $1 instruction, which is the first instruction after the fault. callb $1 corresponds to (SetUpEgo), so the faulting instruction is one of the sends just before that line, which both send to gEgo. You can double-check by viewing the value of gEgo (global 0): In the debugger, press g then enter the number zero. The next input box after that lets you see the current value of global 0, and optionally to change it. I am guessing that gEgo is 0 at this point.
What did you do about the problem in the trite phrase thread?
You are right its gEgo is 0. Do you know why is it not picking up the changes I've made to main.sc ? Or is my syntax in main.sc wrong?
Regards,
-
It must have picked up that change. After all, you couldn't start the game before, and now you can walk into the bar...? So gEgo is either being reset, or there's memory corruption going on. Given those suspicious bits of code I quoted earlier, I'm betting on the latter, though I'm struggling to see how the very first global variable could be affected. Is your string array declared correctly? Did you get rid of that "passwd = ken sent me" thing?
-
Right I've found the issue. I was using this script taken from LSL3 to check the changeState numbers to troubleshoot my changeState scripting. It displays the State number at the top of the screen in realtime. Any ideas why that causes the crash? Its a very handy little script.
I call this on my method for the peephole door changeState:
(method (changeState newState)
(ShowState self newState 1 2)
(switch (= state newState)
;;; Sierra Script 1.0 - (do not remove this comment)
;**
;** Logics for room 797 -- Just a showState procedure
;**
;** Leisure Suit Larry 4 - Never say Nontoonyt (AKA The Missing Floppies)
;**
;** Last Update: October 8th, 2024
;**
(script# 797)
(include sci.sh)
(include game.sh)
(use Main)
(public
ShowState 1
)
(procedure (TestFlag flag)
(return
(if (& [gFlagArray (/ flag 16)] (>> $8000 (mod flag 16))) 1 else 0)
)
)
(procedure (ShowState whatScript newState where color &tmp [str 33])
(if (and gDebugging (not (TestFlag 14)))
(if (< argc 2)
(= where 1)
)
(if (< argc 3)
(= color 7)
)
(Display
(Format @str 797 0 (whatScript name:) (whatScript state:) newState) ; "%s was state %d; is now state %d."
dsCOORD
1
(- (* 8 where) 7)
dsFONT
999
dsCOLOR
color
dsBACKGROUND
0
)
)
)
-
Is script 797 getting disposed when changing rooms? Just a guess, but if it's a memory fragmentation error or heap crash you might try adding (DisposeScript 797) to the Game::newRoom method when it's cleaning up everything else.
-
startRoom is the usual place for this. LSL3 even overrides it, so there's a natural place to add your script (the LoadMany FALSE call).
-
Thanks I looked briefly for that loadmany false, but couldn't remember where they put it. Been a while since I've needed to do that
-
Amazing guys that did the job.
So I've added the show_state script 797 to my main.sc but I dont really understand what's happening and why would I need to add something there? What is it doing and what is best practice here? Should I be adding other scripts here like my regions path script maybe? Does this free up heap space as I'm already skating on thin ice with that.
(method (startRoom roomNum)
(DisposeLoad
NULL
FILEIO_SCRIPT
JUMP_SCRIPT
EXTRA_SCRIPT
WINDOW_SCRIPT
TIMER_SCRIPT
FOLLOW_SCRIPT
REV_SCRIPT
DCICON_SCRIPT
DOOR_SCRIPT
AUTODOOR_SCRIPT
WANDER_SCRIPT
AVOID_SCRIPT
DPATH_SCRIPT
SHOW_STATE ;script 797
)
(DisposeScript DISPOSELOAD_SCRIPT)
-
It's to avoid heap fragmentation (where there is lots of memory available, but you can't allocate a single block that's large enough for the task at hand). I don't know how doomlazer connects this to a NULL gEgo, though. BTW, there is a kernel call ShowFree that tells you how much heap is available. It may be useful if you're having problems with free heap.
-
I don't know how doomlazer connects this to a NULL gEgo, though.
I think there were two issues. Robbo fixed the ego problem you spotted, but the showState script was still fragmenting memory. I'm too lazy to read through everything again to check if that's right.
What is it doing and what is best practice here? Should I be adding other scripts here like my regions path script maybe? Does this free up heap space as I'm already skating on thin ice with that.
Essentially you've got scripts that always live in memory like main, game, etc.. Then you have scripts that are loaded when a room script uses them. On room change, everything that was loaded for that room needs to get disposed to keep memory unfragmented. Some stuff, like actor instances, get cleaned up automatically because the lsl3 (or template game) code was already written to do so.
When I looked at Sluicebox's lsl3 decompile, I couldn't find script 797, so I assumed you had imported this yourself, copied from the lsl3 source code. The game isn't going to automatically know to dispose new scripts you add. Since you are calling this new script from a room script, you need to be responsible for cleaning it up. You're using it across more than one room, so disposing in startRoom is an effective way to ensure it's cleaned up every room change. DisposeScript safely ignores any of the args that weren't loaded in a given room.
I'm guessing you're adding (use SHOW_STATE) to the top of each room script? Another way to call showState would be:
;(ShowState self newState 1 2)
((ScriptId SHOW_STATE 1) self newState 1 2)
(DisposeScript SHOW_STATE)
This loads script 979 then calls method index 1 (ShowState has been assigned 1 in the script's public methods). Once the method returns, it's cleaned up by the DisposeScript call. No need to add (use SHOW_STATE) to the room script and script 979 isn't taking up heap the entire time the player is in the room.
It gives you a bit more control over the heap, but in the case of SHOW_STATE it's probably better to leave it as you have it now. You need enough heap for the room + 979 when ScriptId gets called anyway.
I'm not clear what 'regions path' is, but regions are generally going to be disposed by newRoom automatically.
Here's some great info on heap fragmentation: https://sciprogramming.com/tutorial.php?entry=5402
-
Scripts can even dispose themselves. Sierra used this a lot. It's technically a use-after-free situation, so you must be careful what you do afterwards.