package net.alcuria.umbracraft.editor.widget;
import net.alcuria.umbracraft.Config;
import net.alcuria.umbracraft.definitions.anim.AnimationCollectionDefinition;
import net.alcuria.umbracraft.definitions.anim.AnimationDefinition;
import net.alcuria.umbracraft.definitions.anim.AnimationFrameDefinition;
import net.alcuria.umbracraft.definitions.anim.AnimationGroupDefinition;
import net.alcuria.umbracraft.definitions.component.ComponentDefinition;
import net.alcuria.umbracraft.definitions.component.ComponentDefinition.AnimationCollectionComponentDefinition;
import net.alcuria.umbracraft.definitions.component.ComponentDefinition.AnimationComponentDefinition;
import net.alcuria.umbracraft.definitions.component.ComponentDefinition.ScriptComponentDefinition;
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.npc.ScriptDefinition;
import net.alcuria.umbracraft.editor.Drawables;
import net.alcuria.umbracraft.editor.Editor;
import net.alcuria.umbracraft.editor.widget.MapEditorWidget.EditMode;
import net.alcuria.umbracraft.util.MapUtils;
import net.alcuria.umbracraft.util.StringUtils;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
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.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.Array;
/** A representation of a single tile on the {@link MapEditorWidget} actor.
* Handles updating the {@link MapDefinition} when it is clicked, either by
* adjusting the altitude or adding an {@link EntityDefinition}.
* @author Andrew Keturi */
public class MapTileWidget extends Table {
static int selAlt = -1;
static int selX = -1;
static int selY = -1;
private static TextureRegion side, top, edge, outline;
private final MapDefinition definition;
private Image entityPreview;
private final int i, j;
private final Color overlay = new Color(0, 0, 0, 0.9f);
private final MapEditorWidget widget;
public MapTileWidget(int x, int y, final MapDefinition definition, final MapEditorWidget widget) {
i = x;
j = y;
this.widget = widget;
this.definition = definition;
// initialize static textures if needed
if (side == null) {
Texture skin = new Texture(Gdx.files.internal("editor/skin.png"));
side = new TextureRegion(skin, 4, 0, 1, 1);
outline = new TextureRegion(skin, 5, 0, 1, 1);
edge = new TextureRegion(skin, 2, 0, 1, 1);
top = new TextureRegion(skin, 3, 0, 1, 1);
}
updateEntityPreview();
setBackground(Drawables.get("blue"));
addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
super.clicked(event, x, y);
if (widget.getEditMode() == EditMode.ALTITUDE) {
// adjust altitude
if (Gdx.input.isKeyPressed(Keys.ALT_LEFT) || Gdx.input.isKeyPressed(Keys.CONTROL_LEFT)) {
MapTileWidget.this.definition.tiles.get(i).get(j).altitude--;
} else {
MapTileWidget.this.definition.tiles.get(i).get(j).altitude++;
}
MapTileWidget.this.definition.tiles.get(i).get(j).altitude = MathUtils.clamp(MapTileWidget.this.definition.tiles.get(i).get(j).altitude, 0, 10);
} else {
// add/edit an entity
widget.showEntityPopup(i, definition.getHeight() - j - 1);
}
}
@Override
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
super.enter(event, x, y, pointer, fromActor);
setBackground(Drawables.get("yellow"));
getColor().a = 0.5f;
//TODO: compare old selX and intermittently call entered
float oldSelX = selX;
float oldSelY = selY;
selX = i;
selY = j;
final float step = Math.max(Math.abs(oldSelX - selX), Math.abs(oldSelY) - selY);
float dX = (selX - oldSelX) / step;
float dY = (selY - oldSelY) / step;
while ((int) oldSelX != selX && (int) oldSelY != selY) {
oldSelX += dX;
oldSelY += dY;
updateTile((int) oldSelX, (int) oldSelY);
}
selAlt = definition.tiles.get(selX).get(selY).altitude;
}
@Override
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
super.exit(event, x, y, pointer, toActor);
setBackground(Drawables.get("blue"));
getColor().a = 1f;
}
});
setTouchable(Touchable.enabled);
}
@Override
public void act(float delta) {
super.act(delta);
if (entityPreview != null) {
entityPreview.act(delta);
}
if (widget.getEditMode() == EditMode.ALTITUDE && widget.isEntered()) {
updateTile(selX, selY);
}
}
private int alt(int i, int j) {
if (i < 0 || i >= definition.tiles.size || j < 0 || j >= definition.tiles.get(0).size) {
return 0;
}
return definition.tiles.get(i).get(j).altitude;
}
@Override
public void draw(Batch batch, float parentAlpha) {
super.draw(batch, parentAlpha);
int altitude = alt(i, j);
int type = type(i, j);
int overlayType = overlayType(i, j);
int y = definition.getHeight() - j - 1;
boolean isTeleport = (i == definition.eastX && y == definition.eastY) || //
(i == definition.westX && y == definition.westY) || //
(i == definition.southX && y == definition.southY) || //
(i == definition.northX && y == definition.northY);
// side
batch.draw(side, getX(), getY(), getWidth(), getWidth() * altitude);
// top
batch.draw(outline, getX(), getY() + altitude * getHeight(), getWidth(), getHeight());
if (isTeleport) {
batch.setColor(Color.DARK_GRAY);
}
if (type != 0) {
batch.setColor(MapUtils.getTerrainColor(type));
}
batch.draw(top, getX() + 1, getY() + altitude * getHeight() + 1, getWidth() - 2, getHeight() - 2);
if (overlayType != 0) {
batch.setColor(MapUtils.getTerrainColor(overlayType));
batch.draw(side, getX(), getY() + definition.overlayHeight * getHeight(), getWidth(), getHeight());
batch.draw(top, getX(), getY() + definition.overlayHeight * getHeight(), getWidth(), getHeight());
}
if (type != 0 || overlayType != 0 || isTeleport) {
batch.setColor(Color.WHITE);
}
// left edge
if (alt(i - 1, j) < alt(i, j)) {
batch.draw(edge, getX(), getY() + altitude * getHeight(), 2, getHeight());
}
// right edge
if (alt(i + 1, j) < alt(i, j)) {
batch.draw(edge, getX() + getWidth() - 2, getY() + altitude * getHeight(), 2, getHeight());
}
// bottom edge
if (alt(i, j + 1) < alt(i, j)) {
batch.draw(edge, getX(), getY() + altitude * getHeight(), getWidth(), 2);
}
// top edge
if (alt(i, j - 1) < alt(i, j)) {
batch.draw(edge, getX(), getY() + getHeight() + altitude * getHeight() - 2, getWidth(), 2);
}
// darken bottom clamps
if (y < definition.bottomClamp) {
batch.setColor(overlay);
batch.draw(top, getX(), getY(), getWidth(), getHeight());
batch.setColor(Color.WHITE);
}
}
public void drawEntity(Batch batch) {
int altitude = alt(i, j);
if (entityPreview != null) {
if (entityPreview instanceof AnimationPreview && ((AnimationPreview) entityPreview).getCurrentRegion() != null) {
final TextureRegion region = ((AnimationPreview) entityPreview).getCurrentRegion();
final int offset = (int) ((AnimationPreview) entityPreview).getOriginX();
batch.draw(region, widget.getActor(widget.getZoom()).getX() + getX(), widget.getActor(widget.getZoom()).getY() + (definition.getHeight() - j - 1) * Config.tileWidth * 2 / widget.getZoom() + altitude * getHeight(), region.getRegionWidth() * 2 / widget.getZoom(), region.getRegionHeight() * 2 / widget.getZoom());
} else {
entityPreview.setX(widget.getActor(widget.getZoom()).getX() + getX());
entityPreview.setY(widget.getActor(widget.getZoom()).getY() + (definition.getHeight() - j - 1) * Config.tileWidth * 2 / widget.getZoom() + altitude * getHeight());
entityPreview.setScale(2 * widget.getZoom());
entityPreview.draw(batch, 1);
}
}
}
private int overlayType(int i, int j) {
if (i < 0 || i >= definition.tiles.size || j < 0 || j >= definition.tiles.get(0).size) {
return 0;
}
return definition.tiles.get(i).get(j).overlayType;
}
private int type(int i, int j) {
if (i < 0 || i >= definition.tiles.size || j < 0 || j >= definition.tiles.get(0).size) {
return 0;
}
return definition.tiles.get(i).get(j).type;
}
/** This monolithic function is a nightmare but for now it works. It looks
* through the map's entity references, and if it finds an entity at this
* tile, it sees if that entity has an animation or something to render. If
* it has something to render, we initialize entityPreview and the map
* editor gets a nice indicator. */
public void updateEntityPreview() {
// set a preview image if applicable
// first iterate thru the references on this map
for (EntityReferenceDefinition reference : definition.entities) {
// checking if the reference's coordinates match this tile coordinate
if (reference.name != null && reference.x == i && definition.getHeight() - reference.y - 1 == j) {
// verify this entity exists in the db
EntityDefinition entity = Editor.db().entity(reference.name);
if (entity != null) {
// find an animation component for the preview image
for (ComponentDefinition componentDefinition : entity.components) {
if (componentDefinition instanceof ScriptComponentDefinition) {
final ScriptComponentDefinition scriptComponentDefinition = (ScriptComponentDefinition) componentDefinition;
final ScriptDefinition definition = Editor.db().script(scriptComponentDefinition.script);
if (StringUtils.isNotEmpty(definition.pages.get(0).animationCollection)) {
AnimationCollectionDefinition animDefinition = Editor.db().animCollection(definition.pages.get(0).animationCollection);
if (animDefinition != null) {
final AnimationGroupDefinition animGroup = Editor.db().animGroup(animDefinition.idle);
if (animGroup != null) {
final AnimationDefinition anim = Editor.db().anim(animGroup.down);
if (anim != null) {
entityPreview = new AnimationPreview(anim);
break;
}
}
}
} else if (StringUtils.isNotEmpty(definition.pages.get(0).animationGroup)) {
AnimationGroupDefinition animGroup = Editor.db().animGroup(definition.pages.get(0).animationGroup);
if (animGroup != null) {
final AnimationDefinition anim = Editor.db().anim(animGroup.down);
if (anim != null) {
entityPreview = new AnimationPreview(anim);
break;
}
}
} else if (StringUtils.isNotEmpty(definition.pages.get(0).animation)) {
AnimationDefinition anim = Editor.db().anim(definition.pages.get(0).animation);
if (anim != null) {
entityPreview = new AnimationPreview(anim);
break;
}
}
} else if (componentDefinition instanceof AnimationComponentDefinition) {
// create the definition from the component
final AnimationComponentDefinition animationComponentDefinition = (AnimationComponentDefinition) componentDefinition;
if (StringUtils.isNotEmpty(animationComponentDefinition.animationComponent)) {
AnimationDefinition animDefinition = Editor.db().anim(animationComponentDefinition.animationComponent);
if (animDefinition != null) {
entityPreview = new AnimationPreview(animDefinition);
break;
}
}
} else if (componentDefinition instanceof AnimationCollectionComponentDefinition) {
// create the definition from the component
AnimationCollectionDefinition animDefinition = Editor.db().animCollection(((AnimationCollectionComponentDefinition) componentDefinition).animationCollectionComponent);
if (animDefinition != null) {
if (animDefinition.template != null) {
AnimationFrameDefinition templateFrameDef = new AnimationFrameDefinition();
templateFrameDef.color = Color.WHITE;
templateFrameDef.duration = 10;
templateFrameDef.x = 1;
templateFrameDef.y = 2;
AnimationDefinition templateDef = new AnimationDefinition();
templateDef.filename = animDefinition.template;
templateDef.frames = new Array<AnimationFrameDefinition>();
templateDef.height = Editor.db().config().templateHeight;
templateDef.width = Editor.db().config().templateWidth;
templateDef.keepLast = true;
templateDef.loop = false;
templateDef.name = "Template";
templateDef.originX = Editor.db().config().templateWidth / 2;
templateDef.originY = 4;
templateDef.frames.add(templateFrameDef);
entityPreview = new AnimationPreview(templateDef);
} else {
final AnimationGroupDefinition animGroup = Editor.db().animGroup(animDefinition.idle);
if (animGroup != null) {
final AnimationDefinition anim = Editor.db().anim(animGroup.down);
if (anim != null) {
entityPreview = new AnimationPreview(anim);
break;
}
}
}
}
}
}
} else {
entityPreview = new Image(Drawables.get("entity"));
break;
}
}
}
}
private void updateTile(int x, int y) {
try {
// set the altitude with 0 - 9 number keys
for (int i = 7; i <= 16; i++) {
if (Gdx.input.isKeyPressed(i)) {
definition.tiles.get(x).get(y).altitude = i - 7;
}
}
// terrain setters
if (Gdx.input.isKeyPressed(Keys.Q)) {
definition.tiles.get(x).get(y).type = 0;
}
if (Gdx.input.isKeyPressed(Keys.W)) {
definition.tiles.get(x).get(y).type = 1;
}
if (Gdx.input.isKeyPressed(Keys.E)) {
definition.tiles.get(x).get(y).type = 2;
}
if (Gdx.input.isKeyPressed(Keys.R)) {
definition.tiles.get(x).get(y).type = 3;
}
if (Gdx.input.isKeyPressed(Keys.T)) {
definition.tiles.get(x).get(y).type = 4;
}
// stair setter
if (Gdx.input.isKeyPressed(Keys.S)) {
definition.tiles.get(x).get(y).type = 5;
}
// tree wall setter
if (Gdx.input.isKeyPressed(Keys.A)) {
if (definition.tiles.get(x).get(y).type != 6) {
if (Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Keys.CONTROL_RIGHT)) {
definition.tiles.get(x).get(y).altitude += 4;
}
definition.tiles.get(x).get(y).type = 6;
}
}
// upper layer
if (Gdx.input.isKeyPressed(Keys.Z)) {
definition.tiles.get(x).get(y).overlayType = 0;
}
// overlay
if (Gdx.input.isKeyPressed(Keys.X)) {
definition.tiles.get(x).get(y).overlayType = 1;
}
// obstacle 1
if (Gdx.input.isKeyPressed(Keys.C)) {
definition.tiles.get(x).get(y).overlayType = 2;
}
// obstacle 2
if (Gdx.input.isKeyPressed(Keys.V)) {
definition.tiles.get(x).get(y).overlayType = 3;
}
// obstacle 3
if (Gdx.input.isKeyPressed(Keys.B)) {
definition.tiles.get(x).get(y).overlayType = 4;
}
// obstacle 4
if (Gdx.input.isKeyPressed(Keys.N)) {
definition.tiles.get(x).get(y).overlayType = 5;
}
} catch (Exception e) {
System.err.println("Out of bounds " + selX + ", " + selY);
}
}
}