From: Erik V. <ev...@us...> - 2012-03-14 10:41:27
|
data/1830/Map.xml | 4 rails/game/MapHex.java | 16 rails/game/specific/_1880/OffBoardRevenueModifier.java | 61 +++ rails/game/specific/_1880/ShareSellingRound_1880.java | 252 ++++++++++++++ rails/game/specific/_1880/SpecialTrainBuy_1880.java | 140 +++++++ rails/game/specific/_1880/StartCompany_1880.java | 129 +++++++ rails/ui/swing/gamespecific/_1880/GameUIManager_1880.java | 152 ++++++++ rails/ui/swing/hexmap/GUITile.java | 40 +- 8 files changed, 772 insertions(+), 22 deletions(-) New commits: commit d975e7487015fac50223998ccb613a458f52f4a6 Author: Martin Brumm <Dr....@t-...> Date: Wed Mar 14 11:38:07 2012 +0100 Added the missing new 1880-specific classes diff --git a/rails/game/specific/_1880/OffBoardRevenueModifier.java b/rails/game/specific/_1880/OffBoardRevenueModifier.java new file mode 100644 index 0000000..28b34e8 --- /dev/null +++ b/rails/game/specific/_1880/OffBoardRevenueModifier.java @@ -0,0 +1,61 @@ +/** + * + */ +package rails.game.specific._1880; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.log4j.Logger; + +import rails.algorithms.NetworkVertex; +import rails.algorithms.RevenueAdapter; +import rails.algorithms.RevenueBonus; +import rails.algorithms.RevenueStaticModifier; +import rails.game.Station; + +/** + * @author Martin + * + */ +public class OffBoardRevenueModifier implements RevenueStaticModifier { + + + protected static Logger log = + Logger.getLogger(OffBoardRevenueModifier.class.getPackage().getName()); + + public boolean modifyCalculator(RevenueAdapter revenueAdapter) { + + // 1. get the two off-board type stations (Russia and Wladiwostok) + Set<NetworkVertex> offBoard = new HashSet<NetworkVertex>(); + for (NetworkVertex vertex:revenueAdapter.getVertices()) { // We just need the two offboard Cities + if (vertex.isStation() && ((vertex.getStation().getName().equals("Russia") ||(vertex.getStation().getName().equals("Wladiwostok"))))) { + offBoard.add(vertex); + } + } + // 2. get all base tokens (=> start vertices) + Set<NetworkVertex> bases = revenueAdapter.getStartVertices(); + + // 3. combine those to revenueBonuses + // always two offboard areas and one base + Set<NetworkVertex> destOffBoard = new HashSet<NetworkVertex>(offBoard); + for (NetworkVertex offA:offBoard) { + destOffBoard.remove(offA); + for (NetworkVertex offB:destOffBoard) { + for (NetworkVertex base:bases) { + RevenueBonus bonus = new RevenueBonus(50, "Red-To-Red"); + bonus.addVertex(offA); bonus.addVertex(offB); bonus.addVertex(base); + revenueAdapter.addRevenueBonus(bonus); + } + } + } + + return false; + } + + public String prettyPrint(RevenueAdapter revenueAdapter) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/rails/game/specific/_1880/ShareSellingRound_1880.java b/rails/game/specific/_1880/ShareSellingRound_1880.java new file mode 100644 index 0000000..78c15f8 --- /dev/null +++ b/rails/game/specific/_1880/ShareSellingRound_1880.java @@ -0,0 +1,252 @@ +/** + * + */ +package rails.game.specific._1880; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import rails.common.DisplayBuffer; +import rails.common.LocalText; +import rails.common.parser.GameOption; +import rails.game.Bank; +import rails.game.GameDef; +import rails.game.GameManagerI; +import rails.game.Player; +import rails.game.Portfolio; +import rails.game.PublicCertificateI; +import rails.game.PublicCompanyI; +import rails.game.ReportBuffer; +import rails.game.RoundI; +import rails.game.ShareSellingRound; +import rails.game.StockSpaceI; +import rails.game.action.NullAction; +import rails.game.action.PossibleAction; +import rails.game.action.SellShares; + +/** + * @author Martin + * + */ +public class ShareSellingRound_1880 extends ShareSellingRound { + + /** + * @param gameManager + * @param parentRound + */ + public ShareSellingRound_1880(GameManagerI gameManager, RoundI parentRound) { + super(gameManager, parentRound); + // TODO Auto-generated constructor stub + } + + + @Override + public boolean setPossibleActions() { + + possibleActions.clear(); + + setSellableShares(); + + possibleActions.add(new NullAction(NullAction.DONE)); + + for (PossibleAction pa : possibleActions.getList()) { + log.debug(currentPlayer.getName() + " may: " + pa.toString()); + } + + return true; + } + + + /* (non-Javadoc) + * @see rails.game.StockRound#process(rails.game.action.PossibleAction) + */ + @Override + public boolean process(PossibleAction action) { + + currentPlayer = getCurrentPlayer(); + + if (action instanceof NullAction) { + + currentPlayer.addCash(-cashToRaise.intValue()); + gameManager.finishShareSellingRound(); + return true; + } else { + return super.process(action); + } + } + + + /* (non-Javadoc) + * @see rails.game.ShareSellingRound#sellShares(rails.game.action.SellShares) + */ + @Override + public boolean sellShares(SellShares action) { + Portfolio portfolio = currentPlayer.getPortfolio(); + String playerName = currentPlayer.getName(); + String errMsg = null; + String companyName = action.getCompanyName(); + PublicCompanyI company = + companyManager.getPublicCompany(action.getCompanyName()); + PublicCertificateI cert = null; + PublicCertificateI presCert = null; + List<PublicCertificateI> certsToSell = + new ArrayList<PublicCertificateI>(); + Player dumpedPlayer = null; + int presSharesToSell = 0; + int numberToSell = action.getNumber(); + int shareUnits = action.getShareUnits(); + int currentIndex = getCurrentPlayerIndex(); + + // Dummy loop to allow a quick jump out + while (true) { + + // Check everything + if (numberToSell <= 0) { + errMsg = LocalText.getText("NoSellZero"); + break; + } + + // Check company + if (company == null) { + errMsg = LocalText.getText("NoCompany"); + break; + } + + // May player sell this company + if (!mayPlayerSellShareOfCompany(company)) { + errMsg = LocalText.getText("SaleNotAllowed", companyName); + break; + } + + // The player must have the share(s) + if (portfolio.getShare(company) < numberToSell) { + errMsg = LocalText.getText("NoShareOwned"); + break; + } + + // The pool may not get over its limit. + if (pool.getShare(company) + numberToSell * company.getShareUnit() + > getGameParameterAsInt(GameDef.Parm.POOL_SHARE_LIMIT)) { + errMsg = LocalText.getText("PoolOverHoldLimit"); + break; + } + + // Find the certificates to sell + Iterator<PublicCertificateI> it = + portfolio.getCertificatesPerCompany(companyName).iterator(); + while (numberToSell > 0 && it.hasNext()) { + cert = it.next(); + if (cert.isPresidentShare()) { + // Remember the president's certificate in case we need it + if (cert.isPresidentShare()) presCert = cert; + continue; + } else if (shareUnits != cert.getShares()) { + // Wrong number of share units + continue; + } + // OK, we will sell this one + certsToSell.add(cert); + numberToSell--; + } + if (numberToSell == 0) presCert = null; + + if (numberToSell > 0 && presCert != null + && numberToSell <= presCert.getShares()) { + // Not allowed to dump the company that needs the train + if (company == cashNeedingCompany || !dumpOtherCompaniesAllowed) { + errMsg = + LocalText.getText("CannotDumpTrainBuyingPresidency"); + break; + } + // More to sell and we are President: see if we can dump it. + Player otherPlayer; + for (int i = currentIndex + 1; i < currentIndex + + numberOfPlayers; i++) { + otherPlayer = gameManager.getPlayerByIndex(i); + if (otherPlayer.getPortfolio().getShare(company) >= presCert.getShare()) { + // Check if he has the right kind of share + if (numberToSell > 1 + || otherPlayer.getPortfolio().ownsCertificates( + company, 1, false) >= 1) { + // The poor sod. + dumpedPlayer = otherPlayer; + presSharesToSell = numberToSell; + numberToSell = 0; + break; + } + } + } + } + // Check if we could sell them all + if (numberToSell > 0) { + if (presCert != null) { + errMsg = LocalText.getText("NoDumping"); + } else { + errMsg = LocalText.getText("NotEnoughShares"); + } + break; + } + + break; + } + + int numberSold = action.getNumber(); + if (errMsg != null) { + DisplayBuffer.add(LocalText.getText("CantSell", + playerName, + numberSold, + companyName, + errMsg )); + return false; + } + + // All seems OK, now do the selling. + StockSpaceI sellPrice; + int price; + + // Get the sell price (does not change within a turn) + if (sellPrices.containsKey(companyName) + && GameOption.convertValueToBoolean(getGameOption("SeparateSalesAtSamePrice"))) { + price = (sellPrices.get(companyName)).getPrice(); + } else { + sellPrice = company.getCurrentSpace(); + price = sellPrice.getPrice(); + sellPrices.put(companyName, sellPrice); + } + int cashAmount = ((numberSold * price * shareUnits)-(numberSold * 5)); //Deduct 5 Yuan per Sharecertificate Sold... + + moveStack.start(true).linkToPreviousMoveSet(); + + ReportBuffer.add(LocalText.getText("SELL_SHARES_LOG", + playerName, + numberSold, + company.getShareUnit(), + numberSold * company.getShareUnit(), + companyName, + Bank.format(cashAmount) )); + + boolean soldBefore = sellPrices.containsKey(companyName); + + pay (bank, currentPlayer, cashAmount); + adjustSharePrice (company, numberSold, soldBefore); + + if (!company.isClosed()) { + + executeShareTransfer (company, certsToSell, + dumpedPlayer, presSharesToSell, action.getPresidentExchange()); + } + + cashToRaise.add(-numberSold * price); + + if (cashToRaise.intValue() <= 0) { + gameManager.finishShareSellingRound(); + } else if (getSellableShares().isEmpty()) { + gameManager.finishShareSellingRound(); + } + + return true; // TODO Auto-generated method stub + } + + +} diff --git a/rails/game/specific/_1880/SpecialTrainBuy_1880.java b/rails/game/specific/_1880/SpecialTrainBuy_1880.java new file mode 100644 index 0000000..7dfbb3c --- /dev/null +++ b/rails/game/specific/_1880/SpecialTrainBuy_1880.java @@ -0,0 +1,140 @@ +package rails.game.specific._1880; + +import java.util.List; + +import rails.common.LocalText; +import rails.common.parser.ConfigurationException; +import rails.common.parser.Tag; +import rails.game.*; +import rails.game.special.SpecialProperty; +import rails.util.Util; + +/** + * Special private ability involving deductions in train buying. The deduction + * can be absolute (an amount) or relative (a percentage) + * + * @author Erik Voss + * @author Martin Brumm + * + */ +public class SpecialTrainBuy_1880 extends SpecialProperty { + + String name = "SpecialTrainBuy_1880"; + String trainTypeName = ""; // Default: all train types + List<TrainType> trainTypes =null; + boolean extra = false; + String deductionString; + boolean relativeDeduction = false; + boolean absoluteDeduction = false; + int deductionAmount; // Money or percentage + + public void configureFromXML(Tag tag) throws ConfigurationException { + + super.configureFromXML(tag); + + Tag trainBuyTag = tag.getChild("SpecialTrainBuy_1880"); + if (trainBuyTag == null) { + throw new ConfigurationException("<SpecialTrainBuy> tag missing"); + } + + trainTypeName = + trainBuyTag.getAttributeAsString("trainType", trainTypeName); + if (trainTypeName.equalsIgnoreCase("any")) trainTypeName = ""; + + deductionString = trainBuyTag.getAttributeAsString("deduction"); + if (!Util.hasValue(deductionString)) { + throw new ConfigurationException( + "No deduction found in <SpecialTrainBuy> tag"); + } + String deductionAmountString; + if (deductionString.endsWith("%")) { + relativeDeduction = true; + deductionAmountString = deductionString.replaceAll("%", ""); + } else { + deductionAmountString = deductionString; + } + try { + deductionAmount = Integer.parseInt(deductionAmountString); + } catch (NumberFormatException e) { + throw new ConfigurationException("Invalid deduction " + + deductionString, e); + } + + } + + @Override + public void finishConfiguration (GameManagerI gameManager) + throws ConfigurationException { + trainTypes=gameManager.getTrainManager().parseTrainTypes(trainTypeName); + } + + public int getPrice(int standardPrice) { + + if (absoluteDeduction) { + return standardPrice - deductionAmount; + } else if (relativeDeduction) { + return (int) (standardPrice * (0.01 * (100 - deductionAmount))); + } else { + return standardPrice; + } + } + + public boolean isValidForTrainType(String trainType) { + return trainTypeName.equals("") + || trainTypeName.equalsIgnoreCase(trainType); + } + + public boolean isExecutionable() { + return true; + } + + public boolean isExtra() { + return extra; + } + + public boolean isFree() { + return false; + } + + public String getName() { + return name; + } + + public boolean isAbsoluteDeduction() { + return absoluteDeduction; + } + + public int getDeductionAmount() { + return deductionAmount; + } + + public String getDeductionString() { + return deductionString; + } + + public boolean isRelativeDeduction() { + return relativeDeduction; + } + + public String getTrainTypeName() { + return trainTypeName; + } + + public String toString() { + return "SpecialTrainBuy comp=" + originalCompany.getName() + " extra=" + + extra + " deduction=" + deductionString; + } + + @Override + public String toMenu() { + + return LocalText.getText("SpecialTrainBuy_1880", + trainTypeName, + deductionString, + originalCompany.getName()); + } + + public String getInfo() { + return toMenu(); + } +} diff --git a/rails/game/specific/_1880/StartCompany_1880.java b/rails/game/specific/_1880/StartCompany_1880.java new file mode 100644 index 0000000..616b8bb --- /dev/null +++ b/rails/game/specific/_1880/StartCompany_1880.java @@ -0,0 +1,129 @@ +/** + * + */ +package rails.game.specific._1880; + +import java.util.BitSet; + +import rails.game.PublicCompanyI; +import rails.game.StockSpace; +import rails.game.StockSpaceI; +import rails.game.action.StartCompany; + +/** + * @author Martin + * + */ +public class StartCompany_1880 extends StartCompany { + + /** + * + */ + private static final long serialVersionUID = 1L; + + + /** + * @param company + * @param prices + * @param maximumNumber + */ + public StartCompany_1880(PublicCompanyI company, int[] prices, + int maximumNumber) { + super(company, prices, maximumNumber); + // TODO Auto-generated constructor stub + } + + /** + * @param company + * @param startPrice + */ + public StartCompany_1880(PublicCompanyI company, int[] startPrice) { + this(company, startPrice, 1); + // TODO Auto-generated constructor stub + + } + + /** + * @param company + * @param price + * @param maximumNumber + */ + public StartCompany_1880(PublicCompanyI company, int price, + int maximumNumber) { + super(company, price, maximumNumber); + StockSpaceI parPrice=gameManager.getStockMarket().getStartSpace(price); + this.getCompany().setParSpace(parPrice); + // TODO Auto-generated constructor stub + } + + /** + * @param company + * @param price + */ + public StartCompany_1880(PublicCompanyI company, int price) { + super(company, price); + // TODO Auto-generated constructor stub + } + + + public void setBuildingRight(PublicCompany_1880 company, String buildingRightString ) { + BitSet buildingRight = new BitSet(5); + + if (buildingRightString == "A") { + buildingRight.set(0); + } else if (buildingRightString == "B") { + buildingRight.set(1); + } else if (buildingRightString == "C") { + buildingRight.set(2); + } else if (buildingRightString == "D") { + buildingRight.set(3); + } else if (buildingRightString == "A+B") { + buildingRight.set(0); + buildingRight.set(1); + } else if (buildingRightString == "A+B+C") { + buildingRight.set(0); + buildingRight.set(1); + buildingRight.set(2); + } else if (buildingRightString == "B+C") { + buildingRight.set(1); + buildingRight.set(2); + } else if (buildingRightString == "B+C+D") { + buildingRight.set(1); + buildingRight.set(2); + buildingRight.set(3); + } else if (buildingRightString == "C+D") { + buildingRight.set(2); + buildingRight.set(3); + } + + company.setBuildingRights( buildingRight); + company.setRight("BuildingRight", buildingRightString); + } + + + public void setPresidentPercentage(PublicCompany_1880 company, int percentage) { + company.setPresidentShares(percentage); + } + + /* (non-Javadoc) + * @see rails.game.action.StartCompany#getStartPrices() + */ + @Override + public int[] getStartPrices() { + // TODO Auto-generated method stub + return super.getStartPrices(); + } + + /* (non-Javadoc) + * @see rails.game.action.StartCompany#setStartPrice(int) + */ + @Override + public void setStartPrice(int startPrice) { + // TODO Auto-generated method stub + price = startPrice; + StockSpaceI parPrice=gameManager.getStockMarket().getStartSpace(startPrice); + this.getCompany().setParSpace(parPrice); + ((StockMarket_1880) gameManager.getStockMarket()).setParSlot(startPrice); + } + +} diff --git a/rails/ui/swing/gamespecific/_1880/GameUIManager_1880.java b/rails/ui/swing/gamespecific/_1880/GameUIManager_1880.java new file mode 100644 index 0000000..279e534 --- /dev/null +++ b/rails/ui/swing/gamespecific/_1880/GameUIManager_1880.java @@ -0,0 +1,152 @@ +/** + * + */ +package rails.ui.swing.gamespecific._1880; + + + +import rails.ui.swing.GameUIManager; +import rails.common.LocalText; +import rails.game.action.PossibleORAction; +import rails.game.specific._1880.OperatingRound_1880; +import rails.game.specific._1880.PublicCompany_1880; +import rails.game.specific._1880.StartCompany_1880; +import rails.ui.swing.elements.NonModalDialog; +import rails.ui.swing.elements.RadioButtonDialog; + +/** + * @author Martin Brumm + * @date 5-2-2012 + * + */ +public class GameUIManager_1880 extends GameUIManager { + public static final String COMPANY_SELECT_BUILDING_RIGHT = "SelectBuildingRight"; + public static final String COMPANY_SELECT_PRESIDENT_SHARE_SIZE = "SelectPresidentShareSize"; + public static final String COMPANY_START_PRICE_DIALOG = "CompanyStartPrice"; + public static final String Investor_has_Destination = "Investor_at_Destination"; + + + @Override + public void dialogActionPerformed () { + + String key = ""; + String[] brights; + String[] brights2= {"A", "B", "C","D", "A+B", "A+B+C", "B+C", "B+C+D", "C+D"}; + String[] presidentShareSizes; + if (currentDialog instanceof NonModalDialog) key = ((NonModalDialog) currentDialog).getKey(); + + // Check for the dialogs that are postprocessed in this class. +/* + * The mechanismn for starting a company and getting the necessary decisions by a player + * is implemented with the following steps + * Player chooses Startprice + * | + * Player chooses President share percentage (20, 30 or 40 percent share) + * | + * Player chooses Building Right based on percentage of president share + * + * - 20 percent share will allow to choose from all Building Rights (A+B+C, B+C+D and 2 Phase and single Phase rights) + * - 30 percent share will allow to choose from 2 Phase Building Rights (A+B, B+C, C+D and all single Phase rights) + * - 40 percent share will limit the player to a building right for one Phase (A, B, C, D) + */ + + if (COMPANY_SELECT_PRESIDENT_SHARE_SIZE.equals(key)) { + + RadioButtonDialog dialog = (RadioButtonDialog) currentDialog; + StartCompany_1880 action = (StartCompany_1880) currentDialogAction; + + int index = dialog.getSelectedOption(); + if (index < 0) { + currentDialogAction = null; + return; + } + + if (index > 1) { // 40 Percent Share has been chosen + action.setPresidentPercentage((PublicCompany_1880) action.getCompany(), 40); + brights= new String[] {"A", "B", "C", "D"}; + + } else if ( index == 1) { + action.setPresidentPercentage((PublicCompany_1880) action.getCompany(), 30); + brights= new String[] {"A", "B", "C", "D", "A+B", "B+C", "C+D"}; + } else { // 20 Percent Share chosen + action.setPresidentPercentage((PublicCompany_1880) action.getCompany(), 20); + brights= new String[] {"A", "B", "C","D", "A+B", "A+B+C", "B+C", "B+C+D", "C+D"}; + } + + dialog = new RadioButtonDialog (COMPANY_SELECT_BUILDING_RIGHT, + this, + statusWindow, + LocalText.getText("PleaseSelect"), + LocalText.getText( + "WhichBuildingRight",action.getPlayerName(), + action.getCompanyName()), + brights, -1); + setCurrentDialog(dialog, action); + statusWindow.disableButtons(); + return; + + } else if (COMPANY_SELECT_BUILDING_RIGHT.equals(key)) { + + RadioButtonDialog dialog = (RadioButtonDialog) currentDialog; + StartCompany_1880 action = (StartCompany_1880) currentDialogAction; + + int index = dialog.getSelectedOption(); + if (index < 0) { + currentDialogAction = null; + return; + } + action.setBuildingRight((PublicCompany_1880) action.getCompany(), brights2[index]); + + } else if (COMPANY_START_PRICE_DIALOG.equals(key) + && currentDialogAction instanceof StartCompany_1880) { + + // A start price has been selected (or not) for a stating major company. + RadioButtonDialog dialog = (RadioButtonDialog) currentDialog; + StartCompany_1880 action = (StartCompany_1880) currentDialogAction; + + int index = dialog.getSelectedOption(); + if (index < 0) { + currentDialogAction = null; + return; + } + action.setStartPrice(action.getStartPrices()[index]); + + /* Set up another dialog for the next step + * need to setup Options based on the Presidents Certificate Size... + * The player should only get valid percentages presented to him for selection + * This leads to the check what amount of cash does the player have + */ + + int freePlayerCash = gameManager.getCurrentPlayer().getFreeCash(); + if (freePlayerCash >= (action.getStartPrices()[index]*4)) { //enough Cash for 40 Percent + presidentShareSizes = new String[] {"20 Percent", "30 Percent", "40 Percent"}; + } else if (freePlayerCash >= (action.getStartPrices()[index]*3)) { //enough Cash for 30 Percent + presidentShareSizes = new String[] {"20 Percent", "30 Percent"}; + } else { //enough Cash only for 20 Percent + presidentShareSizes = new String[] {"20 Percent"}; + } + dialog = new RadioButtonDialog (COMPANY_SELECT_PRESIDENT_SHARE_SIZE, + this, + statusWindow, + LocalText.getText("PleaseSelect"), + LocalText.getText( + "WhichPresidentShareSize", + action.getPlayerName(), + action.getCompanyName()), + presidentShareSizes, -1); + setCurrentDialog(dialog, action); + statusWindow.disableButtons(); + return; + } else { + // Current dialog not found yet, try the superclass. + super.dialogActionPerformed(false); + return; + } + + // Dialog action found and processed, let the superclass initiate processing. + super.dialogActionPerformed(true); + + } + +} + commit 3b3c740acc6c0113e53b170d6ca7b8bf268e7484 Author: Erik Vos <eri...@xs...> Date: Wed Mar 14 11:34:59 2012 +0100 Allow 'pic' attribute in Hex definition in Map.xml. To define per-hex alternative pictures, as needed in 1880. diff --git a/data/1830/Map.xml b/data/1830/Map.xml index 9a286e8..ecc6404 100644 --- a/data/1830/Map.xml +++ b/data/1830/Map.xml @@ -113,7 +113,7 @@ <Hex name="H12" tile="-101" city="Altoona" runThrough="tokenOnly"/> </IfOption> <IfOption name="Variant" value="Coalfields,Reading,Coalfields&Reading"> - <Hex name="H12" tile="-30003" pic="-30002" city="Altoona"> + <Hex name="H12" tile="-30003" city="Altoona"> <Access runThrough="tokenOnly"/> </Hex> </IfOption> @@ -121,7 +121,7 @@ <Hex name="H14" tile="0"/> </IfOption> <IfOption name="Variant" value="Reading,Coalfields&Reading"> - <Hex name="H14" tile="-30007" pic="-30006" city="Reading"> + <Hex name="H14" tile="-30007" city="Reading"> <Access runThrough="tokenOnly"/> </Hex> </IfOption> diff --git a/rails/game/MapHex.java b/rails/game/MapHex.java index 0e1af5c..f6225cf 100644 --- a/rails/game/MapHex.java +++ b/rails/game/MapHex.java @@ -64,6 +64,7 @@ StationHolder, TokenHolder { protected int number; protected String tileFileName; protected int preprintedTileId; + protected int preprintedPictureId = 0; protected TileI currentTile; protected int currentTileRotation; protected int preprintedTileRotation; @@ -229,6 +230,7 @@ StationHolder, TokenHolder { } preprintedTileId = tag.getAttributeAsInteger("tile", -999); + preprintedPictureId = tag.getAttributeAsInteger("pic", 0); preprintedTileRotation = tag.getAttributeAsInteger("orientation", 0); currentTileRotation = preprintedTileRotation; @@ -466,6 +468,20 @@ StationHolder, TokenHolder { return preprintedTileRotation; } + /** Return the current picture ID (i.e. the tile ID to be displayed, rather than used for route determination). + * <p> Usually, the picture ID is equal to the tile ID. Different values may be defined per hex or per tile. + * @return The current picture ID + */ + public int getPictureId () { + if (currentTile.getId() == preprintedTileId && preprintedPictureId != 0) { + return preprintedPictureId; + } else if (currentTile.getPictureId() != 0) { + return currentTile.getPictureId(); + } else { + return currentTile.getId(); + } + } + /** * @return Returns the image file name for the tile. */ diff --git a/rails/ui/swing/hexmap/GUITile.java b/rails/ui/swing/hexmap/GUITile.java index becbe56..f7162f9 100644 --- a/rails/ui/swing/hexmap/GUITile.java +++ b/rails/ui/swing/hexmap/GUITile.java @@ -45,7 +45,7 @@ public class GUITile { public static final double SVG_Y_CENTER_LOC = 0.426; protected static Logger log = - Logger.getLogger(GUITile.class.getPackage().getName()); + Logger.getLogger(GUITile.class.getPackage().getName()); public GUITile(int tileId, GUIHex guiHex) { this.guiHex = guiHex; @@ -53,7 +53,7 @@ public class GUITile { this.hex = (MapHex)guiHex.getModel(); TileManager tileManager = guiHex.getHexMap().orUIManager.getTileManager(); tile = tileManager.getTile(tileId); - picId = tile.getPictureId(); + picId = hex.getPictureId(); if (hex.getTileOrientation() == TileOrientation.EW) { baseRotation = 0.5 * DEG60; @@ -122,7 +122,7 @@ public class GUITile { // these must all be preserved. if (prevTile.hasTracks(prevTileSide)) { List<Track> newTracks = - tile.getTracksPerSide(tempTileSide); + tile.getTracksPerSide(tempTileSide); old: for (Track oldTrack : prevTile.getTracksPerSide(prevTileSide)) { if (oldTrack.getEndPoint(prevTileSide) >= 0) { // Old track ending in another side @@ -143,7 +143,7 @@ public class GUITile { //log.debug("[" + i + "," + j + "] Found " // + oldTrack.getEndPoint(prevTileSide)); oldCities.put(prevTileSide, - oldTrack.getEndPoint(prevTileSide)); + oldTrack.getEndPoint(prevTileSide)); } else { // Downgraded // Assume there are only two exits @@ -153,7 +153,7 @@ public class GUITile { int otherNewEndPoint = newTracks.get(0).getEndPoint(tempTileSide); // Calculate the corresponding old tile side number int otherOldEndPoint = (otherNewEndPoint + tempRot - prevTileRotation + 6) % 6; - // That old tile side must have track too + // That old tile side must have track too if (prevTile.getTracksPerSide(otherOldEndPoint) == null || prevTile.getTracksPerSide(otherOldEndPoint).isEmpty()) { continue rot; @@ -203,7 +203,7 @@ public class GUITile { //log.debug("Check " + newCities.get(kkk) + " & " // + newCities.get(ll)); if (newCities.get(kk) == null - || newCities.get(ll) == null) continue rot; + || newCities.get(ll) == null) continue rot; // If connected cities do not correspond, skip // (this is the "OO brown upgrade get-right" feature) // Only apply this check if the number of cities has not decreased @@ -285,7 +285,7 @@ public class GUITile { AffineTransform af = AffineTransform.getRotateInstance(radians, xCenter, yCenter); af.scale(tileScale, tileScale); - AffineTransformOp aop = new AffineTransformOp(af, + AffineTransformOp aop = new AffineTransformOp(af, getTileRenderingHints()); g2.drawImage(tileImage, aop, x - xCenter, y - yCenter); @@ -294,7 +294,7 @@ public class GUITile { log.error("No image for tile "+tileId+" on hex "+guiHex.getName()); } } - + /** * Provides the image of the tile based on the zoomStep. * tileScale is not considered for producing this image. @@ -314,32 +314,32 @@ public class GUITile { int narrowDiagonal = (int)Math.round( wideDiagonal * 0.5 * Math.sqrt(3) ); int border = wideDiagonal - narrowDiagonal; - + // STEP 2: CENTER TILE IN IMAGE - // apply the bottom border also the left / top / right + // apply the bottom border also the left / top / right //center tile by translation AffineTransform centeringAT = AffineTransform.getTranslateInstance( border, border ); AffineTransformOp centeringATOp = new AffineTransformOp(centeringAT, null); - + //centered tile image create manually since it also needs a border on the right - BufferedImage centeredTileImage = new BufferedImage( + BufferedImage centeredTileImage = new BufferedImage( uncenteredTileImage.getWidth() + border * 2, uncenteredTileImage.getHeight() + border, uncenteredTileImage.getType()); centeringATOp.filter(uncenteredTileImage, centeredTileImage); - + // STEP 3: ROTATE TILE IMAGE // feasible only now since there are enough margins to ensure tile won't exceed bounds double radians = baseRotation + rotation * DEG60; - int xCenter = (int) Math.round(centeredTileImage.getWidth() / 2 ); - int yCenter = (int) Math.round(centeredTileImage.getHeight() / 2 ); + int xCenter = Math.round(centeredTileImage.getWidth() / 2 ); + int yCenter = Math.round(centeredTileImage.getHeight() / 2 ); AffineTransform af = AffineTransform.getRotateInstance(radians, xCenter, yCenter); AffineTransformOp aop = new AffineTransformOp(af, getTileRenderingHints()); - + BufferedImage rotatedTileImage = aop.filter(centeredTileImage, null); // STEP 4: CROP ROTATED TILE IMAGE @@ -357,14 +357,14 @@ public class GUITile { } BufferedImage croppedTileImage = rotatedTileImage.getSubimage( - xCenter - croppedWidth / 2, - yCenter - croppedHeight / 2, + xCenter - croppedWidth / 2, + yCenter - croppedHeight / 2, croppedWidth, croppedHeight ); - + return croppedTileImage; } - + public TileI getTile() { return tile; } |