All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.sahits.game.openpatrician.engine.land.city.CityHallEngine Maven / Gradle / Ivy

There is a newer version: 1.0.1
Show newest version
package ch.sahits.game.openpatrician.engine.land.city;

import ch.sahits.game.event.ShipAttackEvent;
import ch.sahits.game.event.data.NewGameClient;
import ch.sahits.game.event.data.PeriodicalTimeDayUpdate;
import ch.sahits.game.event.data.PeriodicalTimeMonthEndUpdate;
import ch.sahits.game.event.data.PeriodicalTimeWeekEndUpdate;
import ch.sahits.game.event.data.PeriodicalTimeYearEndUpdate;
import ch.sahits.game.openpatrician.annotation.ClassCategory;
import ch.sahits.game.openpatrician.annotation.EClassCategory;
import ch.sahits.game.openpatrician.annotation.LazySingleton;
import ch.sahits.game.openpatrician.collections.SortedMapRandomizedSameElements;
import ch.sahits.game.openpatrician.engine.AbstractEngine;
import ch.sahits.game.openpatrician.engine.EngineFactory;
import ch.sahits.game.openpatrician.engine.land.city.internal.CityWallMaterialBuyingTask;
import ch.sahits.game.openpatrician.engine.land.city.internal.ElectionTask;
import ch.sahits.game.openpatrician.engine.land.city.internal.VoteTask;
import ch.sahits.game.openpatrician.model.Date;
import ch.sahits.game.openpatrician.model.DisplayMessage;
import ch.sahits.game.openpatrician.model.IAIPlayer;
import ch.sahits.game.openpatrician.model.ICitizen;
import ch.sahits.game.openpatrician.model.ICompany;
import ch.sahits.game.openpatrician.model.IMap;
import ch.sahits.game.openpatrician.model.IPlayer;
import ch.sahits.game.openpatrician.model.building.IBarn;
import ch.sahits.game.openpatrician.model.building.IBuilding;
import ch.sahits.game.openpatrician.model.building.ICityWall;
import ch.sahits.game.openpatrician.model.building.IMarketplace;
import ch.sahits.game.openpatrician.model.building.ITradingOffice;
import ch.sahits.game.openpatrician.model.city.ECityWall;
import ch.sahits.game.openpatrician.model.city.EPopulationClass;
import ch.sahits.game.openpatrician.model.city.ICity;
import ch.sahits.game.openpatrician.model.city.ICreditor;
import ch.sahits.game.openpatrician.model.city.ILoaner;
import ch.sahits.game.openpatrician.model.city.cityhall.CityHallList;
import ch.sahits.game.openpatrician.model.city.cityhall.ECityViolationPunishment;
import ch.sahits.game.openpatrician.model.city.cityhall.EldermanCandidateList;
import ch.sahits.game.openpatrician.model.city.cityhall.EldermanTaskPlayerMap;
import ch.sahits.game.openpatrician.model.city.cityhall.IAcceptedEldermanTask;
import ch.sahits.game.openpatrician.model.city.cityhall.IBowmen;
import ch.sahits.game.openpatrician.model.city.cityhall.IBuildLandPassage;
import ch.sahits.game.openpatrician.model.city.cityhall.ICapturePirateNest;
import ch.sahits.game.openpatrician.model.city.cityhall.ICityGuard;
import ch.sahits.game.openpatrician.model.city.cityhall.ICityHall;
import ch.sahits.game.openpatrician.model.city.cityhall.ICityHallNotice;
import ch.sahits.game.openpatrician.model.city.cityhall.ICityPetition;
import ch.sahits.game.openpatrician.model.city.cityhall.ICityViolation;
import ch.sahits.game.openpatrician.model.city.cityhall.ICityWallPetition;
import ch.sahits.game.openpatrician.model.city.cityhall.ICrossbowmen;
import ch.sahits.game.openpatrician.model.city.cityhall.ICustomsViolation;
import ch.sahits.game.openpatrician.model.city.cityhall.IEldermanOffice;
import ch.sahits.game.openpatrician.model.city.cityhall.IEldermanTask;
import ch.sahits.game.openpatrician.model.city.cityhall.IFoundNewSettlement;
import ch.sahits.game.openpatrician.model.city.cityhall.IHeadTaxPetition;
import ch.sahits.game.openpatrician.model.city.cityhall.IHelpCity;
import ch.sahits.game.openpatrician.model.city.cityhall.IHuntPirate;
import ch.sahits.game.openpatrician.model.city.cityhall.IMilitiaPetition;
import ch.sahits.game.openpatrician.model.city.cityhall.IMusketeer;
import ch.sahits.game.openpatrician.model.city.cityhall.IOutriggerContract;
import ch.sahits.game.openpatrician.model.city.cityhall.IPikemen;
import ch.sahits.game.openpatrician.model.city.cityhall.IPirateSupportViolation;
import ch.sahits.game.openpatrician.model.city.cityhall.IPlunderTradingOfficesViolation;
import ch.sahits.game.openpatrician.model.city.cityhall.ISpecialTaxPetition;
import ch.sahits.game.openpatrician.model.city.cityhall.ISpecialTaxViolation;
import ch.sahits.game.openpatrician.model.city.cityhall.ITreasury;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.Ballot;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.CityHall;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.CityViolation;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.CustomsViolation;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.EldermanOffice;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.Election;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.PlunderTradingOfficeViolation;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.SpecialTaxPetition;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.SpecialTaxViolation;
import ch.sahits.game.openpatrician.model.city.cityhall.impl.Treasury;
import ch.sahits.game.openpatrician.model.city.impl.CityWall;
import ch.sahits.game.openpatrician.model.city.impl.Debt;
import ch.sahits.game.openpatrician.model.event.TimedUpdatableTaskList;
import ch.sahits.game.openpatrician.model.factory.StateFactory;
import ch.sahits.game.openpatrician.model.impl.BalanceSheet;
import ch.sahits.game.openpatrician.model.people.ISeaPirate;
import ch.sahits.game.openpatrician.model.people.PeopleFactory;
import ch.sahits.game.openpatrician.model.people.impl.SeaPiratesState;
import ch.sahits.game.openpatrician.model.product.AmountablePrice;
import ch.sahits.game.openpatrician.model.product.EWare;
import ch.sahits.game.openpatrician.model.product.IWare;
import ch.sahits.game.openpatrician.model.sea.PirateNest;
import ch.sahits.game.openpatrician.model.util.CityUtilities;
import ch.sahits.game.openpatrician.util.l10n.Locale;
import com.google.common.annotations.VisibleForTesting;
import java.util.Optional;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.Subscribe;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.MessageSource;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;

/**
 * Engine for controlling the aspects of the city government. This engine handles all cities.
 * @author Andi Hotz, (c) Sahits GmbH, 2015
 *         Created on Mar 14, 2015
 */
@LazySingleton
@ClassCategory(EClassCategory.SINGLETON_BEAN)
public class CityHallEngine extends AbstractEngine {
    // Taxes per 100 citizens
    private double weeklyHeadTaxPoor = 0;
    private double weeklyHeadTaxMiddleClass = 0.6;
    private double weeklyHeadTaxRich = 2.0;
    // property tax on the building
    // method for tax value per citizen based on social rank


    @Autowired
    @Qualifier("serverClientEventBus")
    private AsyncEventBus clientServerEventBus;
    @Autowired
    private StateFactory stateFactory;
    @Autowired
    private Random rnd;
    @Autowired
    private Date date;
    @Autowired
    private CityUtilities citiesInteractionService;
    @Autowired
    private IMap map;
    @Autowired
    private SeaPiratesState pirateState;
    @Autowired
    private LoanerEngine loanerEngine;
    @Autowired
    private EngineFactory engineFactory;
    @Autowired
    private PeopleFactory peopleFactory;
    @Autowired
    private TimedUpdatableTaskList timedTaskListener;
    @Autowired
    @Qualifier("clientEventBus")
    private AsyncEventBus clientEventBus;
    @Autowired
    private Locale locale;
    @Autowired
    private MessageSource messageSource;
   @Autowired
    private EldermanCandidateList eldermanCandidates;
    @Autowired
    private CityHallList cityHalls;
    private CityInitialisation initialisation = new CityInitialisation();
    @Autowired
    private EldermanTaskPlayerMap takenTasks;

    @PostConstruct
    private void init() {
 // todo: andi 3/22/15: listen to outrigger changes to steer the captains skills improvement
        clientServerEventBus.register(this);
    }
    @PreDestroy
    private void unregister() {
        clientServerEventBus.unregister(this);
    }

    @Subscribe
    public void handleWeeklyUpdate(PeriodicalTimeWeekEndUpdate event) {
        for (ICityHall cityHall : cityHalls) {
            updateTreasuryWeekly(cityHall);
            List newNotices = citiesInteractionService.createNotices(cityHall.getCity());
            cityHall.getNotices().clear();
            cityHall.getNotices().addAll(newNotices);
            checkPetitions(cityHall);
            if (cityHall.getEldermanOffice().isPresent()) {
                checkTasksFinished(cityHall.getEldermanOffice().get());
                checkViolations(cityHall.getEldermanOffice().get(), cityHall.getElderman(), cityHall);
            }

        }
    }

    private void checkPetitions(ICityHall cityHall) {
        if (cityHall.getPetition().isPresent()) {
            DateTime meeting = date.getCurrentDate().plusDays(20 + rnd.nextInt(40));
            ((CityHall)cityHall).setNextCouncilMeeting(Optional.of(meeting));
        }
        ITreasury treasury = cityHall.getTreasury();
        if (treasury.getCash() < 1000) {
            int random = rnd.nextInt(10);
            if (random == 0) {
                IEldermanOffice office = getEldermanOffice();
                random = rnd.nextInt(3);
                final ICity city = cityHall.getCity();
                if (random == 0) {
                    // Special tax
                    ISpecialTaxViolation violation = new SpecialTaxViolation(city, date.getCurrentDate());
                    DateTime deadline = date.getCurrentDate().plusDays(rnd.nextInt(50));
                    engineFactory.getViolationTask(violation, office, deadline);
                    leveySpecialTax(cityHall, new SpecialTaxPetition(20000));
                }
                if (random == 1) {
                    // Custom tax
                    ICustomsViolation violation = new CustomsViolation(city, date.getCurrentDate());
                    DateTime deadline = date.getCurrentDate().plusDays(rnd.nextInt(50));
                    engineFactory.getViolationTask(violation, office, deadline);
                }
                if (random == 2) {
                    // plunder trading offices
                    IPlunderTradingOfficesViolation violation = new PlunderTradingOfficeViolation(city, date.getCurrentDate());
                    DateTime deadline = date.getCurrentDate().plusDays(rnd.nextInt(50));
                    engineFactory.getViolationTask(violation, office, deadline);
                    List buildings = city.findBuilding(ITradingOffice.class, Optional.empty());
                    for (ITradingOffice trOffice : buildings) {
                        for (EWare ware : EWare.values()) {
                            AmountablePrice amountable = trOffice.getWare(ware);
                            trOffice.move(ware, -amountable.getAmount());
                            city.move(ware,amountable.getAmount(), cityHall.getMayor());

                        }
                    }
                }
            }
        }
    }

    private void checkViolations(IEldermanOffice office, ICitizen elderman, ICityHall cityHall) {
        if (office.getViolation().isPresent()) {
            final CityViolation violation = (CityViolation) office.getViolation().get();
            if (elderman instanceof IPlayer) {
                if (!(elderman instanceof IAIPlayer)) {
                    if (violation.getDate().plusMonths(2).isBefore(date.getCurrentDate()) && violation.getPunishment() == null) {
                        String s = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.missedViolation", new Object[]{violation.getCity().getName()}, locale.getCurrentLocal());  // todo: andi 6/7/15: the city name must be language neutral
                        DisplayMessage msg = new DisplayMessage(s);
                        clientEventBus.post(msg);
                        for (ICity city : map.getCities()) {
                            city.getReputation((IPlayer) elderman).update(-10);
                        }
                        ((EldermanOffice) office).setViolation(Optional.empty());
                    }
                    return; // Nothing more to check for human elderman
                }
            }
            if (elderman instanceof IAIPlayer) {
                if (violation.getDate().plusMonths(2).isBefore(date.getCurrentDate()) && violation.getPunishment() == null) {
                    for (ICity city : map.getCities()) {
                        city.getReputation((IPlayer) elderman).update(-10);
                    }
                }
            }
            if (violation.getDate().plusMonths(2).isBefore(date.getCurrentDate()) && violation.getPunishment() == null) {
                ((EldermanOffice) office).setViolation(Optional.empty());
            }
            if (!violation.getDate().plusMonths(2).isBefore(date.getCurrentDate())) {
                // take measure
                if (elderman.getHometown().equals(violation.getCity())) {
                    violation.setPunishment(ECityViolationPunishment.NONE);
                } else {
                    int index = rnd.nextInt(ECityViolationPunishment.values().length);
                    violation.setPunishment(ECityViolationPunishment.values()[index]);
                }
                DateTime meeting = date.getCurrentDate().plusDays(20 + rnd.nextInt(40));
                ((CityHall)cityHall).setHanseaticMeetingDate(Optional.of(meeting));
            }
        }
    }

    private void checkTasksFinished(IEldermanOffice office) {
        List tasks = office.getWorkedOnTasks();
        DateTime now = date.getCurrentDate();
        for (Iterator iterator = tasks.iterator(); iterator.hasNext(); ) {
            IAcceptedEldermanTask task = iterator.next();
            boolean updateStanding = false;
            if (now.isBefore(task.getDeadline())) {
                updateStanding = true;
            }
            if (task.getTask() instanceof IHelpCity) {
                IHelpCity concreteTask = (IHelpCity) task.getTask();
                final ICity city = concreteTask.getCity();
                if (city.getPopulationBinding().get() >= 2000) {
                    if (updateStanding) {
                        for (ICity iCity : map.getCities()) {
                            if (iCity.equals(city)) {
                                iCity.getReputation(task.getPlayer()).update(50);
                            } else {
                                iCity.getReputation(task.getPlayer()).update(20);
                            }

                        }
                    }
                    iterator.remove();
                }
            }
            if (task.getTask() instanceof IBuildLandPassage) {
                IBuildLandPassage concreteTask = (IBuildLandPassage) task.getTask();
                ICity city1 = concreteTask.getFromCity();
                ICity city2 = concreteTask.getToCity();
                if (!city1.findBuilding(IBarn.class, Optional.empty()).isEmpty() &&
                        !city2.findBuilding(IBarn.class, Optional.empty()).isEmpty()) {
                    if (updateStanding) {
                        for (ICity iCity : map.getCities()) {
                            if (iCity.equals(city1)) {
                                iCity.getReputation(task.getPlayer()).update(70);
                            } else if (iCity.equals(city2)) {
                                iCity.getReputation(task.getPlayer()).update(70);
                            } else {
                                iCity.getReputation(task.getPlayer()).update(35);
                            }
                        }
                    }
                    iterator.remove();
                }
            }
            if (task.getTask() instanceof IFoundNewSettlement) {
                IFoundNewSettlement concreteTask = (IFoundNewSettlement) task.getTask();
                ICity city = map.findCity(concreteTask.getName());
                Optional absent = Optional.empty();
                if (city.getPopulationBinding().get() > 1000 &&
                        !city.findBuilding(ICityWall.class, absent).isEmpty() &&
                        !city.findBuilding(IMarketplace.class, absent).isEmpty() &&
                        !city.findBuilding(ch.sahits.game.openpatrician.model.building.ICityHall.class, absent).isEmpty() &&
                        !city.findBuilding(ITradingOffice.class, Optional.of(task.getPlayer())).isEmpty()) {
                    if (updateStanding) {
                        for (ICity iCity : map.getCities()) {
                            if (iCity.equals(city)) {
                                iCity.getReputation(task.getPlayer()).update(200);
                            } else {
                                iCity.getReputation(task.getPlayer()).update(100);
                            }
                        }
                    }
                    iterator.remove();
                }
            }
            if (task.getTask() instanceof ICapturePirateNest) {
                ICapturePirateNest concreteTask = (ICapturePirateNest) task.getTask();
                for (PirateNest pirateNest : map.getPirateNests()) {
                    if (pirateNest.getLocation().equals(concreteTask.getLocation()) &&
                            pirateNest.getDefendingShips().isEmpty()) {
                        // todo: andi 6/6/15: also check the other armarments
                        if (updateStanding) {
                            for (ICity iCity : map.getCities()) {
                                iCity.getReputation(task.getPlayer()).update(80);
                            }
                        }
                        iterator.remove();
                    }
                }
            }
        }
    }

    @Subscribe
    public void handleDailyUpdate(PeriodicalTimeDayUpdate event) {
        for (ICityHall cityHall : cityHalls) {
            // There was a ballot the day before
            if (cityHall.getBallotResult().isPresent()) {
                Ballot result = (Ballot) cityHall.getBallotResult().get();
                if (result.getPetition() != null) {
                    ICityPetition petition = result.getPetition();
                    if (result.getNumberNo() > result.getNumberYes()) {
                        handleDeniedCityPetition(cityHall, petition);
                    } else {
                        handleAcceptedCityPetition(cityHall, petition);
                    }
                    ((CityHall)cityHall).setBallotResult(Optional.empty());
                    ((CityHall)cityHall).setNextCouncilMeeting(Optional.empty());
                } else {
                    ICityViolation violation = result.getViolation();
                    if (result.getNumberNo() > result.getNumberYes()) {
                        handleDeniedCityViolation(violation);
                    } else {
                        handleCityViolationPunishment(violation);
                    }
                    ((CityHall)cityHall).setBallotResult(Optional.empty());
                    ((CityHall)cityHall).setHanseaticMeetingDate(Optional.empty());
                }
            }

            // There was an election the previous date
            if (cityHall.getElectionResult().isPresent()) {
                notificationElectionWinner(cityHall);
            }

            // Check if today is an election or ballot
            if (date.isToday(cityHall.getElectionDate())) {
               electNewMayor(cityHall);
            } else if (cityHall.getEldermanOffice().isPresent() && date.isToday(cityHall.getEldermanElectionDate())) {
                electNewElderman(cityHall);
            } else if (cityHall.getNextCouncilMeeting().isPresent() && cityHall.getPetition().isPresent() && date.isToday(cityHall.getNextCouncilMeeting().get())) {
                voteOnPetition(cityHall);
            } else if (cityHall.getEldermanOffice().isPresent() && cityHall.getHanseaticMeetingDate().isPresent() && cityHall.getEldermanOffice().get().getViolation().isPresent() && date.isToday(cityHall.getHanseaticMeetingDate().get())){
                voteOnViolation(cityHall);
            }
        }
    }

    private void voteOnViolation(ICityHall cityHall) {
        DateTime now = date.getCurrentDate();
        ICityViolation violation = cityHall.getEldermanOffice().get().getViolation().get();
        Ballot result = new Ballot(violation);
        ((CityHall)cityHall).setBallotResult(Optional.of(result));
        int votingTimeFrame = 18*60;
        for (ICity city : map.getCities()) {
            ICityHall ch = findCityHall(city);
            ICitizen mayor = ch.getMayor();
            if (mayor instanceof IPlayer) { // Human player
                if (!(mayor instanceof IAIPlayer)) {
                    continue;
                }
            }
            DateTime executionTime = now.plusMinutes(rnd.nextInt(votingTimeFrame));
            int random = rnd.nextInt(100);
            int limit = getLimit(city, violation);
            boolean yes = random <= limit;
            VoteTask task = engineFactory.getVoteTask(yes, executionTime, result);
            timedTaskListener.add(task);
        }
    }

    private void voteOnPetition(ICityHall cityHall) {
        DateTime now = date.getCurrentDate();
        ICityPetition petition = cityHall.getPetition().get();
        Ballot result = new Ballot(petition);
        ((CityHall)cityHall).setBallotResult(Optional.of(result));
        List councilmen = cityHall.getCouncilmen();
        int votingTimeFrame = 18*60;
        for (ICitizen citizen : councilmen) {
            if (citizen instanceof IPlayer) { // Human player
                if (!(citizen instanceof IAIPlayer)) {
                    continue;
                }
            }
            DateTime executionTime = now.plusMinutes(rnd.nextInt(votingTimeFrame));
            int random = rnd.nextInt(100);
            int limit = getLimit(cityHall, petition);
            boolean yes = random <= limit;
            VoteTask task = engineFactory.getVoteTask(yes, executionTime, result);
            timedTaskListener.add(task);
        }

    }

    /**
     * Calculate the limit for a petition.
     * @param cityHall
     * @param petition
     * @return
     */
    private int getLimit(ICityHall cityHall, ICityPetition petition) {
        if (petition instanceof ICityWallPetition) {
            int population = cityHall.getCity().getPopulationBinding().get();
            ECityWall cityWall = cityHall.getCity().getCityState().getCityWall().getExtension();
            switch (cityWall) {
                case NOT_EXTENDED:
                    if (population < 8000) return 70;
                    if (population < 15000) return 95;
                    return 100;
                case EXTENDED_ONCE:
                    if (population < 8000) return -1;
                    if (population < 15000) return 70;
                    return 95;
                case EXTENDED_TWICE:  // cannot extend more
                    if (population < 8000) return -1;
                    if (population < 15000) return -1;
                    return -1;
            }
        } else if (petition instanceof IHeadTaxPetition){
            long cash = cityHall.getTreasury().getCash();
            double currentHeadTax = cityHall.getTreasury().getCurrentHeadTaxValue();
            if (cash < 10000) {
                if (((IHeadTaxPetition)petition).getNewTaxValue() > currentHeadTax) {
                    return 60;
                } else {
                    if (cash < 5000) {
                        return 20;
                    } else {
                        return 50;
                    }
                }
            } else {
                if (((IHeadTaxPetition)petition).getNewTaxValue() > currentHeadTax) {
                    return 0;
                } else {
                    return 60;
                }
            }
        } else if (petition instanceof IMilitiaPetition) {
            int population = cityHall.getCity().getPopulationBinding().get();
            int nbGuards = cityHall.getMaxNumberMilita();
            if (population/100 >= nbGuards+5) {
                return 70;
            } else if (population/100 >= nbGuards) {
                return 55;
            } else {
                return 30;
            }
        } else if (petition instanceof ISpecialTaxPetition) {
            long cash = cityHall.getTreasury().getCash();
            if (cash < 2000) {
                return 65;
            } else if (cash < 5000) {
                return 50;
            } else {
                return 20;
            }
        }
        return -1;
    }
    /**
     * Calculate the limit for a violation.
     * @param city
     * @param violation
     * @return
     */
    private int getLimit(ICity city, ICityViolation violation) {
        if (city.equals(violation.getCity())) {
            if (violation.getPunishment() == ECityViolationPunishment.NONE) {
                return 100;
            } else {
                return -1;
            }
        }
        if ( violation instanceof ICustomsViolation) {
            switch (violation.getPunishment()) {
                case NONE:
                    return 10;
                case SMALL_FINE:
                    return 100;
                case MEDIUM_FINE:
                    return 65;
                case LARGE_FINE:
                    return 45;
                case BLOCKADE:
                    return 15;
            }
        } else if(violation instanceof IPirateSupportViolation) {
            switch (violation.getPunishment()) {
                case NONE:
                    return 15;
                case SMALL_FINE:
                    return 100;
                case MEDIUM_FINE:
                    return 85;
                case LARGE_FINE:
                    return 70;
                case BLOCKADE:
                    return 40;
            }
        } else if (violation instanceof IPlunderTradingOfficesViolation) {
            switch (violation.getPunishment()) {
                case NONE:
                    return 10;
                case SMALL_FINE:
                    return 100;
                case MEDIUM_FINE:
                    return 95;
                case LARGE_FINE:
                    return 90;
                case BLOCKADE:
                    return 70;
            }
        } else if (violation instanceof SpecialTaxViolation) {
            switch (violation.getPunishment()) {
                case NONE:
                    return 8;
                case SMALL_FINE:
                    return 100;
                case MEDIUM_FINE:
                    return 90;
                case LARGE_FINE:
                    return 65;
                case BLOCKADE:
                    return 15;
            }

        }
        return -1;
    }

    private void electNewElderman(ICityHall cityHall) {
        // Add the election object
        Election electionResult = new Election(true);
        ((CityHall)cityHall).setElectionResult(Optional.of(electionResult));
        // Create a timer for all councilmen over the next 18h
        DateTime now = date.getCurrentDate();
        // each councilmen selects one of the list using OpenPatricianRandom
        int votingTimeFrame = 18*60;
        for (ICity city : map.getCities()) {
            ICityHall ch = findCityHall(city);
            ICitizen mayor = ch.getMayor();
            if (mayor instanceof IPlayer) { // Human player
                if (!(mayor instanceof IAIPlayer)) {
                    continue;
                }
            }            // Create a SortedMapRandomizedSameElement of the candidates
            final EldermanCandidateList eldermanCandidates = cityHall.getEldermanCandidates();
            SortedMapRandomizedSameElements mappedCandidates = citiesInteractionService.getCandidateMap(eldermanCandidates.getAll(), city);
            DateTime executionTime = now.plusMinutes(rnd.nextInt(votingTimeFrame));
            ElectionTask task = engineFactory.getNewElectionTask(mappedCandidates, electionResult, executionTime);
            timedTaskListener.add(task);
        }
    }

    private void electNewMayor(ICityHall cityHall) {
        // Create a SortedMapRandomizedSameElement of the candidates
        SortedMapRandomizedSameElements mappedCandidates = citiesInteractionService.getCandidateMap(cityHall.getCandidates(), cityHall.getCity());
        // Add the election object
        Election electionResult = new Election(true);
        ((CityHall)cityHall).setElectionResult(Optional.of(electionResult));
        // Create a timer for all councilmen over the next 18h
        DateTime now = date.getCurrentDate();
        // each councilmen selects one of the list using OpenPatricianRandom
        List councilmen = cityHall.getCouncilmen();
        int votingTimeFrame = 18*60;
        for (ICitizen citizen : councilmen) {
            if (citizen instanceof IPlayer) { // Human player
                if (!(citizen instanceof IAIPlayer)) {
                    continue;
                }
            }
            DateTime executionTime = now.plusMinutes(rnd.nextInt(votingTimeFrame));
            ElectionTask task = engineFactory.getNewElectionTask(mappedCandidates, electionResult, executionTime);
            timedTaskListener.add(task);
        }
    }

    /**
     * Publish a message with the winner of the election.
     * @param cityHall
     */
    private void notificationElectionWinner(ICityHall cityHall) {
        DateTime newDate = date.getCurrentDate().plusYears(2).minusDays(1);
        Election result = (Election) cityHall.getElectionResult().get();
        ICitizen winner = null;
        int maxVotes = 0;
        for (Entry entry : result.getVotes().entrySet()) {
            if (entry.getValue() > maxVotes) {
                winner = entry.getKey();
                maxVotes = entry.getValue();
            }
        }
        String name = winner.getName()+" "+winner.getLastName();
        if (result.isMayoral()) {
            String s = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.mayoralElectionResult", new Object[]{cityHall.getCity().getName(), name}, locale.getCurrentLocal());
            DisplayMessage msg = new DisplayMessage(s);
            clientEventBus.post(msg);
            ((CityHall)cityHall).setMayor(winner);
            ((CityHall) cityHall).setElectionDate(newDate);
        } else {
            String s = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.aldermanElectionResult", new Object[]{cityHall.getCity().getName(), name}, locale.getCurrentLocal());
            DisplayMessage msg = new DisplayMessage(s);
            clientEventBus.post(msg);
            ((CityHall)cityHall).setElderman(winner);
            ((CityHall) cityHall).setEldermanElectionDate(newDate);
        }
        ((CityHall) cityHall).setElectionResult(Optional.empty());
    }

    /**
     * The Hanseatic council agreed on a punishment for a city violation, execute it.
     * @param violation
     */
    private void handleCityViolationPunishment(ICityViolation violation) {
        ECityViolationPunishment punishment = violation.getPunishment();
        String message = null;
        if (punishment == ECityViolationPunishment.NONE) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.yesViolationNoAction", new Object[]{violation.getCity().getName()}, locale.getCurrentLocal());     // todo: andi 6/7/15: the city name must be language neutral
            // Nothing to be done
        } else if (punishment == ECityViolationPunishment.BLOCKADE) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.yesViolationBlockade", new Object[]{violation.getCity().getName()}, locale.getCurrentLocal());  // todo: andi 6/7/15: the city name must be language neutral
            // todo: andi 4/24/15: call for blockade ships. See ticket #202
            initializeBlockadeAction(violation.getCity());
        } else {
            ICity otherCity = violation.getCity();
            ICityHall otherCityHall = findCityHall(otherCity);
            Treasury otherTreasury = (Treasury) otherCityHall.getTreasury();
            int fine = citiesInteractionService.getFine(punishment, otherTreasury);
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.yesViolationFine", new Object[]{violation.getCity().getName(), fine}, locale.getCurrentLocal());   // todo: andi 6/7/15: the city name must be language neutral
            otherTreasury.subtractOtherCosts(fine);
            int cashPerCity = fine/(map.getNumberCities() - 1);
            for (ICity city : map.getCities()) {
                if (city.equals(otherCity)) {
                    continue;
                }
                Treasury treasury = (Treasury) findCityHall(city).getTreasury();
                treasury.addOtherIncome(cashPerCity);
            }
        }
        DisplayMessage msg = new DisplayMessage(message);
        clientEventBus.post(msg);
    }

    /**
     * The Hanseatic council decided not to punish a city for its violation, notify.
     * @param violation
     */
    private void handleDeniedCityViolation(ICityViolation violation) {
        String message = null;
        if (violation instanceof ICustomsViolation) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.noSanctionsCustomViolation", new Object[]{violation.getCity().getName()}, locale.getCurrentLocal());
        }
        if (violation instanceof IPirateSupportViolation) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.noSanctionsPirateSupport", new Object[]{violation.getCity().getName()}, locale.getCurrentLocal());
        }
        if (violation instanceof IPlunderTradingOfficesViolation) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.noSanctionsPlunder", new Object[]{violation.getCity().getName()}, locale.getCurrentLocal());
        }
        if (violation instanceof ISpecialTaxViolation) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.noSanctionsSpecialTaxViolation", new Object[]{violation.getCity().getName()}, locale.getCurrentLocal());
        }
        DisplayMessage msg = new DisplayMessage(message);
        clientEventBus.post(msg);
    }

    /**
     * The city council accepted a petition, execute it.
     * @param cityHall
     * @param petition
     */
    private void handleAcceptedCityPetition(final ICityHall cityHall, ICityPetition petition) {
        String message = null;
        if (petition instanceof ICityWallPetition) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.yesCityWall", new Object[]{cityHall.getCity().getName()}, locale.getCurrentLocal());
            CityWall cityWall = cityHall.getCity().getCityState().getCityWall();
            if (cityWall.getExtension() == ECityWall.NOT_EXTENDED) {
                ECityWall next = ECityWall.EXTENDED_ONCE;
                cityWall.setRequiredBricks(next.getRequiredBricks());
                cityWall.setBoughtBricks(0);
                cityWall.setUsedBricks(0);
                cityWall.setExtension(next);
            } else if (cityWall.getExtension() == ECityWall.EXTENDED_ONCE) {
                ECityWall next = ECityWall.EXTENDED_TWICE;
                cityWall.setRequiredBricks(next.getRequiredBricks());
                cityWall.setBoughtBricks(0);
                cityWall.setUsedBricks(0);
                cityWall.setExtension(next);
            }
        }
        if (petition instanceof IHeadTaxPetition) {
            double newTax = ((IHeadTaxPetition)petition).getNewTaxValue();
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.yesHeadTax", new Object[]{cityHall.getCity().getName(), newTax}, locale.getCurrentLocal());
            ((Treasury)cityHall.getTreasury()).setCurrentHeadTaxValue(newTax);
        }
        if (petition instanceof IMilitiaPetition) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.yesMilitia", new Object[]{cityHall.getCity().getName()}, locale.getCurrentLocal());
            ((CityHall)cityHall).setMaxNumberMilita(cityHall.getMaxNumberMilita()+5);
        }
        if (petition instanceof ISpecialTaxPetition) {
            message = leveySpecialTax(cityHall, (ISpecialTaxPetition) petition);
        }
        DisplayMessage msg = new DisplayMessage(message);
        clientEventBus.post(msg);
    }

    private String leveySpecialTax(final ICityHall cityHall, ISpecialTaxPetition petition) {
        String message;
        int taxHeigth =  petition.getTaxValue();
        message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.yesSpecialTax", new Object[]{cityHall.getCity().getName(), taxHeigth}, locale.getCurrentLocal());
        // 50% of the value from all players based on their cash volume.
        List players = cityHall.getCity().getResidentPlayers();
        long totalCashVolume = 0;
        for (IPlayer player : players) {
            totalCashVolume += player.getCompany().getCash();
        }
        long collected = taxHeigth/2;
        for (IPlayer player : players) {
            double percentage = player.getCompany().getCash()*1.0/totalCashVolume * 0.5;
            int amount = (int) (taxHeigth * percentage);
            if (player instanceof IAIPlayer) {
                player.getCompany().updateCash(-amount);
                collected += amount;
            } else {
                // Add debt to the loaner
                ILoaner loaner = loanerEngine.getLoaner(cityHall.getCity());
                ICreditor creditor = new ICreditor() {
                    @Override
                    public void receiveSum(long amount) {
                        ((Treasury)cityHall.getTreasury()).addPaidSpecialTaxes(amount);
                    }
                };
                Debt debt = Debt.builder()
                        .loanTakeOut(date.getCurrentDate())
                        .debitor(player)
                        .creditor(creditor)
                        .interest(0)
                        .amount(amount)
                        .dueDate(date.getCurrentDate().plusMonths(1))
                        .build();
                loaner.addDebt(debt);
                // todo: andi 4/24/15: send out notification once #203 is implemented
            }
        }
        ((Treasury)cityHall.getTreasury()).addPaidSpecialTaxes(collected);
        return message;
    }

    /**
     * The city council decided against a petition, inform.
     * @param cityHall
     * @param petition
     */
    private void handleDeniedCityPetition(ICityHall cityHall, ICityPetition petition) {
        String message = null;
        if (petition instanceof ICityWallPetition) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.noPetitionCityWall", new Object[]{cityHall.getCity().getName()}, locale.getCurrentLocal());
        }
        if (petition instanceof IHeadTaxPetition) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.noPetitionHeadTax", new Object[]{cityHall.getCity().getName()}, locale.getCurrentLocal());
        }
        if (petition instanceof IMilitiaPetition) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.noPetitionMilitia", new Object[]{cityHall.getCity().getName()}, locale.getCurrentLocal());
        }
        if (petition instanceof ISpecialTaxPetition) {
            message = messageSource.getMessage("ch.sahits.game.openpatrician.engine.land.city.CityHallEngine.noPetitionSpecialTax", new Object[]{cityHall.getCity().getName()}, locale.getCurrentLocal());
        }
        DisplayMessage msg = new DisplayMessage(message);
        clientEventBus.post(msg);
    }

    private void initializeBlockadeAction(ICity city) {
        // Issue call for ships (fleet of 10-20 => 1 ship, 20-30 => 2 ships, more 3 ships)
        // At the date of assembly build convoy to ship to the blocked city
        // For any ship not present fine the owner
        // When at the city start a blockade for (60 - 180 days)
        // When event ShipNearingPortEvent engage in battle
    }

    @Subscribe
    public void handleMonthlyUpdate(PeriodicalTimeMonthEndUpdate event) {
        for (ICityHall cityHall : cityHalls) {
            handleMayoralTasks(cityHall);
            if (cityHall.getEldermanOffice().isPresent()) {
                handleEldermansTask(cityHall.getEldermanOffice().get());
            }
        }
    }

    private void handleEldermansTask(IEldermanOffice office) {
        // update tasks
        updateEldermanTasks(office);
    }

    private void updateEldermanTasks(IEldermanOffice office) {
        List tasks = office.getTasks();
        if (tasks.size() < 3) {
            boolean helpTask = false;
            boolean newTown = false;
            boolean landPassage = false;
            boolean huntPirate = false;
            boolean capturePirateNest = false;
            for (IEldermanTask task : tasks) {
                if (task instanceof IHelpCity) {
                    helpTask = true;
                    continue;
                }
                if (task instanceof IHuntPirate) {
                    huntPirate = true;
                    continue;
                }
                if (task instanceof IBuildLandPassage) {
                    landPassage = true;
                    continue;
                }
                if (task instanceof IFoundNewSettlement) {
                    newTown = true;
                    continue;
                }
                if (task instanceof ICapturePirateNest) {
                    capturePirateNest = true;
                    continue;
                }
            } // end for
            if (!helpTask) {
                Optional task = stateFactory.createHelpCityEldermanTask(office);
                if (task.isPresent()) {
                    tasks.add(task.get());
                }
            }
            if (tasks.size() < 3 && !huntPirate) {
                Optional task = stateFactory.createPirateHuntEledermanTask(office);
                if (task.isPresent()) {
                    tasks.add(task.get());
                }
            }
            if (tasks.size() < 3 && !landPassage) {
                Optional task = stateFactory.createNewLandBridgeEldermanTask(office);
                if (task.isPresent()) {
                    tasks.add(task.get());
                }
            }
            if (tasks.size() < 3 && !newTown) {
                Optional task = stateFactory.createNewSettlementEledermanTask(office);
                if (task.isPresent()) {
                    tasks.add(task.get());
                }
            }
            if (tasks.size() < 3 && !capturePirateNest) {
                Optional task = stateFactory.createCapturePirateNestEldermanTask(office);
                if (task.isPresent()) {
                    tasks.add(task.get());
                }
            }
        }
    }

    private void handleMayoralTasks(ICityHall cityHall) {
        // Check city wall
        CityWall cityWall = cityHall.getCity().getCityState().getCityWall();
        boolean isPlayer = isPlayer(cityHall.getMayor());
        if (cityWall.getBoughtBricks() < cityWall.getRequiredBricks() && !isPlayer) {
            int random = rnd.nextInt(28);
            DateTime exection = date.getCurrentDate().plusDays(random);
            CityWallMaterialBuyingTask task = engineFactory.getCityWallBuyMaterialTask(cityHall, exection);
            timedTaskListener.add(task);
        }
        // Check militia
        if (cityHall.getMaxNumberMilita() > cityHall.getCityGuard().size()  && !isPlayer) {
            int random = rnd.nextInt(4);
            if (random == 0) {
                int[] guardNumbers = new int[4];
                guardNumbers[0] = countMilitia(cityHall.getCityGuard(), IBowmen.class);
                guardNumbers[1] = countMilitia(cityHall.getCityGuard(), IPikemen.class);
                guardNumbers[2] = countMilitia(cityHall.getCityGuard(), ICrossbowmen.class);
                guardNumbers[3] = countMilitia(cityHall.getCityGuard(), IMusketeer.class);
                int whatIndex = 0;
                if (guardNumbers[0] > guardNumbers[1]) {
                    whatIndex = 1;
                }
                if (guardNumbers[whatIndex] > guardNumbers[2]) {
                    whatIndex = 2;
                }
                if (guardNumbers[whatIndex] > guardNumbers[3]) {
                    whatIndex = 3;
                }
                Treasury treasury = ((Treasury)cityHall.getTreasury());
                switch (whatIndex) {
                    case 0:
                       treasury.subtractCityGuardCosts(5); // todo: andi 6/5/15: do the transaction with the blacksmith
                       IBowmen bowman = peopleFactory.createBowman();
                        cityHall.getCityGuard().add(bowman);
                        break;
                    case 1:
                        treasury.subtractCityGuardCosts(5); // todo: andi 6/5/15: do the transaction with the blacksmith
                        IPikemen pikeman = peopleFactory.createPikeman();
                        cityHall.getCityGuard().add(pikeman);
                        break;
                    case 2:
                        treasury.subtractCityGuardCosts(10); // todo: andi 6/5/15: do the transaction with the blacksmith
                        ICrossbowmen crossbowman = peopleFactory.createCrossbowman();
                        cityHall.getCityGuard().add(crossbowman);
                        break;
                    case 3:
                        treasury.subtractCityGuardCosts(20); // todo: andi 6/5/15: do the transaction with the blacksmith
                        IMusketeer musketeer = peopleFactory.createMusketeer();
                        cityHall.getCityGuard().add(musketeer);
                        break;
                    default:
                        throw new IllegalStateException("Case "+whatIndex+" is not handled");
                }

            }
        }
    }

    private int countMilitia(List cityGuard, Class clazz) {
        int count = 0;
        for (ICityGuard guard : cityGuard) {
            if (clazz == IBowmen.class) {
                if (guard instanceof IBowmen) {
                    count++;
                }
            }
            if (clazz == ICrossbowmen.class) {
                if (guard instanceof ICrossbowmen) {
                    count++;
                }
            }
            if (clazz == IPikemen.class) {
                if (guard instanceof IPikemen) {
                    count++;
                }
            }
            if (clazz == IMusketeer.class) {
                if (guard instanceof IMusketeer) {
                    count++;
                }
            }
        }
        return count;
    }

    private boolean isPlayer(ICitizen mayor) {
        if (mayor instanceof IPlayer) {
            if (mayor instanceof IAIPlayer) {
                return false;
            }
            return true;
        }
        return false;

    }

    @Subscribe
    public void handleEndOfYearUpdate(PeriodicalTimeYearEndUpdate event) {
        for (ICityHall cityHall : cityHalls) {
        }
    }

    @Override
    public List getChildren() {
        return new ArrayList<>();
    }
    public void establishCityHall(ICity city) {
        if (initialisation.initialisationHappened) {
           initializeCity(city);
        } else {
            initialisation.cities.add(city);
        }
    }

    private ICityHall initializeCity(ICity city) {
        ICityHall cityHall = stateFactory.createCityHall(city);
        return cityHall;
    }

    @Subscribe
    public void handleGameStartEvent(NewGameClient newGameClient) {
        initialisation.initialisationHappened = true;

        for (int i = 0; i < initialisation.cities.size(); i++) {
            ICity city = initialisation.cities.get(i);

            initializeCity(city);
        }
        for (ICityHall cityHall : cityHalls) {
            if (eldermanCandidates.size() < 4) {
                eldermanCandidates.add(cityHall.getMayor());
            }
        }
        ICitizen elderman  = eldermanCandidates.get(rnd.nextInt(eldermanCandidates.size()));
        DateTime election = date.getCurrentDate().plusDays(rnd.nextInt(600));
        for (ICityHall cityHall : cityHalls) {
            ((CityHall)cityHall).setElderman(elderman);
            ((CityHall)cityHall).setEldermanElectionDate(election);
            if (elderman.getHometown().equals(cityHall.getCity())) {
                Optional office = Optional.of(stateFactory.createEldermanOffice());
                  ((CityHall) cityHall).setEldermanOffice(office);
            }
        }

    }




    private static class CityInitialisation {
        private List cities = new ArrayList<>();
        private boolean initialisationHappened = false;
    }

    public ICityHall findCityHall(ICity city) {
        for (ICityHall cityHall : cityHalls) {
            final ICity cityHallCity = cityHall.getCity();
            if (cityHallCity.equals(city)) {
                return cityHall;
            }
        }
        return null;
    }
    public void assignTask(IPlayer player, IEldermanTask task) {
        takenTasks.assignTask(player, task);
    }

    /**
     * Update the treasury. Payed taxes and recurring costs (militia, outrigger).
     * This update is done on a weekly basis.
     */
    @VisibleForTesting
    void updateTreasuryWeekly(ICityHall cityHall) {
        Treasury treasury = (Treasury) cityHall.getTreasury();
        treasury.reset();
        ICity city = cityHall.getCity();
        if (cityHall.getOutriggerContract().isPresent()) {
            final IOutriggerContract outriggerContract = cityHall.getOutriggerContract().get();
            final int weeklyRefund = outriggerContract.getWeeklyRefund();
            treasury.subtractOutriggerCosts(weeklyRefund);
            IPlayer owner = (IPlayer) outriggerContract.getOutrigger().getOwner();
            owner.getCompany().updateCash(weeklyRefund);
        }
        int population = city.getPopulation(EPopulationClass.POOR);
        double taxes = population/100*weeklyHeadTaxPoor*treasury.getCurrentHeadTaxValue();
        population = city.getPopulation(EPopulationClass.MEDIUM);
        taxes += population/100*weeklyHeadTaxMiddleClass*treasury.getCurrentHeadTaxValue();
        population = city.getPopulation(EPopulationClass.RICH);
        taxes += population/100*weeklyHeadTaxRich*treasury.getCurrentHeadTaxValue();
        List residents = city.getResidentPlayers();
        long propertyTax = 0;
        for (IPlayer resident : residents) {
            final ICompany company = resident.getCompany();
            long cash = company.getCash();
            double weeklyPercentage = treasury.getCurrentHeadTaxValue()/(100.0*52);
            long taxAmount = (long)(cash*weeklyPercentage);
            taxes +=taxAmount;
            List buildings = resident.findBuildings(city);
            long propertyTaxPerResedident = 0;
            for (IBuilding building : buildings) {
                propertyTaxPerResedident += building.getPropertyTax() * treasury.getCurrentPropertyTax();
            }
            propertyTax += propertyTaxPerResedident;
            company.updateCash(-(taxAmount+propertyTaxPerResedident));
            ICity hometown = resident.getHometown();
            BalanceSheet sheet = (BalanceSheet) resident.findTradingOffice(hometown).getCurrentWeek();
            sheet.deductPropertyTaxes((int)(taxAmount+propertyTaxPerResedident));
        }
        treasury.addPaidTaxes((long)taxes+propertyTax);
        List guards = cityHall.getCityGuard();
        int guardCosts = 0;
        for (ICityGuard guard : guards) {
            guardCosts += guard.getAmount() * guard.getWeeklySalary();
        }
        treasury.subtractCityGuardCosts(guardCosts);
    }

    /**
     * Check if the destroyed ship was a pirate ship that belongs to an elderman task.
     * @param event
     */
    public void checkPirateKilledEldermanTask(ShipAttackEvent event) {
        if (event.getAttackedShip().getOwner() instanceof ISeaPirate) {
            IEldermanOffice office = getEldermanOffice();
            for (Iterator iterator = office.getWorkedOnTasks().iterator(); iterator.hasNext(); ) {
                IAcceptedEldermanTask task = iterator.next();
                if (task.getTask() instanceof IHuntPirate) {
                    IHuntPirate concreteTask = (IHuntPirate) task.getTask();
                    if (concreteTask.getPirate().equals(event.getAttackedShip().getOwner())) {
                        if (date.getCurrentDate().isBefore(task.getDeadline())) {
                            for (ICity iCity : map.getCities()) {
                                    iCity.getReputation(task.getPlayer()).update(15);

                            }
                        }
                        iterator.remove();
                    }
                }
            }
        }
    }

    private IEldermanOffice getEldermanOffice() {
        IEldermanOffice office = null;
        for (ICityHall cityHall : cityHalls) {
            if (cityHall.getEldermanOffice().isPresent()) {
                office = cityHall.getEldermanOffice().get();
                break;
            }
        }
        return office;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy