Ejemplo n.º 1
0
class MainPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.bdm = None
        self.parent = parent

        self.entry_list = TreeListCtrl(self, style=TL_MULTIPLE)
        self.entry_list.AppendColumn("Entry")
        self.entry_list.Bind(EVT_TREELIST_ITEM_CONTEXT_MENU,
                             self.on_right_click)
        self.entry_list.Bind(EVT_TREELIST_SELECTION_CHANGED, self.on_select)
        self.cdo = wx.CustomDataObject("BDMEntry")

        self.Bind(wx.EVT_MENU, self.on_delete, id=wx.ID_DELETE)
        self.Bind(wx.EVT_MENU, self.on_copy, id=wx.ID_COPY)
        self.Bind(wx.EVT_MENU, self.on_paste, id=wx.ID_PASTE)
        self.Bind(wx.EVT_MENU, self.on_new, id=wx.ID_NEW)
        accelerator_table = wx.AcceleratorTable([
            (wx.ACCEL_CTRL, ord('c'), wx.ID_COPY),
            (wx.ACCEL_CTRL, ord('v'), wx.ID_PASTE),
            (wx.ACCEL_NORMAL, wx.WXK_DELETE, wx.ID_DELETE),
        ])
        self.entry_list.SetAcceleratorTable(accelerator_table)

        pub.subscribe(self.on_select, 'on_select')
        pub.subscribe(self.convert_for_skill_creator,
                      'convert_for_skill_creator')

        # Use some sizers to see layout options
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.entry_list, 1, wx.ALL | wx.EXPAND, 10)

        # Layout sizers
        self.SetSizer(sizer)
        self.SetAutoLayout(1)

    def build_tree(self):
        self.entry_list.DeleteAllItems()
        root = self.entry_list.GetRootItem()
        for entry in sorted(self.bdm.entries, key=lambda e: e.id):
            self.entry_list.AppendItem(root, f'{entry.id}: Entry', data=entry)

    def get_current_entry_ids(self):
        entry_ids = []
        item = self.entry_list.GetFirstItem()
        while item.IsOk():
            data = self.entry_list.GetItemData(item)
            entry_ids.append(data.id)
            item = self.entry_list.GetNextItem(item)
        return entry_ids

    def get_previous_entry(self, entry_id):
        item = self.entry_list.GetFirstItem()
        prev = TLI_FIRST
        while item.IsOk():
            data = self.entry_list.GetItemData(item)
            if data.id > entry_id:
                break
            prev, item = item, self.entry_list.GetNextItem(item)
        return prev

    def on_right_click(self, _):
        selected = self.entry_list.GetSelections()
        if not selected:
            return
        menu = wx.Menu()
        menu.Append(wx.ID_NEW)
        menu.Append(wx.ID_DELETE)
        menu.Append(wx.ID_COPY)
        paste = menu.Append(wx.ID_PASTE)
        add = menu.Append(wx.ID_ADD, '&Add Copied Entry')
        success = False

        # Check Clipboard
        if wx.TheClipboard.Open():
            success = wx.TheClipboard.IsSupported(wx.DataFormat("BDMEntry"))
            wx.TheClipboard.Close()
        add.Enable(success)
        paste.Enable(success)
        self.PopupMenu(menu)
        menu.Destroy()

    def on_select(self, _):
        selected = self.entry_list.GetSelections()
        if len(selected) != 1:
            pub.sendMessage('disable')
            return
        pub.sendMessage('load_entry',
                        entry=self.entry_list.GetItemData(selected[0]))

    def add_entry(self, entry):
        root = self.entry_list.GetRootItem()
        prev = self.get_previous_entry(entry.id)
        item = self.entry_list.InsertItem(root,
                                          prev,
                                          f'{entry.id}: Entry',
                                          data=entry)
        self.entry_list.Select(item)
        self.on_select(None)

    def on_new(self, _):
        # Ask for ID
        if self.bdm is None:
            return
        with NewEntryDialog(self, self.get_current_entry_ids()) as dlg:
            if dlg.ShowModal() != wx.ID_OK:
                return
            entry_id = dlg.GetValue()
        self.entry_list.UnselectAll()

        # Add it
        entry = Entry(entry_id=entry_id)
        self.add_entry(entry)
        pub.sendMessage('set_status_bar', text='Added new entry')

    def on_add(self, _):
        if self.bdm is None:
            return
        cdo = wx.CustomDataObject("BDMEntry")
        success = False
        if wx.TheClipboard.Open():
            success = wx.TheClipboard.GetData(cdo)
            wx.TheClipboard.Close()
        if not success:
            with wx.MessageDialog(self, 'Unable to get copied data') as dlg:
                dlg.ShowModal()
                return
        paste_data = pickle.loads(cdo.GetData())

        # Get new Id's
        with NewEntryDialog(self, self.get_current_entry_ids()) as dlg:
            if dlg.ShowModal() != wx.ID_OK:
                return
            entry_id = dlg.GetValue()
        current_entry_ids = self.get_current_entry_ids()
        self.entry_list.UnselectAll()

        # Paste
        for paste in paste_data:
            while entry_id in current_entry_ids:
                entry_id += 1
            entry = Entry(entry_id=entry_id)
            entry.paste(paste)
            self.add_entry(entry)
            entry_id += 1

        self.on_select(None)
        pub.sendMessage(f'Pasted {len(paste_data)} new entry(s)')

    def on_delete(self, _):
        selected = self.entry_list.GetSelections()
        if not selected:
            return

        for item in selected:
            self.entry_list.DeleteItem(item)
        pub.sendMessage('disable')
        pub.sendMessage('set_status_bar',
                        text=f'Deleted {len(selected)} entries')

    def on_copy(self, _):
        selected = self.entry_list.GetSelections()

        self.cdo = wx.CustomDataObject("BDMEntry")
        self.cdo.SetData(
            pickle.dumps(
                [self.entry_list.GetItemData(item) for item in selected]))
        if wx.TheClipboard.Open():
            wx.TheClipboard.SetData(self.cdo)
            wx.TheClipboard.Flush()
            wx.TheClipboard.Close()
        pub.sendMessage('set_status_bar',
                        text=f'Copied {len(selected)} entries')

    def on_paste(self, _):
        selected = self.entry_list.GetSelections()
        if not selected:
            return

        success = False
        cdo = wx.CustomDataObject("BDMEntry")
        if wx.TheClipboard.Open():
            success = wx.TheClipboard.GetData(cdo)
            wx.TheClipboard.Close()
        if success:
            paste_data = pickle.loads(cdo.GetData())
            paste_length = len(paste_data)
            selected_length = len(selected)
            if selected_length > paste_length:
                for item in selected[paste_length:]:
                    self.entry_list.Unselect(item)
                selected = selected[:paste_length]

            item = selected[-1]
            self.entry_list.Select(item)
            for n in range(paste_length - selected_length):
                item = self.entry_list.GetNextItem(item)
                if not item.IsOk():
                    with wx.MessageDialog(
                            self,
                            f'Not enough entries to paste over. Expected {paste_length}'
                    ) as dlg:
                        dlg.ShowModal()
                        return
                self.entry_list.Select(item)
                selected.append(item)

            if len(selected) > 1:
                msg = '\n'.join([
                    f' * {self.entry_list.GetItemData(item).id}: Entry'
                    for item in selected
                ])
                with MultiMessageDialog(
                        self,
                        'Are you sure you want to replace the following entries?',
                        'Warning', msg, wx.YES | wx.NO) as dlg:
                    if dlg.ShowModal() != wx.ID_YES:
                        return

            for n, paste in enumerate(paste_data):
                data = self.entry_list.GetItemData(selected[n])
                data.paste(paste)

            self.on_select(None)
            pub.sendMessage('set_status_bar',
                            text=f'Pasted {len(paste_data)} entry(s)')

    def convert_for_skill_creator(self):
        if not self.bdm:
            with wx.MessageDialog(self, "No BDM loaded!", "Error") as dlg:
                dlg.ShowModal()
            return

        # Get choices
        choices = set()
        item = self.entry_list.GetFirstItem()
        effect_ids = [
            'effect_1_skill_id', 'effect_2_skill_id', 'effect_3_skill_id'
        ]
        while item.IsOk():
            data = self.entry_list.GetItemData(item)
            item = self.entry_list.GetNextItem(item)
            for sub_entry in data.sub_entries:
                for effect_id in effect_ids:
                    try:
                        if sub_entry[effect_id] != 0 and sub_entry[
                                effect_id] != 0xFFFF and sub_entry[
                                    effect_id] != 0xBACA:
                            choices.update([str(sub_entry[effect_id])])
                    except Exception as e:
                        pass

        if not choices:
            with wx.MessageDialog(self, "Cannot find any Skill IDs to convert",
                                  "Error") as dlg:
                dlg.ShowModal()
            return

        # Show Dialog
        with ConvertDialog(self, list(choices)) as dlg:
            if dlg.ShowModal() != wx.ID_OK:
                return
            skill_id = dlg.GetValue()

        # Do Conversion
        item = self.entry_list.GetFirstItem()
        changed = 0
        while item.IsOk():
            data = self.entry_list.GetItemData(item)
            item = self.entry_list.GetNextItem(item)
            for sub_entry in data.sub_entries:
                for effect_id in effect_ids:
                    if sub_entry[effect_id] == skill_id:
                        try:
                            sub_entry[effect_id] = 0xBACA
                            changed += 1
                        except Exception:
                            pass
        self.on_select(None)
        pub.sendMessage('set_status_bar',
                        text=f'Changed {changed} skill ids to 0xBACA')

    def reindex(self):
        selected = self.entry_list.GetSelections()
        if len(selected) != 1:
            return
        item = selected[0]
        entry = self.entry_list.GetItemData(item)
        self.entry_list.DeleteItem(item)
        self.add_entry(entry)
Ejemplo n.º 2
0
class MainPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.code = ''
        self.bac = None
        self.bdm = None
        self.ean = None
        self.cam_ean = None
        self.dirname = ''
        self.parent = parent
        self.links = defaultdict(
            lambda: defaultdict(lambda: defaultdict(dict)))

        # Name
        self.name = wx.StaticText(self, -1, '(No file loaded)')
        self.font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD)
        self.name.SetFont(self.font)

        # Buttons
        self.open = wx.Button(self, wx.ID_OPEN, "Load")
        self.save = wx.Button(self, wx.ID_SAVE, "Save")
        self.save.Disable()
        self.paste = wx.Button(self, wx.ID_PASTE, "Paste")
        self.paste.Disable()
        self.add = wx.Button(self, wx.ID_ADD, "Add Copy")
        self.add.Disable()

        # Entry List
        self.entry_list = TreeListCtrl(self, style=TL_MULTIPLE)
        self.entry_list.AppendColumn("BAC Entry")
        self.entry_list.Bind(EVT_TREELIST_ITEM_CONTEXT_MENU,
                             self.on_right_click)

        # Bind
        self.Bind(wx.EVT_BUTTON, self.on_open, id=wx.ID_OPEN)
        self.Bind(wx.EVT_BUTTON, self.on_save, id=wx.ID_SAVE)
        self.Bind(wx.EVT_BUTTON, self.on_paste, id=wx.ID_PASTE)
        self.Bind(wx.EVT_BUTTON, self.on_add, id=wx.ID_ADD)
        self.Bind(wx.EVT_MENU, self.on_paste, id=wx.ID_PASTE)
        accelerator_table = wx.AcceleratorTable([
            (wx.ACCEL_CTRL, ord('v'), wx.ID_PASTE),
        ])
        self.entry_list.SetAcceleratorTable(accelerator_table)
        self.SetDropTarget(FileDropTarget(self, "load_main_moveset"))

        # Button Sizer
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        button_sizer.Add(self.open)
        button_sizer.AddSpacer(5)
        button_sizer.Add(self.save)
        button_sizer.AddSpacer(5)
        button_sizer.Add(self.paste)
        button_sizer.AddSpacer(5)
        button_sizer.Add(self.add)

        # Use some sizers to see layout options
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.name, 0, wx.CENTER)
        sizer.Add(button_sizer)
        sizer.Add(self.entry_list, 1, wx.ALL | wx.EXPAND, 10)

        # Layout sizers
        self.SetSizer(sizer)
        self.SetAutoLayout(1)

    def on_open(self, _):
        pub.sendMessage('open_main_moveset')

    def on_save(self, _):
        pub.sendMessage('save_moveset')

    def build_tree(self):
        self.entry_list.DeleteAllItems()
        root = self.entry_list.GetRootItem()
        for i, entry in enumerate(self.bac.entries):
            if not entry.sub_entries:
                continue
            self.entry_list.AppendItem(
                root,
                f'{entry.index}: {KNOWN_ENTRIES.get(entry.index, "Unknown")}',
                data=entry)
        self.save.Enable()
        self.paste.Enable()
        self.add.Enable()

    def on_right_click(self, _):
        selected = self.entry_list.GetSelections()
        if not selected:
            return
        menu = wx.Menu()
        paste = menu.Append(wx.ID_PASTE)
        paste.Enable(self.parent.copied is not None)
        self.PopupMenu(menu)
        menu.Destroy()

    def find_next_available_index(self, item_type):
        if item_type == Animation:
            return len(self.ean.animations)
        elif item_type == Hitbox:
            return max([entry.id for entry in self.bdm.entries]) + 1
        elif item_type == Camera:
            return len(self.cam_ean.animations)
        else:
            raise (TypeError(f'Unsupported type: {item_type.__name__}'))

    def find_conflict(self, item_type, entry_pair, depend_value, selected_data,
                      value):
        if value in list(
                self.links[item_type][entry_pair][depend_value].values()):
            return True
        for entry in self.bac.entries:
            # Skip this entry if its already in the selected data
            if entry in selected_data:
                continue
            for sub_entry in entry.sub_entries:
                # Skip if this is not the type we want
                if item_type != ITEM_TYPES[sub_entry.type]:
                    continue
                for item in sub_entry.items:
                    if item[entry_pair[1]] == depend_value and item[
                            entry_pair[0]] == value:
                        return True
        return False

    def create_new_index(self, item_type):
        new_value = self.find_next_available_index(item_type)
        if item_type == Animation:
            animation = EanAnimation(self.ean)
            self.ean.animations.append(animation)
        elif item_type == Hitbox:
            self.bdm.entries.append(BdmEntry(entry_id=new_value))
        elif item_type == Camera:
            self.cam_ean.animations.append(EanAnimation(self.cam_ean))
        else:
            raise (TypeError(f'Unsupported type: {item_type.__name__}'))
        return new_value

    def file_not_found_dialog(self, filename):
        with wx.MessageDialog(
                self,
                f'{filename} was not opened. Please add it and reload the moveset',
                'Error') as dlg:
            dlg.ShowModal()

    def invalid_index_dialog(self, filename, index):
        with wx.MessageDialog(self,
                              f'{filename} does not contain index {index}',
                              'Error') as dlg:
            dlg.ShowModal()

    def copy_index(self, item_type, old_value, new_value):
        new_code = self.code
        old_code = self.parent.side_panel.code
        if item_type == Animation:
            try:
                animation = self.ean.animations[new_value]
            except IndexError:
                self.invalid_index_dialog(f'{new_code}.ean', new_value)
                return False
            try:
                animation.paste(
                    self.parent.side_panel.ean.animations[old_value],
                    keep_name=True)
            except IndexError:
                self.invalid_index_dialog(f'{old_code}.ean', old_value)
                return False

        elif item_type == Hitbox:
            if not self.bdm:
                self.file_not_found_dialog(f'{new_code}_PLAYER.bdm')
                return False

            if not self.parent.side_panel.bdm:
                self.file_not_found_dialog(f'{old_code}_PLAYER.bdm')
                return False
            try:
                entry = [
                    entry for entry in self.bdm.entries
                    if entry.id == new_value
                ][0]
            except IndexError:
                self.invalid_index_dialog(f'{new_code}_PLAYER.bdm', new_value)
                return False
            try:
                old_entry = [
                    entry for entry in self.parent.side_panel.bdm.entries
                    if entry.id == old_value
                ][0]
            except IndexError:
                self.invalid_index_dialog(f'{old_code}_PLAYER.bdm', old_value)
                return False
            entry.paste(old_entry)
        elif item_type == Camera:
            if not self.cam_ean:
                self.file_not_found_dialog(f'{new_code}.cam.ean')
                return False
            if not self.parent.side_panel.cam_ean:
                self.file_not_found_dialog(f'{old_code}.cam.ean')
                return False
            try:
                camera = self.cam_ean.animations[new_value]
            except IndexError:
                self.invalid_index_dialog(f'{new_code}.cam.ean', new_value)
                return False
            try:
                camera.paste(
                    self.parent.side_panel.cam_ean.animations[old_value],
                    keep_name=True)
            except IndexError:
                self.invalid_index_dialog(f'{old_code}.cam.ean', old_value)
                return False
        else:
            raise (TypeError(f'Unsupported type: {item_type.__name__}'))
        return True

    def changed_value_message(self,
                              entry_index,
                              changed_values,
                              item_type,
                              old_value,
                              new_value,
                              new=True):
        old_string = str(old_value)
        new_string = str(new_value)
        if item_type == Animation:
            old_string += f' ({self.parent.side_panel.ean.animations[old_value].name})'
            new_string += f' ({"*new*" if new else self.ean.animations[new_value].name})'
        changed_values[item_type].append(
            (f'[{entry_index}]', new_string, old_string))

    def get_changed_values(self, changed_values, item_type, entry_pair,
                           depend_value, entry_values, selected_val,
                           selected_data):
        entry_index = selected_val[0]
        selected_val = selected_val[1]
        entry_name = KNOWN_ENTRIES.get(entry_index, 'Unknown')
        n = 0
        for old_value in entry_values:
            # Continue if we already have a link
            old_animation_name = self.parent.side_panel.ean.animations[old_value].name \
                if item_type == Animation and old_value < len(self.parent.side_panel.ean.animations) else ''
            if item_type == Animation and any(
                    word in old_animation_name for word in BLACKLISTED_WORDS) \
                    and not old_animation_name.endswith(entry_name):
                continue
            if old_value in self.links[item_type][entry_pair][depend_value]:
                new_value = self.links[item_type][entry_pair][depend_value][
                    old_value]
                # if (old_value, new_value) in list(zip(*changed_values[item_type])):
                self.changed_value_message(entry_index, changed_values,
                                           item_type, old_value, new_value,
                                           False)
                continue
            # If the current n is bigger then the selected values or selected values doesn't exist
            if item_type in selected_val and entry_pair in selected_val[
                    item_type]:
                if n >= len(selected_val[item_type][entry_pair][depend_value]):
                    new_value = self.create_new_index(item_type)
                    self.changed_value_message(entry_index, changed_values,
                                               item_type, old_value, new_value)
                else:
                    while n < len(
                            selected_val[item_type][entry_pair][depend_value]):
                        new_value = list(selected_val[item_type][entry_pair]
                                         [depend_value])[n]
                        new_animation_name = self.ean.animations[new_value].name \
                            if item_type == Animation and new_value < len(self.ean.animations) else ''
                        if item_type != Animation or not any(
                                word in new_animation_name for word in BLACKLISTED_WORDS) \
                                or new_animation_name.endswith(entry_name):
                            if not new_animation_name.endswith(
                                    entry_name) and self.find_conflict(
                                        item_type, entry_pair, depend_value,
                                        selected_data, new_value):
                                new_value = self.create_new_index(item_type)
                                self.changed_value_message(
                                    entry_index, changed_values, item_type,
                                    old_value, new_value)
                            else:
                                self.changed_value_message(
                                    entry_index, changed_values, item_type,
                                    old_value, new_value, False)
                            break
                        n += 1
                    else:
                        new_value = self.create_new_index(item_type)
                        self.changed_value_message(entry_index, changed_values,
                                                   item_type, old_value,
                                                   new_value)
            else:
                new_value = self.create_new_index(item_type)
                self.changed_value_message(entry_index, changed_values,
                                           item_type, old_value, new_value)

            # Copy EAN/BDM entries
            if not self.copy_index(item_type, old_value, new_value):
                return False
            self.links[item_type][entry_pair][depend_value][
                old_value] = new_value
            n += 1
        return True

    def on_paste(self, _):
        if not self.parent.copied:
            with wx.MessageDialog(
                    self,
                    f'No entries are copied from the right panel to Paste'
            ) as dlg:
                dlg.ShowModal()
            return

        selected = self.entry_list.GetSelections()
        if not selected:
            with wx.MessageDialog(
                    self, f'No entries are selected to Paste onto') as dlg:
                dlg.ShowModal()
            return

        copied = pickle.loads(self.parent.copied)

        # Cut length of selected to match copied
        copy_length = len(copied)
        selected_length = len(selected)
        if selected_length > copy_length:
            for item in selected[copy_length:]:
                self.entry_list.Unselect(item)
            selected = selected[:copy_length]
        item = selected[-1]
        self.entry_list.Select(item)

        # Increase length to match selected
        for n in range(copy_length - selected_length):
            item = self.entry_list.GetNextItem(item)
            if not item.IsOk():
                with wx.MessageDialog(
                        self,
                        f'Not enough entries to paste over. Expected {copy_length}'
                ) as dlg:
                    dlg.ShowModal()
                    return
            self.entry_list.Select(item)
            selected.append(item)

        selected_data = [
            self.entry_list.GetItemData(item) for item in selected
        ]

        # Warn about changing multiple entries
        if len(copied) > 1:
            msg = ''
            for n, copied_data in enumerate(copied):
                msg += f' * {selected_data[n].index} -> {copied_data.index}\n'
            with MultiMessageDialog(
                    self,
                    'Are you sure you want to replace the following entries?',
                    'Warning', msg, wx.YES | wx.NO) as dlg:
                if dlg.ShowModal() != wx.ID_YES:
                    return

        # Paste entries
        selected_values = []
        copied_values = []
        changed_values = defaultdict(list)

        for n, copied_data in enumerate(copied):
            selected_values.append(
                (selected_data[n].index, selected_data[n].get_static_values()))
            copied_values.append(copied_data.get_static_values())

        for selected_val, copied_val in zip(selected_values, copied_values):
            # Example:
            # Item type: Animation
            # entry: Index
            # dependency: Type
            # depend_value: 5
            # entry_values: {1, 2, 3}
            for item_type, v1 in copied_val.items():
                if item_type not in [Animation, Hitbox, Camera]:
                    continue
                for entry_pair, v2 in v1.items():
                    for depend_value, entry_values in v2.items():
                        # Skip if dependency isn't a character
                        if item_type.dependencies[entry_pair][
                                depend_value] != 'Character':
                            continue
                        if not self.get_changed_values(
                                changed_values, item_type, entry_pair,
                                depend_value, entry_values, selected_val,
                                selected_data):
                            return

        # Finally copy BAC Entries
        for n, copied_data in enumerate(copied):
            entry = self.entry_list.GetItemData(selected[n])
            entry.paste(copied_data, self.links)

        # Display message
        msg = f'Pasted {len(copied)} entry(s)'
        pub.sendMessage('set_status_bar', text=msg)
        with ChangedDialog(self, changed_values) as dlg:
            dlg.ShowModal()

        # changed_msg = ''
        # for item_type, values in changed_values.items():
        #     if item_type == Animation:
        #         changed_msg += f'{self.code}.ean -> {self.parent.side_panel.code}.ean\n'
        #     elif item_type == Hitbox:
        #         changed_msg += f'{self.code}_PLAYER.bdm -> {self.parent.side_panel.code}_PLAYER.bdm\n'
        #     elif item_type == Camera:
        #         changed_msg += f'{self.code}.cam.ean -> {self.parent.side_panel.code}.cam.ean\n'
        #     else:
        #         raise(TypeError(f'Unsupported type: {item_type.__name__}'))
        #     changed_msg += ''.join(sorted(list(zip(*values))[1]))

        # with MultiMessageDialog(
        #         self, 'The following entries in the listed files have been changed:', msg, changed_msg, wx.OK) as dlg:
        #     dlg.ShowModal()

    def on_add(self, _):
        if not self.parent.copied:
            with wx.MessageDialog(
                    self, f'No entries are copied from the right panel to Add'
            ) as dlg:
                dlg.ShowModal()
            return

        copied = pickle.loads(self.parent.copied)

        selected_data = [item for item in copied]

        # Paste entries
        selected_values = []
        copied_values = []
        changed_values = defaultdict(list)

        for n, copied_data in enumerate(copied):
            selected_values.append(
                (selected_data[n].index, selected_data[n].get_static_values()))
            copied_values.append(copied_data.get_static_values())

        # same code as in on_paste(), but it compares to the added entry (i really have no idea why this works..)
        for selected_val, copied_val in zip(selected_values, copied_values):
            for item_type, v1 in copied_val.items():
                if item_type not in [Animation, Hitbox, Camera]:
                    continue
                for entry_pair, v2 in v1.items():
                    for depend_value, entry_values in v2.items():
                        # Skip if dependency isn't a character
                        if item_type.dependencies[entry_pair][
                                depend_value] != 'Character':
                            continue
                        if not self.get_changed_values(
                                changed_values, item_type, entry_pair,
                                depend_value, entry_values, selected_val,
                                selected_data):
                            return

        # Add BAC Entry
        for n, copied_data in enumerate(copied):
            # add new entries to the end so we don't override important CMN entries
            index = len(self.bac.entries)
            new_entry = Entry(self.bac, index)
            root = self.entry_list.GetRootItem()
            new_entry.paste(copied_data, self.links)
            self.bac.entries.append(new_entry)
            self.entry_list.AppendItem(
                root,
                f'{new_entry.index}: {KNOWN_ENTRIES.get(new_entry.index, "Unknown")}',
                data=new_entry)

        # Display message
        msg = f'Added {len(copied)} entry(s)'
        pub.sendMessage('set_status_bar', text=msg)
        with ChangedDialog(self, changed_values) as dlg:
            dlg.ShowModal()