def __init__(
        self,
        file_id=-1,
        speaker=-1,
        speaking=False,
        sprite=SpriteId(),
        voice=VoiceId(),
        bgm=-1,
        headshot=-1,
        mode=None,
        special=None,
        format=None,
        box_color=common.BOX_COLORS.yellow,
        box_type=common.BOX_TYPES.normal,
        ammo=-1,
        present=-1,
        bgd=-1,
        cutin=-1,
        flash=-1,
        movie=-1,
        img_filter=None,
        chapter=-1,
        scene=-1,
        room=-1,
        extra_val=-1,
        goto_ch=-1,
        goto_scene=-1,
        goto_room=-1,
    ):
        self.file_id = file_id

        self.speaker = speaker
        self.speaking = speaking
        self.sprite = sprite
        self.voice = voice
        self.bgm = bgm

        self.headshot = headshot

        self.mode = mode
        self.special = special
        self.format = format
        self.box_color = box_color
        self.box_type = box_type

        self.ammo = ammo
        self.present = present
        self.bgd = bgd
        self.cutin = cutin
        self.flash = flash
        self.movie = movie

        self.img_filter = img_filter

        self.chapter = chapter
        self.scene = scene
        self.room = room

        self.extra_val = extra_val
    def load(self, filename):
        filename = filename.lower()

        if not filename in MTB_DIR:
            _LOGGER.error("Invalid MTB file: %s" % filename)
            return

        self.filename = filename

        script_dir = MTB_DIR[filename]
        self.script_pack = ScriptPack(script_dir,
                                      common.editor_config.data01_dir)

        # --- MTB FORMAT ---
        # A nested pak of our standard game paks.
        # Each file comes with three paks (four in the first game).
        # The first pak has a single file with information about the MTB as a whole.
        # The second one contains information about individual lines.
        # The third one, I dunno. Always seems to have 120 items?

        mtb = ConstBitStream(filename=os.path.join(
            common.editor_config.data01_dir, BIN_DIR, self.filename))
        paks = [data for name, data in get_pak_files(mtb)]

        mtb_data = paks[0]
        line_data = paks[1]

        for name, data in get_pak_files(mtb_data):
            mtb_index = data.read("uintle:16")
            sprite_char = data.read("uintle:16")
            voice_char = data.read("uintle:16")
            sprite_id = data.read("uintle:16")

        sprite = SpriteId(SPRITE_TYPE.stand, sprite_char, sprite_id)

        for name, data in get_pak_files(line_data):
            file_id = data.read("uintle:16")
            voice_ch = data.read("uintle:16")
            voice_id = data.read("uintle:16")

            voice = VoiceId(voice_char, voice_ch, voice_id)

            self.script_pack[file_id].scene_info.sprite = sprite
            self.script_pack[file_id].scene_info.voice = voice
Example #3
0
    def load(self, filename):
        filename = filename.lower()

        if not filename in DIR_MAP:
            _LOGGER.error("Invalid nonstop file: %s" % filename)
            return

        self.filename = filename

        script_dir = DIR_MAP[filename]
        self.script_pack = ScriptPack(script_dir,
                                      common.editor_config.data01_dir)

        file_order = []

        # --- NONSTOP FORMAT ---
        # XX XX -- ???
        # XX XX -- Number of lines (not necessarily files)
        #
        # [68 bytes per line]
        # XX XX       -- File number
        # XX XX XX XX
        #  * 0x00000000 = normal line
        #  * 0x01000100 = chatter
        #  * 0x01000000 = ??? (Only appears in SDR2)
        #  * 0x02000000 = ??? (Only appears in SDR2)
        #  * 0x03000000 = ??? (Only appears in SDR2)
        #  * 0x04000000 = ??? (Only appears in SDR2)
        # XX XX       -- Ammo ID that reacts to this line.
        # XX XX       -- Converted line ID that reacts to this line.
        # XX XX       -- ???
        # XX XX       -- 1 = has a weak point, 0 = has no weak point
        # XX XX       -- The amount of time before the next line is shown (in sixtieths of seconds (frames?)).
        # XX XX       -- Unknown (Possibly line orientation? Only 0 in the first game, but sometimes 2 in the second.)
        # XX XX       -- Effect used when transitioning text in.
        # XX XX       -- Effect used when transitioning text out.
        #  * 0: fade
        #  * 1: spin in/out
        #  * 2: zoom out
        #  * 3: slide in/out
        # XX XX       -- The amount of the the line stays on-screen (in sixtieths of seconds (frames?)).
        # XX XX       -- Initial X position (text centered around this pos).
        # XX XX       -- Initial Y position (text centered around this pos).
        # XX XX       -- Text velocity.
        # XX XX       -- Angle of motion.
        # XX XX       -- Initial text zoom (in percent).
        # XX XX       -- Change in zoom over time (in percent).
        #  * 90% means it gradually shrinks.
        #  * 100% means it stays the same size the whole time.
        #  * 110% means it gradually grows.
        # XX XX       -- 0 = no shake, 1 = shake
        # XX XX       -- Rotate the text clockwise to this angle.
        # XX XX       -- Text spins clockwise at this rate.
        # XX XX       -- Speaker (00 00 if chatter)
        # XX XX       -- Sprite ID (00 00 if chatter)
        # XX XX XX XX -- ???
        # XX XX       -- Voice index (FF FF if chatter)
        # XX XX       -- ???
        # XX XX       -- Chapter
        # XX XX XX XX -- ??? (padding?)
        nonstop = ConstBitStream(filename=os.path.join(
            common.editor_config.data01_dir, NONSTOP_DIR, self.filename))

        self.magic = nonstop.read(16)
        num_lines = nonstop.read('uintle:16')

        # Four byte header plus 68 bytes per line.
        if nonstop.len < (4 + (num_lines * 68)) * 8:
            raise Exception("Invalid nonstop file.")

        prev_non_chatter = -1
        self.lines = []

        for i in range(num_lines):
            line = NonstopLine()

            line.file_num = nonstop.read('uintle:16')

            line.line_type = nonstop.read(32)
            if line.line_type in LINE_TYPE_MAP:
                line.line_type = LINE_TYPE_MAP[line.line_type]

            line.ammo_id = nonstop.read('intle:16')
            line.converted_id = nonstop.read('intle:16')
            line.unknown1 = nonstop.read(16)
            line.weak_point = nonstop.read('uintle:16')

            line.delay = nonstop.read('intle:16')
            line.orientation = nonstop.read('intle:16')
            line.in_effect = nonstop.read('intle:16')
            line.out_effect = nonstop.read('intle:16')
            line.time_visible = nonstop.read('intle:16')
            line.x_start = nonstop.read('intle:16')
            line.y_start = nonstop.read('intle:16')
            line.velocity = nonstop.read('intle:16')
            line.angle = nonstop.read('intle:16')
            line.zoom_start = nonstop.read('intle:16')
            line.zoom_change = nonstop.read('intle:16')
            line.shake = nonstop.read('intle:16')
            line.rot_angle = nonstop.read('intle:16')
            line.spin_vel = nonstop.read('intle:16')

            line.speaker = nonstop.read('intle:16')

            # Since we mess with speaker a little bit later, we want to keep the ID for the sprite.
            line.char_id = line.speaker
            line.sprite_id = nonstop.read('intle:16')

            line.unknown3 = nonstop.read(32)
            line.voice_id = nonstop.read('intle:16')
            line.unknown4 = nonstop.read(16)
            line.chapter = nonstop.read('intle:16')
            line.unknown5 = nonstop.read(32)
            line.unknown6 = nonstop.read(64)

            format = copy.deepcopy(TEXT_FORMATS[common.SCENE_MODES.debate])
            format.orient = TEXT_ORIENT.hor if line.orientation == 0 else TEXT_ORIENT.ver
            format.align = TEXT_ALIGN.center

            if format.orient == TEXT_ORIENT.ver:
                format.y = format.h
                format.x = format.w / 3.5

            self.script_pack[line.file_num].scene_info.format = format

            if line.line_type == NONSTOP_LINE_TYPE.normal:
                prev_non_chatter = line.file_num

                # Fixing some weirdness.
                # if filename in ["nonstop_06_003.dat", "nonstop_06_005.dat", "nonstop_06_006.dat", "nonstop_06_007.dat"] and line.speaker == 16:
                # line.speaker = 15
                # if filename[:10] == "nonstop_06" and int(filename[11:14]) >= 10 and line.speaker == 10:
                # line.speaker = 18
                # if filename in ["nonstop_02_003.dat", "nonstop_02_005.dat", "nonstop_04_005.dat", "nonstop_04_006.dat"] and line.speaker == 10:
                # line.speaker = 18

                self.script_pack[
                    line.file_num].scene_info.speaker = line.speaker

                sprite = SpriteId(SPRITE_TYPE.stand, line.char_id,
                                  line.sprite_id)
                self.script_pack[line.file_num].scene_info.sprite = sprite

                voice = VoiceId(line.speaker, line.chapter, line.voice_id)
                self.script_pack[line.file_num].scene_info.voice = voice

                self.script_pack[
                    line.
                    file_num].scene_info.special = common.SCENE_SPECIAL.debate

            elif "hanron" in str(line.line_type):

                self.script_pack[
                    line.file_num].scene_info.speaker = line.speaker

                sprite = SpriteId(SPRITE_TYPE.stand, line.char_id,
                                  line.sprite_id)
                self.script_pack[line.file_num].scene_info.sprite = sprite

                voice = VoiceId(line.speaker, line.chapter, line.voice_id)
                self.script_pack[line.file_num].scene_info.voice = voice

                self.script_pack[
                    line.
                    file_num].scene_info.special = common.SCENE_SPECIAL.hanron

            elif line.line_type == NONSTOP_LINE_TYPE.chatter:
                self.script_pack[line.file_num].scene_info.speaker = -1
                self.script_pack[
                    line.
                    file_num].scene_info.special = common.SCENE_SPECIAL.chatter
                self.script_pack[
                    line.file_num].scene_info.extra_val = prev_non_chatter

            else:
                _LOGGER.error("Invalid line type: %s" % line.line_type)

            file_order.append(line.file_num)
            self.lines.append(line)

        for index in xrange(len(self.script_pack)):
            if not index in file_order:
                file_order.append(index)

        self.script_pack.script_files = [
            self.script_pack[i] for i in file_order
        ]
def to_scene_info(commands):

    cur_speaker = 0x1F
    cur_sprite = SpriteId()
    last_sprite = -1
    cur_voice = VoiceId()
    cur_bgm = -1
    cur_trialcam = None
    cur_sprite_obj = None
    is_option = False
    is_option_pt = False
    option_val = None
    is_floating = False
    show_tag = True
    is_speaking = True

    img_filter = False

    cur_mode = None
    cur_room = -1

    check_obj = -1
    check_char = -1

    cur_ammo = -1
    cur_cutin = -1
    cur_bgd = -1
    cur_flash = -1
    cur_movie = -1

    # Because we can put flashes on top of flashes.
    flash_stack = []

    # If we set the speaker with a speaker tag,
    # don't let a voice file/sprite override it.
    speaker_set = False

    loaded_sprites = {}

    box_color = common.BOX_COLORS.orange
    box_type = common.BOX_TYPES.normal

    wrd_info = []

    for op, params in commands:

        ########################################
        ### Show line
        ########################################
        if op == WRD_SHOW_LINE:
            line = params["line"]

            scene_info = SceneInfo()
            scene_info.file_id = line

            if not cur_mode == None:
                scene_info.mode = cur_mode

            scene_info.room = cur_room

            if not show_tag:
                scene_info.speaker = -1
            else:
                scene_info.speaker = cur_speaker

            scene_info.speaking = is_speaking
            scene_info.sprite = cur_sprite
            scene_info.voice = cur_voice
            scene_info.bgm = cur_bgm

            scene_info.box_color = box_color
            scene_info.box_type = box_type

            scene_info.ammo = cur_ammo
            scene_info.bgd = cur_bgd
            scene_info.cutin = cur_cutin
            scene_info.flash = cur_flash
            scene_info.movie = cur_movie

            scene_info.img_filter = img_filter

            if not check_obj == -1:
                scene_info.special = common.SCENE_SPECIAL.checkobj
                scene_info.extra_val = check_obj
                check_obj = -1
                check_char = -1

            elif not check_char == -1:
                scene_info.special = common.SCENE_SPECIAL.checkchar
                scene_info.extra_val = check_char
                check_obj = -1
                check_char = -1

            if is_option_pt:
                scene_info.special = common.SCENE_SPECIAL.showopt
                scene_info.extra_val = option_val
            elif is_option:
                scene_info.speaker = -1
                scene_info.special = common.SCENE_SPECIAL.option
                scene_info.extra_val = option_val

            #scene_info.trialcam = cur_trialcam

            ##############################
            ### Reset stuff
            ##############################
            cur_ammo = -1

            scene_info.headshot = cur_sprite_obj

            if is_option:
                is_option = False
                option_val = None

            # is_option_pt = False
            is_floating = False
            speaker_set = False

            cur_voice = VoiceId()

            wrd_info.append(scene_info)

        ########################################
        ### Image filter
        ########################################
        elif op == WRD_FILTER_IMG:
            filter = params["filter"]

            if filter == 0x00:
                img_filter = IMG_FILTERS.unfiltered
            elif filter == 0x01:
                img_filter = IMG_FILTERS.sepia
            elif filter == 0x05:
                img_filter = IMG_FILTERS.inverted
            else:
                img_filter = IMG_FILTERS.unfiltered

        ########################################
        ### Play movie
        ########################################
        elif op == WRD_MOVIE:

            id = params["id"]
            state = params["state"]

            # Clear everything first, since a new call takes
            # priority of display over an old call.
            cur_bgd = -1
            cur_flash = -1
            cur_movie = -1

            if state == 1:
                cur_movie = id

        ########################################
        ### Show flash/cutin
        ########################################
        elif op == WRD_FLASH:
            # If id < 1000, then it's a flash event.
            # If id > 3000, it's a cutin.
            # I've seen numbers between 1000 and 3000, but I'm not sure what they're for.
            id = params["id"]
            state = params["state"]

            # An actual cutin.
            if id >= 3000:
                if state == 1:
                    cur_cutin = id - 3000
                else:
                    cur_cutin = -1

            # A flash event.
            elif id < 1000:

                # Clear other stuff first, since a new call takes
                # priority of display over an old call.
                cur_bgd = -1
                cur_movie = -1

                # These flash IDs are special trial animations that kind of screw things up.
                invalid_flash = [500, 501, 502, 503]

                if (state in [1, 3, 4]) and (id not in invalid_flash):
                    if id in flash_stack:
                        flash_stack.remove(id)
                    flash_stack.append(id)

                elif state == -1 and len(flash_stack) > 0:
                    if id in flash_stack:
                        flash_stack.remove(id)
                    else:
                        flash_stack.pop()

                if len(flash_stack) == 0:
                    cur_flash = -1
                else:
                    cur_flash = flash_stack[-1]
                    cur_trialcam = None
                    cur_sprite = SpriteId()

        ########################################
        ### Play a voice
        ########################################
        elif op == WRD_VOICE:
            char_id = params["char_id"]
            chapter = params["chapter"]
            voice_id = params["voice_id"]

            cur_voice = VoiceId(char_id, chapter, voice_id)

            if not speaker_set:
                cur_speaker = char_id

        ########################################
        ### Play BGM
        ########################################
        elif op == WRD_BGM:
            id = params["id"]
            transition = params["transition"]

            cur_bgm = id

        ########################################
        ### Get/update/clear ammo
        ########################################
        elif op == WRD_SET_AMMO:
            # If ID == 0xFF & State == 0x00, clear all ammo from ElectroiD
            # State == 01: Add to ElectroiD
            # State == 02: Update info

            id = params["id"]
            state = params["state"]

            if state in [0x01, 0x02]:
                cur_ammo = id
            else:
                cur_ammo = -1

        ########################################
        ### Move camera during Class Trial
        ########################################
        elif op == WRD_TRIAL_CAM:
            char_id = params["char_id"]
            motion = params["motion"]

            cur_trialcam = char_id

            if not char_id in loaded_sprites:
                cur_sprite = SpriteId()
            else:
                cur_sprite = loaded_sprites[char_id]

        ########################################
        ### Load 3D map
        ########################################
        elif op == WRD_LOAD_MAP:
            room = params["room"]
            state = params["state"]

            if state == 0:
                cur_room = room
                cur_mode = common.SCENE_MODES.normal

        ########################################
        ###
        ########################################
        elif op == WRD_SPRITE:
            obj_id = params["obj_id"]
            char_id = params["char_id"]
            sprite_id = params["sprite_id"]
            # 00 = Kill (?)
            # 01 = Show (?)
            # 03 = Fade out (?)
            # 04 = Hide (?)
            sprite_state = params["sprite_state"]
            sprite_type = params["sprite_type"]

            cur_sprite_obj = obj_id

            sprite_info = SpriteId(SPRITE_TYPE.bustup, char_id, sprite_id)
            loaded_sprites[char_id] = sprite_info

            last_sprite = char_id

            # If we have a camera, that means we might not be showing a sprite
            # just because we loaded it. Wait for the camera flag to point at a sprite.
            if cur_trialcam == None:
                if sprite_state in [0x00, 0x03, 0x04, 0x05, 0x07, 0x0A]:
                    cur_sprite = SpriteId()
                    continue
                else:
                    cur_sprite = sprite_info

            if not speaker_set:
                cur_speaker = char_id

        ########################################
        ### Set speaker tag
        ########################################
        elif op == WRD_SPEAKER:
            id = params["id"]

            if id in common.CHAR_IDS:
                cur_speaker = id
                speaker_set = True
            elif id == 0x1C:
                cur_speaker = last_sprite
                speaker_set = True

        ########################################
        ###
        ########################################
        elif op == WRD_CHANGE_UI:
            # Element / State
            # 00 00 = "Speaking" window
            # 00 01 = "Thoughts" window
            # 01 00 = Hide text box (?)
            # 01 01 = Show text box (?)
            # 02 00 = Hide nametag (?)
            # 02 01 = Show nametag (?)
            # 03 00 = Orange box (?)
            # 03 01 = Green box (?)
            # 03 02 = Blue box (?)
            # 04 YY = ??
            # 06 YY = ??
            # 07 YY = ??
            # 09 YY = ??
            # 0B YY = ??
            # 0D 00 = Stop shaking
            # 0D 01 = Start shaking
            # 33 00 = Normal, round text box (?)
            # 33 01 = Flat, black overlay (?)
            element = params["element"]
            state = params["state"]

            # Text box
            if element == 0x00:
                if state == 0x00:
                    is_speaking = True
                elif state == 0x01:
                    is_speaking = False

                show_tag = True

            # Text box
            elif element == 0x01:
                if state == 0x00:
                    # Is it safe to assume that when we kill the text box
                    # we are also killing any existing BGDs and the like?
                    # cur_bgd   = -1
                    # cur_cutin = -1
                    # cur_flash = -1
                    # cur_movie = -1
                    # cur_ammo  = -1
                    pass

            # Speaker tag
            elif element == 0x02:
                if state == 0x00:
                    show_tag = False
                elif state == 0x01:
                    show_tag = True

            # Box type
            elif element == 0x33:
                if state == 0x00:
                    box_type = common.BOX_TYPES.normal
                elif state == 0x01:
                    box_type = common.BOX_TYPES.flat

        ########################################
        ### Check a person
        ########################################
        elif op == WRD_CHECK_CHAR:
            check_char = params["id"]
            check_obj = -1

        ########################################
        ### Check an object
        ########################################
        elif op == WRD_CHECK_OBJ:
            check_obj = params["id"]
            check_char = -1

        ########################################
        ### An options section
        ########################################
        elif op == WRD_CHOICE:
            # Option flag:
            # 01 = Choice ID (?)
            # 02 = Choice ID (?)
            # 03 = Choice ID (?)
            # 12 = Out of time (?)
            # 13 = Options prompt (?)
            # FF = End of options section (?)
            option_flag = params["flag"]

            if option_flag == 0x13:
                is_option_pt = True
                is_option = False
                option_val = "Prompt"

            elif option_flag == 0x12:
                is_option_pt = True
                is_option = False
                option_val = "Time Up"

            elif option_flag == 0xFF:
                is_option_pt = False
                is_option = False

            elif option_flag < 0x10:
                is_option = True
                is_option_pt = False
                option_val = option_flag

        ########################################
        ### Show a BGD
        ########################################
        elif op == WRD_BGD:
            id = params["id"]
            state = params["state"]

            # Clear everything first, since a new call takes
            # priority of display over an old call.
            cur_bgd = -1
            cur_flash = -1
            cur_movie = -1

            if state == 1:
                cur_bgd = id

                cur_trialcam = None
                cur_sprite = SpriteId()

        ### if op == WRD_??? ###
    ### for op, params in commands ###

    if len(wrd_info) == 0:
        return None
    else:
        return wrd_info


### EOF ###
Example #5
0
def to_scene_info(commands):
  
  cur_speaker   = 0x1F
  cur_sprite    = SpriteId()
  last_sprite   = -1
  cur_voice     = VoiceId()
  cur_bgm       = -1
  cur_trialcam  = None
  cur_sprite_obj  = None
  is_option     = False
  is_option_pt  = False
  option_val    = None
  show_tag      = True
  is_speaking   = True
  
  img_filter    = IMG_FILTERS.unfiltered
  
  cur_mode      = None
  cur_room      = -1
  
  check_obj     = -1
  check_char    = -1
  
  cur_ammo      = -1
  cur_cutin     = -1
  cur_present   = -1
  cur_bgd       = -1
  cur_flash     = -1
  cur_movie     = -1
  
  # Because we can put flashes on top of flashes.
  flash_stack   = []
  
  # If we set the speaker with a speaker tag,
  # don't let a voice file/sprite override it.
  speaker_set = False
  
  loaded_sprites = {}
  char_objects   = {}
  
  box_color = common.BOX_COLORS.yellow
  box_type  = common.BOX_TYPES.normal
  
  wrd_info = []
  
  for op, params in commands:
    
    ########################################
    ### Show line
    ########################################
    if op == WRD_SHOW_LINE or op == WRD_CALL_SCRIPT or op == WRD_GOTO_SCRIPT:
      scene_info = SceneInfo()
      
      if op == WRD_SHOW_LINE:
        scene_info.file_id = params["line"]
      
      else:
        scene_info.file_id    = None
        scene_info.goto_ch    = params["chapter"]
        scene_info.goto_scene = params["scene"]
        scene_info.goto_room  = params["room"]
      
      if not cur_mode == None:
        scene_info.mode = cur_mode
      
      scene_info.room = cur_room
      
      if not show_tag:
        scene_info.speaker = -1
      else:
        scene_info.speaker = cur_speaker
      
      scene_info.speaking   = is_speaking
      scene_info.sprite     = cur_sprite
      scene_info.voice      = cur_voice
      scene_info.bgm        = cur_bgm
      
      scene_info.box_color  = box_color
      scene_info.box_type   = box_type
      
      scene_info.ammo       = cur_ammo
      scene_info.bgd        = cur_bgd
      scene_info.cutin      = cur_cutin
      scene_info.present    = cur_present
      scene_info.flash      = cur_flash
      scene_info.movie      = cur_movie
      
      scene_info.img_filter = img_filter
      
      if not check_obj == -1:
        scene_info.special    = common.SCENE_SPECIAL.checkobj
        scene_info.extra_val  = check_obj
        # check_obj             = -1
        # check_char            = -1
      
      elif not check_char == -1:
        scene_info.special    = common.SCENE_SPECIAL.checkchar
        scene_info.extra_val  = (char_objects[check_char] if check_char in char_objects else "ID %d" % check_char)
        # check_obj             = -1
        # check_char            = -1
      
      if is_option:
        # scene_info.speaker    = -1
        scene_info.special    = common.SCENE_SPECIAL.option
        scene_info.extra_val  = option_val
      elif is_option_pt:
        scene_info.special    = common.SCENE_SPECIAL.showopt
        scene_info.extra_val  = option_val
      
      #scene_info.trialcam = cur_trialcam
      
      ##############################
      ### Reset stuff
      ##############################
      # cur_ammo = -1
      
      scene_info.headshot = cur_sprite_obj
      
      if is_option:
        is_option = False
        # option_val = None
      
      # is_option_pt = False
      speaker_set = False
      
      cur_voice = VoiceId()
      
      wrd_info.append(scene_info)
    
    ########################################
    ### Image filter
    ########################################
    elif op == WRD_FILTER_IMG:
      filter = params["filter"]
    
      if filter == 0x00:
        img_filter = IMG_FILTERS.unfiltered
      elif filter == 0x01:
        img_filter = IMG_FILTERS.sepia
      elif filter == 0x05:
        img_filter = IMG_FILTERS.inverted
      else:
        img_filter = IMG_FILTERS.unfiltered
    
    ########################################
    ### Play movie
    ########################################
    elif op == WRD_MOVIE:
    
      id    = params["id"]
      state = params["state"]
      
      # Clear everything first, since a new call takes
      # priority of display over an old call.
      cur_bgd   = -1
      cur_flash = -1
      cur_movie = -1
      
      if state == 1:
        cur_movie = id
    
    ########################################
    ### Show flash/cutin
    ########################################
    elif op == WRD_FLASH:
      # If id <  1000, then it's a flash event.
      # if id >= 1000, then it's ammo
      # if id >= 1500, then it's an ammo update
      # if id >= 2000, then it's a present
      # If id >= 3000, it's a cutin.
      id    = params["id"]
      state = params["state"]
    
      # An actual cutin.
      if id >= 3000:
        if state == 1:
          cur_cutin = id - 3000
        else:
          cur_cutin = -1
      
      elif id >= 2000:
        if state == 1:
          cur_present = id - 2000
        else:
          cur_present = -1
      
      elif id >= 1500:
        if state == 1:
          cur_ammo = id - 1500
        else:
          cur_ammo = -1
      
      elif id >= 1000:
        if state == 1:
          cur_ammo = id - 1000
        else:
          cur_ammo = -1
      
      # A flash event.
      elif id < 1000:
      
        # Clear other stuff first, since a new call takes
        # priority of display over an old call.
        cur_bgd   = -1
        cur_movie = -1
        
        # These flash IDs are special trial animations that kind of screw things up.
        invalid_flash = range(200, 245) + [2, 27, 246, 247]
        
        if state > 0 and (id not in invalid_flash):
          if id in flash_stack:
            flash_stack.remove(id)
          flash_stack.append(id)
        
        elif state == -1 and len(flash_stack) > 0:
          if id in flash_stack:
            flash_stack.remove(id)
          else:
            flash_stack.pop()
        
        if len(flash_stack) == 0:
          cur_flash = -1
        else:
          cur_flash = flash_stack[-1]
          cur_trialcam = None
          cur_sprite = SpriteId()
    
    ########################################
    ### Play a voice
    ########################################
    elif op == WRD_VOICE:
      char_id   = params["char_id"]
      chapter   = params["chapter"]
      voice_id  = params["voice_id"]
      
      cur_voice = VoiceId(char_id, chapter, voice_id)
      
      if not speaker_set:
        cur_speaker = char_id
    
    ########################################
    ### Play BGM
    ########################################
    elif op == WRD_BGM:
      id          = params["id"]
      transition  = params["transition"]
      
      cur_bgm = id
    
    ########################################
    ### Get/update/clear ammo
    ########################################
    elif op == WRD_SET_AMMO:
      # If ID == 0xFF & State == 0x00, clear all ammo from ElectroiD
      # State == 01: Add to ElectroiD
      # State == 02: Update info
      
      id    = params["id"]
      state = params["state"]
      
      # if state in [0x01, 0x02]:
        # cur_ammo = id
      # else:
        # cur_ammo = -1
    
    ########################################
    ### Move camera during Class Trial
    ########################################
    elif op == WRD_TRIAL_CAM:
      char_id = params["char_id"]
      motion  = params["motion"]
      
      cur_trialcam = char_id
      
      if not char_id in loaded_sprites:
        cur_sprite = SpriteId()
      else:
        cur_sprite = loaded_sprites[char_id]
    
    ########################################
    ### Load 3D map
    ########################################
    elif op == WRD_LOAD_MAP:
      room  = params["room"]
      state = params["state"]
      
      if state == 0:
        cur_room = room
        cur_mode = common.SCENE_MODES.normal
    
    ########################################
    ### 
    ########################################
    elif op == WRD_SPRITE:
      obj_id       = params["obj_id"]
      char_id      = params["char_id"]
      sprite_id    = params["sprite_id"]
      # 00 = Kill (?)
      # 01 = Show (?)
      # 03 = Fade out (?)
      # 04 = Hide (?)
      sprite_state = params["sprite_state"]
      sprite_type  = params["sprite_type"]
      
      cur_sprite_obj = obj_id
      
      if not obj_id in char_objects:
        char_objects[obj_id] = char_id
      
      sprite_info = SpriteId(SPRITE_TYPE.bustup, char_id, sprite_id)
      loaded_sprites[char_id] = sprite_info
      
      last_sprite = char_id
      
      # If we have a camera, that means we might not be showing a sprite
      # just because we loaded it. Wait for the camera flag to point at a sprite.
      if cur_trialcam == None:
        if sprite_state in [0x00, 0x03, 0x04, 0x05, 0x07, 0x0A, 0x10]:
          cur_sprite = SpriteId()
          continue
        # Kind of hackish. Sprites in 2D mode always seem to use obj_id 0 or 1,
        # but those IDs are also used in 3D mode, so I'm not sure how to tell
        # the difference between the two modes yet. So, we ignore any obj_id
        # greater than 1, because we don't care about the sprites in 3D.
        elif obj_id > 1:
          continue
        # Also kind of hackish. ID 98 is the "blank" model for characters
        # displayed in 3D, and sometimes, if the previous check misses when
        # the game is removing a character, it shows up as an "unknown" sprite.
        elif char_id == 98:
          cur_sprite = SpriteId()
          continue
        else:
          cur_sprite = sprite_info
      
      if not speaker_set:
        cur_speaker = char_id
    
    ########################################
    ### Set speaker tag
    ########################################
    elif op == WRD_SPEAKER:
      id = params["id"]
      
      # if id in common.CHAR_IDS:
      if id == 0x3E:
        if not speaker_set:
          cur_speaker = last_sprite
          speaker_set = True
      else:
        cur_speaker = id
        speaker_set = True
    
    ########################################
    ### 
    ########################################
    elif op == WRD_CHANGE_UI:
      # Element / State
      # 00 00 = "Speaking" window
      # 00 01 = "Thoughts" window
      # 01 00 = Hide text box (?)
      # 01 01 = Show text box (?)
      # 02 00 = Hide nametag (?)
      # 02 01 = Show nametag (?)
      # 03 00 = Orange box (?)
      # 03 01 = Green box (?)
      # 03 02 = Blue box (?)
      # 04 YY = ??
      # 06 YY = ??
      # 07 YY = ??
      # 09 YY = ??
      # 0B YY = ??
      # 0D 00 = Stop shaking
      # 0D 01 = Start shaking
      # 33 00 = Normal, round text box (?)
      # 33 01 = Flat, black overlay (?)
      element = params["element"]
      state   = params["state"]
      
      # Text box
      if element == 0x00:
        if state == 0x00:
          is_speaking = True
        elif state == 0x01:
          is_speaking = False
        
        show_tag = True
      
      # Text box
      elif element == 0x01:
        if state == 0x00:
          # Is it safe to assume that when we kill the text box
          # we are also killing any existing BGDs and the like?
          # cur_bgd   = -1
          # cur_cutin = -1
          # cur_flash = -1
          # cur_movie = -1
          # cur_ammo  = -1
          pass
      
      # Speaker tag
      # elif element == 0x02:
        # if state == 0x00:
          # show_tag = False
        # elif state == 0x01:
          # show_tag = True
      
      # Box type
      elif element == 0x33:
        if state == 0x00:
          box_type = common.BOX_TYPES.normal
        elif state == 0x01:
          box_type = common.BOX_TYPES.flat
    
    ########################################
    ### Check a person
    ########################################
    elif op == WRD_CHECK_CHAR:
      check_char  = params["id"]
      if check_char == 255:
        check_char = -1
      
      check_obj   = -1
    
    ########################################
    ### Check an object
    ########################################
    elif op == WRD_CHECK_OBJ:
      check_obj   = params["id"]
      if check_obj == 255:
        check_obj = -1
      check_char  = -1
    
    ########################################
    ### An options section
    ########################################
    elif op == WRD_CHOICE:
      # Option flag:
      # 01 = Choice ID (?)
      # 02 = Choice ID (?)
      # 03 = Choice ID (?)
      # 12 = Out of time (?)
      # 13 = Options prompt (?)
      # FF = End of options section (?)
      option_flag = params["flag"]
      
      if option_flag in [0x13, 0xFE]:
        is_option    = False
        is_option_pt = True
        option_val   = "Prompt"
      
      elif option_flag in [0x12, 0xFD]:
        is_option    = False
        is_option_pt = True
        option_val   = "Time Up"
      
      elif option_flag == 0xFF:
        is_option    = False
        is_option_pt = False
        
      elif option_flag < 0x10:
        is_option    = True
        is_option_pt = True
        option_val   = "Option %d" % option_flag
      
      elif option_flag == 0xFC:
        is_option    = False
        is_option_pt = True
        option_val   = "Generic Wrong"
      
      else:
        is_option    = False
        is_option_pt = True
        option_val   = "Unknown"
    
    ########################################
    ### Show a BGD
    ########################################
    elif op == WRD_BGD:
      id    = params["id"]
      state = params["state"]
      
      # Clear everything first, since a new call takes
      # priority of display over an old call.
      
      if state == 1:
        cur_bgd      = id
        cur_flash    = -1
        cur_movie    = -1
        cur_trialcam = None
        cur_sprite   = SpriteId()
      
      elif id == 65535 or id == -1:
        cur_bgd      = -1
        cur_flash    = -1
        cur_movie    = -1
      
      else:
        cur_bgd = -1
    
    ### if op == WRD_??? ###
  ### for op, params in commands ###
  
  if len(wrd_info) == 0:
    return None
  else:
    return wrd_info

### EOF ###
    def load(self, filename):
        filename = filename.lower()

        if not filename in MTB_DIR:
            _LOGGER.error("Invalid MTB file: %s" % filename)
            return

        self.filename = filename

        script_dir = MTB_DIR[filename]
        self.script_pack = ScriptPack(script_dir,
                                      common.editor_config.umdimage_dir)

        # --- MTB FORMAT ---
        # 12 bytes     -- ???
        # XX XX XX XX  -- Table offset
        # 24 bytes     -- ???
        #
        # XX XX        -- MTB Index
        # XX XX        -- Char ID for sprites
        # XX XX        -- Char ID for voices (chapter for voices is 0x63)
        # XX XX        -- Initial sprite ID (?)

        mtb = ConstBitStream(filename=os.path.join(
            common.editor_config.umdimage_dir, self.filename))

        mtb.read(12 * 8)
        table_offset = mtb.read("uintle:32")
        mtb.read(24 * 8)

        mtb_index = mtb.read("uintle:16")
        sprite_char = mtb.read("uintle:16")
        voice_char = mtb.read("uintle:16")
        sprite_id = mtb.read("uintle:16")

        sprite = SpriteId(SPRITE_TYPE.stand, sprite_char, sprite_id)

        # --- TABLE FORMAT ---
        # XX XX XX XX -- Number of files
        #
        # [for each line]
        # XX XX XX XX -- Offset (from table start) of voice info.
        #
        # -- Voice Info --
        # XX XX -- File ID
        # XX XX -- Voice ID (with char ID above and the chapter ID 0x63, we know which voice file to use)

        mtb.bytepos = table_offset
        num_files = mtb.read("uintle:32")

        for i in range(num_files):
            voice_offset = mtb.read("uintle:32")

            # Store our position in the table so we can jump back to it.
            table_pos = mtb.bytepos
            mtb.bytepos = table_offset + voice_offset

            file_id = mtb.read("uintle:16")
            voice_id = mtb.read("uintle:16")

            # Chapter is 0x63, which is where the non-Trial voice samples are stored,
            # but I don't see the information actually recorded in the MTB files,
            # so I'm magic-numbering it here.
            voice = VoiceId(voice_char, 0x63, voice_id)

            self.script_pack[file_id].scene_info.sprite = sprite
            self.script_pack[file_id].scene_info.voice = voice

            # Restore it to our old position.
            mtb.bytepos = table_pos