diff --git a/core/src/gdx/diablo/ai/AI.java b/core/src/gdx/diablo/ai/AI.java new file mode 100644 index 00000000..ad0cb225 --- /dev/null +++ b/core/src/gdx/diablo/ai/AI.java @@ -0,0 +1,14 @@ +package gdx.diablo.ai; + +import gdx.diablo.entity.Monster; + +public abstract class AI { + + protected Monster entity; + + public AI(Monster entity) { + this.entity = entity; + } + + public void update(float delta) {} +} diff --git a/core/src/gdx/diablo/ai/Npc.java b/core/src/gdx/diablo/ai/Npc.java new file mode 100644 index 00000000..188798c1 --- /dev/null +++ b/core/src/gdx/diablo/ai/Npc.java @@ -0,0 +1,79 @@ +package gdx.diablo.ai; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector3; + +import org.apache.commons.lang3.ArrayUtils; + +import gdx.diablo.entity.Monster; +import gdx.diablo.map.DS1; + +public class Npc extends AI { + private static final String TAG = "Npc"; + + int targetId = ArrayUtils.INDEX_NOT_FOUND; + float actionTimer = 0; + boolean actionPerformed = false; + + public Npc(Monster entity) { + super(entity); + } + + public void update(float delta) { + Vector3 target = entity.target(); + if (target.equals(Vector3.Zero) || (entity.position().epsilonEquals(target) && !entity.targets().hasNext())) { + DS1.Path path = entity.object.path; + if (targetId == ArrayUtils.INDEX_NOT_FOUND) { + targetId = 0; + } else if (actionTimer > 0) { + actionTimer -= delta; + actionPerformed = actionTimer < 0; + return; + } else if (actionPerformed) { + actionPerformed = false; + targetId = MathUtils.random(path.numPoints - 1); + } else { + entity.setMode("NU"); + actionTimer = action(path.points[targetId].action); + actionPerformed = actionTimer < 0; + return; + } + + //entity.setMode("WL"); + DS1.Path.Point dst = path.points[targetId]; + entity.setPath(entity.map, new Vector3(dst.x, dst.y, 0)); + } + } + + private float action(int action) { + // path.actions == look at nearest player, chill, hold time, quest? + // 1 = 4 second hold + // 2 = 6 second hold + // 3 = 4 second hold + // 4 = special action at end of 10 second warriv jamella spell + // 4 = special action at end of 8 second charsi jamella book + // 4? fara = 5 seconds + // 4 == S1 + // 5 == S2 + // etc + + switch (action) { + case 1: + case 3: + return 4; + case 2: + return 6; + // TODO: play anim only once, after timer, ending the action + case 4: + entity.setMode("S1"); + return 10; + case 5: + entity.setMode("S2"); + return 10; + default: + Gdx.app.error(TAG, "Unknown action index: " + action); + return 4; + } + } +} diff --git a/core/src/gdx/diablo/entity/Entity.java b/core/src/gdx/diablo/entity/Entity.java index 338db714..df6a0a26 100644 --- a/core/src/gdx/diablo/entity/Entity.java +++ b/core/src/gdx/diablo/entity/Entity.java @@ -31,6 +31,7 @@ import gdx.diablo.map.DS1; import gdx.diablo.map.DT1.Tile; import gdx.diablo.map.Map; import gdx.diablo.map.MapGraph; +import gdx.diablo.map.MapRenderer; import gdx.diablo.widget.Label; public class Entity { @@ -128,6 +129,10 @@ public class Entity { Vector3 velocity = new Vector3(); float angle = MathUtils.PI * 3 / 2; + boolean running = false; + float walkSpeed = 6; + float runSpeed = 9; + Animation animation; public boolean over = true; Label label; @@ -136,13 +141,13 @@ public class Entity { MapGraph.MapGraphPath path = new MapGraph.MapGraphPath(); Iterator targets = Collections.emptyIterator(); - public static Entity create(DS1 ds1, DS1.Object obj) { + public static Entity create(Map map, DS1 ds1, DS1.Object obj) { final int type = obj.type; switch (type) { case DS1.Object.DYNAMIC_TYPE: - return Monster.create(ds1, obj); + return Monster.create(map, ds1, obj); case DS1.Object.STATIC_TYPE: - return StaticEntity.create(ds1, obj); + return StaticEntity.create(map, ds1, obj); default: throw new AssertionError("Unexpected type: " + type); } @@ -232,6 +237,32 @@ public class Entity { return path; } + public Iterator targets() { + return targets; + } + + public boolean isRunning() { + return running; + } + + public void setRunning(boolean b) { + if (running != b) { + running = b; + } + } + + public void setWalkSpeed(float speed) { + if (walkSpeed != speed) { + walkSpeed = speed; + } + } + + public void setRunSpeed(float speed) { + if (runSpeed != speed) { + runSpeed = speed; + } + } + public void setPath(Map map, Vector3 dst) { setPath(map, dst, -1); } @@ -264,14 +295,13 @@ public class Entity { if (position.epsilonEquals(target)) { if (!targets.hasNext()) { path.clear(); - setMode("NU"); + if (mode.equalsIgnoreCase(running ? "RN" : "WL")) setMode("NU"); return; } } - setMode("RN"); - //float targetLen = target.len(); - float speed = 9f * 2f; + setMode(running ? "RN" : "WL"); + float speed = (running ? walkSpeed + runSpeed : walkSpeed); float distance = speed * delta; float traveled = 0; while (traveled < distance) { @@ -410,13 +440,17 @@ public class Entity { public void drawDebug(ShapeRenderer shapes) { drawDebugStatus(shapes); - drawDebugTarget(shapes); + if (DEBUG_TARGET) drawDebugTarget(shapes); } public void drawDebugStatus(ShapeRenderer shapes) { float x = +(position.x * Tile.SUBTILE_WIDTH50) - (position.y * Tile.SUBTILE_WIDTH50); float y = -(position.x * Tile.SUBTILE_HEIGHT50) - (position.y * Tile.SUBTILE_HEIGHT50); + shapes.setColor(Color.WHITE); + MapRenderer.drawDiamond(shapes, x - Tile.SUBTILE_WIDTH50, y - Tile.SUBTILE_HEIGHT50, Tile.SUBTILE_WIDTH, Tile.SUBTILE_HEIGHT); + //shapes.ellipse(x - Tile.SUBTILE_WIDTH50, y - Tile.SUBTILE_HEIGHT50, Tile.SUBTILE_WIDTH, Tile.SUBTILE_HEIGHT); + final float R = 32; shapes.setColor(Color.RED); shapes.line(x, y, x + MathUtils.cos(angle) * R, y + MathUtils.sin(angle) * R); diff --git a/core/src/gdx/diablo/entity/Monster.java b/core/src/gdx/diablo/entity/Monster.java index 489c796f..031de0aa 100644 --- a/core/src/gdx/diablo/entity/Monster.java +++ b/core/src/gdx/diablo/entity/Monster.java @@ -10,27 +10,36 @@ import com.badlogic.gdx.utils.Align; import org.apache.commons.lang3.StringUtils; import gdx.diablo.Diablo; +import gdx.diablo.ai.AI; +import gdx.diablo.ai.Npc; import gdx.diablo.codec.excel.MonStats; import gdx.diablo.codec.excel.MonStats2; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.map.DS1; import gdx.diablo.map.DT1.Tile; +import gdx.diablo.map.Map; public class Monster extends Entity { private static final String TAG = "Monster"; - DS1.Object object; - MonStats.Entry monstats; - MonStats2.Entry monstats2; + public final Map map; + public final DS1.Object object; + public final MonStats.Entry monstats; + public final MonStats2.Entry monstats2; - public Monster(DS1.Object object, MonStats.Entry monstats) { + AI ai; + + public Monster(Map map, DS1.Object object, MonStats.Entry monstats) { super(monstats.Code, EntType.MONSTER); + this.map = map; this.object = object; this.monstats = monstats; this.monstats2 = Diablo.files.monstats2.get(monstats.MonStatsEx); setName(monstats.NameStr); setWeaponClass(monstats2.BaseW); setMode(monstats.spawnmode.isEmpty() ? "NU" : monstats.spawnmode); + setWalkSpeed(monstats.Velocity); + setRunSpeed(monstats.Run); for (int i = 0; i < monstats2.ComponentV.length; i++) { String ComponentV = monstats2.ComponentV[i]; if (!ComponentV.isEmpty()) { @@ -41,7 +50,7 @@ public class Monster extends Entity { } } - public static Monster create(DS1 ds1, DS1.Object obj) { + public static Monster create(Map map, DS1 ds1, DS1.Object obj) { assert obj.type == DS1.Object.DYNAMIC_TYPE; String id = Diablo.files.obj.getType1(ds1.getAct(), obj.id); @@ -49,29 +58,38 @@ public class Monster extends Entity { Gdx.app.debug(TAG, "Monster: " + monstats); if (monstats == null) return null; // TODO: Which ones fall under this case? Some static entities did, none here yet in testing. //if (!object.Draw) return null; // TODO: Not yet - return new Monster(obj, monstats); + + Monster monster = new Monster(map, obj, monstats); + if (monstats.AI.equalsIgnoreCase("Npc")) { + monster.ai = new Npc(monster); + } + + return monster; } @Override public void drawDebugPath(PaletteIndexedBatch batch, ShapeRenderer shapes) { DS1.Path path = object.path; if (path == null) return; - float p1x = +(position.x * Tile.SUBTILE_WIDTH50) - (position.y * Tile.SUBTILE_WIDTH50); - float p1y = -(position.x * Tile.SUBTILE_HEIGHT50) - (position.y * Tile.SUBTILE_HEIGHT50); + DS1.Path.Point point; + float p1x = 0, p1y = 0; float p2x = 0, p2y = 0; - for (int i = 0; i < path.numPoints; i++) { - DS1.Path.Point point = path.points[i]; + for (int i = 0; i < path.numPoints; i++, p1x = p2x, p1y = p2y) { + point = path.points[i]; + if (p1x == 0 && p1y == 0) { + p2x = +(point.x * Tile.SUBTILE_WIDTH50) - (point.y * Tile.SUBTILE_WIDTH50); + p2y = -(point.x * Tile.SUBTILE_HEIGHT50) - (point.y * Tile.SUBTILE_HEIGHT50); + continue; + } + p2x = +(point.x * Tile.SUBTILE_WIDTH50) - (point.y * Tile.SUBTILE_WIDTH50); p2y = -(point.x * Tile.SUBTILE_HEIGHT50) - (point.y * Tile.SUBTILE_HEIGHT50); shapes.setColor(Color.PURPLE); shapes.rectLine(p1x, p1y, p2x, p2y, 2); - - p1x = p2x; - p1y = p2y; } if (path.numPoints > 1) { - DS1.Path.Point point = path.points[0]; + point = path.points[0]; p1x = +(point.x * Tile.SUBTILE_WIDTH50) - (point.y * Tile.SUBTILE_WIDTH50); p1y = -(point.x * Tile.SUBTILE_HEIGHT50) - (point.y * Tile.SUBTILE_HEIGHT50); shapes.setColor(Color.PURPLE); @@ -85,7 +103,7 @@ public class Monster extends Entity { shapes.setColor(Color.WHITE); shapes.rect(p1x - HALF_BOX, p1y - HALF_BOX, BOX_SIZE, BOX_SIZE); for (int i = 0; i < path.numPoints; i++) { - DS1.Path.Point point = path.points[i]; + 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); shapes.setColor(Color.WHITE); @@ -96,7 +114,7 @@ public class Monster extends Entity { batch.begin(); batch.setShader(null); for (int i = 0; i < path.numPoints; i++) { - DS1.Path.Point point = path.points[i]; + 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); @@ -119,4 +137,10 @@ public class Monster extends Entity { if (!monstats2.isSel) return false; return super.contains(coords); } + + @Override + public void update(float delta) { + if (ai != null) ai.update(delta); + super.update(delta); + } } diff --git a/core/src/gdx/diablo/entity/Player.java b/core/src/gdx/diablo/entity/Player.java index b1516f25..6f3c4101 100644 --- a/core/src/gdx/diablo/entity/Player.java +++ b/core/src/gdx/diablo/entity/Player.java @@ -71,6 +71,9 @@ public class Player extends Entity { public Player(D2S d2s) { super(Diablo.files.PlrType.get(d2s.charClass).Token, EntType.PLAYER); setMode("TN"); + setWalkSpeed(6); + setRunSpeed(9); + setRunning(true); stats = new D2SStats(d2s); loadEquipped(d2s.items.equipped); @@ -80,6 +83,9 @@ public class Player extends Entity { public Player(String name, int classId) { super(Diablo.files.PlrType.get(classId).Token, EntType.PLAYER); setMode("TN"); + setWalkSpeed(6); + setRunSpeed(9); + setRunning(true); stats = new StatsImpl(name, classId); } diff --git a/core/src/gdx/diablo/entity/StaticEntity.java b/core/src/gdx/diablo/entity/StaticEntity.java index 2bb07141..673b1e2f 100644 --- a/core/src/gdx/diablo/entity/StaticEntity.java +++ b/core/src/gdx/diablo/entity/StaticEntity.java @@ -9,6 +9,7 @@ import gdx.diablo.codec.excel.Objects; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.map.DS1; import gdx.diablo.map.DT1.Tile; +import gdx.diablo.map.Map; public class StaticEntity extends Entity { private static final String TAG = "StaticEntity"; @@ -24,7 +25,7 @@ public class StaticEntity extends Entity { init(); } - public static StaticEntity create(DS1 ds1, DS1.Object obj) { + public static StaticEntity create(Map map, DS1 ds1, DS1.Object obj) { assert obj.type == DS1.Object.STATIC_TYPE; int id = Diablo.files.obj.getType2(ds1.getAct(), obj.id); diff --git a/core/src/gdx/diablo/map/Map.java b/core/src/gdx/diablo/map/Map.java index f495dd2e..76035c2d 100644 --- a/core/src/gdx/diablo/map/Map.java +++ b/core/src/gdx/diablo/map/Map.java @@ -513,14 +513,14 @@ public class Map implements Disposable { } Zone addZone(Levels.Entry level, int diff, int gridSizeX, int gridSizeY) { - Zone zone = new Zone(level, diff, gridSizeX, gridSizeY); + Zone zone = new Zone(this, level, diff, gridSizeX, gridSizeY); if (DEBUG_ZONES) Gdx.app.debug(TAG, zone.toString()); zones.add(zone); return zone; } Zone addZone(Levels.Entry level, int gridSizeX, int gridSizeY, int gridsX, int gridsY) { - Zone zone = new Zone(level, gridSizeX, gridSizeY, gridsX, gridsY); + Zone zone = new Zone(this, level, gridSizeX, gridSizeY, gridsX, gridsY); if (DEBUG_ZONES) Gdx.app.debug(TAG, zone.toString()); zones.add(zone); return zone; @@ -569,6 +569,7 @@ public class Map implements Disposable { int tx, ty; int tilesX, tilesY; + Map map; Levels.Entry level; LvlTypes.Entry type; Preset presets[][]; @@ -581,7 +582,8 @@ public class Map implements Disposable { /** * Constructs a zone using sizing info from levels.txt */ - Zone(Levels.Entry level, int diff, int gridSizeX, int gridSizeY) { + Zone(Map map, Levels.Entry level, int diff, int gridSizeX, int gridSizeY) { + this.map = map; this.level = level; this.type = Diablo.files.LvlTypes.get(level.LevelType); this.gridSizeX = gridSizeX; @@ -601,7 +603,8 @@ public class Map implements Disposable { /** * Constructs a zone using custom sizing info */ - Zone(Levels.Entry level, int gridSizeX, int gridSizeY, int gridsX, int gridsY) { + Zone(Map map, Levels.Entry level, int gridSizeX, int gridSizeY, int gridsX, int gridsY) { + this.map = map; this.level = level; this.type = Diablo.files.LvlTypes.get(level.LevelType); this.gridSizeX = gridSizeX; @@ -624,7 +627,7 @@ public class Map implements Disposable { if (entities == EMPTY_ARRAY) entities = new Array<>(); for (int i = 0; i < ds1.numObjects; i++) { DS1.Object obj = ds1.objects[i]; - Entity entity = Entity.create(ds1, obj); + Entity entity = Entity.create(map, ds1, obj); if (entity == null) continue; entity.position().set(x + obj.x, y + obj.y, 0); entities.add(entity); diff --git a/core/src/gdx/diablo/map/MapRenderer.java b/core/src/gdx/diablo/map/MapRenderer.java index 02e1c6c7..4b645341 100644 --- a/core/src/gdx/diablo/map/MapRenderer.java +++ b/core/src/gdx/diablo/map/MapRenderer.java @@ -39,6 +39,7 @@ public class MapRenderer { private static final boolean DEBUG_MOUSE = DEBUG && true; private static final boolean DEBUG_PATHS = DEBUG && true; private static final boolean DEBUG_POPPADS = DEBUG && !true; + private static final boolean DEBUG_ENTITIES = DEBUG && true; public static boolean RENDER_DEBUG_SUBTILE = DEBUG_SUBTILE; public static boolean RENDER_DEBUG_TILE = DEBUG_TILE; @@ -434,6 +435,11 @@ public class MapRenderer { Vector3 position = entity.position(); if ((stx <= position.x && position.x < stx + Tile.SUBTILE_SIZE) && (sty <= position.y && position.y < sty + Tile.SUBTILE_SIZE)) { + entity.update(Gdx.graphics.getDeltaTime()); + if (!entity.position().epsilonEquals(entity.target())) { + entity.setAngle(angle(entity.position(), entity.target())); + } + entity.draw(batch); } } @@ -490,6 +496,16 @@ public class MapRenderer { drawDiamond(shapes, spx, spy, Tile.SUBTILE_WIDTH, Tile.SUBTILE_HEIGHT); } + if (DEBUG_ENTITIES) { + Map.Zone zone = map.getZone(stx, sty); + if (zone != null) { + // TODO: limit range + for (Entity entity : zone.entities) { + entity.drawDebug(shapes); + } + } + } + if (RENDER_DEBUG_PATHS) drawDebugPaths(batch, shapes); @@ -921,7 +937,7 @@ public class MapRenderer { shapes.set(ShapeRenderer.ShapeType.Line); } - private static void drawDiamond(ShapeRenderer shapes, float x, float y, int width, int height) { + public static void drawDiamond(ShapeRenderer shapes, float x, float y, int width, int height) { int hw = width >>> 1; int hh = height >>> 1; shapes.line(x , y + hh , x + hw , y + height);