module LevelLoader # TODO most of these functions should probably be private # TODO I'm not sure if loadSetFile should exist at all def loaderSetFile(file) @_ioFile = file end def loadVERS(chunk_size) @file_version = @_ioFile.getVersion @game_version = @_ioFile.getVersion chunk_size end def loadDATE(chunk_size) # TODO find out if I should use Time.gm or Time.local year = @_ioFile.get16be month = @_ioFile.get8 day = @_ioFile.get8 @creation_date = Time.gm(year, month, day) chunk_size end def loadHEAD(chunk_size) f = @_ioFile @fieldx = f.get8 @fieldy = f.get8 @time = f.get16be @gems_needed = f.get16be @name = f.getCharacters(MAX_LEVEL_NAME_LEN) @score = Array.new(LEVEL_SCORE_ELEMENTS).map { f.get8 } @num_yamyam_contents = STD_ELEMENT_CONTENTS # TODO @num_yamyam_contents exists? Misc::fill3dArray(@yamyam_content, STD_ELEMENT_CONTENTS, 3, 3) do getMappedElement(f.get8) end @amoeba_speed = f.get8 @time_magic_wall = f.get8 @time_wheel = f.get8 @amoeba_content = getMappedElement(f.get8) @initial_player_stepsize = Array.new(MAX_PLAYERS, (f.get8 == 1 ? STEPSIZE_FAST : STEPSIZE_NORMAL)) @initial_player_gravity = Array.new(MAX_PLAYERS, f.getBool) @encoding_16bit_field = f.getBool @em_slippery_gems = f.getBool @use_custom_template = f.getBool @block_last_field = f.getBool @sp_block_last_field = f.getBool @can_move_into_acid_bits = f.get32be @dont_collide_with_bits = f.get8 @use_spring_bug = f.getBool @use_step_counter = f.getBool @instant_relocation = f.getBool @can_pass_to_walkable = f.getBool @grow_into_diggable = f.getBool @game_engine_type = f.get8 chunk_size end def loadNAME(chunk_size) @name = @_ioFile.getCharacters(MAX_LEVEL_NAME_LEN) chunk_size end def loadAUTH(chunk_size) @name = @_ioFile.getCharacters(MAX_LEVEL_AUTHOR_LEN) chunk_size end def loadBODY(chunk_size) chunk_size_expected = @fieldx * @fieldy # Note: "chunk_size" was wrong before version 2.0 when elements are # stored with 16-bit encoding (and should be twice as big then). # Even worse, playfield data was stored 16-bit when only yamyam content # contained 16-bit elements and vice versa. chunk_size_expected *= 2 if @encoding_16bit_field && @file_version >= FILE_VERSION_2_0 if chunk_size_expected != chunk_size @_ioFile.getUnusedBytes(chunk_size) return chunk_size_expected end Misc::fill2dArray(@field, @fieldx, @fieldy) do getMappedElement(@encoding_16bit_field ? @_ioFile.get16be : @_ioFile.get8) end chunk_size end def loadCONT(chunk_size) header_size = 4 content_size = MAX_ELEMENT_CONTENTS * 3 * 3 chunk_size_expected = header_size + content_size # Note: "chunk_size" was wrong before version 2.0 when elements are # stored with 16-bit encoding (and should be twice as big then). # Even worse, playfield data was stored 16-bit when only yamyam content # contained 16-bit elements and vice versa. chunk_size_expected += content_size if @encoding_16bit_field && @file_version >= FILE_VERSION_2_0 if chunk_size_expected != chunk_size file.getUnusedBytes(chunk_size) return chunk_size_expected end @_ioFile.get8 @num_yamyam_contents = @_ioFile.get8 @_ioFile.get8 @_ioFile.get8 # correct invalid number of content fields -- should never happen @num_yamyam_contents = STD_ELEMENT_CONTENTS if !(1..MAX_ELEMENT_CONTENTS).include?(@num_yamyam_contents) Misc::fill3dArray(@yamyam_content, MAX_ELEMENT_CONTENTS, 3, 3) do getMappedElement(@encoding_16bit_field ? @_ioFile.get16be : @_ioFile.get8) end chunk_size end def loadCNT2(chunk_size) content_array = Misc::array(MAX_ELEMENT_CONTENTS, 3, 3, nil) element = getMappedElement(@_ioFile.get16be) num_contents = @_ioFile.get8 content_xsize = @_ioFile.get8 content_ysize = @_ioFile.get8 ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT2_UNUSED) Misc::fill3dArray(content_array, MAX_ELEMENT_CONTENTS, 3, 3) do getMappedElement(@_ioFile.get16be) end # correct invalid number of content fields -- should never happen num_contents = STD_ELEMENT_CONTENTS if !(1..MAX_ELEMENT_CONTENTS).include?(num_contents) case element when EL_YAMYAM @yamyam_content = content_array @num_yamyam_contents = num_contents when EL_BD_AMOEBA @amoeba_content = content_array[0][0][0] else error(ERR_WARN, "cannot load content for element '#{element}'") end chunk_size end def loadCNT3(chunk_size) element = getMappedElement(@_ioFile.get16be) element = EL_ENVELOPE_1 if !IS_ENVELOPE(element) envelope_nr = element - EL_ENVELOPE_1 envelope_len = @_ioFile.get16be @envelope[envelope_nr].xsize = @_ioFile.get8 @envelope[envelope_nr].ysize = @_ioFile.get8 @_ioFile.getUnusedBytes(10) chunk_size_expected = 16 + envelope_len if chunk_size_expected != chunk_size file.getUnusedBytes(chunk_size - 16) return chunk_size_expected end @envelope[envelope_nr].text = @_ioFile.getCharacters(envelope_len) chunk_size end def loadCUS1(chunk_size) num_changed_custom_elements = @_ioFile.get16be chunk_size_expected = 2 + num_changed_custom_elements * 6 if chunk_size_expected != chunk_size file.getUnusedBytes(chunk_size - 2) return chunk_size_expected end num_changed_custom_elements.times do |i| elementId = getMappedElement(@_ioFile.get16be) properties = @_ioFile.get32be if IS_CUSTOM_ELEMENT(elementId) # TODO: is Element@properties an array at all? element_info[elementId].properties[EP_BITFIELD_BASE_NR] = properties else error(ERR_WARN, "invalid custom element number #{elementId}") end # older game versions that wrote level files with CUS1 chunks used # different default push delay values (not yet stored in level file) element_info[elementId].push_delay_fixed = 2 element_info[elementId].push_delay_random = 8 end chunk_size end def loadCUS2(chunk_size) num_changed_custom_elements = @_ioFile.get16be chunk_size_expected = 2 + num_changed_custom_elements * 4 if chunk_size_expected != chunk_size file.getUnusedBytes(chunk_size - 2) return chunk_size_expected end num_changed_custom_elements.times do |i| elementId = getMappedElement(@_ioFile.get16be) custom_target_element = getMappedElement(@_ioFile.get16be) if IS_CUSTOM_ELEMENT(elementId) element_info[elementId].change.target_element = custom_target_element else error(ERR_WARN, "invalid custom element number #{elementId}") end end chunk_size end def loadCUS3(chunk_size) num_changed_custom_elements = @_ioFile.get16be chunk_size_expected = 2 + num_changed_custom_elements * 134 if chunk_size_expected != chunk_size file.getUnusedBytes(chunk_size - 2) return chunk_size_expected end num_changed_custom_elements.times do |i| elementId = getMappedElement(@_ioFile.get16be) el = &element_info[elementId] if !IS_CUSTOM_ELEMENT(elementId) error(ERR_WARN, "invalid custom element number #{elementId}") elementId = EL_INTERNAL_DUMMY end el.description = @_ioFile.getCharacters(MAX_ELEMENT_NAME_LEN) el.properties[EP_BITFIELD_BASE_NR] = file.get32be @_ioFile.getUnusedBytes(7) el.use_gfx_element = @_ioFile.get8 el.gfx_element = getMappedElement(@_ioFile.get16be) el.collect_score_initial = @_ioFile.get8 el.collect_count_initial = @_ioFile.get8 el.push_delay_fixed = @_ioFile.get16be el.push_delay_random = @_ioFile.get16be el.move_delay_fixed = @_ioFile.get16be el.move_delay_random = @_ioFile.get16be el.move_pattern = @_ioFile.get16be el.move_direction_initial = @_ioFile.get8 el.move_stepsize = @_ioFile.get8 Misc::loop2d(3,3) do |x, y| el.content[x][y] = getMappedElement(@_ioFile.get16be) end event_bits = file.get32be NUM_CHANGE_EVENTS.times do |j| ei.change.has_event[j] = true if event_bits & (1 << j) end el.change.target_element = getMappedElement(@_ioFile.get16be) el.change.delay_fixed = @_ioFile.get16be el.change.delay_random = @_ioFile.get16be el.change.delay_frames = @_ioFile.get16be el.change.trigger_element = getMappedElement(@_ioFile.get16be) el.change.explode = @_ioFile.get8 el.change.use_target_content = @_ioFile.get8 el.change.only_if_complete = @_ioFile.get8 el.change.use_random_replace = @_ioFile.get8 el.change.random_percentage = @_ioFile.get8 el.change.replace_when = @_ioFile.get8 Misc::loop2d(3, 3) do |x, y| el.change.target_content[x][y] = getMappedElement(@_ioFile.get16be) end el.slippery_type = @_ioFile.get8 @_ioFile.getUnusedBytes(15) # mark that this custom element has been modified el.modified_settings = true end chunk_size end def loadCUS4(chunk_size) # ---------- custom element base property values (96 bytes) ------------- elementId = getMappedElement(@_ioFile.get16be) if !IS_CUSTOM_ELEMENT(elementId) error(ERR_WARN, "invalid custom element number #{elementId}") @_ioFile.getUnusedBytes(chunk_size - 2) return chunk_size end ei = element_info[elementId] ei.description = @_ioFile.getCharacters(MAX_ELEMENT_NAME_LEN) ei.properties[EP_BITFIELD_BASE_NR] = file.get32be @_ioFile.getUnusedBytes(4) numChangePages = @_ioFile.get8 chunk_size_expected = 96 + ei.changePages.length * 48 if chunk_size_expected != chunk_size @_ioFile.getUnusedBytes(chunk_size - 43) return chunk_size_expected end ei.ce_value_fixed_initial = @_ioFile.get16be ei.ce_value_random_initial = @_ioFile.get16be ei.use_last_ce_value = @_ioFile.get8 ei.use_gfx_element = @_ioFile.get8 ei.gfx_element = getMappedElement(@_ioFile.get16be) ei.collect_score_initial = @_ioFile.get8 ei.collect_count_initial = @_ioFile.get8 ei.drop_delay_fixed = @_ioFile.get8 ei.push_delay_fixed = @_ioFile.get8 ei.drop_delay_random = @_ioFile.get8 ei.push_delay_random = @_ioFile.get8 ei.move_delay_fixed = @_ioFile.get16be ei.move_delay_random = @_ioFile.get16be ei.move_pattern = @_ioFile.get16be # bits 0 - 15 of "move_pattern" ... ei.move_direction_initial = @_ioFile.get8 ei.move_stepsize = @_ioFile.get8 ei.slippery_type = @_ioFile.get8 Misc::loop2d(3, 3) do |x, y| ei.content[x][y] = getMappedElement(@_ioFile.get16be) end ei.move_enter_element = getMappedElement(@_ioFile.get16be) ei.move_leave_element = getMappedElement(@_ioFile.get16be) ei.move_leave_type = @_ioFile.get8 ei.move_pattern |= (@_ioFile.get16be << 16) # ... bits 16 - 31 of "move_pattern" (not nice, but downward compatible) ei.access_direction = @_ioFile.get8 ei.explosion_delay = @_ioFile.get8 ei.ignition_delay = @_ioFile.get8 ei.explosion_type = @_ioFile.get8 @_ioFile.getUnusedBytes(1) # ---------- change page property values (48 bytes) --------------------- ei.changePages = [] numChangePages.times do |i| change = ChangePage.new # bits 0 - 31 of "has_event[]" ... event_bits = file.get32be MIN(NUM_CHANGE_EVENTS, 32).times do |j| change.has_event[j] = true if (event_bits & (1 << j)) end change.target_element = getMappedElement(@_ioFile.get16be) change.delay_fixed = @_ioFile.get16be change.delay_random = @_ioFile.get16be change.delay_frames = @_ioFile.get16be change.trigger_element = getMappedElement(@_ioFile.get16be) change.explode = @_ioFile.get8 change.use_target_content = @_ioFile.get8 change.only_if_complete = @_ioFile.get8 change.use_random_replace = @_ioFile.get8 change.random_percentage = @_ioFile.get8 change.replace_when = @_ioFile.get8 Misc.loop2d(3, 3) do |x, y| change.target_content[x][y]= getMappedElement(@_ioFile.get16be) end change.can_change = @_ioFile.get8 change.trigger_side = @_ioFile.get8 change.trigger_player = @_ioFile.get8 change.trigger_page = @_ioFile.get8 change.trigger_page = (change.trigger_page == CH_PAGE_ANY_FILE ? CH_PAGE_ANY : (1 << change.trigger_page)) change.has_action = @_ioFile.get8 change.action_type = @_ioFile.get8 change.action_mode = @_ioFile.get8 change.action_arg = @_ioFile.get16be # ... bits 32 - 39 of "has_event[]" (not nice, but downward compatible) event_bits = @_ioFile.get8 (32...NUM_CHANGE_EVENTS).each do |j| change.has_event[j] = true if event_bits & (1 << (j - 32)) end # add the change page to the list ei.changePages.push(change) end # mark this custom element as modified ei.modified_settings = true chunk_size end def loadGRP1(chunk_size) elementId = getMappedElement(@_ioFile.get16be) if !IS_GROUP_ELEMENT(elementId) error(ERR_WARN, "invalid group element number #{elementId}") file.getUnusedBytes(chunk_size - 2) return chunk_size end ei = element_info[elementId] ei.description = @_ioFile.getCharacters(MAX_ELEMENT_NAME_LEN) ei.num_elements = @_ioFile.get8 ei.use_gfx_element = @_ioFile.get8 ei.gfx_element = getMappedElement(@_ioFile.get16be) ei.choice_mode = @_ioFile.get8 @_ioFile.getUnusedBytes(3) MAX_ELEMENTS_IN_GROUP.times do |i| ei.gelement[i] = getMappedElement(@_ioFile.get16be) end # mark this group element as modified element_info[elementId].modified_settings = true chunk_size end def loadINFO(chunk_size) real_chunk_size = 0 while !@_ioFile.eof? && real_chunk_size < chunk_size real_chunk_size += self.mcLoad(@_ioFile, $ruleset_INFO, -1, -1) end real_chunk_size end def loadELEM(chunk_size) real_chunk_size = 0 while !@_ioFile.eof? && real_chunk_size < chunk_size element = getMappedElement(@_ioFile.get16be) real_chunk_size += 2 + self.mcLoad(@_ioFile, $ruleset_ELEM, element, element) end real_chunk_size end def loadNOTE(chunk_size) element = getMappedElement(@_ioFile.get16be) real_chunk_size = 2 while !@_ioFile.eof? && real_chunk_size < chunk_size real_chunk_size += @envelope[envelope_nr].mcLoad(@_ioFile, $ruleset_NOTE, nil, element) end real_chunk_size end end