package mekhq.gui.dialog; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.ResourceBundle; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTable; import javax.swing.RowFilter; import javax.swing.RowSorter; import javax.swing.ScrollPaneConstants; import javax.swing.SortOrder; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableRowSorter; import megamek.common.options.PilotOptions; import megamek.common.util.EncodeControl; import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.event.PersonChangedEvent; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.Skill; import mekhq.campaign.personnel.SkillType; import mekhq.gui.model.PersonnelTableModel; import mekhq.gui.sorter.FormattedNumberSorter; import mekhq.gui.sorter.RankSorter; public final class BatchXPDialog extends JDialog { private static final long serialVersionUID = -7897406116865495209L; private final Campaign campaign; private final PersonnelTableModel personnelModel; private final TableRowSorter<PersonnelTableModel> personnelSorter; private final PersonnelFilter personnelFilter; private boolean dataChanged = false; private JTable personnelTable; private JComboBox<PersonTypeItem> choiceType; private JComboBox<PersonTypeItem> choiceExp; private JComboBox<String> choiceSkill; private JSpinner skillLevel; private JCheckBox allowPrisoners; private JButton buttonSpendXP; private final List<Integer> personnelColumns = Arrays.asList( PersonnelTableModel.COL_RANK, PersonnelTableModel.COL_NAME, PersonnelTableModel.COL_AGE, PersonnelTableModel.COL_TYPE, PersonnelTableModel.COL_XP ); private JLabel matchedPersonnelLabel; private transient ResourceBundle resourceMap; private transient String choiceNoSkill; public BatchXPDialog(JFrame parent, Campaign campaign) { super(parent, "", true); //$NON-NLS-1$ this.resourceMap = ResourceBundle.getBundle("mekhq.resources.BatchXPDialog", new EncodeControl()); //$NON-NLS-1$ setTitle(resourceMap.getString("dialogue.title")); //$NON-NLS-1$ choiceNoSkill = resourceMap.getString("skill.choice.text"); //$NON-NLS-1$ this.campaign = Objects.requireNonNull(campaign); this.personnelModel = new PersonnelTableModel(campaign); personnelModel.refreshData(); personnelSorter = new TableRowSorter<PersonnelTableModel>(personnelModel); personnelSorter.setSortsOnUpdates(true); personnelSorter.setComparator(PersonnelTableModel.COL_RANK, new RankSorter(campaign)); personnelSorter.setComparator(PersonnelTableModel.COL_AGE, new FormattedNumberSorter()); personnelSorter.setComparator(PersonnelTableModel.COL_XP, new FormattedNumberSorter()); personnelSorter.setSortKeys(Arrays.asList(new RowSorter.SortKey(1, SortOrder.ASCENDING))); personnelFilter = new PersonnelFilter(); personnelSorter.setRowFilter(personnelFilter); initComponents(); } private void initComponents() { setLayout(new BorderLayout()); add(getPersonnelTable(), BorderLayout.CENTER); add(getButtonPanel(), BorderLayout.WEST); pack(); setLocationRelativeTo(getParent()); } private JComponent getPersonnelTable() { personnelTable = new JTable(personnelModel); personnelTable.setCellSelectionEnabled(false); personnelTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); for(int i = PersonnelTableModel.N_COL - 1; i >= 0 ; -- i) { TableColumn column = personnelTable.getColumnModel().getColumn(i); if(personnelColumns.contains(Integer.valueOf(i))) { column.setPreferredWidth(personnelModel.getColumnWidth(i)); column.setCellRenderer(new CellRenderer()); } else { personnelTable.removeColumn(column); } } personnelTable.setIntercellSpacing(new Dimension(1, 0)); personnelTable.setShowGrid(false); personnelTable.setRowSorter(personnelSorter); JScrollPane pane = new JScrollPane(personnelTable); pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); return pane; } private JComponent getButtonPanel() { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); choiceType = new JComboBox<PersonTypeItem>(); choiceType.setMaximumSize(new Dimension(Short.MAX_VALUE, (int) choiceType.getPreferredSize().getHeight())); DefaultComboBoxModel<PersonTypeItem> personTypeModel = new DefaultComboBoxModel<>(); personTypeModel.addElement(new PersonTypeItem(resourceMap.getString("primaryRole.choice.text"), null)); //$NON-NLS-1$ for(int i = 1; i < Person.T_NUM; ++ i) { personTypeModel.addElement(new PersonTypeItem(Person.getRoleDesc(i,campaign.getFaction().isClan()), i)); } personTypeModel.addElement(new PersonTypeItem(Person.getRoleDesc(0, campaign.getFaction().isClan()), 0)); // Add "none" for generic AsTechs choiceType.setModel(personTypeModel); choiceType.setSelectedIndex(0); choiceType.addActionListener(e -> { personnelFilter.setPrimaryRole(((PersonTypeItem)choiceType.getSelectedItem()).id); updatePersonnelTable(); }); panel.add(choiceType); choiceExp = new JComboBox<PersonTypeItem>(); choiceExp.setMaximumSize(new Dimension(Short.MAX_VALUE, (int) choiceType.getPreferredSize().getHeight())); DefaultComboBoxModel<PersonTypeItem> personExpModel = new DefaultComboBoxModel<>(); personExpModel.addElement(new PersonTypeItem(resourceMap.getString("experience.choice.text"), null)); //$NON-NLS-1$ for(int i = 0; i < 5; ++ i) { personExpModel.addElement(new PersonTypeItem(SkillType.getExperienceLevelName(i), i)); } choiceExp.setModel(personExpModel); choiceExp.setSelectedIndex(0); choiceExp.addActionListener(e -> { personnelFilter.setExpLevel(((PersonTypeItem)choiceExp.getSelectedItem()).id); updatePersonnelTable(); }); panel.add(choiceExp); choiceSkill = new JComboBox<String>(); choiceSkill.setMaximumSize(new Dimension(Short.MAX_VALUE, (int) choiceSkill.getPreferredSize().getHeight())); DefaultComboBoxModel<String> personSkillModel = new DefaultComboBoxModel<>(); personSkillModel.addElement(choiceNoSkill); for(String skill : SkillType.getSkillList()) { personSkillModel.addElement(skill); } choiceSkill.setModel(personSkillModel); choiceSkill.setSelectedIndex(0); choiceSkill.addActionListener(e -> { if(choiceNoSkill.equals(choiceSkill.getSelectedItem())) { personnelFilter.setSkill(null); ((SpinnerNumberModel) skillLevel.getModel()).setMaximum(10); buttonSpendXP.setEnabled(false); } else { String skillName = (String) choiceSkill.getSelectedItem(); personnelFilter.setSkill(skillName); int maxSkillLevel = SkillType.getType(skillName).getMaxLevel(); int currentLevel = (Integer) skillLevel.getModel().getValue(); ((SpinnerNumberModel) skillLevel.getModel()).setMaximum(maxSkillLevel); if(currentLevel > maxSkillLevel) { skillLevel.getModel().setValue(Integer.valueOf(maxSkillLevel)); } buttonSpendXP.setEnabled(true); } updatePersonnelTable(); }); panel.add(choiceSkill); panel.add(Box.createRigidArea(new Dimension(10, 10))); panel.add(new JLabel(resourceMap.getString("targetSkillLevel.text"))); //$NON-NLS-1$ skillLevel = new JSpinner(new SpinnerNumberModel(10, 0, 10, 1)); skillLevel.setMaximumSize(new Dimension(Short.MAX_VALUE, (int) skillLevel.getPreferredSize().getHeight())); skillLevel.addChangeListener(e -> { personnelFilter.setMaxSkillLevel((Integer)skillLevel.getModel().getValue()); updatePersonnelTable(); }); panel.add(skillLevel); allowPrisoners = new JCheckBox(resourceMap.getString("allowPrisoners.text")); //$NON-NLS-1$ allowPrisoners.setHorizontalAlignment(SwingConstants.LEFT); allowPrisoners.setMaximumSize(new Dimension(Short.MAX_VALUE, (int) allowPrisoners.getPreferredSize().getHeight())); allowPrisoners.addChangeListener(e -> { personnelFilter.setAllowPrisoners(allowPrisoners.isSelected()); updatePersonnelTable(); }); JPanel allowPrisonersPanel = new JPanel(new GridLayout(1, 1)); allowPrisonersPanel.setAlignmentY(JComponent.LEFT_ALIGNMENT); allowPrisonersPanel.add(allowPrisoners); allowPrisonersPanel.setMaximumSize(new Dimension(Short.MAX_VALUE, (int) allowPrisonersPanel.getPreferredSize().getHeight())); panel.add(allowPrisonersPanel); panel.add(Box.createVerticalGlue()); matchedPersonnelLabel = new JLabel(""); //$NON-NLS-1$ matchedPersonnelLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, (int) matchedPersonnelLabel.getPreferredSize().getHeight())); panel.add(matchedPersonnelLabel); JPanel buttons = new JPanel(new FlowLayout()); buttons.setMaximumSize(new Dimension(Short.MAX_VALUE, (int) buttons.getPreferredSize().getHeight())); buttonSpendXP = new JButton(resourceMap.getString("spendXP.text")); //$NON-NLS-1$ buttonSpendXP.setEnabled(false); buttonSpendXP.addActionListener(e -> spendXP()); buttons.add(buttonSpendXP); JButton button = new JButton(resourceMap.getString("close.text")); //$NON-NLS-1$ button.addActionListener(e -> setVisible(false)); buttons.add(button); panel.add(buttons); panel.setMaximumSize(new Dimension((int) panel.getPreferredSize().getWidth(), Short.MAX_VALUE)); panel.setMinimumSize(new Dimension((int) panel.getPreferredSize().getWidth(), 300)); return panel; } protected void updatePersonnelTable() { personnelSorter.sort(); if(!choiceNoSkill.equals(choiceSkill.getSelectedItem())) { int rows = personnelTable.getRowCount(); matchedPersonnelLabel.setText(String.format(resourceMap.getString("eligible.format"), rows)); //$NON-NLS-1$ } else { matchedPersonnelLabel.setText(""); //$NON-NLS-1$ } } protected void spendXP() { String skillName = (String) choiceSkill.getSelectedItem(); if(choiceNoSkill.equals(skillName)) { // This shouldn't happen, but guard against it anyway. return; } int rows = personnelTable.getRowCount(); int improvedPersonnelCount = rows; while(rows > 0) { for(int i = 0; i < rows; ++ i) { Person p = personnelModel.getPerson(personnelTable.convertRowIndexToModel(i)); int cost = 0; if(p.hasSkill(skillName)) { cost = p.getCostToImprove(skillName); } else { cost = SkillType.getType(skillName).getCost(0); } int experience = p.getExperienceLevel(false); // Improve the skill and deduce the cost p.improveSkill(skillName); campaign.personUpdated(p); p.setXp(p.getXp() - cost); // The next part is bollocks and doesn't belong here, but as long as we hard code AtB ... if(campaign.getCampaignOptions().getUseAtB()) { if((p.getPrimaryRole() > Person.T_NONE) && (p.getPrimaryRole() <= Person.T_CONV_PILOT) && (p.getExperienceLevel(false) > experience) && (experience >= SkillType.EXP_REGULAR)) { String spa = campaign.rollSPA(p.getPrimaryRole(), p); if(null == spa) { if(campaign.getCampaignOptions().useEdge()) { p.acquireAbility(PilotOptions.EDGE_ADVANTAGES, "edge", p.getEdge() + 1); //$NON-NLS-1$ p.addLogEntry(campaign.getDate(), String.format(resourceMap.getString("gainedEdge.text"))); //$NON-NLS-1$ } } else { p.addLogEntry(campaign.getDate(), String.format(resourceMap.getString("gained.format"), spa)); //$NON-NLS-1$ } } } MekHQ.triggerEvent(new PersonChangedEvent(p)); } // Refresh the filter and continue if we still have anyone available updatePersonnelTable(); rows = personnelTable.getRowCount(); dataChanged = true; } if(improvedPersonnelCount > 0) { campaign.addReport(String.format(resourceMap.getString("improvedSkills.format"), skillName, improvedPersonnelCount)); //$NON-NLS-1$ } } public boolean hasDataChanged() { return dataChanged; } public static class CellRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 8387527228725524653L; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); setOpaque(true); setForeground(Color.BLACK); if((row % 2) == 0) { setBackground(new Color(220, 220, 220)); } else { setBackground(Color.WHITE); } return this; } } public static class PersonnelFilter extends RowFilter<PersonnelTableModel, Integer> { private Integer primaryRole = null; private Integer expLevel = null; private String skill = null; private int maxSkillLevel = 10; private boolean prisoners = false; @Override public boolean include(RowFilter.Entry<? extends PersonnelTableModel, ? extends Integer> entry) { Person p = entry.getModel().getPerson(entry.getIdentifier().intValue()); if(!p.isActive()) { return false; } if(!prisoners && (p.isPrisoner() || p.isBondsman())) { return false; } if((null != primaryRole) && (p.getPrimaryRole() != primaryRole.intValue())) { return false; } if((null != expLevel) && (p.getExperienceLevel(false) != expLevel.intValue())) { return false; } if((null != skill)) { Skill s = p.getSkill(skill); if(null == s) { int cost = SkillType.getType(skill).getCost(0); return (cost >= 0) && (cost <= p.getXp()); } else { int cost = s.getCostToImprove(); return (s.getLevel() < maxSkillLevel) && (cost >= 0) && (cost <= p.getXp()); } } return true; } public void setPrimaryRole(Integer role) { primaryRole = role; } public void setExpLevel(Integer level) { expLevel = level; } public void setSkill(String skillName) { skill = skillName; } public void setMaxSkillLevel(int level) { maxSkillLevel = level; } public void setAllowPrisoners(boolean allowPrisoners) { prisoners = allowPrisoners; } } private static class PersonTypeItem { public String name; public Integer id; public PersonTypeItem(String name, Integer id) { this.name = Objects.requireNonNull(name); this.id = id; } @Override public String toString() { return name; } } }