A step forward for R18+ classification for games in Australia

Posted by

Well, I gotta say, this is something that I did not see coming.

South Australian Attorney General, Michael Atkinson has quit the front bench, and hence his post as South Australia's Attorney General.

At current count, Gamers 4 Croydon has "only" received 3.7% of the vote in the seat of Croydon, and while that's a lot more than the "less than 1 percent" predicted by Mr. Atkinson, it's still pretty paltry.

They've also only received 0.8% of the vote for the Legislative Council. Now, I know very little of how this stuff works, but I'm pretty sure that's well short of what is required to win a seat. Which is a shame, because it'd have been nice to have at least one person from that party representing gamers in South Australia. But maybe preferences will come their way? Of course, I've got no hope of deciphering how the preferences flow...

But never-the-less, Mr Atkinson's resignation is a big step forward and hopefully Australia will (finally) be able to move into the 21st centry and introduce an R18+ category after the next meeting of the Attorney Generals (slated for April). We'll see, I guess. If the outcome is good, it seems we can be pretty confident that it actually was Mr Atkinson who was vetoing the change, dispite the fact that he's said he's not the only one opposed.

Pathfinding in Starcraft 2

Posted by

While I've been working on the pathfinding in War Worlds, I've also been looking around at ways other games have been doing it. Check out the video below for an example of what I'd consider state-of-the-art. It's the pathfinding in the upcoming Starcraft 2:

It's a long video, but some of the stuff there really blows me away (I especially like right at the end where the zerg swarm over the base, destroying it in seconds)

So how do they do it? Obviously I don't know, but I can guess.

The feature that makes it look like it does is the flocking behaviour. Now flocking is a fairly well-studied topic, and while Starcraft 2's implement has some nice features, I don't think it's totally revolutionary. The main feature that I like about their flocking is the way that units in a big group like will "stream out" towards the goal in a long line, rather than trying to all stay in formation. Depending on the style of game, you might want them to stay in formation, but for what is basically a swarm of insects, the behaviour in SC2 looks really nice.

The main problem is how do you define the group so that flocking works, but not so that units get stuck. For example, imagine a situation like the one in the diagram below:

If you imagine you select both the red and blue units and click where the green "X" is. You would expect the red units to flock together and the blue units to flock together, but obviously, they would follow different paths.

Now, I don't know how Starcraft does it, but to me this is a situation where a polygonal navmesh would shine. Basically, you define all units that are on the same polygon in the mesh as being in a single "flock" (and perhaps some additional logic to account for adjacent polygons) and then find a single path for the whole flock. Then the units would use a flocking/path-following algorithm (e.g. something like this) to make it look "natural".

Obviously, the path-finding in War Worlds is extremely basic compared to all of this (I don't even use navmeshes yet!) but one day...

(Special thanks to this thread on gamedev.net for some of my ideas)

SA politician admit they're a bunch of out-of-touch old fogeys

Posted by

It's bizzare. I don't usually post political stuff, so hopefully you'll forgive this indulgence :)

Just yesterday, we hear that our favourite Attorney-General, South Australian AG Michael Atkinson had a law passed saying that nobody would be "allowed" to post comments on South Australian websites without giving their full name and address, and that the first name and postcode needed to be published along with the comment.

From some of his comments, you really get the impression that he's starting to lose his grip on reality:

"The AdelaideNow website is not just a sewer of criminal defamation, it is a sewer of identity theft and fraud."

"You will publish false stories about me, invent things about me to punish me."

So, of course there was an internet backlash. Now, I've only lived in this world for some 30 years, so I don't have the "experience" of people like Mr. Atkinson, but let's be honest here... if you didn't see this coming, you'd have to have your head up your arse.

Then today, we hear that the law will be retroactively repealled (after the election). But still the bizarre comments from Atkinson continue:

"From the feedback we've received... the blogging generation believes that the law supported by all MPs and all political parties is unduly restrictive"

"I miscalculated the strength of feeling among teenagers and people in their 20s who have grown up with the internet and blogging and I underestimated their desire to have as a right the ability to make political commentary in the election period anonymously or under an assumed name."

That first one, he's basically saying "all us politicians are so out-of-touch that we had no idea that this would even register as a blip on the radar!" And as if it's only "teenagers and people in their 20s" - who are all criminals anyway, right Mr. Atkinson? - as if they're the only ones who care about being censored...

There is some evidence that "all MPs" who supported the bill were misled (presumably by Atkinson) into believeing that the laws were not as broad-ranging as they actually were (though you've got to wonder if anybody actually reads what they're voting for?). Says the shadow attorney-general Vickie Chapman:

"He clearly wanted to use this legislation to hunt down any of those who criticised him or the Government and that was made absolutely clear yesterday by his statements and behaviour"

Which seems very much inline with some of the other stuff that Atkinson has been doing over the last few months

This whole thing is really wierd, in my opinion. Atkinson seems to have this bizzare persecution complex... anyway, it'll be interesting to see what happens in March.

War Worlds packet structure

Posted by

I thought today I would take some time to describe the method I'm using for building, serialising and deserialising the network packets in War Worlds. I am using ENet as the underlying network protocol, which means the War Worlds networking system is based on UDP.

I won't go into too much detail on how connections and such are managed, since a lot of that stuff is handled pretty well by ENet already. What ENet doesn't handle, though, is how you structure your packets - from ENet's point of view, you just get an array of bytes. But that's not much use to us, since we think in terms of objects and fields. So the question is, how do we map between our objects and a simple byte array and back again?

When I started doing this stuff, I looked into Boost.Serialization, but I found it to be rather too much for the relatively simple requirements that I had. I decided that I could implement it myself in a short enough time and have full control over the serialised format.

The first part of solution comes from the packet_buffer class which works much like a SC++L stream (though much simplified) and lets us build up the binary data in a fairly easy and intuitive way. A basic outline of it's implementation is below (I've omitted the implementation of the more obvious methods):


class packet_buffer
{
private:
    bool flushed_;
    std::string value_;
    std::stringstream buffer_;
    uint16_t packet_type_;

    void flush();

public:
    packet_buffer(uint16_t packet_type);
    packet_buffer(char const *bytes, std::size_t n);
    ~packet_buffer();

    void add_bytes(char const *bytes, std::size_t offset, std::size_t n);
    void get_bytes(char *bytes, std::size_t offset, std::size_t n);
    char const *get_buffer();
    std::size_t get_size();
    uint16_t get_packet_type() const { return packet_type_; }
};

void packet_buffer::add_bytes(char const *bytes, std::size_t offset, std::size_t n)
{
    flushed_ = false;
    buffer_.write(bytes + offset, n);
}

void packet_buffer::get_bytes(char *bytes, std::size_t offset, std::size_t n)
{
    buffer_.read(bytes + offset, n);
}

void packet_buffer::flush()
{
    if (flushed_)
        return;

    buffer_.flush();
    value_ = buffer_.str();
    flushed_ = true;
}

char const *packet_buffer::get_buffer()
{
    flush();
    return value_.c_str();
}

std::size_t packet_buffer::get_size()
{
    flush();
    return value_.length();
}

This provides the basic implementation of the "buffer”. As you can see, it's a pretty basic wrapper around std::stringstream (though we could've used std::vector<uint8_t> or something as well). The slightly more interesting aspect is the following helpers:


packet_buffer &operator <<(packet_buffer &lhs, int32_t rhs)
{
    lhs.add_bytes(reinterpret_cast<char const *>(&rhs), 0, 4);
    return lhs;
}

packet_buffer &operator >>(packet_buffer &lhs, int32_t &rhs)
{
    lhs.get_bytes(reinterpret_cast<char *>(&rhs), 0, 4);
    return lhs;
}

// (more here for int16_t, uint32_t, etc...

packet_buffer &operator <<(packet_buffer &lhs, vector const &rhs)
{
    lhs.add_bytes(reinterpret_cast<char const *>(rhs.data()), 0, sizeof(float) * 3);
    return lhs;
}

packet_buffer &operator >>(packet_buffer &lhs, vector &rhs)
{
    lhs.get_bytes(reinterpret_cast<char *>(rhs.data()), 0, sizeof(float) * 3);
    return lhs;
}

packet_buffer &operator <<(packet_buffer &lhs, colour const &rhs)
{
    uint32_t rgba = rhs.to_rgba();
    lhs.add_bytes(reinterpret_cast<char const *>(&rgba), 0, sizeof(uint32_t));
    return lhs;
}

packet_buffer &operator >>(packet_buffer &lhs, colour &rhs)
{
    uint32_t rgba;
    lhs.get_bytes(reinterpret_cast<char *>(&rgba), 0, sizeof(uint32_t));
    rhs = fw::colour(rgba);
    return lhs;
}

packet_buffer &operator <<(packet_buffer &lhs, std::string const &rhs)
{
    // strings are length-prefixed
    uint16_t length = static_cast<uint16_t>(rhs.length());
    lhs << length;
    lhs.add_bytes(rhs.c_str(), 0, length);
    return lhs;
}

packet_buffer &operator >>(packet_buffer &lhs, std::string &rhs)
{
    uint16_t length;
    lhs >> length;

    char *value = reinterpret_cast<char *>(_malloca(length));
    lhs.get_bytes(value, 0, length);
    rhs = std::string(value, length);

    return lhs;
}

So as you can see, these functions are what actually make it easy to serialise things. Want to serialise a string? Just use operator << to stream it in. An example of one of my packet classes is below, to show you the basic usage of my class:


// this packet is sent from the server when you connect to it
class join_response_packet : public fw::net::packet
{
private:
    std::string map_name_;
    std::vector<uint32_t> other_users_;
    fw::colour my_colour_;
    fw::colour your_colour_;

protected:
    virtual void serialise(fw::net::packet_buffer &buffer);
    virtual void deserialise(fw::net::packet_buffer &buffer);

public:
    join_response_packet();
    virtual ~join_response_packet();

    // etc...

    // (I'll get to these in a second)
    static const int identifier = 123;
    virtual uint16_t get_identifier() const { return identifier; }
};

void join_response_packet::serialise(fw::net::packet_buffer &buffer)
{
    buffer << map_name_;
    buffer << other_users_.size();
    BOOST_FOREACH(uint32_t sess_id, other_users_)
    {
        buffer << sess_id;
    }
    buffer << my_colour_;
    buffer << your_colour_;
}

void join_response_packet::deserialise(fw::net::packet_buffer &buffer)
{
    typedef std::vector<std::string>::size_type size_type;

    buffer >> map_name_;
    size_type num_other_users;
    buffer >> num_other_users;
    for(size_type i = 0; i < num_other_users; i++)
    {
        uint32_t other_user;
        buffer >> other_user;

        other_users_.push_back(other_user);
    }
    buffer >> my_colour_;
    buffer >> your_colour_;
}

As you can see, when you join a new game, the server sends to you the current name of the map (a string), the session identifiers for all the other connected players (integers) and the "colour" of yourself and the server (this is used to differentiate between players in the game: red vs. blue, etc).

The final difficulty comes when you receive a "bunch of bytes" from your peer. How do you know which class to deserialize? This is the only place where a bit of macro magic happens (and that's really just to save a bit of typing). First of all, the header file:


// this macro is used by the packet class to "register" itself
#define PACKET_REGISTER(type) \
    shared_ptr<fw::net::packet> create_ ## type () { \
        return shared_ptr<fw::net::packet>(new type()); } \
    fw::net::packet_registrar reg_ ## type(type::identifier, create_ ## type)

// this is what you call to create an instance of a packet from a packet_buffer
shared_ptr<packet> create_packet(packet_buffer &buff);

typedef shared_ptr<packet> (*create_packet_fn)();

class packet_registrar
{
public:
    packet_registrar(uint16_t id, create_packet_fn fn);
};

And the corresponding .cpp file:


static std::map<uint16_t, create_packet_fn> *packet_registry = 0;

shared_ptr<packet> create_packet(packet_buffer &buff)
{
    create_packet_fn fn = (*packet_registry)[buff.get_packet_type()];
    if (fn == 0)
    {
        // error!
    }

    return fn();
}

packet_registrar::packet_registrar(uint16_t id, create_packet_fn fn)
{
    if (packet_registry == 0)
        packet_registry = new std::map<uint16_t, create_packet_fn>();

    (*packet_registry)[id] = fn;
}

So at the top of the .cpp for the packet, we just call the PACKET_REGISTER(packet_type) macro and it'll register itself with the system. This is what the identifier member of the packet class is for: it's used by the PACKET_REGISTER macro to assign an integer to that packet type which we put into the packet_buffer when sending it to the other side.

For the time being, I'm just manually giving each packet type a unique identifier by hand. It's not that much trouble (and there's not a whole lot of packet types to do anyway). I've also got some error checking code in my packet_registrar::packet_registrar method so that if two classes have the same identifier, it logs the error so I can fix it.

And so, that's basically it. The system is very simple, doesn't take much to maintain and it will allow me (in the future) to handle some more advanced scenarios with relative ease (for example, I could use a variable-length encoding for integers to save space, I could implement compression, or I could add packet coalescing).

Next time, I'll hopefully have some more screenshots or videos to show. I'm in the process of implementing some basic pathfinding, which has been fun and gives the units some much more "intelligent" (looking) behaviour...

AI in War Worlds

Posted by

It's been a while since I blogged about War Worlds, and there's a good reason for that... but I can't say too much about it for now :) Anyway, I've done a little bit more work on it recently, and so I thought I'd just blog a little about what I've been doing.

I've been working a little on the AI which, as I blogged about before, is being done in Lua.

Now, there's two main functions in the War Worlds AI which make up the bulk of the API. The first is the one I blogged about before, self:find_units. The other one is self:issue_order (note: "self" in these function calls refers to the AI's "player" object, kind of like "this" in C++). With these two APIs combined, you should be able to do pretty much any AI.

Obviously, there was quite a bit of plumbing in the back-end that needed to go into it all before these could work, and they're still not the only thing you'll ever call, but it's a nice start.

Here's an example of some Lua from one of the test scripts I've written:


function check_attack()
    local units = self:find_units({ unit_type="factory", build_state="idle" })
    self:issue_order(units, { order="build", build_unit="simple-tank" })

    self:timer(10, check_attack)
end

So, line 2 of that snippet looks for all "factory" units whose "build_state" is "idle" (that is, all factories that are not currently building anything). The self:find_units function returns a collection of units - you can iterate them and do stuff like that if you like, but we just want to issue them a "build" order to build the "simple-tank" unit type, which is what we do in line 3.

On line 5, we call the self:timer function to schedule ourselves to run again in 10 seconds.

This is the basic way that the AI will be implemented for the initial release. I'm hoping to make the AI interface generic enough that people will feel compelled to write their own AI for the game. Who knows? Maybe we could set up tournaments specifically for the AIs that people have developed!