Beispiel #1
0
 def _fix_error(self, dungeons: List[DungeonDefinition], e: DungeonValidatorError):
     mappa = self.module.get_mappa()
     if isinstance(e, DungeonTotalFloorCountInvalidError):
         dungeons[e.dungeon_id].number_floors_in_group = e.expected_floor_count_in_group
     elif isinstance(e, InvalidFloorListReferencedError) or isinstance(e, FloorReusedError):
         dungeons[e.dungeon_id].mappa_index = self.module.mappa_generate_and_insert_new_floor_list()
         dungeons[e.dungeon_id].start_after = u8(0)
         dungeons[e.dungeon_id].number_floors = u8(1)
         dungeons[e.dungeon_id].number_floors_in_group = u8(1)
     elif isinstance(e, InvalidFloorReferencedError):
         valid_floors = len(mappa.floor_lists[e.dungeon.mappa_index]) - e.dungeon.start_after
         if valid_floors > 0:
             dungeons[e.dungeon_id].number_floors = u8_checked(valid_floors)
         else:
             mappa.floor_lists[e.dungeon.mappa_index].append(self.module.mappa_generate_new_floor())
             dungeons[e.dungeon_id].number_floors = u8(1)
     elif isinstance(e, DungeonMissingFloorError):
         # Special case for Regigigas Chamber
         if self._is_regigias_special_case(dungeons, e):
             # Remove additional floors
             mappa.floor_lists[e.dungeon.mappa_index] = [
                 f for i, f in enumerate(mappa.floor_lists[e.dungeon.mappa_index])
                 if i not in e.floors_in_mappa_not_referenced
             ]
         else:
             # Add additional floors
             # TODO: Raise error or warning if we can't fix it? It should really always be consecutive.
             if min(e.floors_in_mappa_not_referenced) == e.dungeon.start_after + e.dungeon.number_floors:
                 if check_consecutive(e.floors_in_mappa_not_referenced):
                     max_floor_id = max(e.floors_in_mappa_not_referenced)
                     dungeons[e.dungeon_id].number_floors = u8_checked(max_floor_id - dungeons[e.dungeon_id].start_after + 1)
Beispiel #2
0
 def on_cr_td_floor_number_edited(self, widget, path, text):
     store: Gtk.ListStore = self.builder.get_object('dungeon_tileset_store')
     try:
         u8_checked(int(text))
     except ValueError:
         return
     store[path][5] = text
     self._save_td()
Beispiel #3
0
 def on_entry_range_max_changed(self, w, *args):
     try:
         val = u8_checked(int(w.get_text()))
     except ValueError:
         return
     self.item_p.range_max = val
     self.mark_as_modified()
Beispiel #4
0
    def _convert_unknown_data_layer(self) -> bytes:
        """
        Converts the unknown data layer back into bytes
        Every row is NRL encoded separately, because the game decodes the rows separately!
        """
        from skytemple_files.common.types.file_types import FileType

        size = self.model.map_width_camera * self.model.map_height_camera
        assert self.model.unknown_data_block is not None
        assert size == len(self.model.unknown_data_block)

        layer_bytes = bytearray(size)
        layer_bytes_cursor = 0
        # Each tile is separately encoded, so we also build them separately
        for row in range(0, self.model.map_height_camera):
            row_bytes = bytearray(int(size / self.model.map_height_camera))
            for col in range(0, self.model.map_width_camera):
                i = row * self.model.map_width_camera + col
                actual_value = self.model.unknown_data_block[i]
                write_u8(row_bytes, u8_checked(actual_value), col)
            assert len(row_bytes) == int(size / self.model.map_height_camera)
            comp_row_bytes = FileType.GENERIC_NRL.compress(row_bytes)
            len_comp_row_bytes = len(comp_row_bytes)
            layer_bytes[layer_bytes_cursor:layer_bytes_cursor+len_comp_row_bytes] = comp_row_bytes
            layer_bytes_cursor += len_comp_row_bytes

        return layer_bytes[:layer_bytes_cursor]
Beispiel #5
0
 def on_entry_base_pp_changed(self, w, *args):
     try:
         val = u8_checked(int(w.get_text()))
     except ValueError:
         return
     self.move.base_pp = val
     self.mark_as_modified()
Beispiel #6
0
 def on_entry_message_id_changed(self, w, *args):
     try:
         val = u8_checked(int(w.get_text()))
     except ValueError:
         return
     self.move.message_id = val
     self.mark_as_modified()
Beispiel #7
0
 def on_entry_ai_condition1_chance_changed(self, w, *args):
     try:
         val = u8_checked(int(w.get_text()))
     except ValueError:
         return
     self.move.ai_condition1_chance = val
     self.mark_as_modified()
Beispiel #8
0
 def on_entry_number_chained_hits_changed(self, w, *args):
     try:
         val = u8_checked(int(w.get_text()))
     except ValueError:
         return
     self.move.number_chained_hits = val
     self.mark_as_modified()
Beispiel #9
0
 def on_entry_max_upgrade_level_changed(self, w, *args):
     try:
         val = u8_checked(int(w.get_text()))
     except ValueError:
         return
     self.move.max_upgrade_level = val
     self.mark_as_modified()
Beispiel #10
0
 def on_entry_miss_accuracy_changed(self, w, *args):
     try:
         val = u8_checked(int(w.get_text()))
     except ValueError:
         return
     self.move.miss_accuracy = val
     self.mark_as_modified()
Beispiel #11
0
    def on_cr_other_iq_gain_edited(self, widget, path, text):
        store: Gtk.ListStore = self.builder.get_object('iq_gain_other_items')
        static_data = self.module.project.get_rom_module().get_static_data()
        try:
            val = u8_checked(int(text))
        except ValueError:
            return
        typ: IqGainOtherItem = store[path][0]
        store[path][2] = text

        if typ == IqGainOtherItem.NECTAR:
            self.module.project.modify_binary(
                BinaryName.OVERLAY_29, lambda bin: HardcodedIq.set_nectar_gain(val, bin, static_data)
            )
            self.module.mark_iq_as_modified()
        elif typ == IqGainOtherItem.WONDER_GUMMI:
            self.module.project.modify_binary(
                BinaryName.ARM9, lambda bin: HardcodedIq.set_wonder_gummi_gain(val, bin, static_data)
            )
            self.module.mark_iq_as_modified()
        elif typ == IqGainOtherItem.JUICE_BAR_NECTAR:
            self.module.project.modify_binary(
                BinaryName.ARM9, lambda bin: HardcodedIq.set_juice_bar_nectar_gain(val, bin, static_data)
            )
            self.module.mark_iq_as_modified()
Beispiel #12
0
 def on_cr_stats_sp_def_edited(self, widget, path, text):
     store: Gtk.Store = self.builder.get_object('model_stats')
     try:
         v = u8_checked(int(text))
     except ValueError:
         return
     store[path][7] = text
     self.lst_stats[int(store[path][0])].special_defense = v
     self._save()
Beispiel #13
0
 def on_entry_palette_changed(self, w, *args):
     try:
         val = u8_checked(int(w.get_text()))
     except ValueError:
         return
     self.item_p.palette = val
     self.mark_as_modified()
     self._sprite_provider.reset()
     self.builder.get_object('draw_sprite').queue_draw()
Beispiel #14
0
 def on_cr_tiles_room_id_edited(self, widget, path, text):
     store: Gtk.Store = self.builder.get_object('model_tiles')
     try:
         v = u8_checked(int(text))
     except ValueError:
         return
     store[path][10] = text
     self.lst_tile[int(store[path][0])].room_id = v
     self._save()
Beispiel #15
0
    def on_entry_text_speed_changed(self, widget, *args):
        try:
            val = u8_checked(int(widget.get_text()))
        except ValueError:
            return

        static_data = self.module.project.get_rom_module().get_static_data()
        self.module.project.modify_binary(BinaryName.ARM9, lambda bin: HardcodedTextSpeed.set_text_speed(val, bin, static_data))
        self.module.mark_misc_settings_as_modified()
    def _build_list(self):
        dungeon = self._get_current_dungeon()

        self.inter_d.list_dungeons[dungeon] = []

        store_inter: Gtk.ListStore = self.builder.get_object('interrupt_store')

        for v in store_inter:
            e = InterDEntry()
            e.floor = u8_checked(v[0])
            e.ent_type = InterDEntryType(v[1])  # type: ignore
            e.game_var_id = u16_checked(v[2])
            e.param1 = u8_checked(v[3])
            e.param2 = u8_checked(v[4])
            e.continue_music = v[7]
            self.inter_d.list_dungeons[dungeon].append(e)

        self.module.mark_dungeon_interrupts_as_modified()
Beispiel #17
0
 def to_bytes(self):
     mevo_data = bytearray(MEVO_ENTRY_LENGTH)
     write_u16(mevo_data, u16_checked(len(self.evos)), 0)
     for j, x in enumerate(self.evos):
         write_u16(mevo_data, x, j * 2 + 2)
     write_u8(mevo_data, u8_checked(len(self.eggs)), 0x12)
     for j, x in enumerate(self.eggs):
         write_u16(mevo_data, x, j * 2 + 0x14)
     return mevo_data
Beispiel #18
0
    def write(self) -> bytes:
        buffer = bytearray(GRAPHIC_FONT_ENTRY_LEN * len(self.model.entries))

        # Font Data
        for i, e in enumerate(self.model.entries):
            if e:
                write_u8(buffer, u8_checked(e.width),
                         i * GRAPHIC_FONT_ENTRY_LEN + 0x00)
                write_u8(buffer, u8_checked(e.height),
                         i * GRAPHIC_FONT_ENTRY_LEN + 0x01)
                write_u16(buffer, u16_checked(len(buffer)),
                          i * GRAPHIC_FONT_ENTRY_LEN + 0x02)
                data_raw = e.tobytes("raw", "P")
                buffer += bytearray(data_raw)
            else:
                write_u32(buffer, u32(0xffff0000),
                          i * GRAPHIC_FONT_ENTRY_LEN + 0x00)

        return buffer
Beispiel #19
0
 def from_xml(cls, ele: Element) -> 'MappaMonster':
     validate_xml_tag(ele, XML_MONSTER)
     validate_xml_attribs(ele, [
         XML_MONSTER__LEVEL, XML_MONSTER__WEIGHT, XML_MONSTER__WEIGHT2, XML_MONSTER__MD_INDEX
     ])
     return cls(
         u8_checked(int(ele.get(XML_MONSTER__LEVEL))),
         u16_checked(int(ele.get(XML_MONSTER__WEIGHT))),
         u16_checked(int(ele.get(XML_MONSTER__WEIGHT2))),
         u16_checked(int(ele.get(XML_MONSTER__MD_INDEX))),
     )
Beispiel #20
0
 def _adjust_actual_dimensions(self, canvas_width: int, canvas_height: int):
     """ Adjusts the actual dimensions specified by the image mode so the image can fit into those dimensions. """
     width = 0
     while 8 * (2**width) < canvas_width:
         width += 1
     height = 0
     while 8 * (2**height) < canvas_height:
         height += 1
     if width >= 8 or height >= 8:  # Max theoretical size: (2**(7+3), 2**(7+3)) = (1024, 1024)
         raise ValueError('Canvas size too large.')
     self.actual_dim = u8_checked(width + (height << 3))
Beispiel #21
0
 def set_gummi_iq_gains(value: List[List[int]], arm9: bytearray, config: Pmd2Data, add_types_patch_applied: bool) -> None:
     dim, byte_size = IQ_GAINS_TABLES[add_types_patch_applied]
     block = config.binaries['arm9.bin'].symbols['IqGummiGain']
     lst_flattened = list(chain.from_iterable(value))
     if len(lst_flattened) != dim * dim:
         raise ValueError("IQ gain table does not match ROM size")
     if byte_size == 1:
         for i, b in enumerate(lst_flattened):
             write_u8(arm9, u8_checked(b), block.begin + i * byte_size)
     else:
         for i, b in enumerate(lst_flattened):
             write_u16(arm9, u16_checked(b), block.begin + i * byte_size)
Beispiel #22
0
 def _mappa_group_count_invalid(self, dungeons: List[DungeonDefinition],
                                mappa_index: int) -> Tuple[u8, List[int]]:
     count_floor_expected = 0
     dungeons_to_check = []
     invalid = []
     for i, dungeon in enumerate(dungeons):
         if dungeon.mappa_index == mappa_index:
             count_floor_expected += dungeon.number_floors
             dungeons_to_check.append(i)
     for i in dungeons_to_check:
         if dungeons[i].number_floors_in_group != count_floor_expected:
             invalid.append(i)
     return u8_checked(count_floor_expected), invalid
Beispiel #23
0
    def on_cr_location_edited(self, widget, path, text):
        match = PATTERN_LOCATION_ENTRY.match(text)
        if match is None:
            return
        try:
            location_id = u8_checked(int(match.group(1)))
        except ValueError:
            return

        # location_id:
        self._list_store[path][2] = str(location_id)
        # ent_name:
        self._list_store[path][5] = self._location_names[location_id]
Beispiel #24
0
    def sir0_serialize_parts(self) -> Tuple[bytes, List[int], Optional[int]]:
        data = bytearray()
        pointers = []
        pointer_offsets = []
        for i, color_frames in enumerate(self.colors):
            pointers.append(u32_checked(len(data)))
            number_colors = len(color_frames) // 3
            buffer_entry = bytearray(((number_colors + 1) * 4))
            # Number colors
            write_u8(buffer_entry, u8_checked(number_colors), 0)
            # Unk
            write_u8(buffer_entry,
                     u8_checked(self.durations_per_frame_for_colors[i]), 2)
            # Always one null color
            null_color = False
            if len(color_frames) == 0:
                null_color = True
                color_frames = [0, 0, 0]
            cursor = 4
            for j, (r, g, b) in enumerate(chunk(color_frames, 3)):
                write_u8(buffer_entry, r, cursor)
                write_u8(buffer_entry, g, cursor + 1)
                write_u8(buffer_entry, b, cursor + 2)
                write_u8(buffer_entry, u8(128 if not null_color else 0),
                         cursor + 3)
                cursor += 4

            data += buffer_entry
        data_offset = cursor = len(data)
        data += bytes(4 * len(pointers))
        for pnt in pointers:
            write_u32(data, pnt, cursor)
            pointer_offsets.append(cursor)
            cursor += 4

        return data, pointer_offsets, data_offset
Beispiel #25
0
    def on_cr_belly_heal_edited(self, widget, path, text, *, type_id):
        store: Gtk.ListStore = self.builder.get_object('tree_belly_gain').get_model()
        try:
            val = u8_checked(int(text))
        except ValueError:
            return
        store[path][type_id + 2] = text
        gummi_id = store[path][0]

        arm9 = self.module.project.get_binary(BinaryName.ARM9)
        patch_applied = self.module.project.is_patch_applied("AddTypes")
        static_data = self.module.project.get_rom_module().get_static_data()
        gains = HardcodedIq.get_gummi_belly_heal(arm9, static_data, patch_applied)
        gains[type_id][gummi_id] = val
        self.module.project.modify_binary(
            BinaryName.ARM9, lambda bin: HardcodedIq.set_gummi_belly_heal(gains, bin, static_data, patch_applied)
        )

        self.module.mark_iq_as_modified()
Beispiel #26
0
    def on_cr_skill_to_group(self, widget, path, *, group_id):
        selected = not widget.get_active()
        store: Gtk.ListStore = self.builder.get_object('tree_iq_skills').get_model()
        store[path][group_id + 4] = selected
        skill_id = u8_checked(int(store[path][0]))

        arm9 = self.module.project.get_binary(BinaryName.ARM9)
        static_data = self.module.project.get_rom_module().get_static_data()
        assert self.module.project.is_patch_applied(PATCH_IQ_SKILL_GROUPS)
        groups = IqGroupsSkills.read_compressed(arm9, static_data)
        if selected:
            if skill_id not in groups[group_id]:
                groups[group_id].append(skill_id)
                groups[group_id].sort()
        else:
            if skill_id in groups[group_id]:
                groups[group_id].remove(skill_id)
        self.module.project.modify_binary(
            BinaryName.ARM9, lambda bin: IqGroupsSkills.write_compressed(bin, groups, static_data)
        )

        self.module.mark_iq_as_modified()
Beispiel #27
0
    def change_floor_count(self, dungeon_id, number_floors_new): #TODO: Unchanged
        """
        This will update the floor count for the given dungeon:
        - Will add or remove floors from the dungeon's mappa entry, starting at the end of this dungeon's floor
          based on the current floor count for this dungeon
        - Update the dungeon's data entry (floor count + total floor count in group)
        - For all other dungeons in the same group: Update data entries (total floor count + start offset)
        - Regenerate the UI in SkyTemple (dungeon tree)
        """

        dungeon_definitions = self.get_dungeon_list()

        is_group: Union[Literal[False], DungeonGroup] = False
        for dungeon_or_group in self.load_dungeons():
            if dungeon_or_group == dungeon_id:
                break
            elif isinstance(dungeon_or_group, DungeonGroup):
                if dungeon_id in dungeon_or_group.dungeon_ids:
                    is_group = dungeon_or_group
                    break

        mappa_index = dungeon_definitions[dungeon_id].mappa_index
        floor_offset = dungeon_definitions[dungeon_id].start_after
        number_floors_old = dungeon_definitions[dungeon_id].number_floors
        floor_list = self.get_mappa().floor_lists[mappa_index]
        floors_added = number_floors_new - number_floors_old

        # Update Mappa
        if floors_added == 0:
            return  # nothing to do
        if floors_added < 0:
            # We removed floors
            for _ in range(0, -floors_added):
                del floor_list[floor_offset + number_floors_new]
        else:
            # We added floors
            last_floor_xml = floor_list[floor_offset + number_floors_old - 1].to_xml()
            for i in range(0, floors_added):
                floor_list.insert(floor_offset + number_floors_old + i, MappaFloor.from_xml(last_floor_xml))

        # Update floor ranks
        self.extend_nb_floors_ranks(dungeon_id, floor_offset+number_floors_old, floors_added,
                                    self.get_floor_rank(  # type: ignore
                                        dungeon_id, floor_offset+number_floors_old-1
                                    ))
        if self.has_floor_ranks():
            self.project.mark_as_modified(FLOOR_RANKS)
        # Update mission forbidden
        self.extend_nb_floors_mf(dungeon_id, floor_offset+number_floors_old, floors_added,
                                 self.get_floor_mf(  # type: ignore
                                     dungeon_id, floor_offset+number_floors_old-1
                                ))
        if self.has_mission_forbidden():
            self.project.mark_as_modified(FLOOR_MISSION_FORBIDDEN)

        # Update dungeon data
        dungeon_definitions[dungeon_id].number_floors = number_floors_new
        if is_group:
            new_total_floor_count = sum([dungeon_definitions[x].number_floors for x in is_group.dungeon_ids])
            dungeon_definitions[dungeon_id].number_floors_in_group = new_total_floor_count

            for dungeon_in_group in (x for x in is_group.dungeon_ids if x != dungeon_id):
                # Update dungeon data of group floors
                if dungeon_definitions[dungeon_in_group].start_after > dungeon_definitions[dungeon_id].start_after:
                    dungeon_definitions[dungeon_in_group].start_after += floors_added
                dungeon_definitions[dungeon_in_group].number_floors_in_group = u8_checked(new_total_floor_count)
        else:
            dungeon_definitions[dungeon_id].number_floors_in_group = number_floors_new

        # Re-count floors
        for i, floor in enumerate(floor_list):
            floor.layout.floor_number = i + 1

        # Mark as changed
        self.mark_dungeon_as_modified(dungeon_id, True)
        self.save_dungeon_list(dungeon_definitions)
        if is_group:
            for dungeon_in_group in is_group.dungeon_ids:
                self._regenerate_dungeon_floors(dungeon_in_group, dungeon_definitions[dungeon_in_group].start_after)
        else:
            self._regenerate_dungeon_floors(dungeon_id, floor_offset)
        recursive_generate_item_store_row_label(self._tree_model[self._root_iter])
Beispiel #28
0
 def on_partner_start_level_changed(self, w: Gtk.Entry):
     try:
         val = u8_checked(int(w.get_text()))
     except ValueError:
         return
     self.module.set_starter_level_partner(val)
Beispiel #29
0
    def regroup_dungeons(self, new_groups: Iterable[Union[DungeonGroup, int]]):
        """
        Apply new dungeon groups.
        This updates the dungeon list file, the mappa files and the UI tree.
        start_ids of the DungeonGroups may be empty, it is ignored and calculated from the current dungeons instead.
        The the list MUST contain all regular dungeons (before DOJO_DUNGEONS_FIRST), just like self.load_dungeons
        would return it.
        """
        mappa = self.get_mappa()
        old_floor_lists = mappa.floor_lists
        reorder_list: List[List[Tuple[int, Optional[int], Optional[int]]]] = []
        dojo_floors = old_floor_lists[DOJO_MAPPA_ENTRY]
        new_floor_lists: List[List[MappaFloor]] = []
        dungeons = self.get_dungeon_list()
        # Sanity check list.
        dungeons_not_visited = set((i for i in range(0, len(dungeons))))

        # TODO Build new floor lists and update dungeon entries. Insert dojo dungeons at DOJO_MAPPA_ENTRY
        for group_or_dungeon in new_groups:
            # At DOJO_MAPPA_ENTRY insert the dojo:
            if len(new_floor_lists) == DOJO_MAPPA_ENTRY:
                new_floor_lists.append(dojo_floors)
                reorder_list.append([(DOJO_MAPPA_ENTRY, None, None)])
            reorder_list.append([])
            # Process this entry
            next_index = len(new_floor_lists)
            new_floor_list: List[MappaFloor] = []
            if isinstance(group_or_dungeon, DungeonGroup):
                group = group_or_dungeon.dungeon_ids
            else:
                group = [group_or_dungeon]
            floor_count_in_group = sum(dungeons[i].number_floors for i in group)
            for i in group:
                dungeons_not_visited.remove(i)
                old_first = dungeons[i].start_after
                old_last = old_first + dungeons[i].number_floors
                new_floors = old_floor_lists[dungeons[i].mappa_index][old_first:old_last]
                reorder_list[-1].append((dungeons[i].mappa_index, old_first, old_last))
                floor_i = len(new_floor_list)
                for floor in new_floors:
                    floor.layout.floor_number = u8_checked(floor_i)
                    floor_i += 1
                dungeons[i].number_floors_in_group = u8_checked(floor_count_in_group)
                dungeons[i].mappa_index = u8_checked(next_index)
                dungeons[i].start_after = u8_checked(len(new_floor_list))
                new_floor_list += new_floors
            new_floor_lists.append(new_floor_list)

        assert len(dungeons_not_visited) == 0, _("Some dungeons were missing in the new group list. "
                                                 "This is a bug.")

        # If we haven't inserted the dojo dungeon floor list yet, do it now and pad with empty lists.
        if len(new_floor_lists) < DOJO_MAPPA_ENTRY:
            for i in range(len(new_floor_lists), DOJO_MAPPA_ENTRY + 1):
                if i == DOJO_MAPPA_ENTRY:
                    new_floor_lists.append(dojo_floors)
                    reorder_list.append([(DOJO_MAPPA_ENTRY, None, None)])
                else:
                    new_floor_lists.append([])
                    reorder_list.append([])
        
        mappa.floor_lists = new_floor_lists
        assert self._validator
        self._validator.floors = new_floor_lists
        self.mark_root_as_modified()
        self.save_mappa()
        self.save_dungeon_list(dungeons)

        # Update floor attributes
        self.reorder_floors_ranks(reorder_list)
        self.reorder_floors_mf(reorder_list)
        if self.has_floor_ranks():
            self.project.mark_as_modified(FLOOR_RANKS)
        if self.has_mission_forbidden():
            self.project.mark_as_modified(FLOOR_MISSION_FORBIDDEN)

        self.rebuild_dungeon_tree()
Beispiel #30
0
    def from_xml(cls, ele: Element) -> 'MappaFloorLayout':
        validate_xml_tag(ele, XML_FLOOR_LAYOUT)
        generator_settings = None
        chances = None
        terrain_settings = None
        misc = None
        for child in ele:
            if child.tag == XML_FLOOR_LAYOUT__GENSET:
                generator_settings = child
            elif child.tag == XML_FLOOR_LAYOUT__CHANCES:
                chances = child
            elif child.tag == XML_FLOOR_LAYOUT__TERRAINSET:
                terrain_settings = child
            elif child.tag == XML_FLOOR_LAYOUT__MISCSET:
                misc = child
            else:
                raise XmlValidateError(
                    f(
                        _("Unexpected sub-node for {XML_FLOOR_LAYOUT}: {child.tag}"
                          )))

        if generator_settings is None:
            raise XmlValidateError(
                f(
                    _("{XML_FLOOR_LAYOUT__GENSET} missing for {XML_FLOOR_LAYOUT}."
                      )))

        if chances is None:
            raise XmlValidateError(
                f(
                    _("{XML_FLOOR_LAYOUT__CHANCES} missing for {XML_FLOOR_LAYOUT}."
                      )))

        if terrain_settings is None:
            raise XmlValidateError(
                f(
                    _("{XML_FLOOR_LAYOUT__TERRAINSET} missing for {XML_FLOOR_LAYOUT}."
                      )))

        if misc is None:
            raise XmlValidateError(
                f(
                    _("{XML_FLOOR_LAYOUT__MISCSET} missing for {XML_FLOOR_LAYOUT}."
                      )))

        validate_xml_attribs(ele, [
            XML_FLOOR_LAYOUT__STRUCTURE, XML_FLOOR_LAYOUT__TILESET,
            XML_FLOOR_LAYOUT__BGM, XML_FLOOR_LAYOUT__WEATHER,
            XML_FLOOR_LAYOUT__NUMBER, XML_FLOOR_LAYOUT__FIXED_FLOOR_ID,
            XML_FLOOR_LAYOUT__DARKNESS_LEVEL
        ])

        validate_xml_attribs(generator_settings, [
            XML_FLOOR_LAYOUT__GENSET__ROOM_DENSITY,
            XML_FLOOR_LAYOUT__GENSET__FLOOR_CONNECTIVITY,
            XML_FLOOR_LAYOUT__GENSET__INITIAL_ENEMY_DENSITY,
            XML_FLOOR_LAYOUT__GENSET__DEAD_ENDS,
            XML_FLOOR_LAYOUT__GENSET__ITEM_DENSITY,
            XML_FLOOR_LAYOUT__GENSET__TRAP_DENSITY,
            XML_FLOOR_LAYOUT__GENSET__EXTRA_HALLWAY_DENSITY,
            XML_FLOOR_LAYOUT__GENSET__BURIED_ITEM_DENSITY,
            XML_FLOOR_LAYOUT__GENSET__WATER_DENSITY,
            XML_FLOOR_LAYOUT__GENSET__MAX_COIN_AMOUNT
        ])

        validate_xml_attribs(chances, [
            XML_FLOOR_LAYOUT__CHANCES__SHOP,
            XML_FLOOR_LAYOUT__CHANCES__MONSTER_HOUSE,
            XML_FLOOR_LAYOUT__CHANCES__UNUSED,
            XML_FLOOR_LAYOUT__CHANCES__STICKY_ITEM,
            XML_FLOOR_LAYOUT__CHANCES__EMPTY_MONSTER_HOUSE,
            XML_FLOOR_LAYOUT__CHANCES__HIDDEN_STAIRS
        ])

        validate_xml_attribs(terrain_settings, [
            XML_FLOOR_LAYOUT__TERRAINSET__SECONDARY_USED,
            XML_FLOOR_LAYOUT__TERRAINSET__SECONDARY_TYPE,
            XML_FLOOR_LAYOUT__TERRAINSET__IMPERFECT_ROOMS,
            XML_FLOOR_LAYOUT__TERRAINSET__UNK1,
            XML_FLOOR_LAYOUT__TERRAINSET__UNK3,
            XML_FLOOR_LAYOUT__TERRAINSET__UNK4,
            XML_FLOOR_LAYOUT__TERRAINSET__UNK5,
            XML_FLOOR_LAYOUT__TERRAINSET__UNK6,
            XML_FLOOR_LAYOUT__TERRAINSET__UNK7
        ])

        validate_xml_attribs(misc, [
            XML_FLOOR_LAYOUT__MISCSET__UNKE,
            XML_FLOOR_LAYOUT__MISCSET__KECLEON_SHOP_ITEM_POSITIONS,
            XML_FLOOR_LAYOUT__MISCSET__UNK_HIDDEN_STAIRS,
            XML_FLOOR_LAYOUT__MISCSET__ENEMY_IQ,
            XML_FLOOR_LAYOUT__MISCSET__IQ_BOOSTER_BOOST
        ])

        if not hasattr(MappaFloorStructureType,
                       ele.get(XML_FLOOR_LAYOUT__STRUCTURE)):
            raise XmlValidateError(
                f(
                    _("Invalid structure type {ele.get(XML_FLOOR_LAYOUT__STRUCTURE)}"
                      )))
        structure = getattr(MappaFloorStructureType,
                            ele.get(XML_FLOOR_LAYOUT__STRUCTURE))

        if not hasattr(MappaFloorWeather, ele.get(XML_FLOOR_LAYOUT__WEATHER)):
            raise XmlValidateError(
                f(
                    _("Invalid weather type {ele.get(XML_FLOOR_LAYOUT__WEATHER)}"
                      )))
        weather = getattr(MappaFloorWeather,
                          ele.get(XML_FLOOR_LAYOUT__WEATHER))

        if not hasattr(MappaFloorDarknessLevel,
                       ele.get(XML_FLOOR_LAYOUT__DARKNESS_LEVEL)):
            raise XmlValidateError(
                f(
                    _("Invalid darkness level type {ele.get(XML_FLOOR_LAYOUT__DARKNESS_LEVEL)}"
                      )))
        darkness_level = getattr(MappaFloorDarknessLevel,
                                 ele.get(XML_FLOOR_LAYOUT__DARKNESS_LEVEL))

        return cls(
            structure=structure,
            room_density=i8_checked(
                int(
                    generator_settings.get(
                        XML_FLOOR_LAYOUT__GENSET__ROOM_DENSITY))),
            tileset_id=u8_checked(int(ele.get(XML_FLOOR_LAYOUT__TILESET))),
            music_id=u8_checked(int(ele.get(XML_FLOOR_LAYOUT__BGM))),
            weather=weather,
            floor_connectivity=u8_checked(
                int(
                    generator_settings.get(
                        XML_FLOOR_LAYOUT__GENSET__FLOOR_CONNECTIVITY))),
            initial_enemy_density=i8_checked(
                int(
                    generator_settings.get(
                        XML_FLOOR_LAYOUT__GENSET__INITIAL_ENEMY_DENSITY))),
            kecleon_shop_chance=u8_checked(
                int(chances.get(XML_FLOOR_LAYOUT__CHANCES__SHOP))),
            monster_house_chance=u8_checked(
                int(chances.get(XML_FLOOR_LAYOUT__CHANCES__MONSTER_HOUSE))),
            unusued_chance=u8_checked(
                int(chances.get(XML_FLOOR_LAYOUT__CHANCES__UNUSED))),
            sticky_item_chance=u8_checked(
                int(chances.get(XML_FLOOR_LAYOUT__CHANCES__STICKY_ITEM))),
            dead_ends=bool(
                int(generator_settings.get(
                    XML_FLOOR_LAYOUT__GENSET__DEAD_ENDS))),
            secondary_terrain=u8_checked(
                int(
                    terrain_settings.get(
                        XML_FLOOR_LAYOUT__TERRAINSET__SECONDARY_TYPE))),
            terrain_settings=MappaFloorTerrainSettings(
                has_secondary_terrain=bool(
                    int(
                        terrain_settings.get(
                            XML_FLOOR_LAYOUT__TERRAINSET__SECONDARY_USED))),
                unk1=bool(
                    int(
                        terrain_settings.get(
                            XML_FLOOR_LAYOUT__TERRAINSET__UNK1))),
                generate_imperfect_rooms=bool(
                    int(
                        terrain_settings.get(
                            XML_FLOOR_LAYOUT__TERRAINSET__IMPERFECT_ROOMS))),
                unk3=bool(
                    int(
                        terrain_settings.get(
                            XML_FLOOR_LAYOUT__TERRAINSET__UNK3))),
                unk4=bool(
                    int(
                        terrain_settings.get(
                            XML_FLOOR_LAYOUT__TERRAINSET__UNK4))),
                unk5=bool(
                    int(
                        terrain_settings.get(
                            XML_FLOOR_LAYOUT__TERRAINSET__UNK5))),
                unk6=bool(
                    int(
                        terrain_settings.get(
                            XML_FLOOR_LAYOUT__TERRAINSET__UNK6))),
                unk7=bool(
                    int(
                        terrain_settings.get(
                            XML_FLOOR_LAYOUT__TERRAINSET__UNK7))),
            ),
            unk_e=bool(int(misc.get(XML_FLOOR_LAYOUT__MISCSET__UNKE))),
            item_density=u8_checked(
                int(
                    generator_settings.get(
                        XML_FLOOR_LAYOUT__GENSET__ITEM_DENSITY))),
            trap_density=u8_checked(
                int(
                    generator_settings.get(
                        XML_FLOOR_LAYOUT__GENSET__TRAP_DENSITY))),
            floor_number=u8_checked(int(ele.get(XML_FLOOR_LAYOUT__NUMBER))),
            fixed_floor_id=u8_checked(
                int(ele.get(XML_FLOOR_LAYOUT__FIXED_FLOOR_ID))),
            extra_hallway_density=u8_checked(
                int(
                    generator_settings.get(
                        XML_FLOOR_LAYOUT__GENSET__EXTRA_HALLWAY_DENSITY))),
            buried_item_density=u8_checked(
                int(
                    generator_settings.get(
                        XML_FLOOR_LAYOUT__GENSET__BURIED_ITEM_DENSITY))),
            water_density=u8_checked(
                int(
                    generator_settings.get(
                        XML_FLOOR_LAYOUT__GENSET__WATER_DENSITY))),
            darkness_level=darkness_level,
            max_coin_amount=int(
                generator_settings.get(
                    XML_FLOOR_LAYOUT__GENSET__MAX_COIN_AMOUNT)),
            kecleon_shop_item_positions=u8_checked(
                int(
                    misc.get(
                        XML_FLOOR_LAYOUT__MISCSET__KECLEON_SHOP_ITEM_POSITIONS)
                )),
            empty_monster_house_chance=u8_checked(
                int(chances.get(
                    XML_FLOOR_LAYOUT__CHANCES__EMPTY_MONSTER_HOUSE))),
            unk_hidden_stairs=u8_checked(
                int(misc.get(XML_FLOOR_LAYOUT__MISCSET__UNK_HIDDEN_STAIRS))),
            hidden_stairs_spawn_chance=u8_checked(
                int(chances.get(XML_FLOOR_LAYOUT__CHANCES__HIDDEN_STAIRS))),
            enemy_iq=u16_checked(
                int(misc.get(XML_FLOOR_LAYOUT__MISCSET__ENEMY_IQ))),
            iq_booster_boost=i16_checked(
                int(misc.get(XML_FLOOR_LAYOUT__MISCSET__IQ_BOOSTER_BOOST))),
        )