/******************************************************************************* * Copyright (c) 2015 - 2017 * <p> * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * <p> * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * <p> * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package jsettlers.graphics.map.controls.original.panel.selection; import java.util.List; import go.graphics.GLDrawContext; import go.graphics.text.EFontSize; import jsettlers.common.buildings.EBuildingType; import jsettlers.common.buildings.IBuilding; import jsettlers.common.images.EImageLinkType; import jsettlers.common.images.ImageLink; import jsettlers.common.images.OriginalImageLink; import jsettlers.common.material.EMaterialType; import jsettlers.common.material.EPriority; import jsettlers.common.menu.action.EActionType; import jsettlers.common.movable.ESoldierClass; import jsettlers.common.movable.ESoldierType; import jsettlers.common.movable.IMovable; import jsettlers.common.selectable.ISelectionSet; import jsettlers.graphics.action.Action; import jsettlers.graphics.action.AskSetTradingWaypointAction; import jsettlers.graphics.action.ChangeTradingRequestAction; import jsettlers.graphics.action.SetBuildingPriorityAction; import jsettlers.graphics.action.SetTradingWaypointAction; import jsettlers.graphics.action.SetTradingWaypointAction.EWaypointType; import jsettlers.graphics.action.SoldierAction; import jsettlers.graphics.localization.Labels; import jsettlers.graphics.map.controls.original.panel.button.SelectionManagedMaterialButton; import jsettlers.graphics.map.controls.original.panel.button.SelectionManager; import jsettlers.graphics.map.controls.original.panel.button.stock.StockChangeAcceptButton; import jsettlers.graphics.map.controls.original.panel.button.stock.StockControlButton; import jsettlers.graphics.map.controls.original.panel.content.MaterialPriorityContent; import jsettlers.graphics.map.controls.original.panel.selection.BuildingState.OccupierState; import jsettlers.graphics.map.controls.original.panel.selection.BuildingState.StackState; import jsettlers.graphics.map.draw.ImageProvider; import jsettlers.graphics.ui.Button; import jsettlers.graphics.ui.Label; import jsettlers.graphics.ui.UIElement; import jsettlers.graphics.ui.UIPanel; import jsettlers.graphics.ui.layout.BuildingSelectionLayout; import jsettlers.graphics.ui.layout.OccupiableSelectionLayout; import jsettlers.graphics.ui.layout.StockSelectionLayout; import jsettlers.graphics.ui.layout.TradingSelectionLayout; /** * This is the selection content that is used for displaying a selected building. * * @author Michael Zangl */ public class BuildingSelectionContent extends AbstractSelectionContent { private static final int TRADING_MULTIPLE_STEP_INCREASE = 8; private static final OriginalImageLink SOLDIER_MISSING = new OriginalImageLink(EImageLinkType.GUI, 3, 45, 0); private static final OriginalImageLink SOLDIER_COMING = new OriginalImageLink(EImageLinkType.GUI, 3, 48, 0); /** * This defines an element that depends on the state of the building. * * @author Michael Zangl. */ public interface StateDependingElement { void setState(BuildingState state); } /** * A button for a given action for a given soldier type. * * @author Michael Zangl */ public static class SoldierButton extends Button { /** * Create a new soldier button. * * @param actionType * The action to perform on click. * @param type * The soldier type. * @param image * The image to use. */ public SoldierButton(EActionType actionType, ESoldierType type, ImageLink image) { super(new SoldierAction(actionType, type), image, image, Labels.getString("action_" + actionType + "_" + type)); } } /** * This field displays the soldier count. * * @author Michael Zangl */ public static class SoldierCount extends Label { /** * Creates a new soldier count field. * * @param type * The type of soldiers to count. */ public SoldierCount(ESoldierType type) { super("?", EFontSize.NORMAL); } } /** * This displays the number of materials traded. * * @author Michael Zangl */ public static class TradingMaterialCount extends Label implements StateDependingElement { private final EMaterialType material; /** * Creates a new trading material count display. * * @param material * The material. */ public TradingMaterialCount(EMaterialType material) { super("", EFontSize.NORMAL); this.material = material; } @Override public void setState(BuildingState state) { int count = state.getTradingCount(material); setText(formatCount(count)); } private String formatCount(int count) { if (count == Integer.MAX_VALUE) { return "\u221E"; } else { return count + ""; } } } /** * This is the trading path selection display. It shows buttons to select the trading path. * * @author Michael Zangl */ private static class TradingPath extends UIPanel { /** * Creates new trading path buttons. * * @param image * The image to use. */ public TradingPath(ImageLink image) { setBackground(image); } protected Action getActionForStep(int step) { EWaypointType waypoint; if (step <= 0) { waypoint = SetTradingWaypointAction.EWaypointType.WAYPOINT_1; } else if (step <= 1) { waypoint = SetTradingWaypointAction.EWaypointType.WAYPOINT_2; } else if (step <= 2) { waypoint = SetTradingWaypointAction.EWaypointType.WAYPOINT_3; } else { waypoint = SetTradingWaypointAction.EWaypointType.DESTINATION; } return new AskSetTradingWaypointAction(waypoint); } } /** * This displays the land trading path buttons. * * @author Michael Zangl */ public static class LandTradingPath extends TradingPath { private static final int NUMBER_OF_BUTTONS = SetTradingWaypointAction.EWaypointType.VALUES.length; /** * Create a new {@link LandTradingPath}. * * @param image * The image to use. */ public LandTradingPath(ImageLink image) { super(image); } @Override public Action getAction(float relativeX, float relativeY) { int step = (int) (relativeX * NUMBER_OF_BUTTONS); return getActionForStep(step); } } /** * This displays the sea trading path buttons and the button to build a dock. * * @author Michael Zangl */ public static class SeaTradingPath extends TradingPath { private static final int NUMBER_OF_BUTTONS = SetTradingWaypointAction.EWaypointType.VALUES.length + 1; /** * Create a new {@link SeaTradingPath}. * * @param image * The image to use. */ public SeaTradingPath(ImageLink image) { super(image); } @Override public Action getAction(float relativeX, float relativeY) { int step = (int) (relativeX * NUMBER_OF_BUTTONS) - 1; if (step >= 0) { return getActionForStep(step); } else { return new Action(EActionType.ASK_SET_DOCK); } } } /** * This is a trading button that allows to increment or decrement the amount of traded material. * * @author Michael Zangl */ public static class TradingButton extends Button { private SelectionManager selectionManager; private int amount; private boolean relative; /** * Create a new {@link TradingButton}. * * @param image * The image to use. * @param description * The description to display to the user. */ public TradingButton(ImageLink image, String description) { super(null, image, image, description); } @Override public Action getAction() { if (selectionManager != null) { EMaterialType selected = selectionManager.getSelected(); if (selected != null) { return new ChangeTradingRequestAction(selected, amount, relative); } } return null; } /** * Sets the selection manager that decides which material to increment. * * @param selectionManager * The selection manager to use. */ public void setSelectionManager(SelectionManager selectionManager) { this.selectionManager = selectionManager; } } private final IBuilding building; private final UIPanel rootPanel = new ContentRefreshingPanel(); private BuildingState lastState = null; private final SelectionManager selectionManager = new SelectionManager(); /** * Create a new {@link BuildingSelectionContent}. * * @param selection * The selection this content is for. */ public BuildingSelectionContent(ISelectionSet selection) { assert selection.getSize() == 1; building = (IBuilding) selection.get(0); updatePanelContent(); } private void updatePanelContent() { lastState = new BuildingState(building); addPanelContent(lastState); } private void addPanelContent(BuildingState state) { rootPanel.removeAll(); BuildingBackgroundPanel root; if (state.isOccupied()) { root = createOccupiedBuildingContent(state); } else if (state.isStock()) { root = createStockBuildingContent(state); } else if (state.isTrading()) { root = createTradingBuildingContent(state); } else { root = createNormalBuildingContent(state); } ImageLink[] images = building.getBuildingType().getImages(); root.setImages(images); rootPanel.addChild(root, 0, 0, 1, 1); } private BuildingBackgroundPanel createNormalBuildingContent(BuildingState state) { BuildingSelectionLayout layout = new BuildingSelectionLayout(); EPriority[] supported = state.getSupportedPriorities(); if (supported.length < 2) { layout.background.removeChild(layout.priority); } else { layout.priority.setPriority(supported, building.getPriority()); } if (building.getBuildingType().getWorkRadius() <= 0) { layout.background.removeChild(layout.workRadius); } layout.nameText.setType(building.getBuildingType(), state.isConstruction()); String text = ""; if (building.getStateProgress() < 1) { text = Labels.getString("materials_required"); } else if (building instanceof IBuilding.IResourceBuilding) { IBuilding.IResourceBuilding resourceBuilding = (IBuilding.IResourceBuilding) building; text = Labels.getString("productivity", (int) (resourceBuilding.getProductivity() * 100)); } layout.materialText.setText(text); addRequestAndOfferStacks(layout.materialArea, state); BuildingBackgroundPanel root = layout._root; return root; } private void addRequestAndOfferStacks(UIPanel materialArea, BuildingState state) { // hardcoded... float buttonWidth = 18f / (127 - 9); float buttonSpace = 12f / (127 - 9); float requestX = buttonSpace; float offerX = 1 - buttonSpace - buttonWidth; for (StackState mat : state.getStackStates()) { MaterialDisplay display = new MaterialDisplay(mat.getType(), mat.getCount(), -1); if (mat.isOffering()) { materialArea.addChild(display, offerX, 0, offerX + buttonWidth, 1); offerX -= buttonSpace + buttonWidth; } else { materialArea.addChild(display, requestX, 0, requestX + buttonWidth, 1); requestX += buttonSpace + buttonWidth; } } } /** * A button with a number of materials below it. * * @author Michael Zangl */ public static class MaterialDisplay extends UIPanel { private static final float BUTTON_BOTTOM = 1 - 18f / 29; /** * Create a new {@link MaterialDisplay} * * @param type * The type of material. * @param amount * The number of materials to show. * @param required * <code>true</code> if those are required materials for build. */ public MaterialDisplay(EMaterialType type, int amount, int required) { String label; if (required < 0) { label = "building-material-count"; } else { label = "building-material-required"; } String text = Labels.getString(label, amount, required); // TODO: use Labels.getName(type) ? addChild(new Button(null, type.getIcon(), type.getIcon(), ""), 0, BUTTON_BOTTOM, 1, 1); addChild(new Label(text, EFontSize.NORMAL), 0, 0, 1, BUTTON_BOTTOM); } } /** * A panel that displays the name of the building. * * @author Michael Zangl */ public static class NamePanel extends Label { /** * Create a new building name panel. */ public NamePanel() { super("", EFontSize.HEADLINE); } /** * Sets the type of the building to display. * * @param type * The type. * @param workplace * <code>true</code> if it is currently under construction. */ public void setType(EBuildingType type, boolean workplace) { String text = Labels.getName(type); if (workplace) { text = Labels.getString("building-build-in-progress", text); } setText(text); } } /** * A button to change the priority of the current building. * * @author Michael Zangl */ public static class PriorityButton extends Button { private final ImageLink stopped; private final ImageLink low; private final ImageLink high; private EPriority next = EPriority.DEFAULT; private EPriority current = EPriority.DEFAULT; public PriorityButton(ImageLink stopped, ImageLink low, ImageLink high) { super(null, null, null, null); this.stopped = stopped; this.low = low; this.high = high; } /** * Sets the current building priority. * * @param supported * The supported priorities. * @param current * The current priority. */ public void setPriority(EPriority[] supported, EPriority current) { this.current = current; next = supported[0]; for (int i = 0; i < supported.length; i++) { if (supported[i] == current) { next = supported[(i + 1) % supported.length]; } } } @Override protected ImageLink getBackgroundImage() { switch (current) { case HIGH: return high; case LOW: return low; case STOPPED: default: return stopped; } } @Override public Action getAction() { return new SetBuildingPriorityAction(next); } @Override public String getDescription(float relativex, float relativey) { return Labels.getString("priority_" + next); } } private BuildingBackgroundPanel createOccupiedBuildingContent(BuildingState state) { OccupiableSelectionLayout layout = new OccupiableSelectionLayout(); layout.nameText.setType(building.getBuildingType(), false); addOccupyerPlaces(layout.infantry_places, layout.infantry_missing, state.getOccupiers(ESoldierClass.INFANTRY)); addOccupyerPlaces(layout.bowman_places, layout.bowman_missing, state.getOccupiers(ESoldierClass.BOWMAN)); return layout._root; } private void addOccupyerPlaces(UIPanel places, UIPanel missing, List<OccupierState> occupiers) { List<UIElement> buttonPlaces = places.getChildren(); List<UIElement> missingPlaces = missing.getChildren(); for (int i = 0; i < Math.min(buttonPlaces.size(), missingPlaces.size()); i++) { UIPanel icon = (UIPanel) buttonPlaces.get(i); UIPanel missingIcon = (UIPanel) missingPlaces.get(i); icon.setBackground(null); missingIcon.setBackground(null); if (i < occupiers.size()) { OccupierState state = occupiers.get(i); if (state.isComming()) { missingIcon.setBackground(SOLDIER_COMING); } else if (state.isMissing()) { missingIcon.setBackground(SOLDIER_MISSING); } else { icon.setBackground(getIconFor(state.getMovable())); } } } } private static OriginalImageLink getIconFor(IMovable movable) { switch (movable.getMovableType()) { case SWORDSMAN_L1: return new OriginalImageLink(EImageLinkType.GUI, 14, 213, 0); case SWORDSMAN_L2: return new OriginalImageLink(EImageLinkType.GUI, 14, 222, 0); case SWORDSMAN_L3: return new OriginalImageLink(EImageLinkType.GUI, 14, 231, 0); case PIKEMAN_L1: return new OriginalImageLink(EImageLinkType.GUI, 14, 216, 0); case PIKEMAN_L2: return new OriginalImageLink(EImageLinkType.GUI, 14, 225, 0); case PIKEMAN_L3: return new OriginalImageLink(EImageLinkType.GUI, 14, 234, 0); case BOWMAN_L1: return new OriginalImageLink(EImageLinkType.GUI, 14, 219, 0); case BOWMAN_L2: return new OriginalImageLink(EImageLinkType.GUI, 14, 228, 0); case BOWMAN_L3: return new OriginalImageLink(EImageLinkType.GUI, 14, 237, 0); default: System.err.println("A unknown image was requested for gui. " + "Type=" + movable.getMovableType()); return new OriginalImageLink(EImageLinkType.GUI, 24, 213, 0); } } private BuildingBackgroundPanel createStockBuildingContent(BuildingState state) { StockSelectionLayout layout = new StockSelectionLayout(); layout.nameText.setType(building.getBuildingType(), false); selectionManager.setButtons(layout.getAll(StockControlButton.class)); for (StateDependingElement i : layout.getAll(StateDependingElement.class)) { i.setState(state); } layout.stock_accept.configure(selectionManager::getSelected, building::getPos, true, true); layout.stock_reject.configure(selectionManager::getSelected, building::getPos, false, true); return layout._root; } private BuildingBackgroundPanel createTradingBuildingContent(BuildingState state) { TradingSelectionLayout layout = new TradingSelectionLayout(); layout.nameText.setType(building.getBuildingType(), false); selectionManager.setButtons(layout.getAll(SelectionManagedMaterialButton.class)); EPriority[] supported = state.getSupportedPriorities(); if (supported.length < 2) { layout.background.removeChild(layout.priority); } else { layout.priority.setPriority(supported, building.getPriority()); } for (TradingButton b : layout.getAll(TradingButton.class)) { b.setSelectionManager(selectionManager); } for (StateDependingElement i : layout.getAll(StateDependingElement.class)) { i.setState(state); } if (state.isSeaTrading()) { layout._root.removeChild(layout.landTradingPath); } else { layout._root.removeChild(layout.seaTradingPath); } layout.tradeAll.amount = Integer.MAX_VALUE; layout.tradeMore5.relative = true; layout.tradeMore5.amount = TRADING_MULTIPLE_STEP_INCREASE; layout.tradeMore.relative = true; layout.tradeMore.amount = 1; layout.tradeLess.relative = true; layout.tradeLess.amount = -1; layout.tradeLess5.relative = true; layout.tradeLess5.amount = -TRADING_MULTIPLE_STEP_INCREASE; return layout._root; } /** * This is the panel displayed in the background during building selection. * * @author Michael Zangl */ public static class BuildingBackgroundPanel extends UIPanel { private ImageLink[] links = new ImageLink[0]; /** * Sets the images to display. * * @param links * The images. */ public void setImages(ImageLink[] links) { this.links = links; } @Override public void drawAt(GLDrawContext gl) { super.drawAt(gl); } @Override protected void drawBackground(GLDrawContext gl) { float cx = getPosition().getCenterX(); float cy = getPosition().getCenterY(); for (ImageLink link : links) { ImageProvider.getInstance().getImage(link).drawAt(gl, cx, cy); } } } /** * This is a panel that refreshes the content when it is drawn. * * @author Michael Zangl. */ private class ContentRefreshingPanel extends UIPanel { @Override public void drawAt(GLDrawContext gl) { // replaces our children. refreshContentIfNeeded(); super.drawAt(gl); } } @Override public UIPanel getPanel() { return rootPanel; } /** * Checks if this content needs to be refreshed and rebuilds it if the building state has changed. */ public void refreshContentIfNeeded() { if (!lastState.isStillInState(building)) { rootPanel.removeAll(); updatePanelContent(); } } }