gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > The infamous scripting question

#36460 - Kyoufu Kawa - Mon Feb 21, 2005 3:03 pm

This post is no longer relevant. The problem described in it has been solved below.

Last edited by Kyoufu Kawa on Sat Feb 26, 2005 4:21 pm; edited 1 time in total

#36462 - Kyoufu Kawa - Mon Feb 21, 2005 5:00 pm

This post has been deprecated as well.

Last edited by Kyoufu Kawa on Sat Feb 26, 2005 4:22 pm; edited 1 time in total

#36529 - Kyoufu Kawa - Wed Feb 23, 2005 6:34 pm

It's done! All it needs now is simple math and subroutine support. I decided to post the thing in it's entirity to help others, being such a nice guy (ehem cough cough). Many thanks to Cearn for helping me out with this.

Most of the functions and variables referred to herein won't be available for you but the logic and math stuff's clear.

This goes in a .c file, ofcourse...
Code:
#define   SC_NOP 0x00   // No parms
#define   SC_VBLANK 0x01   // No parms
#define   SC_IF 0x02   // u8 val u32 target
#define   SC_IFTRUE 0x03   // u32 target
#define   SC_GOTO 0x04   // u32 target
#define   SC_SETPLOTFLAG 0x05   // u8 flag
#define   SC_CLEARPLOTFLAG 0x06   // u8 flag
#define   SC_CHECKPLOTFLAG 0x07   // u8 flag
#define   SC_TEXT 0x10   // u32 text
#define   SC_ERROR 0x11   // u32 text
#define   SC_ASK 0x18   // u32 text u8 ccnt
#define SC_WAITFORKEY 0x19 // No parms
#define   SC_PLAYSONG 0x20   // u16 song
#define   SC_STOPSONG 0x21   // No parms
#define   SC_PLAYSOUND 0x22   // u16 song
#define   SC_REVEALCGPIC 0x23   // u8 pic
#define   SC_SHOWPICTURE 0x24   // u8 pic
#define   SC_HALTANIMATION 0x25   // No parms
#define   SC_FADE   0x26   // u8 inout
#define   SC_FLASH 0x27   // No parms
#define GetScriptByte      MyByte   =  ScriptData[ScriptCursor++];
#define GetScriptHWord   MyHWord   =  ScriptData[ScriptCursor++];            \
                                    MyHWord   |= ScriptData[ScriptCursor++] << 8;
#define GetScriptWord      MyWord   =  ScriptData[ScriptCursor++];            \
                                    MyWord   |= ScriptData[ScriptCursor++] << 8;      \
                                    MyWord   |= ScriptData[ScriptCursor++] << 16;   \
                                    MyWord   |= ScriptData[ScriptCursor++] << 24;

extern const u8 ScriptData[];
u16 ScriptCursor;
u8 SC_LastResult;
void ScriptTick(void)
{
   u16* pal = (u16*)0x5000000;
   u8 MyByte;
   u16 MyHWord;
   u32   MyWord;

   GetScriptByte;
   WriteText(1,31,2,31,"Cmd:\nPC: \nLst:",0);
   DrawNum(MyByte,5,2);
   DrawNum(ScriptCursor,5,3);
   DrawNum(SC_LastResult,5,4);
   switch(MyByte)
   {
      case SC_NOP: break;
      case SC_VBLANK:
         DoVBlank();
         break;
      case SC_IF:
         GetScriptByte;
         GetScriptWord;
         if(SC_LastResult == MyByte) ScriptCursor = MyWord - ((u32)ScriptData);
         break;
      case SC_IFTRUE:
         GetScriptWord;
         if(SC_LastResult) ScriptCursor = MyWord - ((u32)ScriptData);
         break;
      case SC_GOTO:
         GetScriptWord;
         ScriptCursor = MyWord - ((u32)ScriptData);
         break;
      case SC_SETPLOTFLAG:
         GetScriptByte;
         data.PlotFlags |= MyByte;
         break;
      case SC_CLEARPLOTFLAG:
         GetScriptByte;
         //TODO: Find proper bit operand to clear a bit.
         break;
      case SC_CHECKPLOTFLAG:
         GetScriptByte;
         SC_LastResult = (data.PlotFlags & MyByte) ? 1 : 0;
         //if(data.PlotFlags & MyByte)
         //   SC_LastResult = 1;
         //else
         //   SC_LastResult = 0;
         break;
      case SC_TEXT:
         //DEBUG ONLY: Shouldn't be used in final script builds!
         GetScriptWord;
         WriteText(1,31,1,31,"                            ",0);
         WriteText(1,31,1,31,(u8*)MyWord,0);
         break;
      case SC_ASK:
         GetScriptWord;
         GetScriptByte;
         SC_LastResult = GetChoice((u8*)MyWord,MyByte);
         break;
      case SC_WAITFORKEY:
         Trg = 0;
         while(!(Trg & A_BUTTON))
         {
            DoVBlank();
            KeyRead();
         }
         break;
      case SC_PLAYSONG:
         GetScriptHWord;
         PlaySong(MyHWord);
         break;
      case SC_STOPSONG:
         PlaySong(0);
         break;
      case SC_PLAYSOUND:
         GetScriptHWord;
         m4aSongNumStart(MyHWord);
         break;
      case SC_REVEALCGPIC:
         GetScriptByte;
         data.ImageFlags |= MyByte;
         break;
      case SC_SHOWPICTURE:
         GetScriptByte;
         ShowPic(MyByte);
         break;
      case SC_HALTANIMATION:
         nextPicTimer=0;
         break;
      case SC_FADE:
         GetScriptByte;
         switch(MyByte)
         {
            case 0: FadeOut(); break;
            case 1: FadeIn(); break;
            default: RaiseError("Invalid para-\nmeter for\nFADE.\n \n0 - out\n1 - in");
         }
         break;
      case SC_FLASH:
         Flash();
         break;
      default:
         break;
   }
}


Put this in a .s file...
Code:
   .section .rodata
   .align   2

   .equ   NOP,      0x00   @ No parms
   .equ   VBLANK,      0x01   @ No parms
   .equ   IF,      0x02   @ u8 val u32 target
   .equ   IFTRUE,      0x03   @ u32 target
   .equ   GOTO,      0x04   @ u32 target
   .equ   SETPLOTFLAG,   0x05   @ u8 flag
   .equ   CLEARPLOTFLAG,   0x06   @ u8 flag
   .equ   CHECKPLOTFLAG,   0x07   @ u8 flag
   .equ   TEXT,      0x10   @ u32 text
   .equ   ERROR,      0x11   @ u32 text
   .equ   ASK,      0x18   @ u32 text u8 ccnt
   .equ   WAITFORKEY,   0x19   @ No parms
   .equ   PLAYSONG,   0x20   @ u16 song
   .equ   STOPSONG,   0x21   @ No parms
   .equ   PLAYSOUND,   0x22   @ u16 song
   .equ   REVEALCGPIC,   0x23   @ u8 pic
   .equ   SHOWPICTURE,   0x24   @ u8 pic
   .equ   HALTANIMATION,   0x25   @ No parms
   .equ   FADE,      0x26   @ u8 inout
   .equ   FLASH,      0x27   @ No parms
   @ TODO: release this on GBADev.
   @ TODO: think of a nice name for this little scripting language.
   @ TODO: add simple math functions.
   
   .global ScriptData
ScriptData:

SillyQuestionTest:
   .byte   PLAYSONG
    .short   0x0002
   .byte   SHOWPICTURE
    .byte   6
   .byte   FADE
    .byte   1
AskIt:
   .byte   ASK
    .word   Questions
    .byte   4
   .byte   IF
    .byte   1
    .word   AnswerAChosen
   .byte   IF
    .byte   2
    .word   AnswerBChosen
   .byte   IF
    .byte   3
    .word   AnswerCChosen   
   .byte   IF
    .byte   4
    .word   AnswerDChosen   
   .byte   GOTO
    .word   EndLoop
AnswerAChosen:
   .byte   TEXT
    .word   AnswerAText
   .byte   WAITFORKEY
   .byte   GOTO
    .word   EndLoop
AnswerBChosen:
   .byte   TEXT
    .word   AnswerBText
   .byte   WAITFORKEY
   .byte   GOTO
    .word   EndLoop
AnswerCChosen:
   .byte   TEXT
    .word   AnswerCText
   .byte   WAITFORKEY
   .byte   GOTO
    .word   EndLoop
AnswerDChosen:
   .byte   FADE
    .byte   17   @ Fade can only go in (1) or out (0).
EndLoop:
   .byte   VBLANK
   .byte   GOTO
    .word   AskIt
   
Questions:   .asciz   "Answer A\nAnswer B\nFuck you!\nCause an error"
AnswerAText:   .asciz   "You chose Answer A."
AnswerBText:   .asciz   "You chose Answer B."
AnswerCText:   .asciz   "Yo momma!"


Cearn, your turn to comment on this stuff ;)

#36569 - pollier - Thu Feb 24, 2005 7:56 pm

I'm not Cearn, but I'd like to make comments anyways~

This is pretty cool and has some elegant compact code. However, the ask-a-question, do-an-if-test-4-times bit in the example seem like a waste of space; you might as well just build the gotos straight into the syntax of the SC_IF.

You might like to have a SC_DELAY opcode too. And if you don't need too much math, you could have an opcode that takes a pointer to a regular C function and a few arguments, and calls it.

(Of course, if this was me writing the code, I'd be obsessed with compressing this as much as possible, packing my bits, but then I wouldn't be able to write it in assembly.)

Oh, and I think you can clear a bit by writing data.PlotFlags &= !(u32)MyByte; .

In any case, this is a great little engine! Add some object animation support in there and your game'll look spectacular.

-Paul
_________________
(Works for me!)

#36570 - Kyoufu Kawa - Thu Feb 24, 2005 8:48 pm

Thanks. I already figured out the bit clearing part by myself, added Script Memory (256 bytes worth of temporary variables) and some commands to use it.

Thanks for the suggestion on the multiple IFs, but I'm not you and like it this way. Add it yourself if you want to, see below. If by object animation you mean sprite manipulation support, that's not needed in my game - it uses full-screen FMV.

Anybody who needs a scripting engine and is too mushed in the head to write one may use this one under very non-limiting conditions.

1) Somewhere in the finished product must be a line that reads "JESUS scripting engine by Kyoufu Kawa and Cearn" or something to that effect.
2) I want to know when, where and by who it's used.
3) Any really cool additions would be nice to know of.

That's all really. Yes, the engine's called JESUS. It's an acronym you don't want to know the meaning of.

#36571 - sajiimori - Thu Feb 24, 2005 8:54 pm

It's good that you started with hand-typed bytecode like that. It really makes you focus on data requirements.

If you end up having lots of scripting to do, consider writing a compiler that will let you write scripts in a more structured and readable manner. It's a very fun and challenging task if you haven't done it before.
Code:
function answerAChosen()
   text("You chose Answer A.")
   waitForKey()
end

while 1 do
   playSong(2)
   showPicture(6)
   fade(1)

   ask(
     ["Answer A", answerAChosen],
     ["Answer B", answerBChosen],
     ["Darn you!", answerCChosen],
     ["Cause an error", answerDChosen])
    
   vBlank()
end
Or choose your favorite syntax. ^_^

#36572 - Kyoufu Kawa - Thu Feb 24, 2005 9:25 pm

I would, but all I ever managed to write compiler-wise was Rubikon, for Pokemon Advance. Compilers fall under Rule Three ;)

#36573 - sajiimori - Thu Feb 24, 2005 9:42 pm

I don't know what Rule Three is, but if you need any help I'd be happy to give you some tips (or even some source code). With the right approach, compiler writing can be a very satisfying experience.

#36575 - Kyoufu Kawa - Thu Feb 24, 2005 9:50 pm

Quote:
3) Any really cool additions would be nice to know of.


That's rule three, and maybe I should rephrase that.

Quote:
3) Any really cool additions would be nice to see.


In other words, I can't write a compiler, but if you can provide at least a framework, that'd be very swell.

#36578 - sajiimori - Fri Feb 25, 2005 12:24 am

Heheh... I wasn't looking for a project for myself. It was just a suggestion for you. ^_^ I can offer guidance, but if the motivation isn't there for you then I won't be of much use.

#36594 - Kyoufu Kawa - Fri Feb 25, 2005 2:29 pm

Don't worry. That's what Google and stuff are for ;)

#36622 - AnthC - Sat Feb 26, 2005 4:59 am

Kyoufu Kawa wrote:
Being about halfway through writing a quite solid codebase for my game, I've been contemplating to write a reasonably simple scripting engine. It need not be too complex, but by now it's common knowledge that pointers don't like me and vice versa.

I thought I could just use an .s file full of .byte and .word statements with predefined (more like equ) commands and stuff...
Code:
.byte PLAYSOUND
.byte 6
.byte FADE
.byte 0 @ fade in/out param
.byte SHOWTEXT
.word (some pointer thingy here)


<ANTH : SNIP>


Rather than going to ASM why not code these in C?
I did a similar thing in Cauldron using some macros I wrote.

e.g.

#define SPEED(A) (U8)A_CMD_SPEED,(U8)(A),
#define LOOPS(A) (U8)A_CMD_LOOPS,(U8)(A),
#define SETFR(A) (U8)A_CMD_SETFR,(U8)(A),(U8)((A)>>8),

etc etc.

const U8 AnmWitchStirR[]=
{
SPEED(8)
LOOPS(INFINATE)
SETFR(WITCHSTIR)
NEXTFR
NEXTFR
LOOPE
};

<EDIT : Just looked you have goto's which is a problem since you need labels to access them, ASM Macro's would do the equivalent and give you more flexibility - the main point of the macros is to avoid simple bugs creeping in>

Anth

#36787 - Kyoufu Kawa - Tue Mar 01, 2005 8:19 pm

And now, allow me to present you the final 1.0 release of the JESUS scripting engine kit!

Zip contents:
* HEROD compiler (more like translator but hey)
* Sample script
* How to do loops, for the n00bs
* A bare-bones JESUS interpreter. Just the system commands, nearly nothing from my game left.

JESUS has:
* Subroutines, thanks to a crude stack of user-definable size
* No word-alignment issues.
* Reasonable speed.

Get it here.