+CODE STATUS 11/13
+-----------------
+
+As most of the 'smarts' in Dusk happens in the server the client is
+fairly simple - it basically handles some local input and rendering
+the tiles.
+
+This should be feature complete against the latest DuskServer, but
+needs some aesthetic and usability work.
+
+Original README follows.
+
README
------
This is the client frontend to DuskZ, it uses JavaFX. It connects
Currently a recent Oracle JRE is required to execute this application.
-This is currently in an alpha state and in very active development.
+This is currently in an alpha state.
... to be completed ...
You should have received a copy of the GNU General Public License
along with DuskZ. If not, see <http://www.gnu.org/licenses/>.
+
+
+ DuskZ also uses the ListSpinner widget from JFXExtras.
+
+/**
+ * Copyright (c) 2011, JFXtras
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
\ No newline at end of file
public synchronized void addEntity(Entity e) {
if (entityByID.containsKey(e.ID)) {
- throw new RuntimeException("entity already in table " + e.ID + " " + e.strName);
+ throw new RuntimeException("entity already in table " + e.ID + " " + e.name);
}
entityByID.put(e.ID, e);
import duskz.protocol.*;
import duskz.protocol.DuskMessage.*;
-import static duskz.protocol.DuskProtocol.FIELD_AUTH_NEWPLAYER;
-import static duskz.protocol.DuskProtocol.FIELD_AUTH_REASON;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
/**
* FIXME: all needs a rewrite
*
- * @param entStore
+ * @param e
*/
// add the thing, this renumbers any that have the same name
- void addEntity(Entity entStore) {
- System.out.println("Add entity: " + entStore.ID + " " + entStore.strName);
- map.addEntity(entStore);
+ void addEntity(Entity e) {
+ System.out.println("Add entity: " + e.ID + " " + e.name + " " + e.locx + "x" + e.locy);
+ map.addEntity(e);
// FIXME: call rement if it was out of range so the server knows
}
case MSG_CLEAR_FLAGS:
synchronized (map) {
for (Entity e : map.getEntities()) {
- e.intFlag = 0;
+ e.flags = 0;
}
}
update();
//frame.info.setText("HP: " + inthp + "/" + intmaxhp + " MP: " + intsp + "/" + intmaxsp + " Loc: " + LocX + "/" + LocY);
buyList.clear();
reloadChoiceLookGetAttack();
-
+
// Perhaps i need a status message to go from 'starting' to 'ready'
loaded = true;
break;
for (DuskMessage m : el.value) {
switch (m.name) {
case FIELD_ENTITY_FLAGS:
- e.intFlag = ((IntegerMessage) m).value;
+ e.flags = ((IntegerMessage) m).value;
+ break;
+ case FIELD_ENTITY_CONDITIONS:
+ e.conditions = ((StringListMessage) m).value;
break;
}
}
e.intMoveDirection = dir;
}
}
- reloadChoiceAttack();
+ reloadChoiceLookGetAttack();
// TODO: find out what this is for
//if (addit) {
// thrGraphics.addEntityToMove(ent, dir);
nextQuery();
}
- // TBD - use the individual functions
public void reloadChoiceLookGetAttack() {
final ArrayList<Entity> looks = new ArrayList<>();
final ArrayList<Entity> gets = new ArrayList<>();
frame.setTakeList(gets);
}
- public void reloadChoiceLook() {
- final ArrayList<Entity> looks = new ArrayList<>();
-
- synchronized (map) {
- for (Entity e : map.getEntities()) {
- if (status.canLook(e))
- looks.add(e);
- }
- }
- frame.setLookList(looks);
- }
-
- public void reloadChoiceAttack() {
- final ArrayList<Entity> attacks = new ArrayList<>();
-
- synchronized (map) {
- for (Entity e : map.getEntities()) {
- if (status.canAttack(e))
- attacks.add(e);
- }
- }
- frame.setAttackList(attacks);
- }
-
- public void reloadChoiceGet() {
- final ArrayList<Entity> gets = new ArrayList<>();
-
- synchronized (map) {
- for (Entity e : map.getEntities()) {
- if (status.canTake(e))
- gets.add(e);
- }
- }
- frame.setTakeList(gets);
- }
-
public boolean isConnected() {
return connected;
}
docmd(what);
}
- public void command(String what, String params) {
- docmd(what + " " + params);
+ public void command(String what, String... params) {
+ StringBuilder sb = new StringBuilder(what);
+ for (String s : params) {
+ sb.append(' ');
+ sb.append('"');
+ sb.append(s);
+ sb.append('"');
+ }
+ docmd(sb.toString());
}
public void move(Direction dir) {
}
public void buy(TransactionItem item, int quantity) {
- command("buy " + quantity + " " + item.name);
+ command("buy", String.valueOf(quantity), item.name);
}
public void sell(TransactionItem item, int quantity) {
- command("sell " + quantity + " " + item.name);
+ command("sell", String.valueOf(quantity), item.name);
}
private void ping() {
}
public void drop(String what) {
- command("drop " + what);
+ command("drop", what);
}
public void wear(String what) {
- command("wear " + what);
+ command("wear", what);
}
public void unwear(String what) {
- command("unwear " + what);
+ command("unwear", what);
}
}
*/
package duskz.client;
+import duskz.protocol.DuskProtocol;
import duskz.protocol.EntityUpdateMessage;
+import java.util.ArrayList;
+import java.util.List;
public class Entity {
- public String strName;
+ public String name;
public int locx,
locy;
public int intType,
2 west
3 east
*/
- intFlag = 0;
- /*intFlag
+ flags = 0;
+ List<String> conditions = new ArrayList<>(0);
+ /*flags
0 none
1 ally
2 enemy
Entity entNext = null;
public Entity(String instrName, long inID, int inintLocX, int inintLocY, int inImage, int inStep, int inintType) {
- strName = instrName;
+ name = instrName;
ID = inID;
locx = inintLocX;
locy = inintLocY;
}
public Entity(EntityUpdateMessage msg) {
- strName = msg.entityName;
+ name = msg.entityName;
locx = msg.x;
locy = msg.y;
ID = msg.id;
* @return
*/
public String getSimpleName() {
- int i = strName.lastIndexOf(">");
+ int i = name.lastIndexOf(">");
if (i != -1) {
- return strName.substring(i + 1);
+ return name.substring(i + 1);
} else {
- return strName;
+ return name;
}
}
public String getIndexedName() {
- return intNum == 0 ? strName : intNum + "." + strName;
+ return intNum == 0 ? name : intNum + "." + name;
}
@Override
public String toString() {
- return "[Entity " + ID + ", " + strName + ", " + locx + ", " + locy + "]";
+ return "[Entity " + ID + ", " + name + ", " + locx + ", " + locy + "]";
+ }
+
+ public String getTitle() {
+ if (conditions.isEmpty() && flags == 0)
+ return name;
+
+ StringBuilder sb = new StringBuilder(name);
+
+ sb.append('<');
+ boolean first = true;
+ for (String c : conditions) {
+ if (!first) {
+ sb.append(", ");
+ }
+ first = false;
+ sb.append(c);
+ }
+
+ if ((flags & DuskProtocol.ENTITY_FLAG_SLEEPING) != 0) {
+ if (!first) {
+ sb.append(", ");
+ }
+ sb.append("sleeping");
+ }
+
+ sb.append('>');
+
+ return sb.toString();
}
}
import duskz.protocol.DuskMessage.StringMessage;
import duskz.protocol.ListMessage;
import duskz.protocol.TransactionItem;
-import duskz.protocol.Wearing;
+import static duskz.protocol.Wearing.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
*
* @author notzed
*/
-public class Equipment implements Wearing {
+public class Equipment {
/**
* Worn items
StringMessage sm = (StringMessage) dm;
// remap to local id;
- int i = sm.name - 1;
+ int i = sm.name; //sm.name - 1;
if (i >= 0 && i < available.length) {
available[i].add(sm.value);
wearableAt.put(sm.value, i);
}
public boolean canAttack(Entity e) {
+ // Ugh, i'm not sure what the types were, but i think it just meant 'any living thing'.
+ // Dunno why the server doesn't send this message to the client anyway
return e.ID != id
- && ((e.intType == 0 || e.intType == 1 || e.intType == 4)
+// && ((e.intType == 0 || e.intType == 1 || e.intType == 4)
+ && ((e.intType == 0 || e.intType == 1 || e.intType == 2)
&& (distance(e) <= range));
}
}
return new ImageSetFX();
}
+ public void updateTile(ImageView iv, int tileid, int tilewidth, int tileheight) {
+ for (int i = 0; i < tilesets.length; i++) {
+ ImageSetFX is = (ImageSetFX) tilesets[i];
+ if (tileid >= is.gid && tileid < is.gid + is.count) {
+ tileid -= is.gid;
+ iv.setImage(is.image);
+ //iv.setViewport(new Rectangle2D(tileid * is.width, 0, is.width, is.height));
+ iv.setViewport(is.getViewport(tileid));
+ iv.setTranslateY(-(is.height - tileheight));
+ //iv.relocate(tilex * tilewidth, tiley * tileheight - (is.height - tileheight));
+ return;
+ }
+ }
+ }
+
/**
* Create a tile image and align it to the baseline of the map tile.
*
ImageView iv = new ImageView(is.image);
tileid -= is.gid;
- iv.setViewport(new Rectangle2D(tileid * is.width, 0, is.width, is.height));
+ //iv.setViewport(new Rectangle2D(tileid * is.width, 0, is.width, is.height));
+ iv.setViewport(is.getViewport(tileid));
iv.relocate(tilex * tilewidth, tiley * tileheight - (is.height - tileheight));
return iv;
public class ImageSetFX extends ImageSet {
Image image;
+ Rectangle2D tiles[];
public ImageSetFX() {
}
try (InputStream s = getInputStream()) {
image = new Image(s);
}
+
+ tiles = new Rectangle2D[count];
+ for (int i = 0; i < count; i++) {
+ tiles[i] = new Rectangle2D(i * width, 0, width, height);
+ }
}
public Image getImage() {
return image;
}
+
+ public Rectangle2D getViewport(int tileid) {
+ return tiles[tileid];
+ }
}
}
package duskz.client.fx;
import duskz.client.Equipment;
+import duskz.protocol.Wearing;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.value.ChangeListener;
@Override
public String toString() {
- return wornAt == -1 ? name : name + " [worn: " + Equipment.titles[wornAt] + "]";
+ return wornAt == -1 ? name : name + " [worn: " + Wearing.wornTitles[wornAt] + "]";
}
}
import duskz.client.GUI;
import duskz.client.Status;
import duskz.protocol.TransactionItem;
+import duskz.protocol.Wearing;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.animation.FadeTransitionBuilder;
import javafx.animation.Interpolator;
equip.unwear.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent t) {
EquipmentPane.ItemInfo ii = equip.getItem();
- game.unwear(Equipment.names[ii.wornAt]);
+ game.unwear(Wearing.wornNames[ii.wornAt]);
}
});
equip.drop.setOnAction(new EventHandler<ActionEvent>() {
public void visitFile(String file, String text, boolean canSave) {
throw new UnsupportedOperationException("Not supported yet.");
// FIXME: implement edit text,
- // FIXME: sent using: appParent.outstream.writeBytes("submit "+strName+"\n");
+ // FIXME: sent using: appParent.outstream.writeBytes("submit "+name+"\n");
// appParent.outstream.writeBytes(txtEdit.getText()+"\n--EOF--\n");
}
//Accept key input
Logger.getLogger(MainFrameFX.class.getName()).log(Level.SEVERE, null, ex);
}
}
+ TileAnimator animator;
@Override
public void updateMap(ClientMap map) {
// Since we get a whole update for the map every time, there isn't much we
// can practically do apart from simply build a whole new page to display.
+ // However ... this is jused for every type of changed thing, and that
+ // isn't neccessary.
System.out.println("update map");
if (data == null) {
//if (tileImage == null) {
final ArrayList<Node> upper = new ArrayList<>();
int levelCount = map.getLevelCount();
- // Build map
+ // Animated tiles hack test
+ final Rectangle2D[] anims = new Rectangle2D[2];
+ anims[0] = data.createTile(305, 0, 0, tileSize, tileSize).getViewport();
+ anims[1] = data.createTile(304, 0, 0, tileSize, tileSize).getViewport();
+ final List<ImageView> animated = new ArrayList<>();
+
+ // Build map
for (int l = 0; l < levelCount; l++) {
for (int y = 0; y < map.rows; y++) {
// Draw tiles first for whole row
for (int x = 0; x < map.cols; x++) {
int tileid = map.getTile(l, x, y);
if (tileid != 0) {
- children.add(data.createTile(tileid, x, y, tileSize, tileSize));
+ ImageView iv = data.createTile(tileid, x, y, tileSize, tileSize);
+ children.add(iv);
+
+ if (tileid == 305)
+ animated.add(iv);
}
}
-
+
// Now check for entities over this layer row
if (l == map.getGroundLevel()) {
for (int x = 0; x < map.cols; x++) {
public void run() {
graphics.getChildren().setAll(children);
graphics.getChildren().addAll(upper);
+
+ System.out.println("animated node count = " + animated.size());
+ if (animator == null) {
+ animator = new TileAnimator(animated, Duration.seconds(0.25), anims);
+ animator.setCycleCount(Animation.INDEFINITE);
+ animator.play();
+ } else
+ animator.setNodes(animated);
}
});
}
ImageView iv = new ImageView(playerImage);
iv.setViewport(new Rectangle2D((e.intImage * 8 + e.intStep) * spriteSize, 0,
spriteSize, spriteSize));
- iv.relocate((x * tileSize) - tileSize / 2, (y * tileSize) - tileSize / 2);
- iv.setScaleX(0.5);
- iv.setScaleY(0.5);
+ iv.relocate((x * tileSize) + tileSize / 2 - spriteSize / 2, (y * tileSize) - spriteSize / 2);
+ iv.setScaleX(1);
+ iv.setScaleY(1);
children.add(iv);
}
+ // FIXME: intnum not used anymore
if (e.intNum == 0) {
- Text t = new Text(e.strName);
+ Text t = new Text(e.getTitle());
t.setId("entity-label");
t.relocate((x * tileSize) + tileSize / 2 - t.getLayoutBounds().getWidth() / 2, ((y + 1) * tileSize));
upper.add(t);
} else {
- Text t = new Text(e.intNum + "." + e.strName);
+ Text t = new Text(e.intNum + "." + e.name);
t.setId("entity-label");
t.relocate((x * tileSize) + tileSize / 2 - t.getLayoutBounds().getWidth() / 2, ((y + 1) * tileSize));
upper.add(t);
}
//Draw flag
- if (e.intFlag != 0) {
+ if (e.flags != 0) {
Rectangle r = new Rectangle(1, 1, tileSize - 2, tileSize - 2);
- if (e.intFlag == 1) {
+ if ((e.flags & 3) == 1) {
r.setStroke(Color.GREEN);
- } else if (e.intFlag == 2) {
+ } else if ((e.flags & 3) == 2) {
r.setStroke(Color.RED);
}
r.setStrokeWidth(2);
--- /dev/null
+/*
+ * This file is part of DuskZ, a graphical mud engine.
+ *
+ * Copyright (C) 2013 Michael Zucchi <notzed@gmail.com>
+ *
+ * DuskZ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DuskZ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DuskZ. If not, see <http://www.gnu.org/licenses/>.
+ */
+package duskz.client.fx;
+
+import java.util.List;
+import javafx.animation.Transition;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.image.ImageView;
+import javafx.util.Duration;
+
+/**
+ * Animates a set of tiles by switching the viewport on the texture.
+ *
+ * @author Michael Zucchi <notzed@gmail.com>
+ */
+public class TileAnimator extends Transition {
+
+ Rectangle2D[] viewports;
+ List<ImageView> nodes;
+
+ public TileAnimator(List<ImageView> nodes, Duration duration, Rectangle2D[] images) {
+ setCycleDuration(duration);
+ this.viewports = images;
+ this.nodes = nodes;
+ }
+
+ public void setNodes(List<ImageView> nodes) {
+ this.nodes = nodes;
+ }
+
+ @Override
+ protected void interpolate(double d) {
+ int index = Math.min(viewports.length - 1, (int) (d * viewports.length));
+
+ for (ImageView node : nodes)
+ node.setViewport(viewports[index]);
+ }
+}