|
From: Erik V. <ev...@us...> - 2011-08-11 22:16:26
|
rails/game/OperatingRound.java | 4163 ++++++++++++++++++++---------------------
1 file changed, 2110 insertions(+), 2053 deletions(-)
New commits:
commit 8d9ca907cc522935c710a8349d13a9f97a50eebf
Author: Erik Vos <eri...@xs...>
Date: Fri Aug 12 00:14:14 2011 +0200
Reorganized OperatingRound
No code changes, but methods have been sorted into functional groups,
each preceded by a header.
Hopefully this class will now be easier to grasp.
diff --git a/rails/game/OperatingRound.java b/rails/game/OperatingRound.java
index 957203e..ec8de9c 100644
--- a/rails/game/OperatingRound.java
+++ b/rails/game/OperatingRound.java
@@ -3,9 +3,7 @@ package rails.game;
import java.util.*;
-import rails.common.DisplayBuffer;
-import rails.common.GuiDef;
-import rails.common.LocalText;
+import rails.common.*;
import rails.common.parser.GameOption;
import rails.game.action.*;
import rails.game.correct.ClosePrivate;
@@ -82,9 +80,12 @@ public class OperatingRound extends Round implements Observer {
protected TrainManager trainManager = gameManager.getTrainManager();
+ /*=======================================
+ * 1. OR START and END
+ *=======================================*/
+
/**
* Constructor with no parameters, call the super Class (Round's) Constructor with no parameters
- *
*/
public OperatingRound(GameManagerI gameManager) {
super (gameManager);
@@ -158,7 +159,56 @@ public class OperatingRound extends Round implements Observer {
}
- /*----- METHODS THAT PROCESS PLAYER ACTIONS -----*/
+ @Override
+ public void resume() {
+
+ if (savedAction instanceof BuyTrain) {
+ buyTrain ((BuyTrain)savedAction);
+ } else if (savedAction instanceof SetDividend) {
+ executeSetRevenueAndDividend ((SetDividend) savedAction);
+ } else if (savedAction instanceof RepayLoans) {
+ executeRepayLoans ((RepayLoans) savedAction);
+ } else if (savedAction == null) {
+ //nextStep();
+ }
+ savedAction = null;
+ wasInterrupted.set(true);
+
+ guiHints.setVisibilityHint(GuiDef.Panel.STOCK_MARKET, false);
+ guiHints.setVisibilityHint(GuiDef.Panel.STATUS, true);
+ guiHints.setActivePanel(GuiDef.Panel.MAP);
+ }
+
+ protected void finishOR() {
+
+ // Check if any privates must be closed
+ // (now only applies to 1856 W&SR) - no, that is at end of TURN
+ //for (PrivateCompanyI priv : gameManager.getAllPrivateCompanies()) {
+ // priv.checkClosingIfExercised(true);
+ //}
+
+ ReportBuffer.add(" ");
+ ReportBuffer.add(LocalText.getText("EndOfOperatingRound", thisOrNumber));
+
+ // Update the worth increase per player
+ int orWorthIncrease;
+ for (Player player : gameManager.getPlayers()) {
+ player.setLastORWorthIncrease();
+ orWorthIncrease = player.getLastORWorthIncrease().intValue();
+ ReportBuffer.add(LocalText.getText("ORWorthIncrease",
+ player.getName(),
+ thisOrNumber,
+ Bank.format(orWorthIncrease)));
+ }
+
+ // OR done. Inform GameManager.
+ finishRound();
+ }
+
+ /*=======================================
+ * 2. CENTRAL PROCESSING FUNCTIONS
+ * 2.1. PROCESS USER ACTION
+ *=======================================*/
@Override
public boolean process(PossibleAction action) {
@@ -236,10 +286,10 @@ public class OperatingRound extends Round implements Observer {
} else if (selectedAction instanceof ClosePrivate) {
result = executeClosePrivate((ClosePrivate)selectedAction);
-
+
} else if (selectedAction instanceof UseSpecialProperty
&& ((UseSpecialProperty)selectedAction).getSpecialProperty() instanceof SpecialRight) {
-
+
result = buyRight ((UseSpecialProperty)selectedAction);
} else if (selectedAction instanceof NullAction) {
@@ -275,1073 +325,1284 @@ public class OperatingRound extends Round implements Observer {
return false;
}
- public boolean layTile(LayTile action) {
+ /*=======================================
+ * 2.2. PREPARE NEXT ACTION
+ *=======================================*/
- String errMsg = null;
- int cost = 0;
- SpecialTileLay stl = null;
- boolean extra = false;
+ /**
+ * To be called after each change, to re-establish the currently allowed
+ * actions. (new method, intended to absorb code from several other
+ * methods).
+ *
+ */
+ @Override
+ public boolean setPossibleActions() {
- PublicCompanyI company = action.getCompany();
- String companyName = company.getName();
- TileI tile = action.getLaidTile();
- MapHex hex = action.getChosenHex();
- int orientation = action.getOrientation();
+ /* Create a new list of possible actions for the UI */
+ possibleActions.clear();
+ selectedAction = null;
- // Dummy loop to enable a quick jump out.
- while (true) {
- // Checks
- // Must be correct company.
- if (!companyName.equals(operatingCompany.get().getName())) {
- errMsg =
- LocalText.getText("WrongCompany",
- companyName,
- operatingCompany.get().getName() );
- break;
- }
- // Must be correct step
- if (getStep() != GameDef.OrStep.LAY_TRACK) {
- errMsg = LocalText.getText("WrongActionNoTileLay");
- break;
+ boolean forced = false;
+ doneAllowed = false; // set default (fix of bug 2954654)
+
+ if (getStep() == GameDef.OrStep.INITIAL) {
+ initTurn();
+ if (noMapMode) {
+ nextStep (GameDef.OrStep.LAY_TOKEN);
+ } else {
+ initNormalTileLays(); // new: only called once per turn ?
+ setStep (GameDef.OrStep.LAY_TRACK);
}
+ }
- if (tile == null) break;
+ GameDef.OrStep step = getStep();
+ if (step == GameDef.OrStep.LAY_TRACK) {
- if (!getCurrentPhase().isTileColourAllowed(tile.getColourName())) {
- errMsg =
- LocalText.getText("TileNotYetAvailable",
- tile.getExternalId());
- break;
- }
- if (tile.countFreeTiles() == 0) {
- errMsg =
- LocalText.getText("TileNotAvailable",
- tile.getExternalId());
- break;
+ if (!operatingCompany.get().hasLaidHomeBaseTokens()) {
+ // This can occur if the home hex has two cities and track,
+ // such as the green OO tile #59
+ possibleActions.add(new LayBaseToken (operatingCompany.get().getHomeHexes()));
+ forced = true;
+ } else {
+ possibleActions.addAll(getNormalTileLays(true));
+ possibleActions.addAll(getSpecialTileLays(true));
+ possibleActions.add(new NullAction(NullAction.SKIP));
}
- /*
- * Check if the current tile is allowed via the LayTile allowance.
- * (currently the set if tiles is always null, which means that this
- * check is redundant. This may change in the future.
- */
- if (action != null) {
- List<TileI> tiles = action.getTiles();
- if (tiles != null && !tiles.isEmpty() && !tiles.contains(tile)) {
- errMsg =
- LocalText.getText(
- "TileMayNotBeLaidInHex",
- tile.getExternalId(),
- hex.getName() );
- break;
- }
- stl = action.getSpecialProperty();
- if (stl != null) extra = stl.isExtra();
- }
+ } else if (step == GameDef.OrStep.LAY_TOKEN) {
+ setNormalTokenLays();
+ setSpecialTokenLays();
+ log.debug("Normal token lays: " + currentNormalTokenLays.size());
+ log.debug("Special token lays: " + currentSpecialTokenLays.size());
- /*
- * If this counts as a normal tile lay, check if the allowed number
- * of normal tile lays is not exceeded.
- */
- if (!extra && !validateNormalTileLay(tile)) {
- errMsg =
- LocalText.getText("NumberOfNormalTileLaysExceeded",
- tile.getColourName());
- break;
- }
+ possibleActions.addAll(currentNormalTokenLays);
+ possibleActions.addAll(currentSpecialTokenLays);
+ possibleActions.add(new NullAction(NullAction.SKIP));
+ } else if (step == GameDef.OrStep.CALC_REVENUE) {
+ prepareRevenueAndDividendAction();
+ if (noMapMode)
+ prepareNoMapActions();
+ } else if (step == GameDef.OrStep.BUY_TRAIN) {
+ setBuyableTrains();
+ // TODO Need route checking here.
+ // TEMPORARILY allow not buying a train if none owned
+ //if (!operatingCompany.getObject().mustOwnATrain()
+ // || operatingCompany.getObject().getPortfolio().getNumberOfTrains() > 0) {
+ doneAllowed = true;
+ //}
+ if (noMapMode && (operatingCompany.get().getLastRevenue() == 0))
+ prepareNoMapActions();
- // Sort out cost
- if (stl != null && stl.isFree()) {
- cost = 0;
- } else {
- cost = hex.getTileCost();
- }
+ } else if (step == GameDef.OrStep.DISCARD_TRAINS) {
- // Amount must be non-negative multiple of 10
- if (cost < 0) {
- errMsg =
- LocalText.getText("NegativeAmountNotAllowed",
- Bank.format(cost));
- break;
- }
- if (cost % 10 != 0) {
- errMsg =
- LocalText.getText("AmountMustBeMultipleOf10",
- Bank.format(cost));
- break;
- }
- // Does the company have the money?
- if (cost > operatingCompany.get().getCash()) {
- errMsg =
- LocalText.getText("NotEnoughMoney",
- companyName,
- Bank.format(operatingCompany.get().getCash()),
- Bank.format(cost) );
- break;
- }
- break;
- }
- if (errMsg != null) {
- DisplayBuffer.add(LocalText.getText("CannotLayTileOn",
- companyName,
- tile.getExternalId(),
- hex.getName(),
- Bank.format(cost),
- errMsg ));
- return false;
+ forced = true;
+ setTrainsToDiscard();
}
- /* End of validation, start of execution */
- moveStack.start(true);
+ // The following additional "common" actions are only available if the
+ // primary action is not forced.
+ if (!forced) {
- if (tile != null) {
- if (cost > 0)
- new CashMove(operatingCompany.get(), bank, cost);
- operatingCompany.get().layTile(hex, tile, orientation, cost);
+ setBonusTokenLays();
- if (cost == 0) {
- ReportBuffer.add(LocalText.getText("LaysTileAt",
- companyName,
- tile.getExternalId(),
- hex.getName(),
- hex.getOrientationName(orientation)));
- } else {
- ReportBuffer.add(LocalText.getText("LaysTileAtFor",
- companyName,
- tile.getExternalId(),
- hex.getName(),
- hex.getOrientationName(orientation),
- Bank.format(cost) ));
- }
- hex.upgrade(action);
+ setDestinationActions();
- // Was a special property used?
- if (stl != null) {
- stl.setExercised();
- //currentSpecialTileLays.remove(action);
- log.debug("This was a special tile lay, "
- + (extra ? "" : " not") + " extra");
+ setGameSpecificPossibleActions();
+ // Private Company manually closure
+ for (PrivateCompanyI priv: companyManager.getAllPrivateCompanies()) {
+ if (!priv.isClosed() && priv.closesManually())
+ possibleActions.add(new ClosePrivate(priv));
}
- if (!extra) {
- log.debug("This was a normal tile lay");
- registerNormalTileLay(tile);
- }
- }
- if (tile == null || !areTileLaysPossible()) {
- nextStep();
- }
-
- return true;
- }
+ // Can private companies be bought?
+ if (isPrivateSellingAllowed()) {
- protected boolean validateNormalTileLay(TileI tile) {
- return checkNormalTileLay(tile, false);
- }
+ // Create a list of players with the current one in front
+ int currentPlayerIndex = operatingCompany.get().getPresident().getIndex();
+ Player player;
+ int minPrice, maxPrice;
+ List<Player> players = getPlayers();
+ int numberOfPlayers = getNumberOfPlayers();
+ for (int i = currentPlayerIndex; i < currentPlayerIndex
+ + numberOfPlayers; i++) {
+ player = players.get(i % numberOfPlayers);
+ if (!maySellPrivate(player)) continue;
+ for (PrivateCompanyI privComp : player.getPortfolio().getPrivateCompanies()) {
- protected void registerNormalTileLay(TileI tile) {
- checkNormalTileLay(tile, true);
- }
+ // check to see if the private can be sold to a company
+ if (!privComp.tradeableToCompany()) {
+ continue;
+ }
- protected boolean checkNormalTileLay(TileI tile, boolean update) {
+ minPrice = getPrivateMinimumPrice (privComp);
- // Unspecified tile (e.g. 1889 D private, which is free on mountains)
- if (tile == null) {
- return !tileLaysPerColour.isEmpty();
- }
-
- String colour = tile.getColourName();
- Integer oldAllowedNumberObject = tileLaysPerColour.get(colour);
+ maxPrice = getPrivateMaximumPrice (privComp);
- if (oldAllowedNumberObject == null) return false;
+ possibleActions.add(new BuyPrivate(privComp, minPrice,
+ maxPrice));
+ }
+ }
+ }
- int oldAllowedNumber = oldAllowedNumberObject.intValue();
- if (oldAllowedNumber <= 0) return false;
+ if (operatingCompany.get().canUseSpecialProperties()) {
- if (update) updateAllowedTileColours(colour, oldAllowedNumber);
- return true;
- }
+ // Are there any "common" special properties,
+ // i.e. properties that are available to everyone?
+ List<SpecialPropertyI> commonSP = gameManager.getCommonSpecialProperties();
+ if (commonSP != null) {
+ SellBonusToken sbt;
+ loop: for (SpecialPropertyI sp : commonSP) {
+ if (sp instanceof SellBonusToken) {
+ sbt = (SellBonusToken) sp;
+ // Can't buy if already owned
+ if (operatingCompany.get().getBonuses() != null) {
+ for (Bonus bonus : operatingCompany.get().getBonuses()) {
+ if (bonus.getName().equals(sp.getName())) continue loop;
+ }
+ }
+ possibleActions.add (new BuyBonusToken (sbt));
+ }
+ }
+ }
- /*
- * We will assume that in all cases the following assertions hold: 1. If
- * the allowed number for the colour of the just laid tile reaches zero,
- * all normal tile lays have been consumed. 2. If any colour is laid, no
- * different colours may be laid. THIS MAY NOT BE TRUE FOR ALL GAMES!
- */
+ // Are there other step-independent special properties owned by the company?
+ List<SpecialPropertyI> orsps = operatingCompany.get().getPortfolio().getAllSpecialProperties();
+ List<SpecialPropertyI> compsps = operatingCompany.get().getSpecialProperties();
+ if (compsps != null) orsps.addAll(compsps);
- protected void updateAllowedTileColours (String colour, int oldAllowedNumber) {
-
- if (oldAllowedNumber <= 1) {
- tileLaysPerColour.clear();
- log.debug("No more normal tile lays allowed");
- //currentNormalTileLays.clear();// Shouldn't be needed anymore ??
- } else {
- List<String> coloursToRemove = new ArrayList<String>();
- for (String key:tileLaysPerColour.viewKeySet()) {
- if (colour.equals(key)) {
- tileLaysPerColour.put(key, oldAllowedNumber-1);
- } else {
- coloursToRemove.add(key);
+ if (orsps != null) {
+ for (SpecialPropertyI sp : orsps) {
+ if (!sp.isExercised() && sp.isUsableIfOwnedByCompany()
+ && sp.isUsableDuringOR(step)) {
+ if (sp instanceof SpecialTokenLay) {
+ if (getStep() != GameDef.OrStep.LAY_TOKEN) {
+ possibleActions.add(new LayBaseToken((SpecialTokenLay)sp));
+ }
+ } else {
+ possibleActions.add(new UseSpecialProperty(sp));
+ }
+ }
+ }
+ }
+ // Are there other step-independent special properties owned by the president?
+ orsps = getCurrentPlayer().getPortfolio().getAllSpecialProperties();
+ if (orsps != null) {
+ for (SpecialPropertyI sp : orsps) {
+ if (!sp.isExercised() && sp.isUsableIfOwnedByPlayer()
+ && sp.isUsableDuringOR(step)) {
+ if (sp instanceof SpecialTokenLay) {
+ if (getStep() != GameDef.OrStep.LAY_TOKEN) {
+ possibleActions.add(new LayBaseToken((SpecialTokenLay)sp));
+ }
+ } else {
+ possibleActions.add(new UseSpecialProperty(sp));
+ }
+ }
+ }
}
}
- // Two-step removal to prevent ConcurrentModificatioonException.
- for (String key : coloursToRemove) {
- tileLaysPerColour.remove(key);
- }
- log.debug((oldAllowedNumber - 1) + " additional " + colour
- + " tile lays allowed; no other colours");
}
- }
- public boolean layBaseToken(LayBaseToken action) {
+ if (doneAllowed) {
+ possibleActions.add(new NullAction(NullAction.DONE));
+ }
- String errMsg = null;
- int cost = 0;
- SpecialTokenLay stl = null;
- boolean extra = false;
+ for (PossibleAction pa : possibleActions.getList()) {
+ try {
+ log.debug(operatingCompany.get().getName() + " may: " + pa.toString());
+ } catch (Exception e) {
+ log.error("Error in toString() of " + pa.getClass(), e);
+ }
+ }
- MapHex hex = action.getChosenHex();
- int station = action.getChosenStation();
- String companyName = operatingCompany.get().getName();
+ return true;
+ }
- // TEMPORARY FIX to enable fixing invalidated saved files
- //if ("N11".equals(hex.getName()) && station == 2) {
- // station = 1;
- // action.setChosenStation(1);
- //}
+ /** Stub, can be overridden by subclasses */
+ protected void setGameSpecificPossibleActions() {
- // Dummy loop to enable a quick jump out.
- while (true) {
+ }
- // Checks
- // Must be correct step (exception: home base lay & some special token lay)
- if (getStep() != GameDef.OrStep.LAY_TOKEN
- && action.getType() != LayBaseToken.HOME_CITY
- && action.getType() != LayBaseToken.SPECIAL_PROPERTY) {
- errMsg = LocalText.getText("WrongActionNoTokenLay");
- break;
- }
+ /*=======================================
+ * 2.3. TURN CONTROL
+ *=======================================*/
- if (operatingCompany.get().getNumberOfFreeBaseTokens() == 0) {
- errMsg = LocalText.getText("HasNoTokensLeft", companyName);
- break;
- }
+ protected void initTurn() {
+ log.debug("Starting turn of "+operatingCompany.get().getName());
+ ReportBuffer.add(" ");
+ ReportBuffer.add(LocalText.getText("CompanyOperates",
+ operatingCompany.get().getName(),
+ operatingCompany.get().getPresident().getName()));
+ setCurrentPlayer(operatingCompany.get().getPresident());
- if (!isTokenLayAllowed (operatingCompany.get(), hex, station)) {
- errMsg = LocalText.getText("BaseTokenSlotIsReserved");
- break;
+ if (noMapMode && !operatingCompany.get().hasLaidHomeBaseTokens()){
+ // Lay base token in noMapMode
+ BaseToken token = operatingCompany.get().getFreeToken();
+ if (token == null) {
+ log.error("Company " + operatingCompany.get().getName() + " has no free token to lay base token");
+ } else {
+ log.debug("Company " + operatingCompany.get().getName() + " lays base token in nomap mode");
+ token.moveTo(bank.getUnavailable());
}
+ }
+ operatingCompany.get().initTurn();
+ trainsBoughtThisTurn.clear();
+ }
- if (!hex.hasTokenSlotsLeft(station)) {
- errMsg = LocalText.getText("CityHasNoEmptySlots");
- break;
- }
+ protected void finishTurn() {
- /*
- * TODO: the below condition holds for 1830. in some games, separate
- * cities on one tile may hold tokens of the same company; this case
- * is not yet covered.
- */
- if (hex.hasTokenOfCompany(operatingCompany.get())) {
- errMsg =
- LocalText.getText("TileAlreadyHasToken",
- hex.getName(),
- companyName );
- break;
- }
+ if (!operatingCompany.get().isClosed()) {
+ operatingCompany.get().setOperated();
+ companiesOperatedThisRound.add(operatingCompany.get());
- if (action != null) {
- List<MapHex> locations = action.getLocations();
- if (locations != null && locations.size() > 0
- && !locations.contains(hex) && !locations.contains(null)) {
- errMsg =
- LocalText.getText("TokenLayingHexMismatch",
- hex.getName(),
- action.getLocationNameString() );
- break;
- }
- stl = action.getSpecialProperty();
- if (stl != null) extra = stl.isExtra();
+ // Check if any privates must be closed (now only applies to 1856 W&SR)
+ // Copy list first to avoid concurrent modifications
+ for (PrivateCompanyI priv :
+ new ArrayList<PrivateCompanyI> (operatingCompany.get().getPortfolio().getPrivateCompanies())) {
+ priv.checkClosingIfExercised(true);
}
+ }
- cost = operatingCompany.get().getBaseTokenLayCost(hex);
- if (stl != null && stl.isFree()) cost = 0;
+ if (!finishTurnSpecials()) return;
- // Does the company have the money?
- if (cost > operatingCompany.get().getCash()) {
- errMsg = LocalText.getText("NotEnoughMoney",
- companyName,
- Bank.format(operatingCompany.get().getCash()),
- Bank.format(cost));
- break;
- }
- break;
- }
- if (errMsg != null) {
- DisplayBuffer.add(LocalText.getText("CannotLayBaseTokenOn",
- companyName,
- hex.getName(),
- Bank.format(cost),
- errMsg ));
- return false;
+ if (setNextOperatingCompany(false)) {
+ setStep(GameDef.OrStep.INITIAL);
+ } else {
+ finishOR();
}
+ }
- /* End of validation, start of execution */
- moveStack.start(true);
+ /** Stub, may be overridden in subclasses
+ * Return value:
+ * TRUE = normal turn end;
+ * FALSE = return immediately from finishTurn().
+ */
+ protected boolean finishTurnSpecials () {
+ return true;
+ }
- if (hex.layBaseToken(operatingCompany.get(), station)) {
- /* TODO: the false return value must be impossible. */
+ protected boolean setNextOperatingCompany(boolean initial) {
- operatingCompany.get().layBaseToken(hex, cost);
-
- // If this is a home base token lay, stop here
- if (action.getType() == LayBaseToken.HOME_CITY) {
- return true;
- }
-
- if (cost > 0) {
- new CashMove(operatingCompany.get(), bank, cost);
- ReportBuffer.add(LocalText.getText("LAYS_TOKEN_ON",
- companyName,
- hex.getName(),
- Bank.format(cost) ));
+ while (true) {
+ if (initial || operatingCompany.get() == null || operatingCompany == null) {
+ setOperatingCompany(operatingCompanies.get(0));
+ initial = false;
} else {
- ReportBuffer.add(LocalText.getText("LAYS_FREE_TOKEN_ON",
- companyName,
- hex.getName() ));
- }
+ int index = operatingCompanies.indexOf(operatingCompany.get());
+ if (++index >= operatingCompanies.size()) {
+ return false;
+ }
- // Was a special property used?
- if (stl != null) {
- stl.setExercised();
- currentSpecialTokenLays.remove(action);
- log.debug("This was a special token lay, "
- + (extra ? "" : " not") + " extra");
+ // Check if the operating order has changed
+ List<PublicCompanyI> newOperatingCompanies
+ = setOperatingCompanies (operatingCompanies.viewList(), operatingCompany.get());
+ PublicCompanyI company;
+ for (int i=0; i<newOperatingCompanies.size(); i++) {
+ company = newOperatingCompanies.get(i);
+ if (company != operatingCompanies.get(i)) {
+ log.debug("Company "+company.getName()
+ +" replaces "+operatingCompanies.get(i).getName()
+ +" in operating sequence");
+ operatingCompanies.move(company, i);
+ }
+ }
+ setOperatingCompany(operatingCompanies.get(index));
}
- // Jump out if we aren't in the token laying step
- if (getStep() != GameDef.OrStep.LAY_TOKEN) return true;
-
- if (!extra) {
- currentNormalTokenLays.clear();
- log.debug("This was a normal token lay");
- }
+ if (operatingCompany.get().isClosed()) continue;
- if (currentNormalTokenLays.isEmpty()) {
- log.debug("No more normal token lays are allowed");
- } else if (operatingCompany.get().getNumberOfFreeBaseTokens() == 0) {
- log.debug("Normal token lay allowed by no more tokens");
- currentNormalTokenLays.clear();
- } else {
- log.debug("A normal token lay is still allowed");
- }
- setSpecialTokenLays();
- log.debug("There are now " + currentSpecialTokenLays.size()
- + " special token lay objects");
- if (currentNormalTokenLays.isEmpty()
- && currentSpecialTokenLays.isEmpty()) {
- nextStep();
- }
+ return true;
+ }
+ }
+ protected void setOperatingCompany (PublicCompanyI company) {
+ if (operatingCompany == null) {
+ operatingCompany =
+ new GenericState<PublicCompanyI>("OperatingCompany", company);
+ } else {
+ operatingCompany.set(company);
}
+ }
- return true;
+ /**
+ * Get the public company that has the turn to operate.
+ *
+ * @return The currently operating company object.
+ */
+ public PublicCompanyI getOperatingCompany() {
+ return operatingCompany.get();
}
- public boolean layBonusToken(LayBonusToken action) {
+ public List<PublicCompanyI> getOperatingCompanies() {
+ return operatingCompanies.viewList();
+ }
- String errMsg = null;
- int cost = 0;
- SpecialTokenLay stl = null;
- boolean extra = false;
+ public int getOperatingCompanyIndex() {
+ int index = operatingCompanies.indexOf(getOperatingCompany());
+ return index;
+ }
- MapHex hex = action.getChosenHex();
- BonusToken token = action.getToken();
- // Dummy loop to enable a quick jump out.
- while (true) {
+ /*=======================================
+ * 2.4. STEP CONTROL
+ *=======================================*/
- // Checks
- MapHex location = action.getChosenHex();
- if (location != hex) {
- errMsg =
- LocalText.getText("TokenLayingHexMismatch",
- hex.getName(),
- location.getName() );
- break;
- }
- stl = action.getSpecialProperty();
- if (stl != null) extra = stl.isExtra();
+ /**
+ * Get the current operating round step (i.e. the next action).
+ *
+ * @return The number that defines the next action.
+ */
+ public GameDef.OrStep getStep() {
+ return (GameDef.OrStep) stepObject.get();
+ }
- cost = 0; // Let's assume for now that bonus tokens are always
- // free
- if (stl != null && stl.isFree()) cost = 0;
+ /**
+ * Bypass normal order of operations and explicitly set round step. This
+ * should only be done for specific rails.game exceptions, such as forced
+ * train purchases.
+ *
+ * @param step
+ */
+ protected void setStep(GameDef.OrStep step) {
- // Does the company have the money?
- if (cost > operatingCompany.get().getCash()) {
- errMsg =
- LocalText.getText("NotEnoughMoney",
- operatingCompany.get().getName());
- break;
- }
- break;
- }
- if (errMsg != null) {
- DisplayBuffer.add(LocalText.getText("CannotLayBonusTokenOn",
- token.getName(),
- hex.getName(),
- Bank.format(cost),
- errMsg ));
- return false;
- }
+ stepObject.set(step);
- /* End of validation, start of execution */
- moveStack.start(true);
+ }
- if (hex.layBonusToken(token, gameManager.getPhaseManager())) {
- /* TODO: the false return value must be impossible. */
+ /**
+ * Internal method: change the OR state to the next step. If the currently
+ * Operating Company is done, notify this.
+ *
+ * @param company The current company.
+ */
+ protected void nextStep() {
+ nextStep(getStep());
+ }
- operatingCompany.get().addBonus(new Bonus(operatingCompany.get(),
- token.getName(),
- token.getValue(), Collections.singletonList(hex)));
- token.setUser(operatingCompany.get());
+ /** Take the next step after a given one (see nextStep()) */
+ protected void nextStep(GameDef.OrStep step) {
- ReportBuffer.add(LocalText.getText("LaysBonusTokenOn",
- operatingCompany.get().getName(),
- token.getName(),
- Bank.format(token.getValue()),
- hex.getName() ));
+ PublicCompanyI company = operatingCompany.get();
- // Was a special property used?
- if (stl != null) {
- stl.setExercised();
- currentSpecialTokenLays.remove(action);
- log.debug("This was a special token lay, "
- + (extra ? "" : " not") + " extra");
+ // Cycle through the steps until we reach one where a user action is
+ // expected.
+ int stepIndex;
+ for (stepIndex = 0; stepIndex < steps.length; stepIndex++) {
+ if (steps[stepIndex] == step) break;
+ }
+ while (++stepIndex < steps.length) {
+ step = steps[stepIndex];
+ log.debug("Step " + step);
+ if (step == GameDef.OrStep.LAY_TOKEN
+ && company.getNumberOfFreeBaseTokens() == 0) {
+ continue;
}
- }
+ if (step == GameDef.OrStep.CALC_REVENUE) {
- return true;
- }
+ if (!company.canRunTrains()) {
+ // No trains, then the revenue is zero.
+ executeSetRevenueAndDividend (
+ new SetDividend (0, false, new int[] {SetDividend.NO_TRAIN}));
+ // TODO: This probably does not handle share selling correctly
+ continue;
+ }
+ }
- public boolean buyBonusToken(BuyBonusToken action) {
+ if (step == GameDef.OrStep.PAYOUT) {
+ // This step is now obsolete
+ continue;
+ }
- String errMsg = null;
- int cost;
- SellBonusToken sbt = null;
- CashHolder seller = null;
+ if (step == GameDef.OrStep.TRADE_SHARES) {
- // Dummy loop to enable a quick jump out.
- while (true) {
+ // Is company allowed to trade trasury shares?
+ if (!company.mayTradeShares()
+ || !company.hasOperated()) {
+ continue;
+ }
- // Checks
- sbt = action.getSpecialProperty();
- cost = sbt.getPrice();
- seller = sbt.getSeller();
+ /* Check if any trading is possible.
+ * If not, skip this step.
+ * (but register a Done action for BACKWARDS COMPATIBILITY only)
+ */
+ // Preload some expensive results
+ int ownShare = company.getPortfolio().getShare(company);
+ int poolShare = pool.getShare(company); // Expensive, do it once
+ // Can it buy?
+ boolean canBuy =
+ ownShare < getGameParameterAsInt (GameDef.Parm.TREASURY_SHARE_LIMIT)
+ && company.getCash() >= company.getCurrentSpace().getPrice()
+ && poolShare > 0;
+ // Can it sell?
+ boolean canSell =
+ company.getPortfolio().getShare(company) > 0
+ && poolShare < getGameParameterAsInt (GameDef.Parm.POOL_SHARE_LIMIT);
+ // Above we ignore the possible existence of double shares (as in 1835).
+
+ if (!canBuy && !canSell) {
+ // XXX For BACKWARDS COMPATIBILITY only,
+ // register a Done skip action during reloading.
+ if (gameManager.isReloading()) {
+ gameManager.setSkipDone(GameDef.OrStep.TRADE_SHARES);
+ log.debug("If the next saved action is 'Done', skip it");
+ }
+ log.info("Skipping Treasury share trading step");
+ continue;
+ }
+
+ gameManager.startTreasuryShareTradingRound();
- // Does the company have the money?
- if (cost > operatingCompany.get().getCash()) {
- errMsg =
- LocalText.getText("NotEnoughMoney",
- operatingCompany.get().getName(),
- Bank.format(operatingCompany.get().getCash()),
- Bank.format(cost));
- break;
}
+
+ if (!gameSpecificNextStep (step)) continue;
+
+ // No reason found to skip this step
break;
}
- if (errMsg != null) {
- DisplayBuffer.add(LocalText.getText("CannotBuyBonusToken",
- operatingCompany.get().getName(),
- sbt.getName(),
- seller.getName(),
- Bank.format(cost),
- errMsg ));
- return false;
+
+ if (step == GameDef.OrStep.FINAL) {
+ finishTurn();
+ } else {
+ setStep(step);
}
- /* End of validation, start of execution */
- moveStack.start(true);
+ }
- new CashMove (operatingCompany.get(), seller, cost);
- operatingCompany.get().addBonus(new Bonus(operatingCompany.get(),
- sbt.getName(),
- sbt.getValue(),
- sbt.getLocations()));
+ /** Stub, can be overridden in subclasses to check for extra steps */
+ protected boolean gameSpecificNextStep (GameDef.OrStep step) {
+ return true;
+ }
- ReportBuffer.add(LocalText.getText("BuysBonusTokenFrom",
- operatingCompany.get().getName(),
- sbt.getName(),
- Bank.format(sbt.getValue()),
- seller.getName(),
- Bank.format(sbt.getPrice())));
+ /**
+ * This method is only called at the start of each step (unlike
+ * updateStatus(), which is called after each user action)
+ */
+ protected void prepareStep() {
+ GameDef.OrStep step = stepObject.value();
- sbt.setExercised();
+ if (step == GameDef.OrStep.LAY_TRACK) {
+ // getNormalTileLays();
+ } else if (step == GameDef.OrStep.LAY_TOKEN) {
- return true;
- }
+ } else {
+ currentSpecialProperties = null;
+ }
+ }
+ /*=======================================
+ * 3. COMMON ACTIONS (not bound to steps)
+ * 3.1. NOOPS
+ *=======================================*/
- public boolean setRevenueAndDividend(SetDividend action) {
+ public void skip() {
+ log.debug("Skip step " + stepObject.value());
+ moveStack.start(true);
+ nextStep();
+ }
- String errMsg = validateSetRevenueAndDividend (action);
+ /**
+ * The current Company is done operating.
+ *
+ * @param company Name of the company that finished operating.
+ * @return False if an error is found.
+ */
+ public boolean done() {
- if (errMsg != null) {
- DisplayBuffer.add(LocalText.getText(
- "CannotProcessRevenue",
- Bank.format (action.getActualRevenue()),
- action.getCompanyName(),
- errMsg
- ));
+ if (operatingCompany.get().getPortfolio().getNumberOfTrains() == 0
+ && operatingCompany.get().mustOwnATrain()) {
+ // FIXME: Need to check for valid route before throwing an
+ // error.
+ /* Check TEMPORARILY disabled
+ errMsg =
+ LocalText.getText("CompanyMustOwnATrain",
+ operatingCompany.getObject().getName());
+ setStep(STEP_BUY_TRAIN);
+ DisplayBuffer.add(errMsg);
return false;
+ */
}
- moveStack.start(true);
+ moveStack.start(false);
- ReportBuffer.add(LocalText.getText("CompanyRevenue",
- action.getCompanyName(),
- Bank.format(action.getActualRevenue())));
+ nextStep();
- int remainingAmount = checkForDeductions (action);
- if (remainingAmount < 0) {
- // A share selling round will be run to raise cash to pay debts
- return true;
+ if (getStep() == GameDef.OrStep.FINAL) {
+ finishTurn();
}
- executeSetRevenueAndDividend (action);
-
return true;
-
}
- protected String validateSetRevenueAndDividend (SetDividend action) {
+ /*=======================================
+ * 3.2. DISCARDING TRAINS
+ *=======================================*/
+
+ public boolean discardTrain(DiscardTrain action) {
+
+ TrainI train = action.getDiscardedTrain();
+ PublicCompanyI company = action.getCompany();
+ String companyName = company.getName();
String errMsg = null;
- PublicCompanyI company;
- String companyName;
- int amount = 0;
- int revenueAllocation = -1;
// Dummy loop to enable a quick jump out.
while (true) {
-
// Checks
- // Must be correct company.
- company = action.getCompany();
- companyName = company.getName();
- if (company != operatingCompany.get()) {
- errMsg =
- LocalText.getText("WrongCompany",
- companyName,
- operatingCompany.get().getName() );
- break;
- }
// Must be correct step
- if (getStep() != GameDef.OrStep.CALC_REVENUE) {
- errMsg = LocalText.getText("WrongActionNoRevenue");
+ if (getStep() != GameDef.OrStep.BUY_TRAIN
+ && getStep() != GameDef.OrStep.DISCARD_TRAINS) {
+ errMsg = LocalText.getText("WrongActionNoDiscardTrain");
break;
}
- // Amount must be non-negative multiple of 10
- amount = action.getActualRevenue();
- if (amount < 0) {
- errMsg =
- LocalText.getText("NegativeAmountNotAllowed",
- String.valueOf(amount));
- break;
- }
- if (amount % 10 != 0) {
- errMsg =
- LocalText.getText("AmountMustBeMultipleOf10",
- String.valueOf(amount));
+ if (train == null && action.isForced()) {
+ errMsg = LocalText.getText("NoTrainSpecified");
break;
}
- // Check chosen revenue distribution
- if (amount > 0) {
- // Check the allocation type index (see SetDividend for values)
- revenueAllocation = action.getRevenueAllocation();
- if (revenueAllocation < 0
- || revenueAllocation >= SetDividend.NUM_OPTIONS) {
- errMsg =
- LocalText.getText("InvalidAllocationTypeIndex",
- String.valueOf(revenueAllocation));
- break;
- }
-
- // Validate the chosen allocation type
- int[] allowedAllocations =
- ((SetDividend) selectedAction).getAllowedAllocations();
- boolean valid = false;
- for (int aa : allowedAllocations) {
- if (revenueAllocation == aa) {
- valid = true;
- break;
- }
- }
- if (!valid) {
- errMsg =
- LocalText.getText(SetDividend.getAllocationNameKey(revenueAllocation));
- break;
- }
- } else {
- // If there is no revenue, use withhold.
- action.setRevenueAllocation(SetDividend.WITHHOLD);
- }
+ // Does the company own such a train?
- if (amount == 0 && operatingCompany.get().getNumberOfTrains() == 0) {
- DisplayBuffer.add(LocalText.getText("RevenueWithNoTrains",
- operatingCompany.get().getName(),
- Bank.format(0) ));
+ if (!company.getPortfolio().getTrainList().contains(train)) {
+ errMsg =
+ LocalText.getText("CompanyDoesNotOwnTrain",
+ company.getName(),
+ train.getName() );
+ break;
}
break;
}
+ if (errMsg != null) {
+ DisplayBuffer.add(LocalText.getText("CannotDiscardTrain",
+ companyName,
+ (train != null ?train.getName() : "?"),
+ errMsg ));
+ return false;
+ }
- return errMsg;
- }
+ /* End of validation, start of execution */
+ moveStack.start(true);
+ //
+ if (action.isForced()) moveStack.linkToPreviousMoveSet();
- protected void executeSetRevenueAndDividend (SetDividend action) {
+ // Reset type of dual trains
+ if (train.getCertType().getPotentialTrainTypes().size() > 1) {
+ train.setType(null);
+ }
- int amount = action.getActualRevenue();
- int revenueAllocation = action.getRevenueAllocation();
+ train.moveTo(train.isObsolete() ? scrapHeap : pool);
+ ReportBuffer.add(LocalText.getText("CompanyDiscardsTrain",
+ companyName,
+ train.getName() ));
- operatingCompany.get().setLastRevenue(amount);
- operatingCompany.get().setLastRevenueAllocation(revenueAllocation);
+ // Check if any more companies must discard trains,
+ // otherwise continue train buying
+ if (!checkForExcessTrains()) {
+ // Trains may have been discarded by other players
+ setCurrentPlayer (operatingCompany.get().getPresident());
+ stepObject.set(GameDef.OrStep.BUY_TRAIN);
+ }
- // Pay any debts from treasury, revenue and/or president's cash
- // The remaining dividend may be less that the original income
- amount = executeDeductions (action);
+ //setPossibleActions();
- if (amount == 0) {
+ return true;
+ }
- ReportBuffer.add(LocalText.getText("CompanyDoesNotPayDividend",
- operatingCompany.get().getName()));
- withhold(amount);
+ protected void setTrainsToDiscard() {
- } else if (revenueAllocation == SetDividend.PAYOUT) {
+ // Scan the players in SR sequence, starting with the current player
+ Player player;
+ List<PublicCompanyI> list;
+ int currentPlayerIndex = getCurrentPlayerIndex();
+ for (int i = currentPlayerIndex; i < currentPlayerIndex
+ + getNumberOfPlayers(); i++) {
+ player = gameManager.getPlayerByIndex(i);
+ if (excessTrainCompanies.containsKey(player)) {
+ setCurrentPlayer(player);
+ list = excessTrainCompanies.get(player);
+ for (PublicCompanyI comp : list) {
+ possibleActions.add(new DiscardTrain(comp,
+ comp.getPortfolio().getUniqueTrains(), true));
+ // We handle one company at at time.
+ // We come back here until all excess trains have been
+ // discarded.
+ return;
+ }
+ }
+ }
+ }
+ /*=======================================
+ * 3.3. PRIVATES (BUYING, SELLING, CLOSING)
+ *=======================================*/
- ReportBuffer.add(LocalText.getText("CompanyPaysOutFull",
- operatingCompany.get().getName(), Bank.format(amount) ));
+ public boolean buyPrivate(BuyPrivate action) {
- payout(amount);
+ String errMsg = null;
+ PublicCompanyI publicCompany = action.getCompany();
+ String publicCompanyName = publicCompany.getName();
+ PrivateCompanyI privateCompany = action.getPrivateCompany();
+ String privateCompanyName = privateCompany.getName();
+ int price = action.getPrice();
+ CashHolder owner = null;
+ Player player = null;
+ int upperPrice;
+ int lowerPrice;
- } else if (revenueAllocation == SetDividend.SPLIT) {
+ // Dummy loop to enable a quick jump out.
+ while (true) {
- ReportBuffer.add(LocalText.getText("CompanySplits",
- operatingCompany.get().getName(), Bank.format(amount) ));
+ // Checks
+ // Does private exist?
+ if ((privateCompany =
+ companyManager.getPrivateCompany(
+ privateCompanyName)) == null) {
+ errMsg =
+ LocalText.getText("PrivateDoesNotExist",
+ privateCompanyName);
+ break;
+ }
+ // Is private still open?
+ if (privateCompany.isClosed()) {
+ errMsg =
+ LocalText.getText("PrivateIsAlreadyClosed",
+ privateCompanyName);
+ break;
+ }
+ // Is private owned by a player?
+ owner = privateCompany.getPortfolio().getOwner();
+ if (!(owner instanceof Player)) {
+ errMsg =
+ LocalText.getText("PrivateIsNotOwnedByAPlayer",
+ privateCompanyName);
+ break;
+ }
+ player = (Player) owner;
+ upperPrice = privateCompany.getUpperPrice();
+ lowerPrice = privateCompany.getLowerPrice();
- splitRevenue(amount);
+ // Is private buying allowed?
+ if (!isPrivateSellingAllowed()) {
+ errMsg = LocalText.getText("PrivateBuyingIsNotAllowed");
+ break;
+ }
- } else if (revenueAllocation == SetDividend.WITHHOLD) {
+ // Price must be in the allowed range
+ if (lowerPrice != PrivateCompanyI.NO_PRICE_LIMIT && price < lowerPrice) {
+ errMsg =
+ LocalText.getText("PriceBelowLowerLimit",
+ Bank.format(price),
+ Bank.format(lowerPrice),
+ privateCompanyName );
+ break;
+ }
+ if (upperPrice != PrivateCompanyI.NO_PRICE_LIMIT && price > upperPrice) {
+ errMsg =
+ LocalText.getText("PriceAboveUpperLimit",
+ Bank.format(price),
+ Bank.format(lowerPrice),
+ privateCompanyName );
+ break;
+ }
+ // Does the company have the money?
+ if (price > operatingCompany.get().getCash()) {
+ errMsg =
+ LocalText.getText("NotEnoughMoney",
+ publicCompanyName,
+ Bank.format(operatingCompany.get().getCash()),
+ Bank.format(price) );
+ break;
+ }
+ break;
+ }
+ if (errMsg != null) {
+ if (owner != null) {
+ DisplayBuffer.add(LocalText.getText("CannotBuyPrivateFromFor",
+ publicCompanyName,
+ privateCompanyName,
+ owner.getName(),
+ Bank.format(price),
+ errMsg ));
+ } else {
+ DisplayBuffer.add(LocalText.getText("CannotBuyPrivateFor",
+ publicCompanyName,
+ privateCompanyName,
+ Bank.format(price),
+ errMsg ));
+ }
+ return false;
+ }
- ReportBuffer.add(LocalText.getText("CompanyWithholds",
- operatingCompany.get().getName(),
- Bank.format(amount) ));
+ moveStack.start(true);
- withhold(amount);
+ operatingCompany.get().buyPrivate(privateCompany, player.getPortfolio(),
+ price);
+
+ return true;
+
+ }
+
+ protected boolean isPrivateSellingAllowed() {
+ return getCurrentPhase().isPrivateSellingAllowed();
+ }
+ protected int getPrivateMinimumPrice (PrivateCompanyI privComp) {
+ int minPrice = privComp.getLowerPrice();
+ if (minPrice == PrivateCompanyI.NO_PRICE_LIMIT) {
+ minPrice = 0;
}
+ return minPrice;
+ }
- // Rust any obsolete trains
- operatingCompany.get().getPortfolio().rustObsoleteTrains();
+ protected int getPrivateMaximumPrice (PrivateCompanyI privComp) {
+ int maxPrice = privComp.getUpperPrice();
+ if (maxPrice == PrivateCompanyI.NO_PRICE_LIMIT) {
+ maxPrice = operatingCompany.get().getCash();
+ }
+ return maxPrice;
+ }
- // We have done the payout step, so continue from there
- nextStep(GameDef.OrStep.PAYOUT);
+ protected boolean maySellPrivate (Player player) {
+ return true;
}
- /**
- * Distribute the dividend amongst the shareholders.
- *
- * @param amount
- */
- public void payout(int amount) {
+ protected boolean executeClosePrivate(ClosePrivate action) {
- if (amount == 0) return;
+ PrivateCompanyI priv = action.getPrivateCompany();
- int part;
- int shares;
+ log.debug("Executed close private action for private " + priv.getName());
- Map<CashHolder, Integer> sharesPerRecipient = countSharesPerRecipient();
+ String errMsg = null;
- // Calculate, round up, report and add the cash
+ if (priv.isClosed())
+ errMsg = LocalText.getText("PrivateAlreadyClosed", priv.getName());
- // Define a precise sequence for the reporting
- Set<CashHolder> recipientSet = sharesPerRecipient.keySet();
- for (CashHolder recipient : SequenceUtil.sortCashHolders(recipientSet)) {
- if (recipient instanceof Bank) continue;
- shares = (sharesPerRecipient.get(recipient));
- if (shares == 0) continue;
- part = (int) Math.ceil(amount * shares * operatingCompany.get().getShareUnit() / 100.0);
- ReportBuffer.add(LocalText.getText("Payout",
- recipient.getName(),
- Bank.format(part),
- shares,
- operatingCompany.get().getShareUnit()));
- pay (bank, recipient, part);
+ if (errMsg != null) {
+ DisplayBuffer.add(errMsg);
+ return false;
}
- // Move the token
- operatingCompany.get().payout(amount);
+ moveStack.start(true);
- }
+ priv.setClosed();
- protected Map<CashHolder, Integer> countSharesPerRecipient () {
+ return true;
+ }
- Map<CashHolder, Integer> sharesPerRecipient = new HashMap<CashHolder, Integer>();
+ /*=======================================
+ * 3.4. DESTINATIONS
+ *=======================================*/
- // Changed to accomodate the CGR 5% share roundup rule.
- // For now it is assumed, that actual payouts are always rounded up
- // (the withheld half of split revenues is not handled here, see splitRevenue()).
+ /** Stub for applying any follow-up actions when
+ * a company reaches it destinations.
+ * Default version: no actions.
+ * @param company
+ */
+ protected void reachDestination (PublicCompanyI company) {
- // First count the shares per recipient
- for (PublicCertificateI cert : operatingCompany.get().getCertificates()) {
- CashHolder recipient = getBeneficiary(cert);
- if (!sharesPerRecipient.containsKey(recipient)) {
- sharesPerRecipient.put(recipient, cert.getShares());
- } else {
- sharesPerRecipient.put(recipient,
- sharesPerRecipient.get(recipient) + cert.getShares());
- }
- }
- return sharesPerRecipient;
}
- /** Who gets the per-share revenue? */
- protected CashHolder getBeneficiary(PublicCertificateI cert) {
+ public boolean reachDestinations (ReachDestinations action) {
- Portfolio holder = cert.getPortfolio();
- CashHolder beneficiary = holder.getOwner();
- // Special cases apply if the holder is the IPO or the Pool
- if (operatingCompany.get().paysOutToTreasury(cert)) {
- beneficiary = operatingCompany.get();
+ List<PublicCompanyI> destinedCompanies
+ = action.getReachedCompanies();
+ if (destinedCompanies != null) {
+ for (PublicCompanyI company : destinedCompanies) {
+ if (company.hasDestination()
+ && !company.hasReachedDestination()) {
+ if (!moveStack.isOpen()) moveStack.start(true);
+ company.setReachedDestination(true);
+ ReportBuffer.add(LocalText.getText("DestinationReached",
+ company.getName(),
+ company.getDestinationHex().getName()
+ ));
+ // Process any consequences of reaching a destination
+ // (default none)
+ reachDestination (company);
+ }
+ }
}
- return beneficiary;
+ return true;
}
/**
- * Withhold a given amount of revenue (and store it).
- *
- * @param The revenue amount.
+ * This is currently a stub, as it is unclear if there is a common
+ * rule for setting destination reaching options.
+ * See OperatingRound_1856 for a first implementation
+ * of such rules.
*/
- public void withhold(int amount) {
+ protected void setDestinationActions () {
- PublicCompanyI company = operatingCompany.get();
+ }
- // Payout revenue to company
- pay (bank, company, amount);
+ /*=======================================
+ * ...
[truncated message content] |