package net.alcuria.umbracraft.editor.widget; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import net.alcuria.umbracraft.definitions.ListDefinition; import net.alcuria.umbracraft.definitions.entity.EntityDefinition; import net.alcuria.umbracraft.definitions.map.EntityReferenceDefinition; import net.alcuria.umbracraft.definitions.map.MapDefinition; import net.alcuria.umbracraft.definitions.map.MapTileDefinition; import net.alcuria.umbracraft.editor.Drawables; import net.alcuria.umbracraft.editor.modules.MapListModule; import net.alcuria.umbracraft.editor.modules.Module.PopulateConfig; import net.alcuria.umbracraft.listeners.Listener; import net.dermetfan.utils.Pair; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Stack; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Json; import com.badlogic.gdx.utils.ObjectMap; import com.kotcrab.vis.ui.widget.VisLabel; /** A widget to display a map with options to edit it based on the * {@link EditMode}. * @author Andrew Keturi */ public class MapEditorWidget { /** Defines different edit modes for the {@link MapEditorWidget}. For * example, in {@link EditMode#ALTITUDE}, clicking adjusts the altitude. * @author Andrew Keturi */ public static enum EditMode { ALTITUDE, ENTITY; @Override public String toString() { switch (this) { case ALTITUDE: return "Change Altitude"; case ENTITY: return "Edit Entities"; default: return "No EditMode"; } }; } private static EditMode editMode = EditMode.ALTITUDE; private static Pair<Vector2, MapTileDefinition> tile(final int x, final int y, final MapTileDefinition tile) { return new Pair<Vector2, MapTileDefinition>(new Vector2(x, y), tile); } private Actor actor; private Label coordinates; private boolean entered; private Listener listener; private final MapListModule module; private Table popupTable, helpTable, mapTable; private final Array<MapTileWidget> tiles = new Array<MapTileWidget>(); private int zoom; public MapEditorWidget(MapListModule module) { this.module = module; } private Listener closeListener(final EntityReferenceDefinition entity) { return new Listener() { @Override public void invoke() { popupTable.setVisible(false); if (entity == null || entity.name.length() < 1) { module.getDefinition().entities.removeValue(entity, true); } else { module.refreshMap(); } mapTable.setTouchable(Touchable.enabled); } }; } private Listener deleteListener(final EntityReferenceDefinition entity) { return new Listener() { @Override public void invoke() { popupTable.setVisible(false); module.getDefinition().entities.removeValue(entity, true); mapTable.setTouchable(Touchable.enabled); module.refreshMap(); } }; } /** Fills tiles up to +1/-1 altitude * @param x the x tile coordinate * @param y the y tile coordinate * @param increase whether to increase or decrease the fill level */ private void fill(int x, int y, boolean increase) { final MapDefinition map = module.getDefinition(); final MapTileDefinition tile = map.getTileDefinition(x, y); final int currentAltitude = tile.altitude; final int targetAltitude = increase ? currentAltitude + 1 : currentAltitude - 1; final List<Pair<Vector2, MapTileDefinition>> tilesToFill = new ArrayList<Pair<Vector2, MapTileDefinition>>(); final Set<MapTileDefinition> tilesInList = new HashSet<MapTileDefinition>(); tilesToFill.add(tile(x, y, tile)); tilesInList.add(tile); while (!tilesToFill.isEmpty()) { final Pair<Vector2, MapTileDefinition> currentTile = tilesToFill.remove(tilesToFill.size() - 1); final MapTileDefinition currentMapDefinition = currentTile.getValue(); tilesInList.remove(currentMapDefinition); if (currentMapDefinition.altitude != currentAltitude) { continue; } currentMapDefinition.altitude = targetAltitude; final int currentX = (int) currentTile.getKey().x; final int currentY = (int) currentTile.getKey().y; final MapTileDefinition left = map.getTileDefinition(currentX - 1, currentY); if (left != null && !tilesInList.contains(left)) { tilesToFill.add(tile(currentX - 1, currentY, left)); tilesInList.add(left); } final MapTileDefinition right = map.getTileDefinition(currentX + 1, currentY); if (right != null && !tilesInList.contains(right)) { tilesToFill.add(tile(currentX + 1, currentY, right)); tilesInList.add(right); } final MapTileDefinition top = map.getTileDefinition(currentX, currentY + 1); if (top != null && !tilesInList.contains(top)) { tilesToFill.add(tile(currentX, currentY + 1, top)); tilesInList.add(top); } final MapTileDefinition bottom = map.getTileDefinition(currentX, currentY - 1); if (bottom != null && !tilesInList.contains(bottom)) { tilesToFill.add(tile(currentX, currentY - 1, bottom)); tilesInList.add(bottom); } } } /** @param zoom the amount to zoom * @return a new map widget, consisting of several {@link MapTileWidget} * classes to represent the current {@link MapDefinition}. */ public Actor getActor(final int zoom) { if (actor == null) { this.zoom = zoom; tiles.clear(); actor = new Stack() { { add(mapTable = new Table() { { for (int j = 0; j < module.getDefinition().getHeight(); j++) { Table row = new Table(); for (int i = 0; i < module.getDefinition().getWidth(); i++) { final MapTileWidget widget = new MapTileWidget(i, j, module.getDefinition(), MapEditorWidget.this); row.add(widget).size(32 / zoom).pad(0); tiles.add(widget); } add(row).row(); } setClip(true); addListener(new ClickListener() { @Override public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) { entered = true; }; @Override public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) { entered = false; }; }); } @Override public void act(float delta) { super.act(delta); if (MapTileWidget.selX >= 0 && MapTileWidget.selY >= 0 && editMode == EditMode.ALTITUDE) { if (Gdx.input.isKeyJustPressed(Keys.F)) { if (Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Keys.CONTROL_RIGHT)) { fill(MapTileWidget.selX, MapTileWidget.selY, false); } else { fill(MapTileWidget.selX, MapTileWidget.selY, true); } module.getDefinition().resetFilled(); } else if (Gdx.input.isKeyJustPressed(Keys.LEFT)) { module.getDefinition().westX = MapTileWidget.selX; module.getDefinition().westY = module.getDefinition().getHeight() - MapTileWidget.selY - 1; updateTeleports(); } else if (Gdx.input.isKeyJustPressed(Keys.RIGHT)) { module.getDefinition().eastX = MapTileWidget.selX; module.getDefinition().eastY = module.getDefinition().getHeight() - MapTileWidget.selY - 1; updateTeleports(); } else if (Gdx.input.isKeyJustPressed(Keys.UP)) { module.getDefinition().northX = MapTileWidget.selX; module.getDefinition().northY = module.getDefinition().getHeight() - MapTileWidget.selY - 1; updateTeleports(); } else if (Gdx.input.isKeyJustPressed(Keys.DOWN)) { module.getDefinition().southX = MapTileWidget.selX; module.getDefinition().southY = module.getDefinition().getHeight() - MapTileWidget.selY - 1; updateTeleports(); } } } }); add(new Table() { { add(helpTable = new Table()); helpTable.setBackground(Drawables.get("black")); helpTable.add(new VisLabel("0-9: Set Altitude")).row(); helpTable.add(new VisLabel("Q,W,E,R,T,S: Set Terrain Type")).row(); helpTable.add(new VisLabel("A: Set Tree Wall (Q Reverts)")).row(); helpTable.add(new VisLabel("Z,X,C,V,B,N: Set Upper Terrain Type")).row(); helpTable.add(new VisLabel("F: Fill | Ctrl + F: Dig ")).row(); helpTable.add(new VisLabel("Arrows: Set Teleport")).row(); WidgetUtils.divider(helpTable, "yellow"); helpTable.add(coordinates = new VisLabel()).row(); helpTable.setTouchable(Touchable.disabled); } @Override public void act(float delta) { super.act(delta); helpTable.setVisible(Gdx.input.isKeyPressed(Keys.F2)); //coordinates.setText(String.format("(%d, %d)", MapTileWidget.selX, module.getDefinition().getHeight() - MapTileWidget.selY)); helpTable.setPosition(Gdx.input.getX() - 600, Gdx.graphics.getHeight() - Gdx.input.getY()); } }); add(new Table() { { add(popupTable = new Table()).size(300); popupTable.setVisible(false); } }); } @Override public void draw(Batch batch, float parentAlpha) { super.draw(batch, parentAlpha); batch.setColor(1, 1, 1, popupTable.isVisible() ? 0.2f : 1f); for (int i = 0; i < tiles.size; i++) { tiles.get(i).drawEntity(batch); } batch.setColor(Color.WHITE); } }; } return actor; } /** @return the current {@link EditMode} */ public EditMode getEditMode() { return editMode; } public int getZoom() { return zoom; } /** @return <code>true</code> if the mouse has entered the editor widget. */ public boolean isEntered() { return entered; } /** @return the {@link PopulateConfig} for showing the entity popup */ private PopulateConfig populateConfig() { return new PopulateConfig() { { suggestions = new ObjectMap<String, Array<String>>(); suggestions.put("name", new Array<String>() { { final FileHandle handle = Gdx.files.external("umbracraft/entities.json"); if (handle.exists()) { ObjectMap<String, EntityDefinition> entities = new Json().fromJson(ListDefinition.class, handle).items(); for (EntityDefinition entity : entities.values()) { add(entity.name); } } } }); cols = 1; } }; } /** Sets the edit mode, which dictates the action to take when a tile is * clicked. * @param editMode the {@link EditMode} */ public void setEditMode(EditMode editMode) { MapEditorWidget.editMode = editMode; } /** Sets a listener to be invoked when a teleport tile is placed * @param listener the {@link Listener} */ public void setTeleportChangeListener(Listener listener) { this.listener = listener; } /** Shows an entity popup for the tile at coordinates i,j * @param i * @param j */ public void showEntityPopup(int i, int j) { popupTable.setVisible(true); mapTable.setTouchable(Touchable.disabled); popupTable.clear(); popupTable.setBackground(Drawables.get("black")); EntityReferenceDefinition entity = module.getDefinition().findEntity(i, j); if (entity == null) { // populate with new data entity = new EntityReferenceDefinition(); entity.x = i; entity.y = j; entity.name = ""; module.getDefinition().entities.add(entity); } WidgetUtils.popupTitle(popupTable, "Add/Remove Entity " + entity.name, closeListener(entity)); module.populate(popupTable, EntityReferenceDefinition.class, entity, populateConfig()); popupTable.row(); final Table buttonTable = new Table(); buttonTable.add(WidgetUtils.button("Create", closeListener(entity))); buttonTable.add(WidgetUtils.button("Delete", deleteListener(entity))); popupTable.add(buttonTable).expandX().fill(); } private void updateTeleports() { if (listener != null) { listener.invoke(); } } }