Author Topic: [WIP][Tool] HWSEdit - Hammerwatch Save Editor  (Read 57538 times)

joedf

  • Posts: 19
  • Swift Knight
    • View Profile
[WIP][Tool] HWSEdit - Hammerwatch Save Editor
« on: October 13, 2014, 08:28:37 AM »
Hammerwatch Save Editor (HWSEdit)



Easily edit your Hammerwatch game save files! The goal here... is a collaborative/cooperative effort (just like the game). Feel free to make pull requests and add your name in, if you please. In the future, a character editor could be intergrated (aka the "Players" tab). Also, I would like to give a grand thanks to Myran, for the C# class (the core of this application).

Feel free to some comments, too!

HWSEdit.exe - The GUI version of the Application. Under the "hws2xml" tab, it can also convert HWS from/to XML files.
hws2xml.exe - A simple console application to convert HWS from/to XML files.

DOWNLOADS: https://github.com/joedf/HWSEdit/releases
Revision Date : 2014/12/01
Source on GitHub : https://github.com/joedf/HWSEdit
Prebuilt Binary Download (GUI app): https://github.com/joedf/HWSEdit/releases/download/v0.1.0.1-alpha/HWSEdit.zip
Prebuilt Binary Download (console app) : https://github.com/joedf/HWSEdit/releases/download/v0.1-alpha/hws2xml.zip

mac build @thanks to bburky: https://github.com/joedf/HWSEdit/raw/master/Mac_Builds/hws2xml-mono.exe
Released under the MIT License

Spoiler for oldpost:
Very minimal for the moment. I know about this, but the source is not provided. It is written in AutoHotkey. The goal here... is a collaborative/cooperative effort (just like the game). I did not write an XML converter because I would have to reverse-engineer it :P Of course, it would be easier with XML. Currently, I have only put these 3 settings, because they are the ones I use the most. More can be easily added. Feel free to make pull requests and add your name in, if you please. In the future, a character editor could be intergrated.

Source: https://github.com/joedf/HWSEdit/blob/master/HWSEdit/HWSEdit-AHK/HWSEdit-AHK.ahk
Download: https://github.com/joedf/HWSEdit/raw/master/dist/HWSEdit-AHK.exe
« Last Edit: August 19, 2015, 04:56:16 AM by joedf »

Myran

  • Developer
  • Posts: 183
    • View Profile
Re: [WIP/Proof of Concept][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #1 on: October 13, 2014, 09:45:35 PM »
Cool project! :) I don't really know much about AutoHotkey, but if you want the saves in XML format here is the C# class that the game and editor uses for the binary and XML serialization of data, just load the save file with LoadStream() and then save the result with SaveXML(), the format isn't complicated so you can maybe use it to write your own in AutoHotkey.

Code: [Select]
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using OpenTK;

namespace TiltedEngine
{
    public enum SValueType
    {
        Array,
        Object,
        Boolean,
        Double,
        Float,
        Integer,
        String,
        IntegerArray,
        Vector2,
        Null
    }

   
    public class SValue
    {
        public SValueType Type;
        private object value;

        public SValue()
        {
            Type = SValueType.Null;
            value = null;
        }

        public SValue(SValue[] v)
        {
            Type = SValueType.Array;
            value = v;
        }

        public SValue(SObject v)
        {
            Type = SValueType.Object;
            value = v;
        }

        public SValue(bool v)
        {
            Type = SValueType.Boolean;
            value = v;
        }

        public SValue(double v)
        {
            Type = SValueType.Double;
            value = v;
        }

        public SValue(float v)
        {
            Type = SValueType.Float;
            value = v;
        }

        public SValue(int v)
        {
            Type = SValueType.Integer;
            value = v;
        }

        public SValue(string v)
        {
            Type = SValueType.String;
            value = v;
        }

        public SValue(int[] v)
        {
            Type = SValueType.IntegerArray;
            value = v;
        }

        public SValue(Vector2 v)
        {
            Type = SValueType.Vector2;
            value = v;
        }

        public SValue(float x, float y)
        {
            Type = SValueType.Vector2;
            value = new Vector2(x, y);
        }

        public SValue(object v, SValueType type)
        {
            Type = type;
            value = v;
        }



        public SValue[] GetArray() { return (SValue[])value; }
        public SObject GetObject() { return (SObject)value; }
        public bool GetBoolean() { return (bool)value; }
        public double GetDouble() { return (double)value; }
        public float GetFloat() { return (float)value; }
        public int GetInteger() { return (int)value; }
        public string GetString() { return (string)value; }
        public int[] GetIntegerArray() { return (int[])value; }
        public Vector2 GetVector2() { return (Vector2)value; }

        public object GetRawValue() { return value; }

       
        public static implicit operator SValue(SValue[] val) { return new SValue(val); }
        public static implicit operator SValue(SObject val) { return new SValue(val); }
        public static implicit operator SValue(bool val) { return new SValue(val); }
        public static implicit operator SValue(double val) { return new SValue(val); }
        public static implicit operator SValue(float val) { return new SValue(val); }
        public static implicit operator SValue(int val) { return new SValue(val); }
        public static implicit operator SValue(string val) { return new SValue(val); }
        public static implicit operator SValue(int[] val) { return new SValue(val); }
        public static implicit operator SValue(Vector2 val) { return new SValue(val); }

        public static SValue FromArray(SValue[] val) { return new SValue(val, SValueType.Array); }
        public static SValue FromObject(SObject val) { return new SValue(val, SValueType.Object); }
        public static SValue FromBoolean(bool val) { return new SValue(val, SValueType.Boolean); }
        public static SValue FromDouble(double val) { return new SValue(val, SValueType.Double); }
        public static SValue FromFloat(float val) { return new SValue(val, SValueType.Float); }
        public static SValue FromInteger(int val) { return new SValue(val, SValueType.Integer); }
        public static SValue FromString(string val) { return new SValue(val, SValueType.String); }
        public static SValue FromIntegerArray(int[] val) { return new SValue(val, SValueType.IntegerArray); }
        public static SValue FromVector2(Vector2 val) { return new SValue(val, SValueType.Vector2); }

        public bool IsNull()
        {
            return Type == SValueType.Null;
        }

        public static SValue FromXMLFile(TextReader xml)
        {
            var doc = XDocument.Load(xml);
            return ParseXMLNode(doc.Root);
        }

        public static SValue FromXMLString(string xml)
        {
            var doc = XDocument.Parse(xml);
            return ParseXMLNode(doc.Root);
        }

        public static SValue FromXMLDocument(XElement xml)
        {
            return ParseXMLNode(xml);
        }

        private static SValue ParseXMLNode(XElement node)
        {
            switch (node.Name.LocalName)
            {
                case "array":
                    var arr = new List<SValue>();
                    foreach (var child in node.Elements())
                        arr.Add(ParseXMLNode(child));

                    return FromArray(arr.ToArray());

                case "dictionary":
                    var dict = new SObject();
                    foreach (var child in node.Elements())
                    {
                        var name = child.Attribute("name").Value;
                        if (child.Name == "entry")
                            dict.Set(name, ParseXMLNode(child.Elements().First()));
                        else
                            dict.Set(name, ParseXMLNode(child));
                    }
                    return FromObject(dict);

                case "bool":
                    return FromBoolean(bool.Parse(node.Value));
                case "double":
                    return FromDouble(double.Parse(node.Value, CultureInfo.InvariantCulture));
                case "float":
                    return FromFloat(float.Parse(node.Value, CultureInfo.InvariantCulture));
                case "int":
                    return FromInteger(int.Parse(node.Value, CultureInfo.InvariantCulture));
                case "string":
                    return FromString(node.Value);
                case "vec2":
                    {
                        var sarr = node.Value.Split(' ');
                        return FromVector2(new Vector2(float.Parse(sarr[0], CultureInfo.InvariantCulture), float.Parse(sarr[1], CultureInfo.InvariantCulture)));
                    }

                case "int-arr":
                    {
                        var sarr = node.Value.Split(' ');
                        var iarr = new int[sarr.Length];

                        for (var j = 0; j < sarr.Length; j++)
                            iarr[j] = int.Parse(sarr[j], CultureInfo.InvariantCulture);

                        return FromIntegerArray(iarr);
                    }
            }

            return new SValue(null, SValueType.Null);
        }

        public static void SaveXML(SValue val, TextWriter tw, string indent = "", string name = null, bool compact = false)
        {
            if (!compact)
                tw.Write(indent);

            switch (val.Type)
            {
                case SValueType.Array:
                    {
                        var arr = val.GetArray();
                        var childCompact = compact;
                        if (arr.Length < 10 && !childCompact)
                        {
                            childCompact = true;
                            foreach (var arrEntry in arr)
                                if (arrEntry.Type == SValueType.Object || arrEntry.Type == SValueType.IntegerArray || arrEntry.Type == SValueType.Array)
                                {
                                    childCompact = false;
                                    break;
                                }
                        }

                        tw.Write(name == null ? "<array>" : "<array name=\"" + name + "\">");

                        if (!childCompact)
                            tw.WriteLine();

                       
                        foreach (var arrEntry in arr)
                            SaveXML(arrEntry, tw, indent + "\t", null, childCompact);

                        if (!childCompact)
                            tw.Write(indent);

                        tw.Write("</array>");
                    }
                    break;

                case SValueType.Object:
                    {
                        tw.WriteLine(name == null ? "<dictionary>" : "<dictionary name=\"" + name + "\">");

                        var dict = val.GetObject();
                        foreach (var dictEntry in dict)
                            SaveXML(dictEntry.Value, tw, indent + "\t", dictEntry.Key, compact);

                        if (!compact)
                            tw.Write(indent);

                        tw.Write("</dictionary>");
                    }
                    break;

                case SValueType.Boolean:
                    tw.Write(name == null ? "<bool>" : "<bool name=\"" + name + "\">");
                    tw.Write(val.GetBoolean() + "</bool>");
                    break;
                case SValueType.Double:
                    tw.Write(name == null ? "<double>" : "<double name=\"" + name + "\">");
                    tw.Write(val.GetDouble().ToString(CultureInfo.InvariantCulture) + "</double>");
                    break;
                case SValueType.Float:
                    tw.Write(name == null ? "<float>" : "<float name=\"" + name + "\">");
                    tw.Write(val.GetFloat().ToString(CultureInfo.InvariantCulture) + "</float>");
                    break;
                case SValueType.Integer:
                    tw.Write(name == null ? "<int>" : "<int name=\"" + name + "\">");
                    tw.Write(val.GetInteger() + "</int>");
                    break;
                case SValueType.String:
                    tw.Write(name == null ? "<string>" : "<string name=\"" + name + "\">");
                    tw.Write(val.GetString() + "</string>");
                    break;

                case SValueType.Vector2:
                    tw.Write(name == null ? "<vec2>" : "<vec2 name=\"" + name + "\">");
                    var v2 = val.GetVector2();
                    tw.Write(v2.X.ToString(CultureInfo.InvariantCulture));
                    tw.Write(" ");
                    tw.Write(v2.Y.ToString(CultureInfo.InvariantCulture));
                    tw.Write("</vec2>");
                    break;

                case SValueType.IntegerArray:
                    tw.Write(name == null ? "<int-arr>" : "<int-arr name=\"" + name + "\">");

                    var v = val.GetIntegerArray();
                    for (var i = 0; i < v.Length; i++)
                    {
                        tw.Write(v[i]);
                        if (i != v.Length - 1)
                            tw.Write(' ');
                    }
                    tw.Write("</int-arr>");
                    break;

                case SValueType.Null:
                    tw.Write(name == null ? "<null/>" : "<null name=\"" + name + "\"/>");
                    break;
            }

            if (!compact)
                tw.WriteLine();
        }




        public static SValue FindDictionaryEntry(BinaryReader sr, string name)
        {
            var type = sr.ReadByte();
            switch (type)
            {
                case 1:
                    {
                        var len = sr.ReadInt32();
                        for (var j = 0; j < len; j++)
                        {
                            var ret = FindDictionaryEntry(sr, name);
                            if (ret != null)
                                return ret;
                        }

                        return null;
                    }
                case 2:
                    {
                        var len = sr.ReadInt32();
                        for (var j = 0; j < len; j++)
                        {
                            var nm = sr.ReadString();
                            if (nm == name)
                                return LoadStream(sr);

                            var ret = FindDictionaryEntry(sr, name);
                            if (ret != null)
                                return ret;
                        }

                        return null;
                    }

                case 3:
                    sr.BaseStream.Seek(1, SeekOrigin.Current);
                    return null;
                case 4:
                    sr.BaseStream.Seek(8, SeekOrigin.Current);
                    return null;
                case 5:
                    sr.BaseStream.Seek(4, SeekOrigin.Current);
                    return null;
                case 6:
                    sr.BaseStream.Seek(4, SeekOrigin.Current);
                    return null;
                case 7:
                    sr.ReadString();
                    return null;

                case 8:
                    {
                        var len = sr.ReadInt32();
                        sr.BaseStream.Seek(4 * len, SeekOrigin.Current);
                        return null;
                    }

                case 9:
                    sr.BaseStream.Seek(4 * 2, SeekOrigin.Current);
                    return null;
            }

            return null;
        }


        public static SValue LoadStream(BinaryReader sr)
        {
            var type = sr.ReadByte();
            switch (type)
            {
                case 1:
                    {
                        var len = sr.ReadInt32();

                        var arr = new SValue[len];
                        for (var j = 0; j < arr.Length; j++)
                            arr[j] = LoadStream(sr);

                        return FromArray(arr.ToArray());
                    }
                case 2:
                    {
                        var len = sr.ReadInt32();

                        var dict = new SObject();
                        for (var j = 0; j < len; j++)
                        {
                            var name = sr.ReadString();
                            dict.Set(name, LoadStream(sr));
                        }
                        return FromObject(dict);
                    }

                case 3:
                    return FromBoolean(sr.ReadBoolean());
                case 4:
                    return FromDouble(sr.ReadDouble());
                case 5:
                    return FromFloat(sr.ReadSingle());
                case 6:
                    return FromInteger(sr.ReadInt32());
                case 7:
                    return FromString(sr.ReadString());

                case 8:
                    {
                        var len = sr.ReadInt32();
                       
                        var iarr = new int[len];
                        for (var j = 0; j < iarr.Length; j++)
                            iarr[j] = sr.ReadInt32();

                        return FromIntegerArray(iarr);
                    }

                case 9:
                    return FromVector2(new Vector2(sr.ReadSingle(), sr.ReadSingle()));
            }

            return new SValue(null, SValueType.Null);
        }



        public static void SaveStream(SValue val, BinaryWriter sw)
        {
            switch (val.Type)
            {
                case SValueType.Array:
                    {
                        sw.Write((byte)1);

                        var arr = val.GetArray();
                        sw.Write(arr.Length);

                        foreach (var arrEntry in arr)
                            SaveStream(arrEntry, sw);
                    }
                    break;

                case SValueType.Object:
                    {
                        sw.Write((byte)2);

                        var dict = val.GetObject();
                        sw.Write(dict.Size);

                        foreach (var dictEntry in dict)
                        {
                            sw.Write(dictEntry.Key);
                            SaveStream(dictEntry.Value, sw);
                        }
                    }
                    break;

                case SValueType.Boolean:
                    sw.Write((byte)3);
                    sw.Write(val.GetBoolean());
                    break;
                case SValueType.Double:
                    sw.Write((byte)4);
                    sw.Write(val.GetDouble());
                    break;
                case SValueType.Float:
                    sw.Write((byte)5);
                    sw.Write(val.GetFloat());
                    break;
                case SValueType.Integer:
                    sw.Write((byte)6);
                    sw.Write(val.GetInteger());
                    break;
                case SValueType.String:
                    sw.Write((byte)7);
                    sw.Write(val.GetString() ?? "");
                    break;

                case SValueType.IntegerArray:
                    sw.Write((byte)8);

                    var iArr = val.GetIntegerArray();
                    sw.Write(iArr.Length);

                    for (var i = 0; i < iArr.Length; i++)
                        sw.Write(iArr[i]);

                    break;

                case SValueType.Vector2:
                    sw.Write((byte)9);
                    var v2 = val.GetVector2();
                    sw.Write(v2.X);
                    sw.Write(v2.Y);
                    break;

                case SValueType.Null:
                    sw.Write((byte)0);
                    break;
            }
        }
    }



    public class SObject : IEnumerable<KeyValuePair<string, SValue>>
    {
        Dictionary<string, SValue> values;

        public SObject()
        {
            values = new Dictionary<string, SValue>();
        }

        public SValue this[string index]
        {
            get
            {
                SValue val;
                if (values.TryGetValue(index, out val))
                    return val;

                return new SValue();
            }
            set
            {
                values[index] = value;
            }
        }

        public SValue Get(string index)
        {
            SValue val;
            if (values.TryGetValue(index, out val))
                return val;

            return new SValue(null, SValueType.Null);
        }

        public void Set(string index, SValue value)
        {
            values[index] = value;
        }


        public int Size { get { return values.Count; } }

        public IEnumerator<KeyValuePair<string, SValue>> GetEnumerator()
        {
            return values.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

joedf

  • Posts: 19
  • Swift Knight
    • View Profile
Re: [WIP/Proof of Concept][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #2 on: October 14, 2014, 02:10:51 AM »
Hi Myran,
Thank you very much for your reply. I appreciate it. I have decided to directly use the C# class provided, but I have encountered a problem.
Spoiler for problem:
When loading the HWS format and saving it to XML, the final, produced XML document is clipped or incomplete.
eg.
Code: XML
  1. ... <int>0</int><
or
Code: XML
  1. ... <float>96.38364</f
Am I doing something wrong? I am missing something... The program finishes with no errors.
Here's the code for the console application, just in case.
Code: [Select]
using System;
using System.IO;

namespace hws2xml
{
class Program
{
public static void Main(string[] args)
{
if (args.Length < 2) {
Console.WriteLine("hws2xml [infile] [outfile]");
Console.WriteLine("if [infile] has .hws it will converted to .xml");
Console.WriteLine("if [infile] has .xml it will converted to .hws");
} else {
string inFile = args[0];
string outFile = args[1];

if (File.Exists(inFile)) {
string inType = Path.GetExtension(inFile).ToUpper();
if (inType == ".XML") {
TextReader TR = new StreamReader(inFile);
SValue OBJ = SValue.FromXMLFile(TR);
BinaryWriter BW = new BinaryWriter(File.Open(outFile,FileMode.Create));
SValue.SaveStream(OBJ,BW);
Console.WriteLine("Converted from XML to HWS.");
} else { // .HWS
BinaryReader BR = new BinaryReader(File.Open(inFile,FileMode.Open));
SValue OBJ = SValue.LoadStream(BR);
TextWriter TW = new StreamWriter(outFile);
SValue.SaveXML(OBJ,TW);
Console.WriteLine("Converted from HWS to XML.");
}
} else {
Console.WriteLine("Error: Invalid input file.");
}
Console.WriteLine("Done.");
}
}
}
}
And thanks again.

EDIT: Nevermind, :P forgot to close the files.
« Last Edit: October 14, 2014, 02:20:23 AM by joedf »

joedf

  • Posts: 19
  • Swift Knight
    • View Profile
Re: [WIP/Proof of Concept][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #3 on: October 14, 2014, 02:49:25 AM »
One question. I see that OpenTK is used, but why doesn't Hammerwatch have OpenTK.dll included?
EDIT : Anyway, I have simply included some code from OpenTK instead. Seems to work fine.
Code: [Select]
#region --- License ---
/*
Copyright (c) 2006 - 2008 The Open Toolkit library.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
 */
#endregion

//THIS IS A BARE-BONES/STRIPPED VERSION OF THE ORIGINAL.
//This way, we don't need to include the whole OpenTK.dll
//We only need Vector2

using System;
using System.Runtime.InteropServices;
using System.Xml.Serialization;

namespace OpenTK
{
    /// <summary>Represents a 2D vector using two single-precision floating-point numbers.</summary>
    /// <remarks>
    /// The Vector2 structure is suitable for interoperation with unmanaged code requiring two consecutive floats.
    /// </remarks>
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Vector2 : IEquatable<Vector2>
    {
        #region Fields

        /// <summary>
        /// The X component of the Vector2.
        /// </summary>
        public float X;

        /// <summary>
        /// The Y component of the Vector2.
        /// </summary>
        public float Y;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructs a new instance.
        /// </summary>
        /// <param name="value">The value that will initialize this instance.</param>
        public Vector2(float value)
        {
            X = value;
            Y = value;
        }

        /// <summary>
        /// Constructs a new Vector2.
        /// </summary>
        /// <param name="x">The x coordinate of the net Vector2.</param>
        /// <param name="y">The y coordinate of the net Vector2.</param>
        public Vector2(float x, float y)
        {
            X = x;
            Y = y;
        }

        #endregion

        #region IEquatable<Vector2> Members

        /// <summary>Indicates whether the current vector is equal to another vector.</summary>
        /// <param name="other">A vector to compare with this vector.</param>
        /// <returns>true if the current vector is equal to the vector parameter; otherwise, false.</returns>
        public bool Equals(Vector2 other)
        {
            return
                X == other.X &&
                Y == other.Y;
        }

        #endregion
    }
}
« Last Edit: October 14, 2014, 03:39:32 AM by joedf »

Myran

  • Developer
  • Posts: 183
    • View Profile
Re: [WIP/Proof of Concept][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #4 on: October 14, 2014, 02:09:36 PM »
Hammerwatch used to use the full OpenTK library, but when we switched to SDL2# we no longer need to use the dll. And yeah, shouldn't be a problem to just include the Vector2 class.

joedf

  • Posts: 19
  • Swift Knight
    • View Profile
Re: [WIP/Proof of Concept][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #5 on: October 15, 2014, 03:14:58 AM »
Thanks again :)
I have updated my post with a new version.

bburky

  • Posts: 1
    • View Profile
Re: [WIP][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #6 on: November 08, 2014, 10:12:31 PM »
Hey, I was on a Mac and wanted to use this tool. After recompiling using Mono, it runs great.

https://github.com/joedf/HWSEdit/pull/1

dburden

  • Posts: 2
  • Maggot Crusher.
    • View Profile
Re: [WIP][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #7 on: February 20, 2015, 05:14:09 AM »
Hey all. I've been working on improving this and its functionality. I modified the GUI, fixed some bugs, added some miscellaneous expected functionality, and began work on the player editing.
Here's the GitHub fork: https://github.com/drewaburden/HWSEdit



There's no release build up right now, so you'll have to compile it yourself (joedf, feel free to pull upstream; not trying to take any credit for the work you already did). I also haven't tested this on any platform other than Windows, but I tried my best not to use anything that was obviously Windows-specific. I'm hoping to get a solid build out within the next few weeks. I work a full time job, so I can only work on the evenings, but I really would like to see this program make some more progress!
« Last Edit: February 20, 2015, 05:16:36 AM by dburden »

joedf

  • Posts: 19
  • Swift Knight
    • View Profile
Re: [WIP][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #8 on: February 20, 2015, 05:27:02 AM »
It looks absolutely fantastic!
You definitely nailed the players tab!
I didn't have much to work on it recently...
Great work, I'll definitely pull these changes in.
This is what I meant by "a collaborative effort"!
If you prefer, I can add you as a collaborator on github. :)

Hipshot

  • Developer
  • Posts: 455
  • Level Designer
    • View Profile
Re: [WIP][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #9 on: February 20, 2015, 10:25:24 AM »
Wow, that looks really good, well done.

dburden

  • Posts: 2
  • Maggot Crusher.
    • View Profile
Re: [WIP][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #10 on: February 21, 2015, 04:17:44 AM »
Yeah, sure joedf, if you'd like. Also, thanks for the compliments! I also forgot to mention that I made stuff localization-ready, so if anybody wants to translate this tool to their language, let me know.
« Last Edit: February 21, 2015, 04:56:47 AM by dburden »

joedf

  • Posts: 19
  • Swift Knight
    • View Profile
Re: [WIP][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #11 on: February 21, 2015, 05:21:54 AM »
Done! ;)
i could translate for french and german

hiiplayhw

  • Posts: 14
  • Maggot Crusher.
    • View Profile
Re: [WIP][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #12 on: March 06, 2015, 08:52:55 PM »
Hello,

Would you please post a compiled version that has the updated character editor? I have hopelessly wasted a good hour and a half trying to figure out how to install ant, git, etc. to compile it myself, but I've given up as there are just too many confusing install guides for someone with no knowledge of this stuff. Thanks.

ironsteve

  • Posts: 1
    • View Profile
Re: [WIP][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #13 on: August 18, 2015, 08:56:55 PM »
Hello,

Would you please post a compiled version that has the updated character editor? I have hopelessly wasted a good hour and a half trying to figure out how to install ant, git, etc. to compile it myself, but I've given up as there are just too many confusing install guides for someone with no knowledge of this stuff. Thanks.

There you go...  ;)

joedf

  • Posts: 19
  • Swift Knight
    • View Profile
Re: [WIP][Tool] HWSEdit - Hammerwatch Save Editor
« Reply #14 on: August 19, 2015, 03:57:07 AM »
Hmm, sorry i must have missed this. :P