About Me
Michael Zucchi
B.E. (Comp. Sys. Eng.)
also known as Zed
to his mates & enemies!
< notzed at gmail >
< fosstodon.org/@notzed >
de-awt-ed
``I'll just fix up a few little things'' turned into ``Another full-day of hacking'' on my fork of Dusk.
I finally managed to de-awt everything. Well there are a couple of missing features (text viewer/editor), but the last major window to convert is in:
To make this a bit nicer I think I need to modify the protocol so the server tells me more about the items such as item counts as it does with the inventory command. And there are other possibilities such as graphics, and "detailed description". It should probably also split up equipment and general inventory in some way or another ...
Well at least the layout didn't take any time today even if it looks it.
What took all the time was rewriting another big chunk of the client code map tracking and abstracting away the user interface from the client logic.
For the map code I again abstracted it into a separate structure and provided accessors and indices of various things, although now i'm reflecting on it I don't think some of the stuff I coded is even used since the algorithms that used them are gone. For example there was code to find entities by name - but internally i'm just using unique ids now so having an index by name it isn't required anymore. One major change I did was to just keep track of entities (players, mobs etc) using absolute coordinates rather than trying to keep them forever up-to-date with the current viewing window. Apart from being a great deal easier to use it is obviously more efficient. There were a lot of "x - offset1 + offset2" type things everywhere anything was accessed, and instead I just made the map to be relative to the top-left corner rather than the centre, and track the player location separately.
I also changed entity based actions to use id numbers instead of indexed names. Basically if there is more than one object of a given type on the screen then some messy code gives each one a unique number. The problem is the client and server do this independently, and I couldn't be stuffed finding out how it works - one of the benefits of the map abstraction was killing a pile of ugly code, and it was buggy anyway. Since the server and client already uses unique id's for everything I just added support in the server for doing targeted actions based on id instead. The client can always do the lookup if the command line needs to support it, and I guess I can now clean up that mess in the server too.
And finally the entire user interface and system interaction is hidden behind a re-usable interface, for the obvious sub-java/sub-gnu operating system option.
So far the de-awting seems to have made is more stable too. Although I also fixed some thread issues in the client as the game runs on a separate thread to JavaFX.
Shopfront
Given the day started raining I spent most of it hacking on Dusk again. Coded up a shop-front.
Yeah the styling is naff, but it isn't meant to be better. The main window blurs when it's shown.
I gotta say the layout was a lot messier than I would have thought. VBox and HBox only allow consistent alignment for all items in the stack - which seems pretty pointless to me. I'd have thought a common use-case for a VBox would be a stack of things with different alignments. And I couldn't get GridPane or BorderPane to work very well either, although perhaps I could now with the following knowledge.
Basically to perform arbitrary alignment and expansion of cells within a vbox or hbox, one has to embed a hbox or vbox for each containee, set it to fill, and then set the containee alingment on that. It's just rather clumsy. As for the table view I had to hard-code everything to make it behave at all.
And learning a new toolkit is always a pain - probably took 5 hours to do this one simple window even though i've been working with layout based toolkits since gtk 1. And even then it seems to have some nasty javafx bug in which the table view in the Sell tab doesn't render properly. I really must try this stuff on another box to see if it is just something wrong locally like the shitty Intel IGP or it being 32-bit.
Playstation 4
Ok so I guess I do have a passing interest in the Playstation 4 hardware after all. Although I have only read a couple of articles about it and can't be bothered watching the press conference.
The unified GDDR5 memory sounds really awesome - I think it would make a great workstation. Memory bandwidth is a big bottleneck on any current PC, particularly with the separated memory spaces and the PCI barrier. Pity it's going to be a locked down proprietary system though, so unless it can run GNU/Linux completely then it really just isn't going to interest me this time. I really wonder if the time of the proprietary console hasn't passed ... it's hard justifying the expensive games and expensive blu-rays if most people are happy with flash games and youtube. For entertainment i'm more inclined to read a book, write software, or make something with my hands than play a game - i've got a few unopened PS3 games as it is (and no it isn't ironic that my latest foray into software is a game ...).
On the other hand, the hardware itself isn't particularly proprietary this time - maybe there are some customisations but it is basically just off-the-shelf AMD hardware. The unified memory is one thing mentioned with AMD's HSA plans, and is critical to their performance road-map, so with any luck we'll see comparable APU motherboards available for other OEMS. In the SteamBox perhaps? AMD are hinting at some 'good news for gamers' coming up at any rate. It's about time PC architecture got a bit of a rev-up, Intel has held it back for too long with their over-engineered-junk-as-a-competitive-strategy approach. Well fingers crossed anyway, although this is just a bit of hopeful optimism at this point.
And as an aside it's sad to see CELL BE go. Or it would be if you could ever have gotten a PC with one in it, and they kept up the development to keep it competitive. I learnt a lot about hetereogenous processing, SIMD, and parallel processing from my short stint with it.
On the software side - what's this obsession with "social" junk? Gah. No thanks. Wasted enough of my life on that shit in the past, and when I do want to interact with people I want to be with them. And when I don't i'd rather be properly alone. I can understand the Google's and other people-spying companies of this world wanting everyone to perform all interactions on line (whilst some rentier takes a cut of the cost!), but physically meeting friends is much more satisfying, private and totally free in all meanings of the word.
Early dawn of a new dusk.
Well I basically spent the whole weekend hacking on the Dusk server code. A lot of refactoring and code cleaning, fixing messy logic, abstraction and information hiding. Getting rid of the nasty hungarian notation, moving to modern java types and constructs, and so on.
Broke a lot on the way, but managed to fix some of it and found a few bugs here and there too.
Some of the 'new' Java features that have been particularly handy:
- Switch on string. I didn't think much of the feature when I heard about it, but with some of the code having "symbol" tables with 50+ tests implemented using if(), the switch statement fit in very well. I did try converting the command parser to use a hash table but it required so many classes it blew out the code-size too much;
- Generic containers just to clean up a lot of unnecessary casts;
- The container classes other than "Vector". This replaced some custom container classes or eliminated a pile of code;
- Iterators. Let me do nice things with the map (more below);
- Subroutines. Quite a lot of code was just cut and pasted from place to place, and simply moving the snippets to parametrised functions just cleaned up a lot of clutter. Don't underestimate the power of subroutines;
- foreach came in handy in many places;
- inner classes allow for information hiding and better abstraction.
The Tile Map
So apart from just general code cleaning I also replaced big chunks of code with simpler or better implementations. One of the bigger pieces was the TileMap class.
Previously the code had a couple of 2D arrays to hold the tile numbers and the entities on the given tiles. Every bit of code (from anywhere in the class structure) that needed it just accessed it directly - sometimes with a lock, sometimes with the wrong lock, and sometimes with no lock at all. So apart from having a public array being accessed willy-nilly, most of the uses simply fell into the category of scanning the player-viewable area for information. i.e. ripe for code re-use.
As an aside, much of it just wasn't very well done code, it looks like whomever wrote it had some ArrayIndexOutOfBounds exception problems and kept poking it till it didn't crash then cut & pasted the result everywhere. For example:
int i,
i2,
i3,
i4;
DuskObject objStore;
i=0;
if (thnRefresh.intLocX-viewrange < 0) {
i = -1*(thnRefresh.intLocX-viewrange);
}
i2=0;
if (thnRefresh.intLocY-viewrange < 0) {
i2 = -1*(thnRefresh.intLocY-viewrange);
}
for (;i<mapsize;i++) {
if (thnRefresh.intLocX+i-viewrange < MapColumns) {
for (i3=i2;i3<mapsize;i3++) {
if (thnRefresh.intLocY+i3-viewrange < MapRows) {
objStore = objEntities[thnRefresh.intLocX+i-viewrange][thnRefresh.intLocY+i3-viewrange];
while (objStore != null) {
... process ...
objStore = objStore.objNext;
}
}
}
}
}
And this example is synchronized on this - which is the wrong lock.
So apart from the curious convention of post-fixing almost every reference with "Store", and giving loop indices meaningful names like 'i2', the logic is rather convoluted. Took me too much effort to realise it is a simple 2D scanning loop over (x-viewrange, x+viewrange) and (y-viewrange, y+viewrange) with some bounds checking; which can obviously go outside the loop.
As this was repeated in a few places I decided to encapsulate it into the TileMap and provide an Iterable interface that lets me share that logic. It also allows it to hide implementation details (e.g. rather than a 2d array, it is a 1D array - and it should probably just use a hash-table anyway) and handle locking reliably.
Of course the primary benefit is that it just simplifies every use:
for (TileMap.MapData md : map.range(refresh.x, refresh.y, viewrange)) {
for (DuskObject o : md.entities) {
... process ...
}
}
MapData includes both the tile info and the entities on that tile, and may in the future include per-tile scripts and so on (i'm not sure yet as the script stuff needs a context - which is currently the main game object). The object itself (MapData) and the list is only allocated once on the iterator, so it is cheap and relatively gc friendly to iterate.
But wait, there's more! There were two other main users of the map which required poking around it's internals: visibility testing (i.e. who can see what) and path finding. So I also implemented these inside the map, and took the opportunity to improve the algorithms (although they might need tweaking).
For looking I used Bresenham's algorithm for testing of obstructions through the line of sight. It does give 'enhanced vision' in that it will allow for looking diagonally through gaps which was not possible before, but that can be tweaked if it is a problem.
For path finding I used an implementation of A* (I started with this one) which is restricted to moves in cardinal directions. Since traverse-ability testing requires script execution a callback is used so the map code doesn't need to know about such things. Actually it is perhaps a bit too powerful - it will find a route to any tile you can see even if it has to traipse all the way around the map to get there. But again this can be tweaked without having to change every bit of code that uses it.
In both cases the meat of the code is somewhat smaller than that which it replaced and it is much easier to re-use, at only a modest cost of a bit of plumbing. e.g. a very simple example which prints the directions required to get from start to end, only moving on empty tiles:
MoveListener callback = new MoveListener() {
public boolean canMoveto(MapData md) {
return md.tile == 0;
}
};
for (TileMap.MoveData md : map.move(startx, starty, endx, endy, flags, callback)) {
System.out.println("Move: " + md.direction);
}
Next?
Well there is still on-going renaming, hiding, and abstraction work in the server. Most i/o isn't implemented reliably (e.g. delete file + write RandomAccessfile). The script compiler/executor uses non-symbolic integers as opcodes. There's too much use of a try-catch to hide bugs rather than fixing them. And there's some bugs in visibility syncing with the client which i've had troubles with - probably ripe for a complete re-do because after too many failed attempts at understanding the code, maybe it's the code at fault and not me.
As for the client, it still has some awt windows which need replacing. The shop and equipment screens are the primary ones, and it could always use an inventory screen as well. Once i've had enough of poking the server I might look at that. One problem on the client is that I fairly regularly get some exception inside javafx which stops it rendering - requiring a restart of the application. Seems to be a javafx bug on this system (i'm coding on my laptop, which I normally only use for C).
And for efficiency's sake at some point the server protocol should be converted to binary, or at least have a binary option. Well, actually a more robust protocol needs to be created.
Then at some point there will also be work on a code release, a couple of issues to sort out first and I think the client should be awt-free before then too. But i'll try to do it before I lose interest and find something better to occupy my time!
Update: Just found out i'm back to work in a week so I might get a bit more in while it's hot in my mind but I need to wind down to prepare for work again too.
The hourly WTF?
Ok so a bit of "cut and paste" code-re-use is one thing, but I think we have a grand-prize-medal winner here ...
public void updateEquipment() {
try {
String strResult = "" + (char) 7;
try {
strResult += equWorn.wield.name + "\n";
} catch (Exception e) {
strResult += "none\n";
}
try {
strResult += equWorn.arms.name + "\n";
} catch (Exception e) {
strResult += "none\n";
}
try {
strResult += equWorn.legs.name + "\n";
} catch (Exception e) {
strResult += "none\n";
}
try {
strResult += equWorn.torso.name + "\n";
} catch (Exception e) {
strResult += "none\n";
}
... repeats a few more times ...
I'm not having a go at the original author - I think it was written a long time ago when he was inexperienced, but it's pretty funny nonetheless.
More Dusk
Kept poking on the Dusk source code over the last few days, trying to put more of the "G" into the GMUD name.
I've converted the server list to an embedded Node, which handles login and character creation as well.
This is current the server/login window complete with a pretty shithouse stylsheet.
And if the server asks for it, the race.
I managed to fit these in at first by just screen-scraping some of the responses and changing them to graphical output, but I will probably look at extending the server protocol to handle it in a more controllable manner.
First I had to get the server built - which was pretty easy - except then I decided to move a big chunk of it to a different namespace. Well I got it done eventually but had to make a lot of stuff public - the code is pretty poor overall. I also spent a bit of time modernising the Java but only scratched the surface.
One thing I did need to extend the server protocol for was to "enhance" the battle display. So after a few hours refactoring the whole battle engine. Pretty much everything was done twice, by cut and pasting large blocks of code. I moved these into parametrised functions which halved most of the logic although I added some bugs too. Then I looked at adding a new message type which reports who did how much damage to whom.
Which let me add some damage bubbles ...
Of course being JavaFX they are animated: they drift away from the attacker and fade out to blank.
Apart from the old container classes (Vector everywhere) most of the code is a bit of a mess. Code and data is all over the place, and little things like object references are no limitation to who wants to use what. There is also quite a lot of it - from the script system to the command handler and the main game controller - so any real improvement will require a deep understanding.
But for example I just came across this sequence ... which is repeated about 8 times through a couple of classes:
if (engGame.scrCanSeeLivingThing != null) {
synchronized (engGame.scrCanSeeLivingThing) {
engGame.scrCanSeeLivingThing.varVariables.clearVariables();
engGame.scrCanSeeLivingThing.varVariables.addVariable("seeing", thnStore);
engGame.scrCanSeeLivingThing.varVariables.addVariable("seen", this);
blnCanSee = engGame.scrCanSeeLivingThing.rewindAndParseScript();
}
} else {
blnCanSee = true;
}
Apart from poking at some internal variable on another object, it's a prime candidate for a helper function. This sort of stuff is everywhere. The Hungarian notation is really shitting me off too ...
Dusk - fx
I'm not sure exactly how I came across it but I found a blog DuskRPG about an old Java graphical MUD called Dusk with some source on the net.
The start of this week was hot and idle so I spent a lot of time just reading shit on the web. I should be back to work soon so I thought i'd "do nothing" for a bit. One thing I noticed was that the liberated pixel cup finally got judged - and the games look a lot better than when I first looked at it some time ago.
It looked interesting so i've contacted the blog author and also had a poke at porting it to JavaFX.
So a few hours down I have enough to play it with the main window running as JavaFX code, although the other popup windows are still pretty nasty wrongly-threaded awt.
Not really sure how far i'll get but i'll see how it goes. I have a few ideas I want to try to "modernise" the gui and take advantage of features in JavaFX although some of these will involve delving into the server code too.
I have a habit of going nowhere with game code, but at least this time it's already done ...
Quick and dirty Biltong Cutter
Boy, the gutter thing turned into a much bigger job - a load of wood-rot (part of the reason I was doing it in the first place), so I had to re-enforce/repair the fascia before I could even get to mounting the leaf eater.
Nearly didn't get to the biltong cutter - i'm a bit sick of the sawdust and noise and not having a workbench. But I had a couple of hours free so had a go at a "quick job".
Of course, nothing is ever "quick", particularly if you haven't done anything like this for a while. The main hassle was creating a curved cutting surface as the knife is curved. It's a bit rough but it works quite well. I could probably use a better axle but the one I have works. I could also do with a drill press.
The uprights are at an angle as I already had some wood with angled cuts in it, and I thought it might be stronger having the screws going across the grain a bit. The cutting block it positioned so the blade has the closest tolerance - although I got sick of sanding so it isn't perfect.
So the trick was to find a cheap/fairly robust blade. I tried finding old tools but that was pointless and in the end I was going through an asian grocer's knife section and found a bunch of cleaver type knives, some with holes already at the top/front. I went for the medium sized one at all of $6.90 which already had a ~13mm hole in it. I guess I could always re-grind it to have a straight, chiselled edge, but the curve adds a slicing action.
The only thing i'm wary of is mixing such a dangerous tool with alcohol ... i'm sure it'd make short work of fingers or toes!
Update: It works quite well and I've still got the appendages, but I decided to make a slight improvement. With no clamping support the uprights tended to drift apart slightly which made the blade a bit loose after a while.
I took the shim out and trimmed it to size and then put a bolt through the middle. I just happened to have a brand new bolt exactly the right diameter and length sitting around. It expanded the shim slightly to be a tighter fit with the axel mounting holes and also a better fit with the existing hole in the knife. Having a threaded bolt allows me to tighten up the gap between the uprights so the knife stays straighter during a cut. A few drops of grapeseed oil had it moving smoothly even when fairly tight. The curved blade works well with the slicing action and gives a bit more cutting power than a direct downward action does.
Not bad for 15 minutes after a warm afternoon boozing it up.
With the changes everything feels a good bit tighter and more controlled and subsequently it is a bit easier to use with extra-dry meat or other harder material. It would still be better to have a chisel point but the knife edge is ok enough. Perhaps in hindsight the extra $3 for the next knife size up with the thicker blade might have been a good idea too but for a $7 experiment i'm happy enough and it is nice and compact.
It also makes a great over-dried galangal (the only kind available at the moment) cutter or anything similarly hard one wishes to slice extra-fine.
Copyright (C) 2019 Michael Zucchi, All Rights Reserved.
Powered by gcc & me!