diff --git a/core/src/gdx/diablo/Client.java b/core/src/gdx/diablo/Client.java index 884743a2..d4f28684 100644 --- a/core/src/gdx/diablo/Client.java +++ b/core/src/gdx/diablo/Client.java @@ -104,6 +104,7 @@ public class Client extends Game { private boolean forceWindowed; private boolean forceDrawFps; private byte drawFpsMethod; + private String realm; public Client(FileHandle home) { this(home, Diablo.VIRTUAL_WIDTH, Diablo.VIRTUAL_HEIGHT); @@ -140,6 +141,16 @@ public class Client extends Game { forceDrawFps = b; } + public String getRealm() { + return realm; + } + + public void setRealm(String realm) { + if (!this.realm.equalsIgnoreCase(realm)) { + Cvars.Client.Realm.setString(realm); + } + } + @Override public void create() { Gdx.app.setLogLevel(Application.LOG_DEBUG); @@ -491,6 +502,13 @@ public class Client extends Game { batch.setGamma(to); } }); + + Cvars.Client.Realm.addStateListener(new CvarStateAdapter() { + @Override + public void onChanged(Cvar cvar, String from, String to) { + realm = to; + } + }); } public static class InputProcessor extends InputMultiplexer { diff --git a/core/src/gdx/diablo/Cvars.java b/core/src/gdx/diablo/Cvars.java index e578b654..fa6685a4 100644 --- a/core/src/gdx/diablo/Cvars.java +++ b/core/src/gdx/diablo/Cvars.java @@ -61,6 +61,12 @@ public class Cvars { .validator(Validator.ACCEPT_NON_NULL) .build(); + Cvar Realm = Cvar.builder(String.class) + .alias("Client.Realm") + .description("Realm to connect to.") + .defaultValue("hydra") + .build(); + interface Console { Cvar Font = Cvar.builder(String.class) .alias("Client.Console.Font") diff --git a/core/src/gdx/diablo/cvar/Cvar.java b/core/src/gdx/diablo/cvar/Cvar.java index c90e8f99..9788ee8d 100644 --- a/core/src/gdx/diablo/cvar/Cvar.java +++ b/core/src/gdx/diablo/cvar/Cvar.java @@ -133,6 +133,11 @@ public class Cvar implements SuggestionProvider { } } + // FIXME: Workaround for issue calling set(String) when is also String + public void setString(@NonNull String str) { + set(str); + } + public void set(@NonNull String str, @NonNull StringSerializer deserializer) { try { T value = ((StringSerializer) deserializer).deserialize(str); diff --git a/core/src/gdx/diablo/entity/Component.java b/core/src/gdx/diablo/entity/Component.java new file mode 100644 index 00000000..87f95db0 --- /dev/null +++ b/core/src/gdx/diablo/entity/Component.java @@ -0,0 +1,46 @@ +package gdx.diablo.entity; + +import com.badlogic.gdx.Gdx; + +public enum Component { + HD, + TR, + LG, + RA, + LA, + RH, + LH, + SH, + S1, + S2, + S3, + S4, + S5, + S6, + S7, + S8; + + public static Component valueOf(int i) { + switch (i) { + case 0x0: return HD; + case 0x1: return TR; + case 0x2: return LG; + case 0x3: return RA; + case 0x4: return LA; + case 0x5: return RH; + case 0x6: return LH; + case 0x7: return SH; + case 0x8: return S1; + case 0x9: return S2; + case 0xA: return S3; + case 0xB: return S4; + case 0xC: return S5; + case 0xD: return S6; + case 0xE: return S7; + case 0xF: return S8; + default: + Gdx.app.error("Component", "Unknown component: " + i); + return null; + } + } +} diff --git a/core/src/gdx/diablo/entity/Entity.java b/core/src/gdx/diablo/entity/Entity.java new file mode 100644 index 00000000..68f2ca52 --- /dev/null +++ b/core/src/gdx/diablo/entity/Entity.java @@ -0,0 +1,336 @@ +package gdx.diablo.entity; + +import android.support.annotation.CallSuper; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.assets.AssetDescriptor; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector3; + +import org.apache.commons.lang3.ArrayUtils; + +import java.util.Arrays; + +import gdx.diablo.Diablo; +import gdx.diablo.codec.Animation; +import gdx.diablo.codec.COF; +import gdx.diablo.codec.COFD2; +import gdx.diablo.codec.DCC; +import gdx.diablo.graphics.PaletteIndexedBatch; +import gdx.diablo.map.DT1.Tile; + +public class Entity { + private static final String TAG = "Entity"; + private static final boolean DEBUG = true; + private static final boolean DEBUG_COMPONENTS = DEBUG && true; + private static final boolean DEBUG_COF = DEBUG && !true; + private static final boolean DEBUG_DIRTY = DEBUG && true; + private static final boolean DEBUG_ASSETS = DEBUG && true; + private static final boolean DEBUG_STATE = DEBUG && true; + + protected enum EntType { + OBJECT("OBJECT"), + MONSTER("MONSTER"), + PLAYER("CHARS"); + + public final String PATH; + + EntType(String path) { + PATH = "data\\global\\" + path + "\\"; + } + } + + public static final class Dirty { + public static final int NONE = 0; + public static final int HD = 1 << 0; + public static final int TR = 1 << 1; + public static final int LG = 1 << 2; + public static final int RA = 1 << 3; + public static final int LA = 1 << 4; + public static final int RH = 1 << 5; + public static final int LH = 1 << 6; + public static final int SH = 1 << 7; + public static final int S1 = 1 << 8; + public static final int S2 = 1 << 9; + public static final int S3 = 1 << 10; + public static final int S4 = 1 << 11; + public static final int S5 = 1 << 12; + public static final int S6 = 1 << 13; + public static final int S7 = 1 << 14; + public static final int S8 = 1 << 15; + public static final int ALL = 0xFFFF; + + public static String toString(int bits) { + StringBuilder builder = new StringBuilder(); + if (bits == NONE) { + builder.append("NONE"); + } else { + if ((bits & HD) == HD) builder.append("HD").append("|"); + if ((bits & TR) == TR) builder.append("TR").append("|"); + if ((bits & LG) == LG) builder.append("LG").append("|"); + if ((bits & RA) == RA) builder.append("RA").append("|"); + if ((bits & LA) == LA) builder.append("LA").append("|"); + if ((bits & RH) == RH) builder.append("RH").append("|"); + if ((bits & LH) == LH) builder.append("LH").append("|"); + if ((bits & SH) == SH) builder.append("SH").append("|"); + if ((bits & S1) == S1) builder.append("S1").append("|"); + if ((bits & S2) == S2) builder.append("S2").append("|"); + if ((bits & S3) == S3) builder.append("S3").append("|"); + if ((bits & S4) == S4) builder.append("S4").append("|"); + if ((bits & S5) == S5) builder.append("S5").append("|"); + if ((bits & S6) == S6) builder.append("S6").append("|"); + if ((bits & S7) == S7) builder.append("S7").append("|"); + if ((bits & S8) == S8) builder.append("S8").append("|"); + if (builder.length() > 0) builder.setLength(builder.length() - 1); + } + return builder.toString(); + } + + public static boolean isDirty(int flags, int component) { + return ((1 << component) & flags) != 0; + } + } + + protected static final String DEFAULT_LAYER = "LIT"; + private static final String[] DEFAULT_LAYERS; + static { + DEFAULT_LAYERS = new String[16]; + Arrays.fill(DEFAULT_LAYERS, DEFAULT_LAYER); + } + + String type; + EntType entType; + + int dirty; + String mode; + String code; + String layers[]; + String weaponClass; + Vector3 position = new Vector3(); + Vector3 velocity = new Vector3(); + float angle = MathUtils.PI * 3 / 2; + + Animation animation; + + Entity(String type) { + this(type, EntType.OBJECT); + } + + Entity(String type, EntType entType) { + this.type = type; + this.entType = entType; + mode = code = "NU"; + weaponClass = "HTH"; + layers = DEFAULT_LAYERS; + invalidate(); + } + + public void setMode(String mode) { + setMode(mode, mode); + } + + public void setMode(String mode, String code) { + if (!this.mode.equalsIgnoreCase(mode)) { + if (DEBUG_STATE) Gdx.app.debug(TAG, "mode: " + this.mode + " -> " + mode); + this.mode = mode; + invalidate(); + } + + this.code = code; + } + + public void setWeaponClass(String weaponClass) { + if (!this.weaponClass.equalsIgnoreCase(weaponClass)) { + if (DEBUG_STATE) Gdx.app.debug(TAG, "weaponClass: " + this.weaponClass + " -> " + weaponClass); + this.weaponClass = weaponClass; + invalidate(); + } + } + + public void setArmType(Component component, String armType) { + if (layers == DEFAULT_LAYERS) { + if (!DEFAULT_LAYER.equalsIgnoreCase(armType)) { + layers = ArrayUtils.clone(DEFAULT_LAYERS); + } else { + return; + } + } + + int ordinal = component.ordinal(); + if (layers[ordinal].equalsIgnoreCase(armType)) { + return; + } + + if (DEBUG_COMPONENTS) Gdx.app.debug(TAG, component + " " + layers[ordinal] + " -> " + armType); + layers[ordinal] = armType; + dirty |= (1 << ordinal); + } + + protected byte getTransform(Component component) { + return (byte) 0xFF; + } + + public Vector3 position() { + return position; + } + + public Vector3 velocity() { + return velocity; + } + + public float getAngle() { + return angle; + } + + public void setAngle(float rad) { + if (angle != rad) { + angle = rad; + if (animation != null) animation.setDirection(getDirection()); + } + } + + public int getDirection() { + int numDirs = animation.getNumDirections(); + return Direction.radiansToDirection(angle, numDirs); + } + + public final void invalidate() { + dirty = Dirty.ALL; + } + + public final void validate() { + if (dirty == 0) { + return; + } + + update(); + } + + @CallSuper + protected void update() { + String path = getCOF(); + //Gdx.app.debug(TAG, path); + + COF cof = getCOFs().lookup(path); + if (DEBUG_COF) Gdx.app.debug(TAG, "" + cof); + + boolean changed = updateAnimation(cof); + if (changed) { + dirty = Dirty.ALL; + animation.setDirection(getDirection()); + } + + if (DEBUG_DIRTY) Gdx.app.debug(TAG, "dirty layers: " + dirty); + for (int l = 0; l < cof.getNumLayers(); l++) { + COF.Layer layer = cof.getLayer(l); + final int c = layer.component; + if (!Dirty.isDirty(dirty, c)) { + continue; + } + + final Component comp = Component.valueOf(c); + if (comp == null) continue; + String component = comp.name(); + String armType = layers[c]; + String weaponClass = layer.weaponClass; + path = entType.PATH + type + "\\" + component + "\\" + type + component + armType + mode + weaponClass + ".dcc"; + if (armType.isEmpty()) { + animation.setLayer(c, null); + continue; + } + Gdx.app.log(TAG, path); + + AssetDescriptor descriptor = new AssetDescriptor<>(path, DCC.class); + Diablo.assets.load(descriptor); + Diablo.assets.finishLoadingAsset(descriptor); + DCC dcc = Diablo.assets.get(descriptor); + animation.setLayer(c, dcc); + + /*Runnable loader = new Runnable() { + @Override + public void run() { + if (!Diablo.assets.isLoaded(descriptor)) { + Gdx.app.postRunnable(this); + return; + } + + DCC dcc = Diablo.assets.get(descriptor); + animation.setLayer(c, dcc); + + Item item = getItem(comp); + if (item != null) { + animation.getLayer(c).setTransform(item.charColormap, item.charColorIndex); + } + } + };*/ + //Gdx.app.postRunnable(loader); + + byte transform = getTransform(comp); + animation.getLayer(c).setTransform(transform); + /* + if (item != null) { + // FIXME: colors don't look right for sorc Tirant circlet changing hair color + // putting a ruby in a white circlet not change color on item or character + // circlets and other items with hidden magic level might work different? + animation.getLayer(layer.component).setTransform(item.charColormap, item.charColorIndex); + //System.out.println(item.getName() + ": " + item.charColormap + " ; " + item.charColorIndex); + } + */ + } + + dirty = 0; + } + + private boolean updateAnimation(COF cof) { + if (animation == null) { + animation = Animation.newAnimation(cof); + return true; + } else { + return animation.reset(cof); + } + } + + public String getCOF() { + return type + mode + weaponClass; + } + + protected COFD2 getCOFs() { + return Diablo.cofs.active; + } + + public void drawDebug(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); + + final float R = 32; + shapes.setColor(Color.RED); + shapes.line(x, y, x + MathUtils.cos(angle) * R, y + MathUtils.sin(angle) * R); + + // FIXME: Should be number of direction dependent, not 16, one of 4,8,16,32 + float rounded = Direction.radiansToDirection16Radians(angle); + shapes.setColor(Color.GREEN); + shapes.line(x, y, x + MathUtils.cos(rounded) * R * 0.5f, y + MathUtils.sin(rounded) * R * 0.5f); + } + + public void draw(Batch batch) { + draw((PaletteIndexedBatch) batch); + } + + public void draw(PaletteIndexedBatch batch) { + validate(); + animation.act(); + float x = +(position.x * Tile.SUBTILE_WIDTH50) - (position.y * Tile.SUBTILE_WIDTH50); + float y = -(position.x * Tile.SUBTILE_HEIGHT50) - (position.y * Tile.SUBTILE_HEIGHT50); + animation.draw(batch, x, y); + } + + public boolean move() { + int x = Direction.getOffX(angle); + int y = Direction.getOffY(angle); + position.add(x, y, 0); + return true; + } +} diff --git a/core/src/gdx/diablo/entity/Player.java b/core/src/gdx/diablo/entity/Player.java index 2a4efd50..33727a49 100644 --- a/core/src/gdx/diablo/entity/Player.java +++ b/core/src/gdx/diablo/entity/Player.java @@ -3,14 +3,10 @@ package gdx.diablo.entity; import com.google.common.base.Preconditions; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.assets.AssetDescriptor; -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.glutils.ShapeRenderer; -import com.badlogic.gdx.math.GridPoint2; -import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.GdxRuntimeException; -import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.ObjectUtils; import java.util.EnumMap; import java.util.Map; @@ -19,154 +15,305 @@ import java.util.concurrent.CopyOnWriteArraySet; import gdx.diablo.CharClass; import gdx.diablo.Diablo; -import gdx.diablo.codec.Animation; -import gdx.diablo.codec.COF; +import gdx.diablo.ItemCodes; +import gdx.diablo.codec.COFD2; import gdx.diablo.codec.D2S; -import gdx.diablo.codec.DCC; import gdx.diablo.codec.excel.Armor; -import gdx.diablo.codec.excel.ItemEntry; -import gdx.diablo.codec.excel.PlrMode; -import gdx.diablo.codec.excel.PlrType; -import gdx.diablo.codec.excel.WeaponClass; import gdx.diablo.codec.excel.Weapons; -import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.item.BodyLoc; import gdx.diablo.item.Item; +import gdx.diablo.server.Connect; -public class Player { +public class Player extends Entity { private static final String TAG = "Player"; - private static final boolean DEBUG = true; - private static final boolean DEBUG_COF = DEBUG && !true; - private static final boolean DEBUG_EQUIPPED = DEBUG && true; - private static final boolean DEBUG_INVENTORY = DEBUG && true; - - private static final String CHARS = "data\\global\\chars\\"; + private static final boolean DEBUG = true; + private static final boolean DEBUG_STATE = DEBUG && true; public static final int MAX_NAME_LENGTH = 15; - D2S d2s; - String name; - GridPoint2 origin; - float angle; + public enum Slot { + HEAD, NECK, TORS, RARM, LARM, RRIN, LRIN, BELT, FEET, GLOV; - int plrModeId; - PlrMode.Entry plrMode; + public BodyLoc toBodyLoc(boolean alternate) { + return toBodyLoc(this, alternate); + } - int plrTypeId; - PlrType.Entry plrType; + public static BodyLoc toBodyLoc(Slot slot, boolean alternate) { + switch (slot) { + case HEAD: return BodyLoc.HEAD; + case NECK: return BodyLoc.NECK; + case TORS: return BodyLoc.TORS; + case RARM: return alternate ? BodyLoc.RARM2 : BodyLoc.RARM; + case LARM: return alternate ? BodyLoc.LARM2 : BodyLoc.LARM; + case RRIN: return BodyLoc.RRIN; + case LRIN: return BodyLoc.LRIN; + case BELT: return BodyLoc.BELT; + case FEET: return BodyLoc.FEET; + case GLOV: return BodyLoc.GLOV; + default: + throw new GdxRuntimeException("Invalid slot: " + slot); + } + } + } - //int weaponClassId; - WeaponClass.Entry weaponClass; - - boolean dirty; - String cofId; - Animation anim; - - EnumMap equipped; - Array inventory; - boolean usingAlternate; + boolean alternate; + boolean ignoreUpdate; + byte[] transforms; + EnumMap equipped = new EnumMap<>(BodyLoc.class); + Array inventory = new Array<>(); + public Stats stats; final Set SLOT_LISTENERS = new CopyOnWriteArraySet<>(); - public Player(D2S d2s) { - this.d2s = d2s; - name = d2s.name; - plrTypeId = d2s.charClass; - plrType = Diablo.files.PlrType.get(plrTypeId); - origin = new GridPoint2(); - init(); - //setWeaponClass("hth"); + public Player(String name, CharClass clazz) { + this(name, clazz.id); + } - equipped = d2s.items.equipped; + public Player(D2S d2s) { + super(Diablo.files.PlrType.get(d2s.charClass).Token, EntType.PLAYER); + setMode("TN"); + + stats = new D2SStats(d2s); + loadEquipped(d2s.items.equipped); + loadInventory(d2s.items.inventory); + } + + public Player(String name, int classId) { + super(Diablo.files.PlrType.get(classId).Token, EntType.PLAYER); + setMode("TN"); + + stats = new StatsImpl(name, classId); + } + + public Player(Connect connect) { + this(connect.name, connect.classId); + + ignoreUpdate = true; + transforms = connect.colors; + setWeaponClass("1HS"); + for (int i = 0; i < 16; i++) { + String code = ItemCodes.getCode(connect.composites[i] & 0xFF); + if (code == null) code = ItemCodes.getCode(ItemCodes.LIT); + setArmType(Component.valueOf(i), code); + } + } + + private void loadEquipped(EnumMap items) { + equipped.putAll(items); for (Map.Entry entry : equipped.entrySet()) { entry.getValue().load(); - if (DEBUG_EQUIPPED) Gdx.app.debug(TAG, entry.getKey() + ": " + entry.getValue()); + //if (DEBUG_EQUIPPED) Gdx.app.debug(TAG, entry.getKey() + ": " + entry.getValue()); } + } - inventory = d2s.items.inventory; - for (Item item : inventory) { + private void loadInventory(Array items) { + inventory.addAll(items); + for (Item item : items) { item.load(); - if (DEBUG_INVENTORY) Gdx.app.debug(TAG, item.gridX + "," + item.gridY + ": " + item); + //if (DEBUG_INVENTORY) Gdx.app.debug(TAG, item.gridX + "," + item.gridY + ": " + item); } } - public Player(String name, CharClass clazz) { - this.name = name; - plrTypeId = clazz.id; - plrType = Diablo.files.PlrType.get(plrTypeId); - origin = new GridPoint2(); - init(); - //setWeaponClass("hth"); - dirty = true; + public Item getSlot(Slot slot) { + BodyLoc loc = slot.toBodyLoc(alternate); + return getSlot(loc); } - private void init() { - setMode("TN"); - setAngle(MathUtils.PI * 3 / 2); + public Item getSlot(BodyLoc loc) { + return equipped.get(loc); } - public int getClassId() { - return d2s.charClass; + public Item setSlot(Slot slot, Item item) { + Preconditions.checkState(item == null || getSlot(slot) == null, "Slot must be empty first!"); + BodyLoc loc = slot.toBodyLoc(alternate); + return setSlot(loc, item); } - public CharClass getCharClass() { - return CharClass.get(d2s.charClass); - } + public Item setSlot(BodyLoc loc, Item item) { + Item oldItem = equipped.put(loc, item); - public String getName() { - return d2s.name; - } + //invalidate(); + //setArmType(slot, item.base.alternateGfx); + int components = loc.components(); + if (components > 0) dirty |= components; + updateWeaponClass(); - public int getLevel() { - return d2s.stats.level; - } - - public long getExperience() { - return d2s.stats.xp; - } - - public int getStrength() { - return d2s.stats.strength; - } - - public int getDexterity() { - return d2s.stats.dexterity; - } - - public int getVitality() { - return d2s.stats.vitality; - } - - public int getEnergy() { - return d2s.stats.energy; - } - - public int getFireResistance() { - return 0; - } - - public int getColdResistance() { - return 0; - } - - public int getLightningResistance() { - return 0; - } - - public int getPoisonResistance() { - return 0; - } - - public Item getBodyLoc(BodyLoc bodyLoc) { - return equipped.get(bodyLoc); - } - - public Item setBodyLoc(BodyLoc bodyLoc, Item item) { - Preconditions.checkState(item == null || getBodyLoc(bodyLoc) == null, "Slot must be empty first!"); - Item oldItem = equipped.put(bodyLoc, item); - for (SlotListener l : SLOT_LISTENERS) l.onChanged(this, bodyLoc, oldItem, item); + notifySlotChanged(loc, oldItem, item); return oldItem; } + @Override + protected byte getTransform(Component component) { + if (ignoreUpdate) { + return transforms[component.ordinal()]; + } + + switch (component) { + case HD: return packTransform(Slot.HEAD); + case TR: + case RA: + case LA: + case S1: + case S2: return packTransform(Slot.TORS); + // TODO: Shield/weapons? + default: return super.getTransform(component); + } + } + + private byte packTransform(Slot slot) { + Item item = getSlot(slot); + if (item == null) return super.getTransform(null); + return (byte) ((item.base.Transform << 5) | (item.charColorIndex & 0x1F)); + } + + public Array getInventory() { + return inventory; + } + + public boolean isAlternate() { + return alternate; + } + + public void setAlternate(boolean b) { + if (alternate != b) { + alternate = b; + updateWeaponClass(); + Item LH = getSlot(BodyLoc.LARM); + Item RH = getSlot(BodyLoc.RARM); + Item LH2 = getSlot(BodyLoc.LARM2); + Item RH2 = getSlot(BodyLoc.RARM2); + if (b) { + notifyAlternate(LH2, RH2); + } else { + notifyAlternate(LH, RH); + } + } + } + + @Override + protected COFD2 getCOFs() { + return Diablo.cofs.chars_cof; + } + + public void update() { + if (ignoreUpdate) { + super.update(); + return; + } + + updateWeaponClass(); + + Item head = getSlot(Slot.HEAD); + setArmType(Component.HD, head != null ? head.base.alternateGfx : "LIT"); + + Item body = getSlot(Slot.TORS); + if (body != null) { + Armor.Entry armor = body.getBase(); + setArmType(Component.TR, Diablo.files.ArmType.get(armor.Torso).Token); + setArmType(Component.LG, Diablo.files.ArmType.get(armor.Legs ).Token); + setArmType(Component.RA, Diablo.files.ArmType.get(armor.rArm ).Token); + setArmType(Component.LA, Diablo.files.ArmType.get(armor.lArm ).Token); + setArmType(Component.S1, Diablo.files.ArmType.get(armor.lSPad).Token); + setArmType(Component.S2, Diablo.files.ArmType.get(armor.rSPad).Token); + } else { + setArmType(Component.TR, DEFAULT_LAYER); + setArmType(Component.LG, DEFAULT_LAYER); + setArmType(Component.RA, DEFAULT_LAYER); + setArmType(Component.LA, DEFAULT_LAYER); + setArmType(Component.S1, DEFAULT_LAYER); + setArmType(Component.S2, DEFAULT_LAYER); + } + + super.update(); + } + + private void updateWeaponClass() { + Item RH = null, LH = null, SH = null; + Item rArm = getSlot(Slot.RARM); + if (rArm != null) { + if (rArm.type.is("weap")) { + RH = rArm; + } else if (rArm.type.is("shld")) { + SH = rArm; + } + } + + Item lArm = getSlot(Slot.LARM); + if (lArm != null) { + if (lArm.type.is("weap")) { + LH = lArm; + } else if (lArm.type.is("shld")) { + SH = lArm; + } + } + + if (DEBUG_STATE) { + Gdx.app.debug(TAG, "RH = " + RH); + Gdx.app.debug(TAG, "LH = " + LH); + Gdx.app.debug(TAG, "SH = " + SH); + } + + if (LH != null && RH != null) { + Weapons.Entry LHEntry = LH.getBase(); + Weapons.Entry RHEntry = RH.getBase(); + if ( LHEntry.wclass.equals("1hs") && RHEntry.wclass.equals("1hs")) { + setWeaponClass("1SS"); // Left Swing Right Swing + } else if (LHEntry.wclass.equals("1hs") && RHEntry.wclass.equals("1ht")) { + setWeaponClass("1ST"); // Left Swing Right Thrust + } else if (LHEntry.wclass.equals("1ht") && RHEntry.wclass.equals("1hs")) { + setWeaponClass("1JS"); // Left Jab Right Swing + } else if (LHEntry.wclass.equals("1ht") && RHEntry.wclass.equals("1ht")) { + setWeaponClass("1JT"); // Left Jab Right Thrust + } else if (LH.type.is("miss") || RH.type.is("miss")) { + setWeaponClass(LH.type.is("miss") ? LHEntry.wclass : RHEntry.wclass); + } else if (LH.type.is("h2h") || RH.type.is("h2h")) { + setWeaponClass("HT2"); // Two Hand-to-Hand + } else { + setWeaponClass("HTH"); + Gdx.app.error(TAG, String.format( + "Unknown weapon combination: LH=%s RH=%s", LHEntry.wclass, RHEntry.wclass)); + } + } else if (LH != null || RH != null) { + RH = ObjectUtils.firstNonNull(RH, LH); + LH = null; + if (RH.type.is("bow")) { + LH = RH; + RH = null; + Weapons.Entry LHEntry = LH.getBase(); + setWeaponClass(LHEntry.wclass); + } else if (RH.type.is("weap")) { // make sure weap and not e.g. misl, might not be required + Weapons.Entry RHEntry = RH.getBase(); + setWeaponClass(RHEntry.wclass); + } else { + setWeaponClass("HTH"); + } + } else { + setWeaponClass("HTH"); + } + + setArmType(Component.RH, RH != null ? RH.base.alternateGfx : ""); + setArmType(Component.LH, LH != null ? LH.base.alternateGfx : ""); + setArmType(Component.SH, SH != null ? SH.base.alternateGfx : ""); + } + + @Override + public boolean move() { + if (!mode.equalsIgnoreCase("WL") + && !mode.equalsIgnoreCase("RN") + && !mode.equalsIgnoreCase("TW")) { + return false; + } + + return super.move(); + } + + private void notifySlotChanged(BodyLoc bodyLoc, Item oldItem, Item item) { + for (SlotListener l : SLOT_LISTENERS) l.onChanged(this, bodyLoc, oldItem, item); + } + + private void notifyAlternate(Item LH, Item RH) { + for (SlotListener l : SLOT_LISTENERS) l.onAlternate(this, LH, RH); + } + public boolean addSlotListener(SlotListener l) { boolean added = SLOT_LISTENERS.add(l); return added; @@ -186,218 +333,165 @@ public class Player { return !empty; } - public Item getComponentSlot(int component) { - switch (component) { - case COF.Component.HD: return getBodyLoc(BodyLoc.HEAD); - case COF.Component.TR: - case COF.Component.RA: - case COF.Component.LA: - case COF.Component.S1: - case COF.Component.S2: return getBodyLoc(BodyLoc.TORS); - // TODO: Shield/weapons? - default: return null; - } - } - - public Array getInventory() { - return inventory; - } - - public boolean isAlternate() { - return usingAlternate; - } - - public void setAlternate(boolean b) { - if (usingAlternate != b) { - usingAlternate = b; - } - } - - public static Player obtain(D2S d2s) { - return new Player(d2s); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .append("name", name) - .append("origin", origin) - .append("plrMode", plrMode) - .append("plrType", plrType) - .append("weaponClass", weaponClass) - .build(); - } - - public void setMode(String code) { - setMode(Diablo.files.PlrMode.index(code)); - } - - public void setMode(int plrModeId) { - if (this.plrModeId != plrModeId || plrMode == null) { - this.plrModeId = plrModeId; - plrMode = Diablo.files.PlrMode.get(plrModeId); - dirty = true; - } - } - - /* - public void setWeaponClass(String code) { - setWeaponClass(Diablo.files.WeaponClass.index(code)); - } - - public void setWeaponClass(int weaponClassId) { - if (this.weaponClassId != weaponClassId || weaponClass == null) { - this.weaponClassId = weaponClassId; - weaponClass = Diablo.files.WeaponClass.get(weaponClassId); - dirty = true; - } - } - */ - - public void setAngle(float rad) { - if (this.angle != rad) { - this.angle = rad; - if (anim != null) anim.setDirection(getDirection()); - } - } - - public int getDirection() { - return Direction.radiansToDirection(angle, 16); - } - - public GridPoint2 getOrigin() { - return origin; - } - - public void update() { - if (!dirty) { - return; - } - - dirty = false; - - String[] components = new String[COF.Component.NUM_COMPONENTS]; - Item rHand, lHand; - if (!usingAlternate) { - rHand = getBodyLoc(BodyLoc.RARM); - lHand = getBodyLoc(BodyLoc.LARM); - } else { - rHand = getBodyLoc(BodyLoc.RARM2); - lHand = getBodyLoc(BodyLoc.LARM2); - } - - // TODO: custom code for barbarian _1or2handed - if (rHand != null) { - ItemEntry entry = rHand.base; - components[entry.component] = entry.alternateGfx; - if (entry instanceof Weapons.Entry) { - weaponClass = Diablo.files.WeaponClass.get(((Weapons.Entry) entry).wclass); - } - } - if (lHand != null) { - ItemEntry entry = lHand.base; - components[entry.component] = entry.alternateGfx; - if (entry instanceof Weapons.Entry) { - weaponClass = Diablo.files.WeaponClass.get(((Weapons.Entry) entry).wclass); - } - } - if (weaponClass == null) { - weaponClass = Diablo.files.WeaponClass.get("hth"); - } - - Item head = getBodyLoc(BodyLoc.HEAD); - components[COF.Component.HD] = head != null ? head.base.alternateGfx : null; - - Item body = getBodyLoc(BodyLoc.TORS); - if (body != null) { - Armor.Entry armor = body.getBase(); - components[COF.Component.TR] = Diablo.files.ArmType.get(armor.Torso).Token; - components[COF.Component.LG] = Diablo.files.ArmType.get(armor.Legs).Token; - components[COF.Component.RA] = Diablo.files.ArmType.get(armor.rArm).Token; - components[COF.Component.LA] = Diablo.files.ArmType.get(armor.lArm).Token; - components[COF.Component.S1] = Diablo.files.ArmType.get(armor.lSPad).Token; - components[COF.Component.S2] = Diablo.files.ArmType.get(armor.rSPad).Token; - } else { - components[COF.Component.TR] = - components[COF.Component.LG] = - components[COF.Component.RA] = - components[COF.Component.LA] = - components[COF.Component.S1] = - components[COF.Component.S2] = "lit"; - } - - String cofId = plrType.Token + plrMode.Token + weaponClass.Code; - if (DEBUG_COF) Gdx.app.debug(TAG, "COF: " + this.cofId + " -> " + cofId); - COF cof = Diablo.cofs.chars_cof.lookup(cofId); - this.cofId = cofId; - - // FIXME: dispose/unload old animation layer - //if (animation != null) animation.dispose(); - - Animation oldAnim = anim; - anim = Animation.newAnimation(cof); - // TODO: This might be a problem - anim.setDirection(oldAnim != null ? oldAnim.getDirection() : getDirection()); - - for (int i = 0; i < cof.getNumLayers(); i++) { - COF.Layer layer = cof.getLayer(i); - String component = Diablo.files.Composit.get(layer.component).Token; - String armorClass = components[layer.component]; - if (armorClass == null) continue; - - String weaponClass = layer.weaponClass; - String path = CHARS + plrType.Token + "\\" + component + "\\" + plrType.Token + component + armorClass + plrMode.Token + weaponClass + ".dcc"; - - AssetDescriptor descriptor = new AssetDescriptor<>(path, DCC.class); - Diablo.assets.load(descriptor); - Diablo.assets.finishLoadingAsset(descriptor); - DCC dcc = Diablo.assets.get(descriptor); - anim.setLayer(layer.component, dcc); - - Item item = getComponentSlot(layer.component); - if (item != null) { - anim.getLayer(layer.component).setTransform(item.charColormap, item.charColorIndex); - /* - int trans = item.charTransformation; - if (trans != 0xFFFFFFFF) { - anim.getLayer(layer.component).setTransform(trans >>> 8); - anim.getLayer(layer.component).setTransformColor(trans & 0xFF); - } - */ - } - } - } - - public void move() { - switch (plrModeId) { - case 2: case 3: case 6: - break; - default: - return; - } - - int x = Direction.getOffX(angle); - int y = Direction.getOffY(angle); - origin.add(x, y); - } - - public void draw(PaletteIndexedBatch batch, int x, int y) { - update(); - anim.act(); - anim.draw(batch, x, y); - } - - public void drawDebug(ShapeRenderer shapes, int x, int y) { - final float R = 32; - shapes.setColor(Color.RED); - shapes.line(x, y, x + MathUtils.cos(angle) * R, y + MathUtils.sin(angle) * R); - - float rounded = Direction.radiansToDirection16Radians(angle); - shapes.setColor(Color.GREEN); - shapes.line(x, y, x + MathUtils.cos(rounded) * R * 0.5f, y + MathUtils.sin(rounded) * R * 0.5f); - } - public interface SlotListener { void onChanged(Player player, BodyLoc bodyLoc, Item oldItem, Item item); + void onAlternate(Player player, Item LH, Item RH); + } + + public static class SlotAdapter implements SlotListener { + @Override public void onChanged(Player player, BodyLoc bodyLoc, Item oldItem, Item item) {} + @Override public void onAlternate(Player player, Item LH, Item RH) {} + } + + public interface Stats { + int getClassId(); + CharClass getCharClass(); + String getName(); + int getLevel(); + long getExperience(); + int getStrength(); + int getDexterity(); + int getVitality(); + int getEnergy(); + int getFireResistance(); + int getColdResistance(); + int getLightningResistance(); + int getPoisonResistance(); + } + + public class StatsImpl implements Stats { + final String name; + final int classId; + StatsImpl(String name, int classId) { + this.name = name; + this.classId = classId; + } + + @Override + public int getClassId() { + return classId; + } + + @Override + public CharClass getCharClass() { + return CharClass.get(getClassId()); + } + + @Override + public String getName() { + return name; + } + + @Override + public int getLevel() { + return 0; + } + + @Override + public long getExperience() { + return 0; + } + + @Override + public int getStrength() { + return 0; + } + + @Override + public int getDexterity() { + return 0; + } + + @Override + public int getVitality() { + return 0; + } + + @Override + public int getEnergy() { + return 0; + } + + @Override + public int getFireResistance() { + return 0; + } + + @Override + public int getColdResistance() { + return 0; + } + + @Override + public int getLightningResistance() { + return 0; + } + + @Override + public int getPoisonResistance() { + return 0; + } + } + + public class D2SStats implements Stats { + public final D2S d2s; + D2SStats(D2S d2s) { + this.d2s = d2s; + } + + @Override + public int getClassId() { + return d2s.charClass; + } + + @Override + public CharClass getCharClass() { + return CharClass.get(getClassId()); + } + + @Override + public String getName() { + return d2s.name; + } + + public int getLevel() { + return d2s.stats.level; + } + + public long getExperience() { + return d2s.stats.xp; + } + + public int getStrength() { + return d2s.stats.strength; + } + + public int getDexterity() { + return d2s.stats.dexterity; + } + + public int getVitality() { + return d2s.stats.vitality; + } + + public int getEnergy() { + return d2s.stats.energy; + } + + public int getFireResistance() { + return 0; + } + + public int getColdResistance() { + return 0; + } + + public int getLightningResistance() { + return 0; + } + + public int getPoisonResistance() { + return 0; + } } } diff --git a/core/src/gdx/diablo/map/Map.java b/core/src/gdx/diablo/map/Map.java index 6c405636..2651e7d9 100644 --- a/core/src/gdx/diablo/map/Map.java +++ b/core/src/gdx/diablo/map/Map.java @@ -363,6 +363,7 @@ public class Map implements Disposable { public GridPoint2 find(int id) { GridPoint2 origin = zones.first().presets[0][0].ds1.find(id); if (origin == null) return null; + origin = origin.cpy(); origin.x *= DT1.Tile.SUBTILE_SIZE; origin.y *= DT1.Tile.SUBTILE_SIZE; return origin.add(DT1.Tile.SUBTILE_CENTER); diff --git a/core/src/gdx/diablo/panel/CharacterPanel.java b/core/src/gdx/diablo/panel/CharacterPanel.java index 1e9b903a..ac0b8f2a 100644 --- a/core/src/gdx/diablo/panel/CharacterPanel.java +++ b/core/src/gdx/diablo/panel/CharacterPanel.java @@ -16,7 +16,7 @@ import java.text.NumberFormat; import gdx.diablo.Cvars; import gdx.diablo.Diablo; import gdx.diablo.codec.DC6; -import gdx.diablo.entity3.Player; +import gdx.diablo.entity.Player; import gdx.diablo.loader.DC6Loader; import gdx.diablo.screen.GameScreen; import gdx.diablo.widget.Button; diff --git a/core/src/gdx/diablo/panel/InventoryPanel.java b/core/src/gdx/diablo/panel/InventoryPanel.java index 6d623282..0f135aae 100644 --- a/core/src/gdx/diablo/panel/InventoryPanel.java +++ b/core/src/gdx/diablo/panel/InventoryPanel.java @@ -22,7 +22,7 @@ import gdx.diablo.codec.DC6; import gdx.diablo.codec.excel.BodyLocs; import gdx.diablo.codec.excel.Inventory; import gdx.diablo.codec.util.BBox; -import gdx.diablo.entity3.Player; +import gdx.diablo.entity.Player; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.item.BodyLoc; import gdx.diablo.item.Item; @@ -309,7 +309,7 @@ public class InventoryPanel extends WidgetGroup implements Disposable { Item cursor = Diablo.cursor.getItem(); if (cursor != null) { if (!ArrayUtils.contains(cursor.type.BodyLoc, bodyPart)) { - Diablo.audio.play("sorceress_impossible_1", false); + Diablo.audio.play(gameScreen.player.stats.getCharClass().name().toLowerCase() + "_impossible_1", false); return; } diff --git a/core/src/gdx/diablo/screen/CreateCharacterScreen.java b/core/src/gdx/diablo/screen/CreateCharacterScreen.java index 700d3209..6be84b62 100644 --- a/core/src/gdx/diablo/screen/CreateCharacterScreen.java +++ b/core/src/gdx/diablo/screen/CreateCharacterScreen.java @@ -26,7 +26,7 @@ import gdx.diablo.CharClass; import gdx.diablo.Diablo; import gdx.diablo.codec.Animation; import gdx.diablo.codec.DC6; -import gdx.diablo.entity3.Player; +import gdx.diablo.entity.Player; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.loader.DC6Loader; import gdx.diablo.widget.CharButton; diff --git a/core/src/gdx/diablo/screen/GameScreen.java b/core/src/gdx/diablo/screen/GameScreen.java index b78a6f96..21b37192 100644 --- a/core/src/gdx/diablo/screen/GameScreen.java +++ b/core/src/gdx/diablo/screen/GameScreen.java @@ -22,7 +22,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.scenes.scene2d.utils.UIUtils; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.utils.Timer; import org.apache.commons.io.IOUtils; @@ -34,7 +34,7 @@ import java.io.PrintWriter; import gdx.diablo.Diablo; import gdx.diablo.Keys; -import gdx.diablo.entity3.Player; +import gdx.diablo.entity.Player; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.graphics.PaletteIndexedColorDrawable; import gdx.diablo.key.MappedKey; @@ -49,6 +49,7 @@ import gdx.diablo.panel.InventoryPanel; import gdx.diablo.panel.MobilePanel; import gdx.diablo.panel.StashPanel; import gdx.diablo.server.Connect; +import gdx.diablo.server.ConnectResponse; import gdx.diablo.server.Disconnect; import gdx.diablo.server.Message; import gdx.diablo.server.MoveTo; @@ -92,7 +93,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable //Char character; public Player player; - ObjectMap otherPlayers = new ObjectMap<>(); + IntMap entities = new IntMap<>(); Timer.Task updateTask; Socket socket; @@ -132,7 +133,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable this.fontColor = Diablo.colors.white; this.cursor = new TextureRegionDrawable(Diablo.textures.white); }}); - output.setDebug(true); + //output.setDebug(true); output.setSize(Diablo.VIRTUAL_WIDTH * 0.75f, Diablo.fonts.fontformal12.getLineHeight() * 8); output.setPosition(10, Diablo.VIRTUAL_HEIGHT - 10, Align.topLeft); output.setAlignment(Align.topLeft); @@ -321,20 +322,30 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable Connect connect = packet.readValue(Connect.class); output.appendText(Diablo.string.format(3641, connect.name)); output.appendText("\n"); - Player q = player.clone(); - q.setOrigin(player.origin().cpy()); - otherPlayers.put(connect.name, q); + + // FIXME: Default position is in subtiles? Divide 5 temp fix + Player connector = new Player(connect); + GridPoint2 startPos = map.find(Map.ID.TOWN_ENTRY_1); + connector.position().set(startPos.x, startPos.y, 0); + entities.put(connect.id, connector); break; case Packets.DISCONNECT: Disconnect disconnect = packet.readValue(Disconnect.class); output.appendText(Diablo.string.format(3642, disconnect.name)); output.appendText("\n"); - otherPlayers.remove(disconnect.name); + entities.remove(disconnect.id); break; case Packets.MOVETO: MoveTo moveTo = packet.readValue(MoveTo.class); - Player p = otherPlayers.get(moveTo.name); - if (p != null) p.origin().set(moveTo.x, moveTo.y); + Player p = entities.get(moveTo.id); + if (p != null) { + p.position().set(moveTo.x, moveTo.y, 0); + p.setAngle(moveTo.angle); + } + break; + case Packets.CONNECT_RESPONSE: + ConnectResponse connectResponse = packet.readValue(ConnectResponse.class); + entities.put(connectResponse.id, player); break; } } @@ -357,9 +368,9 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable //int spx = + (player.getOrigin().x * Tile.SUBTILE_WIDTH50) - (player.getOrigin().y * Tile.SUBTILE_WIDTH50); //int spy = - (player.getOrigin().x * Tile.SUBTILE_HEIGHT50) - (player.getOrigin().y * Tile.SUBTILE_HEIGHT50); //player.draw(b, spx, spy); - player.draw(b); + //player.draw(b); - for (Player p : otherPlayers.values()) { + for (Player p : entities.values()) { p.draw(b); } @@ -398,8 +409,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable //character.x = origin.x; //character.y = origin.y; - player.origin().set(origin); - Gdx.app.debug(TAG, player.toString()); + player.position().set(origin.x, origin.y, 0); Keys.Esc.addStateListener(mappedKeyStateListener); Keys.Inventory.addStateListener(mappedKeyStateListener); @@ -414,15 +424,22 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable Gdx.app.log(TAG, "connecting to " + socket.getRemoteAddress() + "..."); in = IOUtils.buffer(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); + + String connect = Packets.build(new Connect(player)); + out.println(connect); } updateTask = Timer.schedule(new Timer.Task() { + GridPoint2 position = new GridPoint2(); + @Override public void run() { if (UIUtils.shift()) return; - player.move(); - mapRenderer.setPosition(player.origin()); - String moveTo = Packets.build(new MoveTo(player.stats.getName(), player.origin())); + boolean moved = player.move(); + position.set((int) player.position().x, (int) player.position().y); + mapRenderer.setPosition(position); + if (!moved) return; + String moveTo = Packets.build(new MoveTo(position, player.getAngle())); out.println(moveTo); } }, 0, 1 / 25f); @@ -463,7 +480,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable @Override public void pause() { - escapePanel.setVisible(true); + //escapePanel.setVisible(true); } @Override diff --git a/core/src/gdx/diablo/screen/LobbyScreen.java b/core/src/gdx/diablo/screen/LobbyScreen.java index d20d71e2..d37b3cc3 100644 --- a/core/src/gdx/diablo/screen/LobbyScreen.java +++ b/core/src/gdx/diablo/screen/LobbyScreen.java @@ -39,7 +39,7 @@ import java.net.SocketTimeoutException; import gdx.diablo.Diablo; import gdx.diablo.codec.DC6; -import gdx.diablo.entity3.Player; +import gdx.diablo.entity.Player; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.loader.DC6Loader; import gdx.diablo.server.Account; @@ -280,7 +280,7 @@ public class LobbyScreen extends ScreenAdapter { Net.HttpRequest request = new HttpRequestBuilder() .newRequest() .method(Net.HttpMethods.POST) - .url("http://hydra:6112/create-session") + .url("http://" + Diablo.client.getRealm() + ":6112/create-session") .jsonContent(new Session.Builder() {{ name = tfGameName.getText(); password = tfPassword.getText(); @@ -462,7 +462,7 @@ public class LobbyScreen extends ScreenAdapter { Net.HttpRequest request = new HttpRequestBuilder() .newRequest() .method(Net.HttpMethods.GET) - .url("http://hydra:6112/get-sessions") + .url("http://" + Diablo.client.getRealm() + ":6112/get-sessions") .build(); Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() { @Override @@ -528,7 +528,7 @@ public class LobbyScreen extends ScreenAdapter { @Override public void run() { try { - socket = Gdx.net.newClientSocket(Net.Protocol.TCP, "hydra", 6113, new SocketHints()); + socket = Gdx.net.newClientSocket(Net.Protocol.TCP, Diablo.client.getRealm(), 6113, new SocketHints()); in = IOUtils.buffer(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); } catch (Throwable t) { diff --git a/core/src/gdx/diablo/screen/LoginScreen.java b/core/src/gdx/diablo/screen/LoginScreen.java index d085817f..1427e1cc 100644 --- a/core/src/gdx/diablo/screen/LoginScreen.java +++ b/core/src/gdx/diablo/screen/LoginScreen.java @@ -115,7 +115,7 @@ public class LoginScreen extends ScreenAdapter { Net.HttpRequest request = new HttpRequestBuilder() .newRequest() .method(Net.HttpMethods.POST) - .url("http://hydra:6112/login") + .url("http://" + Diablo.client.getRealm() + ":6112/login") .jsonContent(new Account.Builder() {{ account = "test"; }}) .build(); Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() { diff --git a/core/src/gdx/diablo/screen/SelectCharacterScreen.java b/core/src/gdx/diablo/screen/SelectCharacterScreen.java index 08c441fd..b71a552a 100644 --- a/core/src/gdx/diablo/screen/SelectCharacterScreen.java +++ b/core/src/gdx/diablo/screen/SelectCharacterScreen.java @@ -17,7 +17,7 @@ import com.badlogic.gdx.utils.Array; import gdx.diablo.Diablo; import gdx.diablo.codec.D2S; import gdx.diablo.codec.DC6; -import gdx.diablo.entity3.Player; +import gdx.diablo.entity.Player; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.loader.DC6Loader; import gdx.diablo.widget.SelectButton; diff --git a/core/src/gdx/diablo/screen/SelectCharacterScreen2.java b/core/src/gdx/diablo/screen/SelectCharacterScreen2.java index 20bd52e8..176baa54 100644 --- a/core/src/gdx/diablo/screen/SelectCharacterScreen2.java +++ b/core/src/gdx/diablo/screen/SelectCharacterScreen2.java @@ -17,7 +17,7 @@ import com.badlogic.gdx.utils.Array; import gdx.diablo.Diablo; import gdx.diablo.codec.D2S; import gdx.diablo.codec.DC6; -import gdx.diablo.entity3.Player; +import gdx.diablo.entity.Player; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.loader.DC6Loader; import gdx.diablo.server.Account; diff --git a/core/src/gdx/diablo/server/Connect.java b/core/src/gdx/diablo/server/Connect.java index c1d47701..239277b0 100644 --- a/core/src/gdx/diablo/server/Connect.java +++ b/core/src/gdx/diablo/server/Connect.java @@ -1,13 +1,29 @@ package gdx.diablo.server; +import gdx.diablo.entity.Player; + public class Connect { + public int id; public String name; + public int classId; + public byte[] composites; + public byte[] colors; private Connect() {} - public Connect(String name) { - this.name = name; + public Connect(String name, int classId, byte[] composites, byte[] colors) { + this.name = name; + this.classId = classId; + this.composites = composites; + this.colors = colors; } + public Connect(Player player) { + Player.D2SStats stats = (Player.D2SStats) player.stats; + this.name = stats.d2s.name; + this.classId = stats.d2s.charClass; + this.composites = stats.d2s.composites; + this.colors = stats.d2s.colors; + } } diff --git a/core/src/gdx/diablo/server/ConnectResponse.java b/core/src/gdx/diablo/server/ConnectResponse.java new file mode 100644 index 00000000..4890c9fb --- /dev/null +++ b/core/src/gdx/diablo/server/ConnectResponse.java @@ -0,0 +1,13 @@ +package gdx.diablo.server; + +public class ConnectResponse { + + public int id; + + private ConnectResponse() {} + + public ConnectResponse(int id) { + this.id = id; + } + +} diff --git a/core/src/gdx/diablo/server/Disconnect.java b/core/src/gdx/diablo/server/Disconnect.java index e2921340..ea0575e6 100644 --- a/core/src/gdx/diablo/server/Disconnect.java +++ b/core/src/gdx/diablo/server/Disconnect.java @@ -2,11 +2,13 @@ package gdx.diablo.server; public class Disconnect { + public int id; public String name; private Disconnect() {} - public Disconnect(String name) { + public Disconnect(int id, String name) { + this.id = id; this.name = name; } diff --git a/core/src/gdx/diablo/server/Event.java b/core/src/gdx/diablo/server/Event.java new file mode 100644 index 00000000..dc0fc2d7 --- /dev/null +++ b/core/src/gdx/diablo/server/Event.java @@ -0,0 +1,16 @@ +package gdx.diablo.server; + +import gdx.diablo.Diablo; + +public class Event { + + public int id; + public String[] args; + + private Event() {} + + @Override + public String toString() { + return Diablo.string.format(id, (Object[]) args); + } +} diff --git a/core/src/gdx/diablo/server/MoveTo.java b/core/src/gdx/diablo/server/MoveTo.java index 1abd6be5..5e4307cc 100644 --- a/core/src/gdx/diablo/server/MoveTo.java +++ b/core/src/gdx/diablo/server/MoveTo.java @@ -4,16 +4,17 @@ import com.badlogic.gdx.math.GridPoint2; public class MoveTo { - public String name; - public int x; - public int y; + public int id; + public int x; + public int y; + public float angle; private MoveTo() {} - public MoveTo(String name, GridPoint2 origin) { - this.name = name; + public MoveTo(GridPoint2 origin, float angle) { x = origin.x; y = origin.y; + this.angle = angle; } } diff --git a/core/src/gdx/diablo/server/Packets.java b/core/src/gdx/diablo/server/Packets.java index 12de4c1e..178d5339 100644 --- a/core/src/gdx/diablo/server/Packets.java +++ b/core/src/gdx/diablo/server/Packets.java @@ -15,14 +15,16 @@ public class Packets { public static final int CONNECT = 2; public static final int DISCONNECT = 3; public static final int MOVETO = 4; + public static final int CONNECT_RESPONSE = 5; private static final ObjectIntMap MAP; static { MAP = new ObjectIntMap<>(); - MAP.put(Message.class, 1); - MAP.put(Connect.class, 2); - MAP.put(Disconnect.class, 3); - MAP.put(MoveTo.class, 4); + MAP.put(Message.class, 1); + MAP.put(Connect.class, 2); + MAP.put(Disconnect.class, 3); + MAP.put(MoveTo.class, 4); + MAP.put(ConnectResponse.class, 5); } public static T parse(Class type, String json) { diff --git a/core/src/gdx/diablo/server/Server.java b/core/src/gdx/diablo/server/Server.java index 9e39b285..e16d1a8c 100644 --- a/core/src/gdx/diablo/server/Server.java +++ b/core/src/gdx/diablo/server/Server.java @@ -6,6 +6,7 @@ import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.net.ServerSocket; import com.badlogic.gdx.net.Socket; import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.utils.Json; import com.badlogic.gdx.utils.JsonReader; @@ -18,6 +19,8 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; +import gdx.diablo.entity.Entity; + public class Server implements Disposable, Runnable { private static final String TAG = "Server"; @@ -30,6 +33,7 @@ public class Server implements Disposable, Runnable { Thread connectionListener; int port; String name; + IntMap entities = new IntMap<>(); // TODO: synchronize public Server(int port) { this(port, ""); @@ -52,7 +56,7 @@ public class Server implements Disposable, Runnable { while (!kill.get()) { try { Socket socket = server.accept(null); - new Client(socket, "Tirant").start(); + new Client(socket).start(); Gdx.app.log(name, "connection from " + socket.getRemoteAddress()); } catch (Throwable t) { Gdx.app.log(name, t.getMessage(), t); @@ -123,12 +127,13 @@ public class Server implements Disposable, Runnable { Socket socket; BufferedReader in; PrintWriter out; - String name; - public Client(Socket socket, String name) { + int id; + Connect connect; + + public Client(Socket socket) { super(clientThreads, "Client-" + String.format("%08X", MathUtils.random(1, Integer.MAX_VALUE - 1))); this.socket = socket; - this.name = name; } @Override @@ -137,7 +142,18 @@ public class Server implements Disposable, Runnable { in = IOUtils.buffer(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); - String connect = Packets.build(new Connect(name)); + connect = Packets.parse(Connect.class, in.readLine()); + id = connect.id = entities.size + 1; + entities.put(id, null); + + String connectResponse = Packets.build(new ConnectResponse(id)); + out.println(connectResponse); + for (Client client : clients) { + out.println(Packets.build(client.connect)); + } + + String connect = Packets.build(this.connect); + Gdx.app.log(getName(), connect); for (Client client : clients) { client.out.println(connect); //client.out.println("CONNECT " + socket.getRemoteAddress()); @@ -160,6 +176,9 @@ public class Server implements Disposable, Runnable { } break; case Packets.MOVETO: + MoveTo moveTo = packet.readValue(MoveTo.class); + moveTo.id = id; + input = Packets.build(moveTo); for (Client client : clients) { if (client == this) continue; client.out.println(input); @@ -171,10 +190,11 @@ public class Server implements Disposable, Runnable { } catch (Throwable t) { Gdx.app.log(getName(), "ERROR " + socket.getRemoteAddress() + ": " + t.getMessage()); } finally { + entities.remove(id); clients.remove(this); String message = "DISCONNECT " + socket.getRemoteAddress(); Gdx.app.log(getName(), message); - String disconnect = Packets.build(new Disconnect(name)); + String disconnect = Packets.build(new Disconnect(id, connect.name)); for (Client client : clients) { client.out.println(disconnect); } diff --git a/core/src/gdx/diablo/widget/BNetConnectDialog.java b/core/src/gdx/diablo/widget/BNetConnectDialog.java index 84931bfd..108168d8 100644 --- a/core/src/gdx/diablo/widget/BNetConnectDialog.java +++ b/core/src/gdx/diablo/widget/BNetConnectDialog.java @@ -101,7 +101,7 @@ public class BNetConnectDialog extends Dialog { Net.HttpRequest request = new HttpRequestBuilder() .newRequest() .method(Net.HttpMethods.GET) - .url("http://hydra:6112/find-server") + .url("http://" + Diablo.client.getRealm() + ":6112/find-server") .build(); Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() { @Override diff --git a/core/src/gdx/diablo/widget/ItemGrid.java b/core/src/gdx/diablo/widget/ItemGrid.java index 50e475ca..280214f2 100644 --- a/core/src/gdx/diablo/widget/ItemGrid.java +++ b/core/src/gdx/diablo/widget/ItemGrid.java @@ -20,7 +20,7 @@ import gdx.diablo.BlendMode; import gdx.diablo.Diablo; import gdx.diablo.codec.excel.Inventory; import gdx.diablo.codec.excel.ItemEntry; -import gdx.diablo.entity3.Player; +import gdx.diablo.entity.Player; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.item.Item; diff --git a/server/src/gdx/diablo/server/ServerBrowser.java b/server/src/gdx/diablo/server/ServerBrowser.java index a299c848..11f769c1 100644 --- a/server/src/gdx/diablo/server/ServerBrowser.java +++ b/server/src/gdx/diablo/server/ServerBrowser.java @@ -13,9 +13,11 @@ import com.badlogic.gdx.utils.Json; import org.apache.commons.io.IOUtils; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.InetAddress; +import java.net.URL; import java.net.UnknownHostException; import java.text.DateFormat; import java.util.Calendar; @@ -31,6 +33,9 @@ public class ServerBrowser extends ApplicationAdapter { new HeadlessApplication(new ServerBrowser(), config); } + private static final boolean EXT_HOST = true; + private String host; + private final Json json = new Json(); private Map sessions = new ConcurrentHashMap<>(); @@ -43,15 +48,39 @@ public class ServerBrowser extends ApplicationAdapter { ServerBrowser() {} + private static String getIp() { + if (!EXT_HOST) { + try { + InetAddress address = InetAddress.getLocalHost(); + return address.getHostAddress(); + } catch (UnknownHostException e) { + Gdx.app.error(TAG, e.getMessage(), e); + return "hydra"; + } + } + + BufferedReader in = null; + try { + in = new BufferedReader(new InputStreamReader(new URL("http://checkip.amazonaws.com").openStream())); + return in.readLine(); + } catch (IOException e) { + Gdx.app.error(TAG, e.getMessage(), e); + return "hydra"; + } finally { + IOUtils.closeQuietly(in); + } + } + @Override public void create() { final Calendar calendar = Calendar.getInstance(); DateFormat format = DateFormat.getDateTimeInstance(); Gdx.app.log(TAG, format.format(calendar.getTime())); + host = getIp(); try { InetAddress address = InetAddress.getLocalHost(); - Gdx.app.log(TAG, "IP Address: " + address.getHostAddress()); + Gdx.app.log(TAG, "IP Address: " + host); Gdx.app.log(TAG, "Host Name: " + address.getHostName()); } catch (UnknownHostException e) { Gdx.app.error(TAG, e.getMessage(), e); @@ -143,7 +172,7 @@ public class ServerBrowser extends ApplicationAdapter { } Session session = builder.build(); - session.host = "hydra"; + session.host = host; session.port = 6114 + sessions.size(); sessions.put(session.getName(), session);