Added NPC menus

Added NPC menus with submenu support
Fixed issue with cursor movement stopping when touching certain actors
Changed some debug fonts to use consolas12
Changed MapListener implementation to work using client updates instead of InputListener
Fixed MapListener input flow with menus and entity targeting
Introduced Entity.getLabelOffset to replace setting label position manually per implementation
This commit is contained in:
Collin Smith 2019-03-03 02:11:43 -08:00
parent 162862771c
commit 008730d2c9
9 changed files with 259 additions and 53 deletions

View File

@ -3,6 +3,8 @@ package gdx.diablo.ai;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import org.apache.commons.lang3.ArrayUtils;
@ -10,20 +12,25 @@ import gdx.diablo.Diablo;
import gdx.diablo.entity.Monster;
import gdx.diablo.map.DS1;
import gdx.diablo.screen.GameScreen;
import gdx.diablo.widget.NpcMenu;
public class Npc extends AI {
private static final String TAG = "Npc";
// data\\global\\ui\\MENU\\boxpieces.DC6
int targetId = ArrayUtils.INDEX_NOT_FOUND;
float actionTimer = 0;
boolean actionPerformed = false;
NpcMenu menu;
int activeAudio;
public Npc(Monster entity) {
super(entity);
}
@Override
public void interact(GameScreen gameScreen) {
public void interact(final GameScreen gameScreen) {
String name = entity.getName().toLowerCase();
String id = name + "_greeting_1";
int index = Diablo.audio.play(id, false);
@ -32,10 +39,53 @@ public class Npc extends AI {
Diablo.audio.play(id, false);
}
actionTimer = 4;
actionTimer = Float.POSITIVE_INFINITY;
actionPerformed = false;
entity.target().set(entity.position());
entity.lookAt(gameScreen.player);
entity.update(0);
if (menu == null) {
menu = new NpcMenu(entity, gameScreen, entity.getName());
menu
// Talk
.addItem(3381, new NpcMenu(3381)
// Introduction
.addItem(3399, new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
String name = entity.getName().toLowerCase();
String id = name + "_act1_intro";
Diablo.audio.play(id, false);
}
})
// Gossip
.addItem(3395, new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
String name = entity.getName().toLowerCase();
String id = name + "_act1_gossip_1";
Diablo.audio.play(id, false);
}
})
.addCancel(new NpcMenu.CancellationListener() {
@Override
public void onCancelled() {
// TODO: stop audio
}
})
.build())
.addCancel(new NpcMenu.CancellationListener() {
@Override
public void onCancelled() {
actionTimer = 4;
entity.target().setZero();
}
})
.build();
}
gameScreen.setMenu(menu, entity);
}
public void update(float delta) {

View File

@ -534,7 +534,11 @@ public class Entity {
}
protected void updateLabel(Label label, float x, float y) {
label.setPosition(x, y + animation.getMinHeight() + label.getHeight() / 2, Align.center);
label.setPosition(x, y + getLabelOffset() + label.getHeight() / 2, Align.center);
}
public float getLabelOffset() {
return animation.getMinHeight();
}
/*

View File

@ -20,7 +20,6 @@ import gdx.diablo.map.DS1;
import gdx.diablo.map.DT1.Tile;
import gdx.diablo.map.Map;
import gdx.diablo.screen.GameScreen;
import gdx.diablo.widget.Label;
public class Monster extends Entity {
private static final String TAG = "Monster";
@ -123,7 +122,7 @@ public class Monster extends Entity {
point = path.points[i];
p1x = +(point.x * Tile.SUBTILE_WIDTH50) - (point.y * Tile.SUBTILE_WIDTH50);
p1y = -(point.x * Tile.SUBTILE_HEIGHT50) - (point.y * Tile.SUBTILE_HEIGHT50);
Diablo.fonts.consolas16.draw(batch, Integer.toString(point.action), p1x, p1y - BOX_SIZE, 0, Align.center, false);
Diablo.fonts.consolas12.draw(batch, Integer.toString(point.action), p1x, p1y - BOX_SIZE, 0, Align.center, false);
}
batch.end();
batch.setShader(Diablo.shader);
@ -131,8 +130,8 @@ public class Monster extends Entity {
}
@Override
protected void updateLabel(Label label, float x, float y) {
label.setPosition(x, y + monstats2.pixHeight + label.getHeight() / 2, Align.center);
public float getLabelOffset() {
return monstats2.pixHeight;
}
@Override

View File

@ -4,7 +4,6 @@ import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Align;
import gdx.diablo.Diablo;
import gdx.diablo.codec.excel.Objects;
@ -12,7 +11,6 @@ import gdx.diablo.graphics.PaletteIndexedBatch;
import gdx.diablo.map.DS1;
import gdx.diablo.map.Map;
import gdx.diablo.screen.GameScreen;
import gdx.diablo.widget.Label;
public class StaticEntity extends Entity {
private static final String TAG = "StaticEntity";
@ -76,8 +74,8 @@ public class StaticEntity extends Entity {
}
@Override
protected void updateLabel(Label label, float x, float y) {
label.setPosition(x, y - base.NameOffset + label.getHeight() / 2, Align.center);
public float getLabelOffset() {
return -base.NameOffset;
}
@Override

View File

@ -2,7 +2,6 @@ package gdx.diablo.map;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.math.GridPoint2;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
@ -11,7 +10,7 @@ import com.badlogic.gdx.utils.Array;
import gdx.diablo.entity.Entity;
import gdx.diablo.screen.GameScreen;
public class MapListener extends InputAdapter {
public class MapListener {
private final Vector2 tmpVec2 = new Vector2();
private final Vector3 tmpVec3 = new Vector3();
private final GridPoint2 tmpVec2i = new GridPoint2();
@ -27,35 +26,30 @@ public class MapListener extends InputAdapter {
this.mapRenderer = mapRenderer;
}
@Override
public boolean mouseMoved(int x, int y) {
mapRenderer.unproject(x, y, tmpVec2);
// TODO: assert only 1 entity can be selected at once, once found, deselect other and set new and return
private void updateLabel(Vector2 position) {
gameScreen.clearLabels();
for (Map.Zone zone : map.zones) {
for (Entity entity : zone.entities) {
entity.over = entity.contains(tmpVec2);
entity.over = entity.contains(position);
if (entity.over) gameScreen.addLabel(entity.getLabel());
}
}
return false;
}
@Override
public boolean touchDown(int x, int y, int pointer, int button) {
setTarget(null);
mapRenderer.unproject(x, y, tmpVec2);
private boolean touchDown() {
//setTarget(null);
for (Map.Zone zone : new Array.ArrayIterator<>(map.zones)) {
for (Entity entity : zone.entities) {
if (entity.over) {
if (entity.position().dst(gameScreen.player.position()) <= entity.getInteractRange()) {
setTarget(null);
entity.interact(gameScreen);
return true;
} else {
setTarget(entity);
return true;
}
return true;
}
}
}
@ -64,23 +58,25 @@ public class MapListener extends InputAdapter {
}
public void update() {
if (target != null) {
if (target.position().dst(gameScreen.player.position()) <= target.getInteractRange()) {
Entity entity = target;
setTarget(null);
entity.interact(gameScreen);
mapRenderer.unproject(tmpVec2.set(Gdx.input.getX(), Gdx.input.getY()));
updateLabel(tmpVec2);
if (Gdx.input.isButtonPressed(Input.Buttons.LEFT)) {
if (gameScreen.getMenu() != null) gameScreen.setMenu(null, null);
boolean touched = touchDown();
if (!touched) {
mapRenderer.coords(tmpVec2.x, tmpVec2.y, tmpVec2i);
tmpVec3.set(tmpVec2i.x, tmpVec2i.y, 0);
gameScreen.player.setPath(map, tmpVec3);
}
} else {
if (target != null) {
if (target.position().dst(gameScreen.player.position()) <= target.getInteractRange()) {
Entity entity = target;
setTarget(null);
entity.interact(gameScreen);
}
}
return;
}
if (!Gdx.input.isButtonPressed(Input.Buttons.LEFT)) return;
int x = Gdx.input.getX();
int y = Gdx.input.getY();
mapRenderer.unproject(x, y, tmpVec2);
mapRenderer.coords(tmpVec2.x, tmpVec2.y, tmpVec2i);
tmpVec3.set(tmpVec2i.x, tmpVec2i.y, 0);
gameScreen.player.setPath(map, tmpVec3);
}
private void setTarget(Entity entity) {

View File

@ -1032,12 +1032,12 @@ public class MapRenderer {
batch.begin();
batch.setShader(null);
BitmapFont font = Diablo.fonts.consolas16;
BitmapFont font = Diablo.fonts.consolas12;
String str = String.format(String.format("%s%n%08x", Map.ID.getName(tile.cell.id), tile.cell.value));
GlyphLayout layout = new GlyphLayout(font, str, 0, str.length(), font.getColor(), 0, Align.center, false, null);
font.draw(batch, layout,
px + Tile.WIDTH50,
py + Tile.HEIGHT50 + layout.height / 2);
py + Tile.HEIGHT50 + font.getLineHeight() / 4);
batch.end();
batch.setShader(Diablo.shader);

View File

@ -37,12 +37,13 @@ import java.io.PrintWriter;
import gdx.diablo.Diablo;
import gdx.diablo.Keys;
import gdx.diablo.entity.Entity;
import gdx.diablo.entity.Player;
import gdx.diablo.graphics.PaletteIndexedBatch;
import gdx.diablo.graphics.PaletteIndexedColorDrawable;
import gdx.diablo.key.MappedKey;
import gdx.diablo.key.MappedKeyStateAdapter;
import gdx.diablo.map.DT1;
import gdx.diablo.map.DT1.Tile;
import gdx.diablo.map.Map;
import gdx.diablo.map.MapListener;
import gdx.diablo.map.MapLoader;
@ -61,6 +62,7 @@ import gdx.diablo.server.MoveTo;
import gdx.diablo.server.Packet;
import gdx.diablo.server.Packets;
import gdx.diablo.server.PipedSocket;
import gdx.diablo.widget.NpcMenu;
import gdx.diablo.widget.TextArea;
public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable {
@ -93,6 +95,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
MapListener mapListener;
InputProcessor inputProcessorTest;
final Array<Actor> labels = new Array<>();
Actor menu;
public TextArea input;
TextArea output;
@ -381,7 +384,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
//player.setPath(map, new Vector3(x, y, 0).add(player.position()), 3);
Vector2 position = new Vector2(player.position().x, player.position().y);
Vector2 target = new Vector2(x, y).scl(DT1.Tile.WIDTH).add(mapRenderer.project(position.x, position.y, new Vector2()));
Vector2 target = new Vector2(x, y).scl(Tile.WIDTH).add(mapRenderer.project(position.x, position.y, new Vector2()));
GridPoint2 coords = mapRenderer.coords(target.x, target.y, new GridPoint2());
target.set(coords.x, coords.y);
Ray<Vector2> ray = new Ray<>(position, target);
@ -421,9 +424,10 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
//System.out.println("hit " + hit + "; " + collision.point + "; " + collision.normal);
}
} else {
// TODO: this requires a bit more thorough checking - touchable flags need to be checked on each panel and unset on output/input areas
stage.screenToStageCoordinates(tmpVec2.set(Gdx.input.getX(), Gdx.input.getY()));
Actor hit = stage.hit(tmpVec2.x, tmpVec2.y, false);
if (hit == null) mapListener.update();
if (hit == null || hit == output || hit == input) mapListener.update();
//if (Gdx.input.isButtonPressed(Input.Buttons.LEFT)) {
// GridPoint2 coords = mapRenderer.coords();
// player.setPath(map, new Vector3(coords.x, coords.y, 0));
@ -465,12 +469,12 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
}
b.end();*/
layoutLabels();
//mapRenderer.prepare(b);
b.begin();
for (Actor label : labels) label.draw(b, 1);
b.end();
b.setProjectionMatrix(Diablo.viewport.getCamera().combined);
if (menu == null && !labels.isEmpty()) {
layoutLabels();
b.begin();
for (Actor label : labels) label.draw(b, 1);
b.end();
}
}
@Override
@ -500,7 +504,6 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
Keys.Enter.addStateListener(mappedKeyStateListener);
Diablo.input.addProcessor(stage);
Diablo.input.addProcessor(inputProcessorTest);
Diablo.input.addProcessor(mapListener);
if (socket != null && socket.isConnected()) {
Gdx.app.log(TAG, "connecting to " + socket.getRemoteAddress() + "...");
@ -545,7 +548,6 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
Keys.Enter.removeStateListener(mappedKeyStateListener);
Diablo.input.removeProcessor(stage);
Diablo.input.removeProcessor(inputProcessorTest);
Diablo.input.removeProcessor(mapListener);
//updateTask.cancel();
}
@ -593,4 +595,34 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
label.setPosition(tmp.x, tmp.y);
}
}
public Actor getMenu() {
return menu;
}
public void setMenu(Actor menu, Entity owner) {
if (this.menu != menu) {
if (this.menu != null) {
if (this.menu instanceof NpcMenu) ((NpcMenu) this.menu).cancel();
stage.getRoot().removeActor(this.menu);
}
this.menu = menu;
if (menu != null && owner != null) {
stage.addActor(menu);
Vector3 position = owner.position();
float x = +(position.x * Tile.SUBTILE_WIDTH50) - (position.y * Tile.SUBTILE_WIDTH50);
float y = -(position.x * Tile.SUBTILE_HEIGHT50) - (position.y * Tile.SUBTILE_HEIGHT50);
menu.setPosition(x, y + owner.getLabelOffset(), Align.center | Align.bottom);
Vector2 tmp = new Vector2();
tmp.x = menu.getX();
tmp.y = menu.getY();
mapRenderer.projectScaled(tmp);
tmp.x = MathUtils.clamp(tmp.x, 0, Diablo.VIRTUAL_WIDTH - menu.getWidth());
tmp.y = MathUtils.clamp(tmp.y, 0, Diablo.VIRTUAL_HEIGHT - menu.getHeight());
menu.setPosition(tmp.x, tmp.y);
}
}
}
}

View File

@ -0,0 +1,42 @@
package gdx.diablo.widget;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import gdx.diablo.Diablo;
import gdx.diablo.graphics.PaletteIndexedBatch;
public class LabelButton extends Label {
ClickListener clickListener;
public LabelButton(int id, BitmapFont font) {
super(id, font);
init();
}
public LabelButton(int id, BitmapFont font, Color color) {
super(id, font, color);
init();
}
public LabelButton(String text, BitmapFont font) {
super(text, font);
init();
}
public LabelButton(BitmapFont font) {
super(font);
init();
}
private void init() {
addListener(clickListener = new ClickListener());
}
@Override
public void draw(PaletteIndexedBatch batch, float a) {
setColor(clickListener.isOver() ? Diablo.colors.blue : Diablo.colors.white);
super.draw(batch, a);
}
}

View File

@ -0,0 +1,85 @@
package gdx.diablo.widget;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import gdx.diablo.Diablo;
import gdx.diablo.entity.Entity;
import gdx.diablo.graphics.PaletteIndexedColorDrawable;
import gdx.diablo.screen.GameScreen;
public class NpcMenu extends Table {
private static final float PADDING = 8;
private static final float SPACING = 2;
Entity owner;
NpcMenu parent;
GameScreen gameScreen;
CancellationListener cancellationListener;
public NpcMenu(Entity owner, GameScreen gameScreen, String header) {
parent = null;
this.owner = owner;
this.gameScreen = gameScreen;
setBackground(new PaletteIndexedColorDrawable(Diablo.colors.modal50));
pad(PADDING);
add(new Label(header, Diablo.fonts.font16) {{
setColor(Diablo.colors.gold);
}}).space(SPACING).row();
}
public NpcMenu(int id) {
setBackground(new PaletteIndexedColorDrawable(Diablo.colors.modal50));
pad(PADDING);
add(new Label(id, Diablo.fonts.font16, Diablo.colors.gold)).space(SPACING).row();
}
public NpcMenu addItem(int id, ClickListener clickListener) {
LabelButton button = new LabelButton(id, Diablo.fonts.font16);
button.addListener(clickListener);
add(button).space(SPACING).row();
return this;
}
public NpcMenu addItem(int id, final NpcMenu menu) {
menu.parent = this;
menu.owner = owner;
menu.gameScreen = gameScreen;
addItem(id, new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
gameScreen.setMenu(menu, owner);
}
});
return this;
}
public NpcMenu addCancel(CancellationListener cancellationListener) {
this.cancellationListener = cancellationListener;
addItem(3400, new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
cancel();
gameScreen.setMenu(parent, owner);
}
});
return this;
}
public NpcMenu build() {
pack();
return this;
}
public void cancel() {
if (cancellationListener != null) cancellationListener.onCancelled();
}
public interface CancellationListener {
void onCancelled();
}
}