/*******************************************************************************
* Copyright (c) 2015 - 2017
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package jsettlers.logic.map.grid.partition.manager;
import jsettlers.common.material.EMaterialType;
import jsettlers.common.movable.EDirection;
import jsettlers.common.movable.EMovableType;
import jsettlers.common.position.ILocatable;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.logic.buildings.Building;
import jsettlers.logic.buildings.MaterialProductionSettings;
import jsettlers.logic.buildings.workers.WorkerBuilding;
import jsettlers.logic.map.grid.partition.data.MaterialCounts;
import jsettlers.logic.map.grid.partition.manager.datastructures.PositionableList;
import jsettlers.logic.map.grid.partition.manager.datastructures.PredicatedPositionableList;
import jsettlers.logic.map.grid.partition.manager.manageables.IManageableBearer;
import jsettlers.logic.map.grid.partition.manager.manageables.IManageableBearer.IWorkerRequester;
import jsettlers.logic.map.grid.partition.manager.manageables.IManageableBricklayer;
import jsettlers.logic.map.grid.partition.manager.manageables.IManageableDigger;
import jsettlers.logic.map.grid.partition.manager.manageables.IManageableWorker;
import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IBarrack;
import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IDiggerRequester;
import jsettlers.logic.map.grid.partition.manager.materials.MaterialsManager;
import jsettlers.logic.map.grid.partition.manager.materials.interfaces.IJoblessSupplier;
import jsettlers.logic.map.grid.partition.manager.materials.interfaces.IManagerBearer;
import jsettlers.logic.map.grid.partition.manager.materials.interfaces.IOfferEmptiedListener;
import jsettlers.logic.map.grid.partition.manager.materials.offers.EOfferPriority;
import jsettlers.logic.map.grid.partition.manager.materials.offers.IOffersCountListener;
import jsettlers.logic.map.grid.partition.manager.materials.offers.MaterialOffer;
import jsettlers.logic.map.grid.partition.manager.materials.offers.OffersList;
import jsettlers.logic.map.grid.partition.manager.materials.requests.MaterialRequestObject;
import jsettlers.logic.map.grid.partition.manager.objects.BricklayerRequest;
import jsettlers.logic.map.grid.partition.manager.objects.DiggerRequest;
import jsettlers.logic.map.grid.partition.manager.objects.SoldierCreationRequest;
import jsettlers.logic.map.grid.partition.manager.objects.WorkerCreationRequest;
import jsettlers.logic.map.grid.partition.manager.objects.WorkerRequest;
import jsettlers.logic.map.grid.partition.manager.settings.PartitionManagerSettings;
import jsettlers.logic.timer.IScheduledTimerable;
import jsettlers.logic.timer.RescheduleTimer;
import java.io.Serializable;
import java.util.Iterator;
import java.util.LinkedList;
/**
* This is a manager for a partition. It stores offers, requests and jobless to build up jobs and give them to the jobless.
*
* @author Andreas Eberle
*/
public class PartitionManager implements IScheduledTimerable, Serializable, IWorkerRequester {
private static final long serialVersionUID = 3759772044136966735L;
private static final int SCHEDULING_PERIOD = 25;
private static final byte priorityForTool[] = new byte[EMaterialType.NUMBER_OF_MATERIALS];
static { // Tools with higher priorities are produced first by auto production.
priorityForTool[EMaterialType.AXE.ordinal] = 10;
priorityForTool[EMaterialType.SAW.ordinal] = 10;
priorityForTool[EMaterialType.PICK.ordinal] = 10;
priorityForTool[EMaterialType.SCYTHE.ordinal] = 5;
priorityForTool[EMaterialType.FISHINGROD.ordinal] = 5;
priorityForTool[EMaterialType.HAMMER.ordinal] = 1;
priorityForTool[EMaterialType.BLADE.ordinal] = 1;
}
private final PartitionManagerSettings settings = new PartitionManagerSettings();
private final PositionableList<IManageableBearer> joblessBearer = new PositionableList<>();
private final OffersList materialOffers;
private final MaterialsManager materialsManager;
private final LinkedList<WorkerRequest> workerRequests = new LinkedList<>();
private final PredicatedPositionableList<IManageableWorker> joblessWorkers = new PredicatedPositionableList<>();
private final LinkedList<DiggerRequest> diggerRequests = new LinkedList<>();
private final PositionableList<IManageableDigger> joblessDiggers = new PositionableList<>();
private final LinkedList<BricklayerRequest> bricklayerRequests = new LinkedList<>();
private final PositionableList<IManageableBricklayer> joblessBricklayers = new PositionableList<>();
private final LinkedList<WorkerCreationRequest> workerCreationRequests = new LinkedList<>();
private final LinkedList<SoldierCreationRequest> soldierCreationRequests = new LinkedList<>();
private boolean stopped = true;
public PartitionManager(IOffersCountListener offersCountListener) {
materialOffers = new OffersList(offersCountListener);
materialsManager = new MaterialsManager(new IJoblessSupplier() {
private static final long serialVersionUID = -113397265091126902L;
@Override
public IManagerBearer removeJoblessCloseTo(ShortPoint2D position) {
return joblessBearer.removeObjectNextTo(position);
}
@Override
public boolean isEmpty() {
return joblessBearer.isEmpty();
}
}, materialOffers, settings);
}
public void startManager() {
stopped = false;
RescheduleTimer.add(this, SCHEDULING_PERIOD);
}
public void stopManager() {
stopped = true;
}
public boolean isStopped() {
return stopped;
}
public MaterialProductionSettings getMaterialProduction() {
return settings.getMaterialProductionSettings();
}
public void addOffer(ShortPoint2D position, EMaterialType materialType, EOfferPriority offerPriority) {
materialOffers.addOffer(position, materialType, offerPriority);
}
public void addOffer(ShortPoint2D position, EMaterialType materialType, EOfferPriority offerPriority, IOfferEmptiedListener offerListener) {
materialOffers.addOffer(position, materialType, offerPriority, offerListener);
}
public void updateOfferPriority(ShortPoint2D position, EMaterialType materialType, EOfferPriority newPriority) {
materialOffers.updateOfferPriority(position, materialType, newPriority);
}
public void request(EMaterialType materialType, MaterialRequestObject requestObject) {
materialsManager.addRequestObject(materialType, requestObject);
}
public void requestDiggers(IDiggerRequester requester, byte amount) {
diggerRequests.offer(new DiggerRequest(requester, amount));
}
public void requestBricklayer(Building building, ShortPoint2D bricklayerTargetPos, EDirection direction) {
bricklayerRequests.offer(new BricklayerRequest(building, bricklayerTargetPos, direction));
}
public void requestBuildingWorker(EMovableType workerType, WorkerBuilding workerBuilding) {
workerRequests.offer(new WorkerRequest(workerType, workerBuilding));
}
public void requestSoldierable(IBarrack barrack) {
soldierCreationRequests.offer(new SoldierCreationRequest(barrack));
}
public void addJobless(IManageableBearer bearer) {
this.joblessBearer.insert(bearer);
}
public void removeJobless(IManageableBearer bearer) {
this.joblessBearer.remove(bearer);
}
public void addJobless(IManageableDigger digger) {
joblessDiggers.insert(digger);
}
public void removeJobless(IManageableDigger digger) {
joblessDiggers.remove(digger);
}
public void addJobless(IManageableBricklayer bricklayer) {
joblessBricklayers.insert(bricklayer);
}
public void removeJobless(IManageableBricklayer bricklayer) {
joblessBricklayers.remove(bricklayer);
}
public void addJobless(IManageableWorker worker) {
joblessWorkers.insert(worker);
}
public void removeJobless(IManageableWorker worker) {
joblessWorkers.remove(worker);
}
/**
* @param x
* x coordinate of the position to be removed from this manager and added to the given manager
* @param y
* y coordinate of the position to be removed from this manager and added to the given manager
* @param newManager
* new manager of the given position <br>
* NOTE: the new manager MUST NOT be null!
* @param newHasSamePlayer
* Specifies if the new manager has the same player. If so, requests also need to be moved.
*/
public void removePositionTo(final int x, final int y, PartitionManager newManager, boolean newHasSamePlayer) {
ShortPoint2D position = new ShortPoint2D(x, y);
materialOffers.moveOffersAtPositionTo(position, newManager.materialOffers);
if (newHasSamePlayer) {
materialsManager.movePositionTo(position, newManager.materialsManager);
IManageableBearer bearer = joblessBearer.removeObjectAt(position);
if (bearer != null) {
newManager.addJobless(bearer);
}
IManageableBricklayer bricklayer = joblessBricklayers.removeObjectAt(position);
if (bricklayer != null) {
newManager.addJobless(bricklayer);
}
IManageableDigger digger = joblessDiggers.removeObjectAt(position);
if (digger != null) {
newManager.addJobless(digger);
}
IManageableWorker worker = joblessWorkers.removeObjectAt(position);
if (worker != null) {
newManager.addJobless(worker);
}
}
removePositionTo(position, this.workerCreationRequests, newManager.workerCreationRequests, newHasSamePlayer);
removePositionTo(position, this.bricklayerRequests, newManager.bricklayerRequests, newHasSamePlayer);
removePositionTo(position, this.diggerRequests, newManager.diggerRequests, newHasSamePlayer);
removePositionTo(position, this.workerRequests, newManager.workerRequests, newHasSamePlayer);
removePositionTo(position, this.soldierCreationRequests, newManager.soldierCreationRequests, newHasSamePlayer);
}
private <T extends ILocatable> void removePositionTo(ShortPoint2D pos, LinkedList<T> fromList, LinkedList<T> toList, boolean newHasSamePlayer) {
Iterator<T> iter = fromList.iterator();
while (iter.hasNext()) {
T curr = iter.next();
if (curr.getPos().equals(pos)) {
iter.remove();
if (newHasSamePlayer) {
toList.offer(curr);
}
}
}
}
public final void mergeInto(PartitionManager newManager) {
newManager.bricklayerRequests.addAll(this.bricklayerRequests);
newManager.diggerRequests.addAll(this.diggerRequests);
newManager.joblessBearer.moveAll(this.joblessBearer);
newManager.joblessBricklayers.moveAll(this.joblessBricklayers);
newManager.joblessDiggers.moveAll(this.joblessDiggers);
newManager.joblessWorkers.moveAll(this.joblessWorkers);
newManager.materialOffers.moveAll(this.materialOffers);
this.materialsManager.mergeInto(newManager.materialsManager);
newManager.soldierCreationRequests.addAll(this.soldierCreationRequests);
newManager.workerCreationRequests.addAll(this.workerCreationRequests);
newManager.workerRequests.addAll(this.workerRequests);
}
@Override
public final int timerEvent() {
if (stopped) {
return -1; // unschedule
}
materialsManager.distributeJobs();
handleDiggerRequest();
handleBricklayerRequest();
handleWorkerRequest();
handleWorkerCreationRequests();
handleSoldierCreationRequest();
return SCHEDULING_PERIOD;
}
private void handleWorkerRequest() {
WorkerRequest workerRequest = workerRequests.poll();
if (workerRequest != null) {
IManageableWorker worker = joblessWorkers.removeObjectNextTo(workerRequest.getPos(), currentWorker -> currentWorker.getMovableType() == workerRequest.movableType);
if (worker != null && worker.isAlive()) {
worker.setWorkerJob(workerRequest.building);
} else {
if (!workerRequest.creationRequested) {
workerRequest.creationRequested = true;
createNewToolUser(workerRequest);
}
workerRequests.offerLast(workerRequest);
}
}
}
private void createNewToolUser(WorkerCreationRequest workerCreationRequest) {
workerCreationRequests.offer(workerCreationRequest);
}
private void handleWorkerCreationRequests() {
for (Iterator<WorkerCreationRequest> iterator = workerCreationRequests.iterator(); iterator.hasNext() && !joblessBearer.isEmpty(); ) {
WorkerCreationRequest workerCreationRequest = iterator.next();
if (!workerCreationRequest.isRequestAlive() || tryToCreateWorker(workerCreationRequest)) {
iterator.remove();
}
}
}
private boolean tryToCreateWorker(WorkerCreationRequest workerCreationRequest) {
EMovableType movableType = workerCreationRequest.requestedMovableType();
EMaterialType tool = movableType.getTool();
if (tool != EMaterialType.NO_MATERIAL) { // try to create a worker with a tool
MaterialOffer offer = materialOffers.getOfferCloseTo(tool, EOfferPriority.LOWEST, workerCreationRequest.getPos());
if (offer != null) {
IManageableBearer manageableBearer = joblessBearer.removeObjectNextTo(offer.getPos());
if (manageableBearer != null) {
return manageableBearer.becomeWorker(this, workerCreationRequest, offer);
} else { // no free movable found => cannot create worker
return false;
}
} else { // no tool found => cannot create worker
workerCreationRequest.setToolProductionRequired(true);
return false;
}
} else { // create worker without a tool
IManageableBearer manageableBearer = joblessBearer.removeObjectNextTo(workerCreationRequest.getPos());
if (manageableBearer != null) {
return manageableBearer.becomeWorker(this, workerCreationRequest);
} else {
return false;
}
}
}
@Override
public void workerCreationRequestFailed(WorkerCreationRequest failedRequest) {
workerCreationRequests.offer(failedRequest);
}
private void handleSoldierCreationRequest() {
SoldierCreationRequest soilderRequest = soldierCreationRequests.poll();
if (soilderRequest != null) {
IManageableBearer manageableBearer = joblessBearer.removeObjectNextTo(soilderRequest.getPos());
if (manageableBearer == null || !manageableBearer.becomeSoldier(soilderRequest.getBarrack())) {
soldierCreationRequests.addLast(soilderRequest);
}
}
}
private void handleDiggerRequest() {
DiggerRequest request = diggerRequests.peek();
if (request == null) {
return;
}
if (request.isRequestAlive()) {
IManageableDigger digger = joblessDiggers.removeObjectNextTo(request.getPos());
if (digger != null) {
if (digger.setDiggerJob(request.requester)) {
request.amount--;
if (request.creationRequested > 0) {
request.creationRequested--;
}
}
} else {
if (request.amount > request.creationRequested) {
createNewToolUser(request);
request.creationRequested++;
}
}
if (request.amount <= 0) {
diggerRequests.poll();
}
} else {
diggerRequests.poll();
}
}
private void handleBricklayerRequest() {
BricklayerRequest bricklayerRequest = bricklayerRequests.poll();
if (bricklayerRequest != null && bricklayerRequest.isRequestAlive()) {
IManageableBricklayer bricklayer = joblessBricklayers.removeObjectNextTo(bricklayerRequest.getPos());
if (bricklayer != null) {
if (!bricklayer.setBricklayerJob(bricklayerRequest.building, bricklayerRequest.bricklayerTargetPos, bricklayerRequest.direction)) {
bricklayerRequests.add(bricklayerRequest);
}
} else if (!bricklayerRequest.isCreationRequested()) { // if the creation hasn't been requested yet => request it.
createNewToolUser(bricklayerRequest);
bricklayerRequest.setCreationRequested();
bricklayerRequests.offerLast(bricklayerRequest);
} else { // no bricklayer available and creation already requested => nothing to do.
bricklayerRequests.offerLast(bricklayerRequest);
}
}
}
public final EMaterialType popToolProduction(ShortPoint2D closeTo) {
byte bestPrio = 0;
EMaterialType bestTool = null;
for (WorkerCreationRequest request : workerCreationRequests) { // go through all requests and select the best one
if (!request.isRequestAlive() || !request.isToolProductionRequired()) {
continue; // skip inactive requests and requests not needing a tool production
}
request.setToolProductionRequired(false); // FIXME @andreas-eberle: Investigate if this is correct! And why it doesn't break the system
EMaterialType tool = request.requestedMovableType().getTool();
byte prio = priorityForTool[tool.ordinal];
if (prio > bestPrio) {
bestPrio = prio;
bestTool = tool;
}
}
return bestTool;
}
@Override
public void kill() {
throw new UnsupportedOperationException("CAN'T KILL PARTITION MANAGER!! THIS REALLY SHOULD NOT HAPPEN!");
}
/**
* FOR TESTS ONLY!
*
* @param position
* position to look for the offer
* @param materialType
* type of material of the offer
* @param offerPriority
* offerPriority of the offer
* @return Returns the offer at the given position of given materialType and offerPriority or <code>null</code>
*/
public MaterialOffer getMaterialOfferAt(ShortPoint2D position, EMaterialType materialType, EOfferPriority offerPriority) {
return this.materialOffers.getOfferObjectAt(position, materialType, offerPriority);
}
public PartitionManagerSettings getPartitionSettings() {
return settings;
}
public MaterialCounts getMaterialCounts() {
return materialOffers.getMaterialCounts();
}
}