About Me
Michael Zucchi
B.E. (Comp. Sys. Eng.)
also known as Zed
to his mates & enemies!
< notzed at gmail >
< fosstodon.org/@notzed >
Boolean logic, or the art of making the simple complex
I've been slowly rewriting big chunks of code, i've basically decided to rewrite almost everything apart from the battle system, but it's days may also be numbered.
Things are looking promising because I finally understand why some parts of the code is so complicated where it doesn't seem to need it - it's trying to monitor changes of state every time they happen and then propagate thing to the client. This just isn't necessary, all it needs to do is keep track of 'what i think the client has' (which it does already), and then perform a search of 'what the client should have' at the end of every turn and propagate that to the client. No need to have some convoluted cascade of 'you can now see this, lets set it right now and incidentally use that to track other state at the same time'. Because it's already doing the former anyway to implement the latter ... which also means extra update messages.
But that is not the topic of this post.
There is also quite a bit of convoluted logic which is hard to decipher. For example when an entity tries to follow another one. I will break it into two parts, 'can you do this', and 'do this'.
First, the 'can' test:
if (lt.isSleeping) {
return "You can't do that while you're sleeping";
}
DuskObject objStore = lt.getLocalObject(args);
if (objStore == null) {
return "You don't see that here.";
}
if (objStore.isLivingThing()) {
LivingThing thnStore = (LivingThing) objStore;
if (lt.getMaster() != null && thnStore != lt.getMaster()) {
if (lt.isPet()) {
return "You can only follow your owner.";
}
return "You're already following someone. Leave them first.";
}
if (Math.abs(lt.x - thnStore.x) + Math.abs(lt.y - thnStore.y) > 1) {
return "They're too far away.";
}
if (thnStore == lt) {
return "You can't follow yourself.";
}
if (!thnStore.isPlayer() && !lt.isMob()) {
return "You can only follow players.";
}
if (thnStore.noFollow || (thnStore.isPet() && thnStore.getMaster().noFollow)) {
return "They won't let you follow them.";
}
Here 'lt' is the thing that wants to follow, and 'objStore' and 'thnStore' are the thing it wants to lead it.
The tests in order ...
- Sleeping?
- Obvious.
- Visible in surrounding area?
- Obvious.
- LivingThing?
- Only LivingThings can move and fight.
- Already following something?
- Can only follow one leader, pets can only follow their owner.
- They're on an adjacent location?
- Proximity test.
- Insanity test.
- Obvious.
- Player/Pet can only follow Player. But mobs can follow anything.
- This was the hardest bit to understand ... blah. I got it wrong a few times and much refactoring ensued.
- Honour no-follow setting
- Pets are treated as proxies for their masters.
The problem is not so much the amount of code - although this type of logic is littered throughout the code-base - it's understanding the finer points. Just looking at that it's pretty hard to tell who can be leaders - e.g. can you follow a pet.
It does a lot of tests so it can't really be simplified much but maybe it can be made more obvious. I tried implementing it over the 4 Active/Player/Pet/Mobile classes by using the class structure to most of the need for the 'what am i' checks.
First, the code in Active:
public void follow(Active master) {
if (sleeping) {
chatMessage("You can't do that while you're sleeping");
return;
}
if (distanceL1(master) > 1) {
chatMessage("They're too far away.");
return;
}
if (group != null) {
chatMessage("You're already following someone. Leave them first.");
return;
}
if (this.ID == master.ID) {
chatMessage("You can't follow yourself.");
return;
}
if (!isCanLead()) {
chatMessage("They won't let you follow them.");
return;
}
This does the generic tests applicable to all classes. I've done some obvious things like add a distance measure function - rather than cut and paste the code everywhere. The isCanLead() is overriden in the Pet to proxy it's master's setting rather than having to have a class test inline.
Player only needs to add an additional test to ensure they're following a Player either directly of via a Pet.
public void follow(Active master) {
if (master.getType() != TYPE_PLAYER
|| master.getType() != TYPE_PET) {
chatMessage("You can only follow players.");
return;
}
super.follow(master);
}
Pets can only follow their owner so don't need the "is it a player" test.
public void follow(Active master) {
if (this.master.ID != master.ID) {
chatMessage("You can only follow your owner.");
return;
}
super.follow(master);
}
And Mobile just uses the Active method.
I think that covers all the cases and defines the same behaviour? The local-visibility and 'is it a living thing' tests are performed before it gets to this point for obvious reasons.
To me it is simpler, and the class types enforce behaviour at the language level and so are less error prone.
The downside of this is that the logic is now scattered over 4 separate files. Tooling helps to track what's going on, but it still requires some effort. Another problem is that adding such specificity in the class structure means changes are more difficult and perhaps costlier. For some reason I can't recall I nearly added this extra level in the class structure at this point before, I think it might be useful elsewhere (battle code).
And now the 'do' bit:
if (lt.isPet()) {
thnStore.setFollowing(lt);
lt.setMaster(thnStore);
thnStore.updateStats();
lt.updateStats();
return "You are now following " + lt.getMaster().name + ".";
}
LivingThing thnStore2 = thnStore;
while (thnStore2 != null) {
if (lt == thnStore2) {
return "You're already in that group.";
}
thnStore2 = thnStore2.getMaster();
}
thnStore.chatMessage("You are now being followed by " + lt.name + ".");
while (thnStore.getFollowing() != null) {
thnStore = thnStore.getFollowing();
if (thnStore.isPlayer()) {
thnStore.chatMessage("You are now being followed by " + lt.name + ".");
}
}
thnStore.setFollowing(lt);
lt.setMaster(thnStore);
thnStore.updateStats();
lt.updateStats();
return "You are now following " + lt.getMaster().name + ".";
}
return "That's not something you can follow.";
The pet part is fairly obvious (and again you see the immediate update calls, whereas a 'has changed' indicator would suffice). Although if the player is already following someone, it seems to just overwrite the link pointers - corrupt list?
But the rest is kind of not too obvious - and I suspect buggy anyway.
First some background to explain the loops. The entities use two pointers 'following', and 'master' to create a double-linked list of the 'following' group to which the entity belongs. This is for movement but mainly for battle (and knowing this will help me simplify the battle code too). Although there is a direct ordering relationship, it isn't really used apart from notification - when you follow you're placed at the end of the group.
So the first loop just determines if you're already in the pack. It only checks in one direction though which I think is a bug.
The second loop notifies everyone else in the pack that you've just joined. Again it only checks in one direction, which think is also a bug.
And finally the Active is linked into the end of the group, and stats updated.
I suspect this doesn't work very well in practice as you need to be next to someone to follow them, but if you follow the leader you are actually set to follow someone at the end the a conga line. I think this will immediately break when you move, since you need to be adjacent for movement.
What's worse this confusing ('thnStore'??) and error prone double-linked-list code is littered throughout the code - this snippet came from the command executor, but it is repeated throughout LivingThing, DuskEngine, and Script in several places.
ADT to the rescue!
The first big thing is to just wrap the pack in an ADT. A list is pretty much enough but since it probably needs some policy enforcement I wrapped it in a new Group object (actually I might call it Pack since Group is a bit too generic).
The code then becomes something like below, although i'm still trying to work out what's going on with the pet mechanism - I probably need a separate relationship for that. Actually now I think about it, perhaps a completely separate mechanism should be used for Pets to follow Players.
if (master.group == null) {
master.group = new Group();
master.group.addFollower(master);
}
for (Active a: master.group.members()) {
a.chatMessage("You are now being followed by " + name + ".");
}
group = master.group;
group.addFollower(this);
chatMessage("You are now following " + master.name + ".");
Which I think we can all agree is a marked improvement.
Whilst writing this I re-designed this stuff several times, mostly trying to understand that one troublesome if statement in the can follow block. I'm sure it isn't finished either - one I delve into the battle code I might find surprises.
Just start from scratch?
I guess I wasn't quite at the top of my game when I looked at this (it's been a long week, apart from 30+ hours on dusk i've done 30+ hours for work) - but it took far too long just to work out what was happening here. Trying to understand someone else's code - even in such a simple and harder-to-abuse language like Java - is pretty much a headfuck. It rapidly gets to the point where starting from scratch becomes an attractive proposition.
The server code less the scripting engine is only about 6KLOC (counting semicolons). If I fully knew what I wanted to do and didn't need to muck around making decisions or writing big blog posts I could easily polish that off in a week or two - and still have long weekends.
Although and i'm pretty sure it can be done in significantly less actual code anyway (but the deeper object hierarchy will add some overhead in locs).
Poking continues ...
Class heirarchy and stuff
I was looking through the duskz code as it is right now and there are still plenty of messy bits that I want to do something about. In some places there just seems to be more code than there should be to do fairly simple things, often a simple routine is just coded inline and repeated multiple times. And each object implements it's own parser as well as a different format/convention for storing it's state.
Although there is an object class hierarchy for the in-game objects, there is little to no use of the facilities in Java for object oriented programming such as virtual methods, or even inheriting during object initialisation. Apart from the classes there is also class information stored in type fields as well. e.g. a basic LivingThing can be a player, or a pet, an Item can be any type of item from armour to a backpack. Each container includes fields which are only applicable depending on some other field. They then have testing methods to determine what type they are, but it leads to ugly code with lots of manual checking in places. Actually the code looks more like a C design, where the objects are just data containers.
The existing class structure is something like:
DuskObject
LivingThing
Mob
Item
Merchant
PlayerMerchant
Prop
Sign
Each class either does its own i/o completely, or has it's i/o done for it in a completely separate class. I started an inconsistent stab at addressing this but I think to do it properly will require a common format/parser too. I'm going to stick with a Properties-like format for simplicity (name=value), but relax the requirement for unique keys (to allow multi-valued items).
First requirement is to clean up the class structure. I started at the base class and came up with a new hierarchy, which grew somewhat large by the end. I've also filled out most of the fields - in some cases it was hard to work out just which type of object a given field is restricted to. I gave them mostly unique names with the aim of avoiding clashes as I develop it, although they'll probably mostly stick.
Thing
Active
Player
Pet
Mobile
Holdable
Useable
Food
Drink
Item
Wearable
Weapon
Armour
Container
Shop
Merchant
PlayerMerchant
Sign
Prop
This will let me do things like move the 'get list of sellable items' to a function on Shop, rather than having it appear in multiple places throughout the code, each having to test which type of merchant too.
The code for game logic is also spread across mostly LivingThing and DuskEngine in random ways (some of it my fault by this time). Even things like the game pulse - there are two separate tick threads, for no particularly obvious reason? It might have been created to run enemy a/i on a separate thread, but extra stuff got added later - in any event it should run on a common clock.
I started just investigating the current structure to see how it's used, but I think i'll just rewrite the whole lot, the objects, the i/o & file formats, and well, most of the behaviour code too. The game logic itself is fairly simple so i'm hoping I can hide some of the dumb-but-necessary code like i/o away to expose the simplicity. There wont be much left of the original code apart from the battle logic, and the overall behaviour.
Could keep me busy for a while though ...
Update: I found there was a single weapon - a "runed shortsword", which was also "useable", so I decided to put the useable fields into Holdable instead so anything can be "used", if the game so desires. Non-usability is set by simply not setting hose fields. This could be extended to other types of item, but doesn't seem useful.
Layers & multiple maps
Ok just a quick post, but it's just a quick feature ... I needed a break from coding Android all day so i whipped this up.
I added layers to the server, protocol, and client. Only took a couple of hours so I haven't had time to do a very good example, but here's a shot in-game:
And because it isn't clear what's going on since the tiles aren't properly transparent (i.e. apart from appearing over the player, the visual appearance could just as easily been created using tall tiles), here it is in tiled with the opacity of the upper layer changed to show what it includes:
As part of the tiled converter it automatically shrinks each layer to only include the used region (apart from the ground layer), and the server only sends non-empty layers to the client even if they contain sparse data. So for example as the player moves and the 'forest' disappears, the server only sends a single layer of information. It keeps track of which layer is the ground layer so the client can render things in the correct order.
Update: I started hacking a bit later in the evening and got multiple maps working to some limited extent. There are plenty of bugs and various state files are broken and location scripts are still global, plus new issues to deal with like what to do with the blank area around maps, but I just got it to the point where I can walk inside the inn and then out again (through the door). Had to add a new script command to jump to a location in a map, and maps are referenced by their filename.
Outside (64x64, 2-layer map):
Inside (16x16, 1-layer map):
Scripting thoughts
So last night I had a bit of a look at how a javascript thing might work for the game engine. It tempered some of my enthusiasm but I also need to adjust my expectations. Security is a pain, and JavaScript itself turns out a bit ugly, although i can probably do something about that. Strangely the script engine doesn't let you implement new functions for the script? That's probably the weirdest thing about it; you can add objects but not top-level functions. The fact the javascript engine keeps variables around between invocations also makes things a bit more interesting, and not all in a good way.
I had a look at how I might implement a simple existing item: absinthe (it's not a drink i'm a fan of btw so the choice is arbitrary).
The existing item is defined over multiple separate files: the item descriptor and one for each script - 3 total files for absinthe.
Object
Obvious is to implement it as a full object with methods. The object name would match the script name, and all objects could be loaded at once into the script engine.
var absinthe = {
onUse: function(thing) {
thing.emote('wails: oh, my head!');
if (thing.getInte() > 10)
thing.incrInte(-1);
thing.remove('absinthe');
},
onDrop: function(thing) {
thing.order("get absinthe");
thing.remove('absinthe');
}
};
Drawback here is the scaffolding required. It's almost to the point that you may as well just use Java directly. I will probably do that too.
If the script engine were a proper execution container then things could be very interesting - the state could live inside it too, but I don't want to go down that path.
Script fragments
This is similar to the existing system, separate standalone scripts define each action. They are referenced via names in the objects.
absinthe_use:
thing.emote('wails: oh, my head!');
if (thing.getInte() > 10)
thing.incrInte(-1);
thing.remove('absinthe');
absinthe_drop:
thing.order("get absinthe");
thing.remove('absinthe');
It has similar drawbacks and advantages to the existing system - lots of little files to manage, separated logic, but also isolation and simplicity. It does allow for pre-compiled scripts though.
Script of functions
Here the script just defines top-level functions. It makes it a bit easier for the user to develop as they don't need to include all the scaffolding.
var onUse =function(thing) {
thing.emote('wails: oh, my head!');
if (thing.getInte() > 10)
thing.incrInte(-1);
thing.remove('absinthe');
};
var onDrop = function(thing) {
thing.order("get absinthe");
thing.remove('absinthe');
};
This approach has the drawback that the script must be parsed and executed separately, and must be parsed and executed every time.
Scripted actions
A compromise between the first two is to have a single script but also pass the script action to the script and let it decide what to do. e.g. use a switch statement. i.e. lets one put all behaviour in one file, but 'simplifies' the scaffolding.
switch (action) {
case 'use':
thing.emote('wails: oh, my head!');
if (thing.getInte() > 10)
thing.incrInte(-1);
thing.remove('absinthe');
break;
case 'drop':
thing.order("get absinthe");
thing.remove('absinthe');
break;
}
Problem is that it doesn't really simplify the scaffolding, although it allows for precompiled scripts.
Thoughts
One problem is i'm thinking about the scripting system from the wrong viewpoint - more of a library of potentially compiled functions rather than thinking of it as scripts. I need to shift my thinking because they really should just be scripts, and not self-contained applications. If I support Java classes to define behaviour as well then efficiency of the scripting language itself isn't of a primary concern.
The fact that Java can't directly define functions sucks a bit - for example the "thing." prefix to every call. But I guess i can hide that with some simple global functions I inject on my own which hide the details for the main target object.
At this point i'm leaning toward the second option - i.e. much the same as the existing system. But I will look at some techniques for simplifying their configuration such as convention-based lookup rather than needing a pile of settings for each object. A convention-based mechanism would let me support multiple mechanisms without needing to change the file formats.
I might need to let this stew a bit for the moment, and whilst that is coalescing have a look at the multi-map/layer stuff which will be more fun to hack on too.
Binary Protocol
So I mowed the lawn and swept the yard and did some of the things i've been neglecting for a few weeks. Although now with no milk in my coffee I wasn't in need of the shop ...
By late afternoon i was a bit bored so I had a look at the binary protocol work I had started.
I decided to change tack rather than use the manual serialisation I had started. It was a variant of Externalizable, which although the fastest mechanism requires the client and server to have total knowledge of each end. Being java there are other possibilities such as sending the decoding library to the client at run-time, but I wanted to keep things simple.
I started creating general purpose containers because I saw I was just re-typing the same stuff again, and there was a fair bit of re-use possible. After a few iterations I refined it and came up with something i'm pretty happy with. Basically it's a typed and tagged data structure which can be encoded in such a way that it can only be decoded one way. It is a bit like BER encoding but stripped to the bare-bones without any of the complication only a committee and the requirement to solve every possible conceivable future use can provide. But I can also include custom objects for efficiency or convenience sake.
So this defines the syntax - defining, encoding, decoding, and access.
But the semantics are completely separate and are defined by the application. This is very important as for example it allows me to create messages with variable content or new messages that the client/server can ignore if they wish - without breaking the protocol exchange.
DuskMessage
So I basically have a thing called a dusk message, and every dusk message has a type and a name. The type defines the container, and the name is application specific. They are encoded on the wire as 2 separate bytes, limiting each to 256 which is more than enough for an application-specific protocol.
DuskMessage {
byte type;
byte name;
};
This on it's own is enough to pass simple notification messages that require no arguments. Then there are the simple types, which includes byte, short, int, long, float, and string.
ValueMessage<T> {
DuskMessage;
T value;
};
And I created a couple of list types, one is just a list of strings, and the other is a list of DuskMessages (i.e. 'any's) which is how more complex datatypes are created without requiring custom types.
ListMessage {
DuskMessage;
List<DuskMessage> value;
};
(etc, you get the idea)
List are just encoded in the obvious way - a length (short), followed by length-items of the DuskMessage. I've got some simple accessors like getByte(int name) and so on for easy client use.
There are also some custom types such as the map and entity updates which occur very frequently which should benefit from the reduced protocol and run-time overhead. I just hard-coded these into the system but with a small amount of work it would be possible to turn it into a reusable and extensible library where customisations were provided by the application code.
And finally I created another copy of all of these which also contain an ID - for messages which are intended for a specific Entity. They don't occur very often but it provides a bit more flexibility and efficiency for a small coding overhead.
Of course when I went to use it there were bugs in my code so I then wrote some protocol dumping routines which dump to a human-readable format. As a human never needs to read it other than for debugging there isn't much point sending a human-readable format for any other reason.
DuskProtocol
The semantics are then defined by the DuskProtocol interface. This is just a pile of integer constants which define the names to use for objects. Any class in the client or server that wants to use the constants just 'implements' the interface to gain access to them. There are two sets of constants.
- MSG_*
-
The message constants define the message type and are set on the name of the highest level message. For simple types this is all they contain but the list can contain others. I decided to make these globally unique, although they could also be made unique-per-container-type. I tried that at first but it just got too messy and complicated to manage to make it worth doing. The more I worked on this the simpler it got, which is always a nice side-effect.
- FIELD_*
-
The field constants define fields in compound (ListMessage) messages. These only have to be unique to each message type. I initially tried to work on creating a global namespace for them where I could pick and choose what to include but that just complicated things again and all it did was save some source-code space and one-off typing. I will move to per-message or at least related-groups grouping, and start each group at 0. Otherwise it's just unmaintainable.
So for example the player info update which is the most complex message (all the str/dex and so on) has some constants like:
public final static int MSG_INFO_PLAYER = 15;
...
public final static int FIELD_INFO_CASH = 0;
public final static int FIELD_INFO_EXP = 1;
public final static int FIELD_INFO_STR = 2;
public final static int FIELD_INFO_STRBON = 3;
...
Currently i'm sending all of them every time - as the previous version did - but I can cut out things which aren't changes now, without having to change the client code (if i implement it the right way).
I did have a thought of having the server send some meta-data in an initial protocol exchange (say, allowing symbolic names for all messages and fields), but what the fuck for? It's already engineered enough to work, it doesn't need more than that.
So why bother/why not use another library/etc?
Firstly i find this kind of problem solving fun - it's just a never-ending puzzle trying to aim for a good solution knowing there are always trade-offs.. I've also done it quite a few times in the past - from LDAP which uses BER, to object serialisation in Evolution/camel, to using BerkelyDB, and plenty of other places besides. It just keeps cropping up and although I know this wont solve every problem it works for this one.
What I don't find particularly fun is learning how some behemoth general-purpose library works in the first place, let alone learning it's bugs and limitations. Even something as "trivial" as json requires some rather large library, and with all that it doesn't even interoperate reliably without fucking around.
For the application itself, as mentioned having a protocol with atomically-decodable messages allows for extension without completely breaking the protocol.
Having dynamic messages means I don't have to write code to handle dynamic messages into a custom protocol; it's already provided. Field access requires code for each field, a general list can be iterated - potentially reducing code required too. All the parsing and i/o handling is done and all I have to do is use the objects or their accessors.
It also means the encode/decode is in one place, and not scattered throughout the code as printf statements. So I can also force encapsulation at each end by forcing communications through this choke point. Which provides all sorts of benefits ...
... such as if i should decide to change the wire protocol in the future, or more likelty to support "web friendly" text-mode protocols.
In DuskZ
So I didn't just design the protocol syntax last night (ok it stretched into the night a bit - nothing on tv), I implemented it and changed DuskServer and DuskZ to use it. And debugged it enough to the point that you can log in and play some of the game ...
At this point the client is still talking the original protocol outward (i.e. a command line!), but I will need to change that to support some required features. For example scripts are able to ask the user questions but at the moment do some really nasty hackery like telling the client to pause, then ask the question, then flush the incoming stream, then hope it got it right. That can all go.
And the server protocol still has the same basic structure with only some minor tweaks.
There are other messages which could be combined in a more general 'update some object' type message. e.g. set range could become an optional field in an 'update player' message (although it looks like range isn't sent anyway). And others that could be combined for other reasons, such as wanting an atomic change of state, such as entering a battle - rather than sending a bunch of messages for each individual entity. Fortunately with the simple design I can just as easily embed messages inside a list as a field.
I also had a think about the auth protocol - right now it just creates a new user if you didn't exist, but it might be useful to get the player to confirm their password, and also go through the "new user" stuff like choosing a race all in one go.
Before i commit I want to clean up some of the naming conventions (netbeans refactor tools have had a work-out this week, although they're not bug free they work surprisingly well with 'broken' code), see about moving some of the fields to grouped messages, and change the login stuff. The login stuff goes a bit deeper so will require more work.
Update: So I managed to do most of what I intended - boy it was a lot more work than I thought. Getting the login working at both ends took a bit more thinking than I expected, but I managed to fit in a query mechanism that lets the game query the user for arbitrary values (currently: an item from a list) and the same convention is used during user creation to ask for whatever is needed. It took a while to work out how the current code needed it all to work too, and then I merged some of the message types (e.g. resize map + update images became map init), and then had to fix the client. It isn't quite done but it's enough to login and play around a bit.
Here's a protocol dump of the client creating a new user. It still goes through the same single login window I did before, but the client behaviour can be altered quite freely without changing the protocol. e.g. it could instead show 'unknown user', with a button for 'create' which runs a setup wizard locally, and then creates the user at the end.
sending: MSG_AUTH ListMessage name=0 value = {
StringMessage name=3 value=x username
StringMessage name=4 value=x password
}
state=Username: MSG_AUTH ListMessage name=0 value = {
ListMessage name=2 value = {
ListMessage name=0 value = { name = name of response string below
StringMessage name=0 value=Choose race
StringListMessage name=1 value= {
'lizardman'
'ork'
'indian'
'demon'
'elf'
'darkelf'
'halfling'
'dwarf'
'human'
}
}
}
EntityIntegerMessage id=-1 name=0 value=3
StringMessage name=1 value=Insufficient information to create player.
}
sending: MSG_AUTH ListMessage name=0 value = {
StringMessage name=3 value=x
StringMessage name=4 value=x
ListMessage name=2 value = {
StringMessage name=0 value=human name = name of choice above
}
}
state=Username: MSG_AUTH ListMessage name=0 value = {
EntityIntegerMessage id=195 name=0 value=0
StringMessage name=1 value=Login ok.
}
state=Ready: MSG_CHAT StringMessage name=7 value=DuskZ Server 3.0 dev ...
So fairly clean and concise, and no screen-scraping required. And the query can include as many questions as needed, ... or even more complex content with some appropriate glue (such as a web form using the WebView?).
The brain dump
Last night I was pretty exhausted - the neighbours had some bricks delivered around 7am which woke me too early after what was a pretty lousy night of sleep. I got up with the idea of turning off the watering system (starts at 7am) after it had done the pot plants and kind of forgot about that and ended up hacking for nearly 15 hours straight.
Ok so it was probably a bit obsessively intensive, but i'm pretty surprised I still get such a level of buzz out of hacking code after 25 years of doing it for fun. In terms of effort and intensity, it could have been any other xmas holiday from any of those previous years. On the other hand i'm writing more and better code than ever before because the platform is fuller than ever before. The only downside is that I can't keep up the intensity for as many consecutive weeks as I used to and end up with aches and pains from sitting still for so long.
But as I intended to take a bit of a break for a few days I thought i'd jot down some feature ideas, todo's and wishlists in my engineering notebook (a paper one) while the ideas were still hot.
As I wrote things down my memory started to vanish very rapidly - as if waking from a dream. I got the main feature points down fairly completely but I was pretty stumped at the todos. Although only minutes before I had a mind full of all those little refactoring, renaming, and tweaking ideas for improving the codebase, by the time I went to write them down I drew a near blank. I've experienced something similar before when I left the Evolution project; as I wrote all my knowledge down all the anxieties from keeping track of the 'known bugs' and 'future plans' in my head just melted away. But it wasn't quite so abrupt, although both instances were very cathartic. To be honest i'm still surprised I can keep so much in my head for weeks at a time - when I often forget where I put a screwdriver only a minute prior.
So it was a very good idea to write it down and let me get a good nights sleep. And although my brain is now bereft of most of the details I managed to write most of them down, and i'm sure the others are in the source as FIXMEs or will come back when I get into it again - if they were important.
Scrollbars!
So apparently one of the side-effects of the touch-interface 'revolution' is the abolishment of scrollbars.
Well fuck that for a poke up the arse with a glowing poker.
Even before the latest firefox came along with a serious bug on my *ainol tablet (I love it, even the the logo includes a diagrammatic anus) which makes it almost unusable - flicking the screen normally-but-not-always makes it flick in the wrong direction - this no-scrollbar idea makes the device itself very clumsy for reading any web pages more than about 3 screens high. The android browser is just the same. And given some of the pages I read are dozens of pages long, i'm faced with a usability nightmare.
Just bring the friggan scrollbars back ...
It's probably related to another annoying "web 3" feature - infinite scrolling screens that load on demand. They might solve a technical problem and have a bit of 'wow' factor, but they are not a usability improvement. And they kill some pretty important features we've come to expect like searching within a page. But I suppose we have to get used to this sort of dumbing down of the internet - such a big chunk it is all about advertising wrapped in entertainment; not learning, education, or information.
While i'm ranting about firefox mobile i find it pretty much a pain to work with even without that killer bug - there are too many small buttons too close together, the tab system is really annoying, as is the click-to-title-bar-to-go-to-another-screen thing, with the popup keyboard always getting in the way. It has a habit of zooming far too often when I go to move around - a mis-fingered 'scroll' turning into a 'press'. If we already have a two-finger zoom, why not bloody stick to it? I just don't see the point of overloading gestures to the point of confusion. Well here's two fingers for the designers: Y, and while i'm at it: !). Mouse/keyboard users have other mechanisms for this.
The whole point of "touch" is it is supposed to be natural and "intuitive", but if you have to concentrate hard every time you try to press on something it has lost any edge it had.
I don't really know the answer for the tiny-button-problems as it would need some serious thought but for all the serious thought the UI designers have put into the current offerings they still have a distance to go. The only practical thing I can think of from the top of my head is an icon/menu bar which POPS onto the screen (doesn't slide, doesn't fade, doesn't push the content over to one side, doesn't fucking animate) and has nice big buttons for the most useful functions. You know, like the microsoft 'charms' thing, although it sounds like they overloaded 'gestures' to the point of absurdity (i'd really like to know what that dickhead in the 'clean up the party' ad is supposed to be doing when he draws a circle and a line on the photo near the start). Gestures suck because they're completely non-discoverable and require a lot of practice to get consistent with.
But pressing inside a page should be reserved for links or focus. Not for zooming-sometimes-in-sometimes-out. It's just too easy to do it by mistake.
Inflexion points
Hmm, and on a completely unrelated note, I think i've hit one of those inflexion points where I suddenly change a food forever. It's getting close to the point where my coffee goes black, strong, sugarless. I use a Baletti stove-top coffee maker which makes great coffee easily and cheaply and with the right coffee, amount of milk and temperature it's pretty near to a decent 'bought one', but it's a bit hit and miss. Yesterday I ran out of milk and although I usually can't stomach the black coffee I managed ok. About the time I started using one of these machines my coffee went sugarless - I can't stand even a small amount of sugar now. Although a little sugar and milk is still needed in tea, but I don't drink that as often.
It reminds me that one day after having had cornflakes every morning for breakfast for years I suddenly decided it was too sweet and decided I vaguely remembered vita-brits being pretty nice. Can't stand cornflakes now, although I could probably go some sugarless vita-brits once in a while. Although these days breakfast is usually left-overs, coffee, toast, or often just reading my morning round of blogs and organising my on-screen workbench.
Hmm, my nice neighbours are getting some work done in their yard next to this room, I should go mow the lawn or something before I get rooted to the chair for another day.
DuskZ GMUD, & Source
I just got confirmation of the original source license so I decided to get it all out of the way tonight. I kept thinking it was Friday today, but since that's tomorrow I wanted to clean up then then try harder not to get sucked into hacking on it for the rest of my long-weekend.
I've kept most of the code GPL 2 or later but decided to exercise the 'or later' on the client side and used GPLv3 for the new files and for the whole package.
The source has been checked in to the DuskZ project. Well the DuskZ keeps with my naming convention theme, and it will soon diverge enough to need another name.
As I started without a version control system there is no detailed changelogs, but lets just say the changes have been 'considerable'.
Test Run
The code is pre-alpha but it should run the current test game from Zabin (with some bugs), although only the initially imported version will work. I already have pending changes to break it.
First check out all 3 modules from the DuskZ googlecode and build it. I use NetBeans 7.3 beta but presumably ant will work.
Then checkout the old test game from DuskRPG github. The compatible game files are in "DuskFiles/Dusk2.7.3"
Start the server:
$ cd {path-to-DuskRPG}/DuskFiles/Dusk7.7.3
$ java -jar {path-to-DuskServer}/dist/DuskServer.jar
Start the client:
$ cd {path-toDuskRPG}/DuskFiles/Dusk7.7.3/www
$ java -jar {path-to-DuskZ}dist/DuskZ.jar
A recent Oracle version of java is currently required as the JavaFX stuff hasn't been fully freed yet for the openjdk. Fingers crossed it will be a matter of months.
Connect to localhost, port 7474 and enter a username/password to create a new account. If all's well you'll get dumped into the game, or not - no promises at this point.
Update: I forgot to add www to the client starting directory.
Copyright (C) 2019 Michael Zucchi, All Rights Reserved.
Powered by gcc & me!