/*******************************************************************************
* 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.input;
import java8.util.stream.Collectors;
import jsettlers.algorithms.construction.ConstructionMarksThread;
import jsettlers.common.buildings.EBuildingType;
import jsettlers.common.buildings.IBuilding;
import jsettlers.common.map.shapes.MapCircle;
import jsettlers.common.material.EPriority;
import jsettlers.common.menu.IMapInterfaceConnector;
import jsettlers.common.menu.IMapInterfaceListener;
import jsettlers.common.menu.UIState;
import jsettlers.common.menu.action.EActionType;
import jsettlers.common.menu.action.IAction;
import jsettlers.common.movable.EMovableType;
import jsettlers.common.movable.ESoldierType;
import jsettlers.common.movable.IIDable;
import jsettlers.common.movable.IMovable;
import jsettlers.common.position.ILocatable;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.common.selectable.ESelectionType;
import jsettlers.common.selectable.ISelectable;
import jsettlers.graphics.action.BuildAction;
import jsettlers.graphics.action.ChangeTradingRequestAction;
import jsettlers.graphics.action.ConvertAction;
import jsettlers.graphics.action.PointAction;
import jsettlers.graphics.action.ScreenChangeAction;
import jsettlers.graphics.action.SelectAreaAction;
import jsettlers.graphics.action.SetAcceptedStockMaterialAction;
import jsettlers.graphics.action.SetBuildingPriorityAction;
import jsettlers.graphics.action.SetMaterialDistributionSettingsAction;
import jsettlers.graphics.action.SetMaterialPrioritiesAction;
import jsettlers.graphics.action.SetMaterialProductionAction;
import jsettlers.graphics.action.SetTradingWaypointAction;
import jsettlers.graphics.action.ShowConstructionMarksAction;
import jsettlers.graphics.action.SoldierAction;
import jsettlers.input.tasks.ChangeTowerSoldiersGuiTask;
import jsettlers.input.tasks.ChangeTowerSoldiersGuiTask.EChangeTowerSoldierTaskType;
import jsettlers.input.tasks.ChangeTradingRequestGuiTask;
import jsettlers.input.tasks.ConstructBuildingTask;
import jsettlers.input.tasks.ConvertGuiTask;
import jsettlers.input.tasks.DestroyBuildingGuiTask;
import jsettlers.input.tasks.EGuiAction;
import jsettlers.input.tasks.MovableGuiTask;
import jsettlers.input.tasks.MoveToGuiTask;
import jsettlers.input.tasks.SetAcceptedStockMaterialGuiTask;
import jsettlers.input.tasks.SetBuildingPriorityGuiTask;
import jsettlers.input.tasks.SetMaterialDistributionSettingsGuiTask;
import jsettlers.input.tasks.SetMaterialPrioritiesGuiTask;
import jsettlers.input.tasks.SetMaterialProductionGuiTask;
import jsettlers.input.tasks.SetTradingWaypointGuiTask;
import jsettlers.input.tasks.SimpleGuiTask;
import jsettlers.input.tasks.UpgradeSoldiersGuiTask;
import jsettlers.input.tasks.WorkAreaGuiTask;
import jsettlers.logic.buildings.Building;
import jsettlers.logic.buildings.military.OccupyingBuilding;
import jsettlers.logic.constants.MatchConstants;
import jsettlers.logic.movable.interfaces.IDebugable;
import jsettlers.logic.player.Player;
import jsettlers.network.client.interfaces.IGameClock;
import jsettlers.network.client.interfaces.ITaskScheduler;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
/**
* Class to handle the events provided by the user through jsettlers.graphics.
*
* @author Andreas Eberle
*/
public class GuiInterface implements IMapInterfaceListener, ITaskExecutorGuiInterface {
private static final float SELECT_BY_TYPE_RADIUS = 30;
private final IMapInterfaceConnector connector;
private final IGameClock clock;
private final ITaskScheduler taskScheduler;
private final IGuiInputGrid grid;
private final IGameStoppable gameStoppable;
private final byte playerId;
private final boolean multiplayer;
private final ConstructionMarksThread constructionMarksCalculator;
private final Timer refreshSelectionTimer;
/**
* The current selection. This is updated by game logic.
*/
private SelectionSet currentSelection = new SelectionSet();
public GuiInterface(IMapInterfaceConnector connector, IGameClock clock, ITaskScheduler taskScheduler, IGuiInputGrid grid,
IGameStoppable gameStoppable, byte playerId, boolean multiplayer) {
this.connector = connector;
this.clock = clock;
this.taskScheduler = taskScheduler;
this.grid = grid;
this.gameStoppable = gameStoppable;
this.playerId = playerId;
this.multiplayer = multiplayer;
this.constructionMarksCalculator = new ConstructionMarksThread(grid.getConstructionMarksGrid(), clock, playerId);
this.refreshSelectionTimer = new Timer("refreshSelectionTimer");
this.refreshSelectionTimer.schedule(new TimerTask() {
@Override
public void run() {
refreshSelection();
}
}, 1000, 1000);
Player player = grid.getPlayer(playerId);
if (player != null) {
player.setMessenger(connector);
}
clock.setTaskExecutor(new GuiTaskExecutor(grid, this, this.playerId));
connector.addListener(this);
}
@Override
public void action(IAction action) {
if (action.getActionType() != EActionType.SCREEN_CHANGE) {
System.out.println("action(Action): " + action.getActionType() + " at game time: " + MatchConstants.clock().getTime());
}
switch (action.getActionType()) {
case BUILD:
this.setSelection(new SelectionSet());
final BuildAction buildAction = (BuildAction) action;
EBuildingType buildingType = buildAction.getBuildingType();
final ShortPoint2D pos2 = grid.getConstructablePosition(buildAction.getPosition(), buildingType, playerId,
InputSettings.USE_NEIGHBOR_POSITIONS_FOR_CONSTRUCTION);
if (pos2 != null) {
scheduleTask(new ConstructBuildingTask(EGuiAction.BUILD, playerId, pos2, buildingType));
}
System.out.println("build: " + buildingType);
break;
case SHOW_CONSTRUCTION_MARK: {
buildingType = ((ShowConstructionMarksAction) action).getBuildingType();
constructionMarksCalculator.setBuildingType(buildingType);
break;
}
case DEBUG_ACTION:
for (final ISelectable curr : currentSelection) {
if (curr instanceof IDebugable) {
((IDebugable) curr).debug();
}
}
break;
case SPEED_TOGGLE_PAUSE:
clock.invertPausing();
break;
case SPEED_SET_PAUSE:
clock.setPausing(true);
break;
case SPEED_UNSET_PAUSE:
clock.setPausing(false);
break;
case SPEED_SLOW:
if (!multiplayer) {
clock.setGameSpeed(0.5f);
}
break;
case SPEED_FAST:
if (!multiplayer) {
clock.setGameSpeed(2.0f);
}
break;
case SPEED_FASTER:
if (!multiplayer) {
clock.multiplyGameSpeed(1.2f);
}
break;
case SPEED_SLOWER:
if (!multiplayer) {
clock.multiplyGameSpeed(1 / 1.2f);
}
break;
case SPEED_NORMAL:
if (!multiplayer) {
clock.setGameSpeed(1.0f);
}
break;
case FAST_FORWARD:
if (!multiplayer) {
clock.fastForward();
}
break;
case SELECT_POINT:
handleSelectPointAction((PointAction) action);
break;
case SELECT_AREA:
selectArea((SelectAreaAction) action);
break;
case DESELECT:
deselect();
break;
case SELECT_POINT_TYPE:
selectPointType((PointAction) action);
break;
case MOVE_TO: {
final PointAction moveToAction = (PointAction) action;
if (currentSelection.getSelectionType() == ESelectionType.BUILDING && currentSelection.getSize() == 1) {
setBuildingWorkArea(moveToAction.getPosition());
} else {
moveTo(moveToAction.getPosition());
}
break;
}
case SHOW_MESSAGE: {
break;
}
case SET_WORK_AREA:
setBuildingWorkArea(((PointAction) action).getPosition());
break;
case DESTROY:
destroySelected();
break;
case STOP_WORKING:
stopOrStartWorkingAction(true);
break;
case START_WORKING:
stopOrStartWorkingAction(false);
break;
case SHOW_SELECTION:
showSelection();
break;
case SCREEN_CHANGE:
constructionMarksCalculator.setScreen(((ScreenChangeAction) action).getScreenArea());
break;
case TOGGLE_DEBUG:
grid.resetDebugColors();
break;
case TOGGLE_FOG_OF_WAR:
if (MatchConstants.ENABLE_FOG_OF_WAR_DISABLING) {
grid.toggleFogOfWar();
}
break;
case SAVE:
taskScheduler.scheduleTask(new SimpleGuiTask(EGuiAction.QUICK_SAVE, playerId));
break;
case CONVERT:
sendConvertAction((ConvertAction) action);
break;
case SET_BUILDING_PRIORITY:
setBuildingPriority(((SetBuildingPriorityAction) action).getNewPriority());
break;
case SET_MATERIAL_DISTRIBUTION_SETTINGS: {
final SetMaterialDistributionSettingsAction a = (SetMaterialDistributionSettingsAction) action;
taskScheduler.scheduleTask(new SetMaterialDistributionSettingsGuiTask(playerId, a.getManagerPosition(), a.getMaterialType(), a
.getProbabilities()));
break;
}
case SET_MATERIAL_PRIORITIES: {
final SetMaterialPrioritiesAction a = (SetMaterialPrioritiesAction) action;
taskScheduler.scheduleTask(new SetMaterialPrioritiesGuiTask(playerId, a.getPosition(), a.getMaterialTypeForPriority()));
break;
}
case SET_MATERIAL_STOCK_ACCEPTED: {
final SetAcceptedStockMaterialAction a = (SetAcceptedStockMaterialAction) action;
taskScheduler.scheduleTask(new SetAcceptedStockMaterialGuiTask(playerId, a.getPosition(), a.getMaterial(), a.shouldAccept(), a
.isLocalSetting()));
break;
}
case SET_MATERIAL_PRODUCTION: {
final SetMaterialProductionAction a = (SetMaterialProductionAction) action;
taskScheduler.scheduleTask(new SetMaterialProductionGuiTask(playerId, a.getPosition(), a.getMaterialType(), a.getProductionType(), a
.getRatio()));
break;
}
case NEXT_OF_TYPE:
selectNextOfType();
break;
case UPGRADE_SOLDIERS: {
final SoldierAction a = (SoldierAction) action;
taskScheduler.scheduleTask(new UpgradeSoldiersGuiTask(playerId, a.getSoldierType()));
break;
}
case CHANGE_TRADING_REQUEST: {
final ISelectable selected = currentSelection.getSingle();
if (selected instanceof Building) {
final ChangeTradingRequestAction a = (ChangeTradingRequestAction) action;
scheduleTask(new ChangeTradingRequestGuiTask(EGuiAction.CHANGE_TRADING, playerId, ((Building) selected).getPos(), a.getMaterial(),
a.getAmount(), a.isRelative()));
}
break;
}
case SET_TRADING_WAYPOINT: {
final ISelectable selected = currentSelection.getSingle();
if (selected instanceof Building) {
final SetTradingWaypointAction a = (SetTradingWaypointAction) action;
scheduleTask(new SetTradingWaypointGuiTask(EGuiAction.SET_TRADING_WAYPOINT, playerId, ((Building) selected).getPos(),
a.getWaypointType(), a.getPosition()));
}
}
case SOLDIERS_ALL:
requestSoldiers(EChangeTowerSoldierTaskType.FULL, null);
break;
case SOLDIERS_ONE:
requestSoldiers(EChangeTowerSoldierTaskType.ONE, null);
break;
case SOLDIERS_LESS:
requestSoldiers(EChangeTowerSoldierTaskType.LESS, ((SoldierAction) action).getSoldierType());
break;
case SOLDIERS_MORE:
requestSoldiers(EChangeTowerSoldierTaskType.MORE, ((SoldierAction) action).getSoldierType());
break;
case ABORT:
break;
case EXIT:
gameStoppable.stopGame();
break;
default:
System.out.println("WARNING: GuiInterface.action() called, but event can't be handled... (" + action.getActionType() + ")");
}
}
private void requestSoldiers(EChangeTowerSoldierTaskType taskType, ESoldierType soldierType) {
ISelectable selectable = currentSelection.getSingle();
if (selectable instanceof OccupyingBuilding) {
OccupyingBuilding building = ((OccupyingBuilding) selectable);
scheduleTask(new ChangeTowerSoldiersGuiTask(playerId, building.getPos(), taskType, soldierType));
}
}
private void selectNextOfType() {
if (currentSelection.getSize() != 1) {
return;
}
if (currentSelection.getSelectionType() == ESelectionType.BUILDING) {
final Building building = (Building) currentSelection.get(0);
final EBuildingType buildingType = building.getBuildingType();
Building first = null;
Building next = null;
boolean buildingFound = false;
for (final Building currBuilding : Building.getAllBuildings()) {
if (currBuilding == building) {
buildingFound = true;
} else {
if (currBuilding.getBuildingType() == buildingType && currBuilding.getPlayerId() == playerId) {
if (first == null) {
first = currBuilding;
}
if (buildingFound) {
next = currBuilding;
break;
}
}
}
}
if (next != null) {
setSelection(new SelectionSet(next));
} else if (first != null) {
setSelection(new SelectionSet(first));
}
}
}
private void setBuildingWorkArea(ShortPoint2D workAreaPosition) {
final ISelectable selected = currentSelection.getSingle();
if (selected instanceof Building) {
scheduleTask(new WorkAreaGuiTask(EGuiAction.SET_WORK_AREA, playerId, workAreaPosition, ((Building) selected).getPos()));
}
}
private void sendConvertAction(ConvertAction action) {
final List<ISelectable> convertables = new LinkedList<>();
switch (action.getTargetType()) {
case BEARER:
for (final ISelectable curr : currentSelection) {
if (curr instanceof IMovable) {
final EMovableType currType = ((IMovable) curr).getMovableType();
if (currType == EMovableType.PIONEER) {
convertables.add(curr);
if (convertables.size() >= action.getAmount()) {
break;
}
}
}
}
break;
case PIONEER:
case GEOLOGIST:
case THIEF:
for (final ISelectable curr : currentSelection) {
if (curr instanceof IMovable) {
final EMovableType currType = ((IMovable) curr).getMovableType();
if (currType == EMovableType.BEARER) {
convertables.add(curr);
if (convertables.size() >= action.getAmount()) {
break;
}
}
}
}
break;
default:
System.out.println("WARNING: can't handle convert to this movable type: " + action.getTargetType());
return;
}
if (convertables.size() > 0) {
taskScheduler.scheduleTask(new ConvertGuiTask(playerId, getIDsOfIterable(convertables), action.getTargetType()));
}
}
private void destroySelected() {
if (currentSelection == null || currentSelection.getSize() == 0) {
return;
} else if (currentSelection.getSize() == 1 && currentSelection.iterator().next() instanceof Building) {
taskScheduler.scheduleTask(new DestroyBuildingGuiTask(playerId, ((Building) currentSelection.iterator().next()).getPos()));
} else {
taskScheduler.scheduleTask(new MovableGuiTask(EGuiAction.DESTROY_MOVABLES, playerId, getIDsOfSelected()));
}
setSelection(new SelectionSet());
}
private void setBuildingPriority(EPriority newPriority) {
if (currentSelection != null && currentSelection.getSize() == 1 && currentSelection.iterator().next() instanceof Building) {
taskScheduler
.scheduleTask(new SetBuildingPriorityGuiTask(playerId, ((Building) currentSelection.iterator().next()).getPos(), newPriority));
}
}
private void showSelection() {
int x = 0;
int y = 0;
int count = 0;
for (final ISelectable member : currentSelection) {
if (member instanceof ILocatable) {
x += ((ILocatable) member).getPos().x;
y += ((ILocatable) member).getPos().y;
count++;
}
}
System.out.println("locatable: " + count);
if (count > 0) {
final ShortPoint2D point = new ShortPoint2D(x / count, y / count);
connector.scrollTo(point, false);
}
}
/**
* @param stop
* if true the members of currentSelection will stop working<br>
* if false, they will start working
*/
private void stopOrStartWorkingAction(boolean stop) {
taskScheduler.scheduleTask(new MovableGuiTask(stop ? EGuiAction.STOP_WORKING : EGuiAction.START_WORKING, playerId, getIDsOfSelected()));
}
private void moveTo(ShortPoint2D pos) {
final List<Integer> selectedIds = getIDsOfSelected();
scheduleTask(new MoveToGuiTask(playerId, pos, selectedIds));
}
private final List<Integer> getIDsOfSelected() {
return getIDsOfIterable(currentSelection);
}
private final static List<Integer> getIDsOfIterable(Iterable<? extends ISelectable> iterable) {
final List<Integer> selectedIds = new LinkedList<Integer>();
for (final ISelectable curr : iterable) {
if (curr instanceof IIDable) {
selectedIds.add(((IIDable) curr).getID());
}
}
return selectedIds;
}
private void selectArea(SelectAreaAction action) {
final SelectionSet selectionSet = new SelectionSet();
action.getArea().stream().filterBounds(grid.getWidth(), grid.getHeight()).forEach((x, y) -> {
final IGuiMovable movable = grid.getMovable(x, y);
if (movable != null && canSelectPlayer(movable.getPlayerId())) {
selectionSet.add(movable);
}
final IBuilding building = grid.getBuildingAt(x, y);
if (building != null && canSelectPlayer(building.getPlayerId())) {
selectionSet.add(building);
}
});
setSelection(selectionSet);
}
private boolean canSelectPlayer(byte playerIdOfSelected) {
return MatchConstants.ENABLE_ALL_PLAYER_SELECTION || playerIdOfSelected == playerId;
}
private void deselect() {
setSelection(new SelectionSet());
}
private void handleSelectPointAction(PointAction action) {
final ShortPoint2D pos = action.getPosition();
// only for debugging
grid.positionClicked(pos.x, pos.y);
// check what's to do
final ISelectable selected = getSelectableAt(pos);
if (selected != null) {
setSelection(new SelectionSet(selected));
} else {
setSelection(new SelectionSet());
}
}
private void scheduleTask(SimpleGuiTask guiTask) {
taskScheduler.scheduleTask(guiTask);
}
private ISelectable getSelectableAt(ShortPoint2D pos) {
if (grid.isInBounds(pos)) {
final short x = pos.x;
final short y = pos.y;
final IGuiMovable selectableMovable = getSelectableMovable(x, y);
if (selectableMovable != null) {
return selectableMovable;
} else {
// search buildings
final IBuilding building = grid.getBuildingAt(pos.x, pos.y);
if (building != null && canSelectPlayer(building.getPlayerId())) {
return building;
} else {
return null;
}
}
} else {
return null;
}
}
private IGuiMovable getSelectableMovable(short x, short y) {
final IGuiMovable m1 = grid.getMovable(x, y);
final IGuiMovable m3 = grid.getMovable((short) (x + 1), (short) (y + 1));
final IGuiMovable m2 = grid.getMovable((x), (short) (y + 1));
final IGuiMovable m4 = grid.getMovable((short) (x + 1), (short) (y + 2));
if (m1 != null && canSelectPlayer(m1.getPlayerId())) {
return m1;
} else if (m2 != null && canSelectPlayer(m2.getPlayerId())) {
return m2;
} else if (m3 != null && canSelectPlayer(m3.getPlayerId())) {
return m3;
} else if (m4 != null && canSelectPlayer(m4.getPlayerId())) {
return m4;
} else {
return null;
}
}
private void selectPointType(PointAction action) {
final ShortPoint2D actionPosition = action.getPosition();
final IGuiMovable selectedMovable = getSelectableMovable(actionPosition.x, actionPosition.y);
if (selectedMovable == null) { // nothing found at the location
setSelection(new SelectionSet());
return;
}
EMovableType selectedType = selectedMovable.getMovableType();
byte selectedPlayerId = selectedMovable.getPlayerId();
Set<EMovableType> selectableTypes;
if (selectedType.isSwordsman()) {
selectableTypes = EMovableType.swordsmen;
} else if (selectedType.isPikeman()) {
selectableTypes = EMovableType.pikemen;
} else if (selectedType.isBowman()) {
selectableTypes = EMovableType.bowmen;
} else {
selectableTypes = EnumSet.of(selectedType);
}
final List<ISelectable> selected = new LinkedList<>();
MapCircle.stream(actionPosition, SELECT_BY_TYPE_RADIUS).forEach((x, y) -> {
final IGuiMovable movable = grid.getMovable(x, y);
if (movable != null && selectableTypes.contains(movable.getMovableType()) && selectedPlayerId == movable.getPlayerId()) {
selected.add(movable);
}
});
setSelection(new SelectionSet(selected));
}
/**
* Sets the selection.
*
* @param selection
* The selected items. Not null!
*/
private void setSelection(SelectionSet selection) {
currentSelection.setSelected(false);
selection.setSelected(true);
connector.setSelection(selection);
currentSelection = selection;
}
@Override
public void refreshSelection() {
if (!currentSelection.isEmpty()) {
SelectionSet newSelection = new SelectionSet(currentSelection.stream()
.filter(ISelectable::isSelected)
.filter(selected -> canSelectPlayer(selected.getPlayerId()))
.collect(Collectors.toList()));
if (currentSelection.getSize() != newSelection.getSize() || currentSelection.getSelectionType() != newSelection.getSelectionType()) {
setSelection(newSelection);
}
}
}
@Override
public UIState getUIState() {
return connector.getUIState();
}
/**
* Shuts down used threads.
*/
public void stop() {
constructionMarksCalculator.cancel();
connector.removeListener(this);
refreshSelectionTimer.cancel();
}
}