From 008730d2c911f04f8c95b95a6f53259242385610 Mon Sep 17 00:00:00 2001 From: Collin Smith Date: Sun, 3 Mar 2019 02:11:43 -0800 Subject: [PATCH] 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 --- core/src/gdx/diablo/ai/Npc.java | 54 ++++++++++++- core/src/gdx/diablo/entity/Entity.java | 6 +- core/src/gdx/diablo/entity/Monster.java | 7 +- core/src/gdx/diablo/entity/StaticEntity.java | 6 +- core/src/gdx/diablo/map/MapListener.java | 54 ++++++------- core/src/gdx/diablo/map/MapRenderer.java | 4 +- core/src/gdx/diablo/screen/GameScreen.java | 54 ++++++++++--- core/src/gdx/diablo/widget/LabelButton.java | 42 ++++++++++ core/src/gdx/diablo/widget/NpcMenu.java | 85 ++++++++++++++++++++ 9 files changed, 259 insertions(+), 53 deletions(-) create mode 100644 core/src/gdx/diablo/widget/LabelButton.java create mode 100644 core/src/gdx/diablo/widget/NpcMenu.java diff --git a/core/src/gdx/diablo/ai/Npc.java b/core/src/gdx/diablo/ai/Npc.java index e1147bc3..1e40a56b 100644 --- a/core/src/gdx/diablo/ai/Npc.java +++ b/core/src/gdx/diablo/ai/Npc.java @@ -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) { diff --git a/core/src/gdx/diablo/entity/Entity.java b/core/src/gdx/diablo/entity/Entity.java index 6a92319e..898e4c0c 100644 --- a/core/src/gdx/diablo/entity/Entity.java +++ b/core/src/gdx/diablo/entity/Entity.java @@ -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(); } /* diff --git a/core/src/gdx/diablo/entity/Monster.java b/core/src/gdx/diablo/entity/Monster.java index a183283e..f37d9c8b 100644 --- a/core/src/gdx/diablo/entity/Monster.java +++ b/core/src/gdx/diablo/entity/Monster.java @@ -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 diff --git a/core/src/gdx/diablo/entity/StaticEntity.java b/core/src/gdx/diablo/entity/StaticEntity.java index ec45e107..34d07334 100644 --- a/core/src/gdx/diablo/entity/StaticEntity.java +++ b/core/src/gdx/diablo/entity/StaticEntity.java @@ -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 diff --git a/core/src/gdx/diablo/map/MapListener.java b/core/src/gdx/diablo/map/MapListener.java index 4053b524..2b214a21 100644 --- a/core/src/gdx/diablo/map/MapListener.java +++ b/core/src/gdx/diablo/map/MapListener.java @@ -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) { diff --git a/core/src/gdx/diablo/map/MapRenderer.java b/core/src/gdx/diablo/map/MapRenderer.java index 5755746d..821fa180 100644 --- a/core/src/gdx/diablo/map/MapRenderer.java +++ b/core/src/gdx/diablo/map/MapRenderer.java @@ -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); diff --git a/core/src/gdx/diablo/screen/GameScreen.java b/core/src/gdx/diablo/screen/GameScreen.java index 77843b83..b62ff4f2 100644 --- a/core/src/gdx/diablo/screen/GameScreen.java +++ b/core/src/gdx/diablo/screen/GameScreen.java @@ -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 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 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); + } + } + } } diff --git a/core/src/gdx/diablo/widget/LabelButton.java b/core/src/gdx/diablo/widget/LabelButton.java new file mode 100644 index 00000000..fec2eca6 --- /dev/null +++ b/core/src/gdx/diablo/widget/LabelButton.java @@ -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); + } +} diff --git a/core/src/gdx/diablo/widget/NpcMenu.java b/core/src/gdx/diablo/widget/NpcMenu.java new file mode 100644 index 00000000..b7fd1744 --- /dev/null +++ b/core/src/gdx/diablo/widget/NpcMenu.java @@ -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(); + } +}