Tuesday 31 January 2017

Lets add buttons!

In my previous article (https://winter-in-hnh.blogspot.ru/2017/01/extending-haven-and-hearth-client.html) we extended Haven and Hearth client of our choice (Amber) with AutoKin functionality. We used GameUI::globtype() method to bind our AutoKin functionality to Control+K combination. This time we are going to try adding a menu button under Xtensions menu.




Placing icons into the menu

We'll start by adding a new icon into the menu - it's really easy to do, and requires addition of just one line. So lets have a look at ManuGrid.java class and it's implementation of attach() method.
    protected void attach(UI ui) {
        super.attach(ui);
        Glob glob = ui.sess.glob;
        synchronized (glob.paginae) {
            Collection<Pagina> p = glob.paginae;
            p.add(glob.paginafor(Resource.local().load("paginae/amber/coal11")));
            p.add(glob.paginafor(Resource.local().load("paginae/amber/coal12")));
            p.add(glob.paginafor(Resource.local().load("paginae/amber/branchoven")));
           // p.add(glob.paginafor(Resource.local().load("paginae/amber/steel")));
            p.add(glob.paginafor(Resource.local().load("paginae/amber/autosurvey")));
            p.add(glob.paginafor(Resource.local().load("paginae/amber/torch")));
            p.add(glob.paginafor(Resource.local().load("paginae/amber/clover")));
            p.add(glob.paginafor(Resource.local().load("paginae/amber/timers")));
            p.add(glob.paginafor(Resource.local().load("paginae/amber/autokin")));
        }
    }
Now we should have our icon in the menu, or maybe not? Lets build, run and have a look...

Crash and Burn!

Quiet expectidly client crashes with below exception:
java.lang.RuntimeException: Delayed error in resource paginae/amber/autokin (v-1), from local res source
 at haven.Resource$Pool$Queued.get(Resource.java:352)
 at haven.Resource$Pool$Queued.get(Resource.java:321)
 at haven.Glob$Pagina.res(Glob.java:150)
 at haven.Glob$Pagina.act(Glob.java:154)
 at haven.MenuGrid.cons(MenuGrid.java:93)
 at haven.MenuGrid.updlayout(MenuGrid.java:149)
 at haven.MenuGrid.tick(MenuGrid.java:433)
 at haven.Widget.tick(Widget.java:651)
 at haven.Widget.tick(Widget.java:651)
 at haven.GameUI.tick(GameUI.java:677)
 at haven.Widget.tick(Widget.java:651)
 at haven.UI.tick(UI.java:132)
 at haven.HavenPanel.run(HavenPanel.java:580)
 at java.lang.Thread.run(Unknown Source)
Caused by: haven.Resource$LoadException: Load error in resource paginae/amber/autokin(v-1), from local res source
 at haven.Resource$Pool.handle(Resource.java:409)
 at haven.Resource$Pool.access$1100(Resource.java:298)
 at haven.Resource$Pool$Loader.run(Resource.java:550)
 ... 1 more
 Suppressed: haven.Resource$LoadException: Load error in resource paginae/amber/autokin(v-1), from filesystem res source (res)
  ... 4 more
 Caused by: java.io.FileNotFoundException: res\paginae\amber\autokin.res (The system cannot find the path specified)
  at java.io.FileInputStream.open0(Native Method)
  at java.io.FileInputStream.open(Unknown Source)
  at java.io.FileInputStream.<init>(Unknown Source)
  at haven.Resource$FileSource.get(Resource.java:190)
  at haven.Resource$Pool.handle(Resource.java:393)
  ... 3 more
Caused by: java.io.FileNotFoundException: Could not find resource locally: paginae/amber/autokin
 at haven.Resource$JarSource.get(Resource.java:202)
 at haven.Resource$Pool.handle(Resource.java:393)
 ... 3 more
I guess I will post more on exception debugging in Haven and Hearth client at some point later, but now what is immediately evident from this exception is that it wasn't able to find a resource associated with something in MenuGrid and it's definitely related to our change since we have "autokin" all over the place. Solution to our problem is right in front of us:
Caused by: java.io.FileNotFoundException: res\paginae\amber\autokin.res (The system cannot find the path specified)
Which corresponds to our code looking like this:
p.add(glob.paginafor(Resource.local().load("paginae/amber/autokin")));
So we have instructed a menu item to be added to the MenuGrid using autokin icon, which is translated to local path "res\paginae\amber\autokin.res". By the looks of it path is relative to current directory - directory from where the client was started. But where do we get a RES file?

Lets mine some buttons

There are several utilities that allow RES file creation to name a few:
I had some poor experience with Boshaw's Hafen Layer Utility and ended-up using his LayerUtil the Java version - it worked really well and that is what we will use for this post. It can be compiled using the same approach as described in this post - https://winter-in-hnh.blogspot.ru/2017/01/haven-and-hearth-client.html.
But before we get into that, lets start by simply duplicating one of existing RES files. In your "build" folder locate "amber-res.jar" and open it with your favourite Zip archiver - I'll be using 7Zip:
Lets extract "amber.res" and rename it to "autokin.res" placing it under "res\paginae\amber\" directory relative to our "build". Like this:
This should solve the problem of non-existent file and we are still sure that our code does the right thing - so no changes needed and thus no recompilation required, lets start the client... again.

Expect the unexpected

This time client starts without any problem, we get in the game and get a new button:
Not exactly what I would expect, but kina makes sense - RES file is not just an image, but rather a compiled resource. This means we need our own resource file that we would be able to use for our purpose. So lets decode one file as a template. Since we want our icon to be "inside Xtensions" lets pick one of the resources for menu items placed there - so go ahead and unpack another RES file. I will be using coal12.res. 
Ok, so now you should have your "template" extracted and LayerUtil downloaded and built somewhere. I created a project for it in the same workspace, so it is placed next to my amber project directory:
Now lets decode coal12.res and finally have a look at what is inside:

Running this you should end up having a "dout" folder with several files and folders in it looking like the following:
First of all lets change the image associated with the resource, I'll use something like:
Put it into "image" folder as "image_0.png". In "pagina" folder we have "pagina_0.data" which contains description for our menu item, so lets change it to "Auto add Kin's from secrets.txt file." here is how it looks like in my Notepad++:
Now the most challanging part - modifying the "action"; in "action" folder modify "action_0.data" file to look like the following:

So I changed the name to be "Winter AutoKin", ad length to be "2" - used to be "3", and set ad[1] to "autokin". Great, now lets recompile our resource.
Now we have our recompiled coal12.res - lets rename it to "autokin.res" and replace our previous version in "res\paginae\amber\", and lets start the client:
So we have successfully created our button with name, description, image and action associated with it. The part missing now is binding the button to functionality - so lets go ahead and back to code.

The binding of the button

To bind the button we need to add it's action into MenuGrid's use() method, there are two use() methods for MenuGrid class - we need the public one that takes array of String's as an argument:
    public void use(String[] ad) {
There is a big IF block describing all known actions and their arguments, we will add our action at the bottom of the list:
        } else if (ad[1].equals("clover")) {
            Thread t = new Thread(new FeedClover(gui), "FeedClover");
            t.start();
        } else if (ad[1].equals("autokin")){
         Thread t = new Thread(new AutoKin(gui), "AutoKin");
         t.start();
        }
    }

As you can see this is pretty much the same as in https://winter-in-hnh.blogspot.ru/2017/01/extending-haven-and-hearth-client.html where we did exactly the same for a key chord - create a new thread with AutoKin class as an argument and start the thread.
Now, lets rebuild and start the client hopefully for the last time in this post.

...and it works!

Recap

Honestly speaking it feels like adding a button in Haven and Hearth client is a bit overcomplicated, resource compilation in particular is not as straightforward and clear, but I guess developers had a reason to do it so. Looking back at the post this is what we've done to make it all happen:
  1. Prepare resource file by decoding an existing pagina resource as a template, modifying "image", "pagina" and "action" content, encoding it into a new file and placing it into appropriate local directory
  2. Load the resource file created from within MenuGrid.java class attach() method 
  3. Modify MenuGrid.java class use() method to utilize your ad's defined in "action" in step 1
Now we are able to create menu items and bind functionality to them! Good luck!

No comments:

Post a Comment