This is the eight post in the Building a Roguelike in Javascript series. I recommend you start at the beginning unless you've been following along. This part corresponds to the sixth part in Trystan's series. All the code for this part can be found at the part 6 tag of the jsrogue repository. At the time of writing, I am still using the d4ea2ab commit for rot.js.

In the last post we introduced basic combat allowing our hero to whack down any pesky fungi by simply bumping into them. We're now going to make this combat system a bit more realistic, giving attack and defense stats to entities and adding a bit of a random factor to attack damage. As we play our game we're also going to want to know what's going on so we're going to add a simple way to show messages on the screen.

Demo Link

The results after this post can be seen here

Combat - assets/entities.js

Our combat system will work by giving a defense value to Destructible mixins as well as an attack value to Attacker mixins. We will calculate the difference between the two (attackValue - defenseValue) and if the result is greater than zero, randomly select a damage amount in the range [1, attackValue - defenseValue ]. If our defense value is greater than or equal to our attack value, than we only do a damage of 1.

We're going to update the Destructible mixin first. In the last post this mixin always had 1 HP. We will change this so that an entity starts out with a given level of health (maxHp) which can be passed in the template. We also add some getters for the health point stats.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Game.Mixins.Destructible = {
    name: 'Destructible',
    init: function(template) {
        this._maxHp = template['maxHp'] || 10;
        // We allow taking in health from the template incase we want
        // the entity to start with a different amount of HP than the 
        // max specified.
        this._hp = template['hp'] || this._maxHp;
    },
    getHp: function() {
        return this._hp;
    },
    getMaxHp: function() {
        return this._maxHp;
    },
    // ...
};

Now we want to add the defense value. This is going to be a function which all Destructible mixins will implement and will simply return an int. By doing it this way we can have all sorts of neat effects, such as an entity who can be in a defensive mode to get a defense bonus or a chest which gets harder to break the further in the game we are. To make our base destructible mixin more flexible, let's update our constructor to allow specifying a defense value in template. For now we'll make the default defense value function return 0.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Game.Mixins.Destructible = {
    // ...
    init: function(template) {
        // ...
        this._defenseValue = template['defenseValue'] || 0;
    },
    getDefenseValue: function() {
        return this._defenseValue;
    },
    // ...
};

Now to take care of the attacking mixin. We have a bit of refactoring to do from last post. We are going to rename SimpleAttacker to Attacker.

1
2
3
4
5
Game.Mixins.Attacker = {
    name: 'Attacker',
    groupName: 'Attacker',
    // ...
}

Now we want to add an attack value function to the Attacker mixin similar to the defense value. As the default defense value was 0, our default attack value will be 1. We also want to add an option to read the attack value from the template used to create the mixin.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Game.Mixins.Attacker = {
    // ...
    init: function(template) {
        this._attackValue = template['attackValue'] || 1;
    },
    getAttackValue: function() {
        return this._attackValue;
    },
    // ...
};

Finally we update the default attack method to calculate the damage using the formula we mentioned above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Game.Mixins.Attacker = {
    // ...
    attack: function(target) {
        // If the target is destructible, calculate the damage
        // based on attack and defense value
        if (target.hasMixin('Destructible')) {
            var attack = this.getAttackValue();
            var defense = target.getDefenseValue();
            var max = Math.max(0, attack - defense);
            target.takeDamage(this, 1 + Math.floor(Math.random() * max));
        }
    }
}

Our combat system is now much more flexible and the aspect of randomness certainly makes it more interesting. Let's update our templates to give our player and fungus some stats. Our player will have a max HP of 40 for now and an attack value of 10. Our fungi will have the default defense value and 10 HP.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Player template
Game.PlayerTemplate = {
    character: '@',
    foreground: 'white',
    maxHp: 40,
    attackValue: 10,
    mixins: [Game.Mixins.Moveable, Game.Mixins.PlayerActor,
             Game.Mixins.Attacker, Game.Mixins.Destructible]
}
// Fungus template
Game.FungusTemplate = {
    character: 'F',
    foreground: 'green',
    maxHp: 10,
    mixins: [Game.Mixins.FungusActor, Game.Mixins.Destructible]
}

Messaging - assets/entities.js

Now that we can hit our fungi with varying damage, we're going to want to know how much damage we're inflicting as well as when we sucesfully kill an enemy. To do this, we're going to want to be able to display messages on the screen. To keep in line with our mixin system, we are going to have a mixin for entities which can receive messages. Our player will have this mixin. As the entities take their turn, they will fill up the player's message queue. When the player's turn finally comes, we will display these messages on the screen. We will need a way to clear this message queue as well in order to make sure we don't show the same messages every single turn.

Let's create the MessageRecipient mixin. This will add an internal array of messages to the entity, and provide a method for receiving a message, which will be a string for now, as well methdods for fetching and clearing the messages.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Game.Mixins.MessageRecipient = {
    name: 'MessageRecipient',
    init: function(template) {
        this._messages = [];
    },
    receiveMessage: function(message) {
        this._messages.push(message);
    },
    getMessages: function() {
        return this._messages;
    },
    clearMessages: function() {
        this._messages = [];
    }
}

We are now going to want a function to send a message to an entity if it can receive them. As this will be primarily used in the entity code, we will place the function here for now, although I may move it in the future if I can think of a better place or if we start needing more advanced message processing. Our message sending function will specify the entity we wish to target as well as the message itself. To make message formatting simple, I will be using the fantastic sprintf.js library by Alexandru Marasteanu, which is currently at commit 2e852e4b7e. This will allow us to send messages like so:

1
Game.sendMessage(player, "You hit the %s for %d damage", [name, damage]);

To install this library you will want to download the sprintf.min.js file (this is at the right commit) and add it to the assets folder.

Let's define our sending function! This will take at least a recipient entity and a message as arguments, as well as an optional array of parameters to pass to vsprintf, which is a function similar to sprintf that accepts it's formatting arguments as an array. If our recipient entity has the MessageRecipient mixin, then we will send them the message!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Game.sendMessage = function(recipient, message, args) {
    // Make sure the recipient can receive the message 
    // before doing any work.
    if (recipient.hasMixin(Game.Mixins.MessageRecipient)) {
        // If args were passed, then we format the message, else
        // no formatting is necessary
        if (args) {
            message = vsprintf(message, args);
        }
        recipient.receiveMessage(message);
    }
}

Now we are all set up to send some messages! Before we do that, we have some small updates to do to our entity templates. We will start using the name property to give a textual name to our entities so that our message could say something along the lines of 'you hit the fungus' rather than 'you hit the F'. We will also make the player a message recipient!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Player template
Game.PlayerTemplate = {
    character: '@',
    foreground: 'white',
    maxHp: 40,
    attackValue: 10,
    mixins: [Game.Mixins.Moveable, Game.Mixins.PlayerActor,
             Game.Mixins.Attacker, Game.Mixins.Destructible,
             Game.Mixins.MessageRecipient]
}
// Fungus template
Game.FungusTemplate = {
    name: 'fungus',
    character: 'F',
    foreground: 'green',
    maxHp: 10,
    mixins: [Game.Mixins.FungusActor, Game.Mixins.Destructible]
}

Now we're ready to add some messages! We want a message to appear when our hero attacks another entity as well as when damage is received in the future! So let's modify the Attacker mixin's attack function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Game.Mixins.Attacker = {
    // ...
    attack: function(target) {
        // If the target is destructible, calculate the damage
        // based on attack and defense value
        if (target.hasMixin('Destructible')) {
            var attack = this.getAttackValue();
            var defense = target.getDefenseValue();
            var max = Math.max(0, attack - defense);
            var damage = 1 + Math.floor(Math.random() * max);

            Game.sendMessage(this, 'You strike the %s for %d damage!', 
                [target.getName(), damage]);
            Game.sendMessage(target, 'The %s strikes you for %d damage!', 
                [this.getName(), damage]);

            target.takeDamage(this, damage);
        }
    }
}

Another potential message we could want is when an entity is destroyed! Let's modify the Destructible mixin!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Game.Mixins.Destructible = {
    // ...
    takeDamage: function(attacker, damage) {
        this._hp -= damage;
        // If have 0 or less HP, then remove ourseles from the map
        if (this._hp <= 0) {
            Game.sendMessage(attacker, 'You kill the %s!', [this.getName()]);
            Game.sendMessage(this, 'You die!');
            this.getMap().removeEntity(this);
        }
    }
}

Our system seems pretty straightforward for direct entity to entity messaging! We may have cases where we wish to broadcast a message to all entities within a certain distance! In order to do this, we'll need to create some helper functions in our map class first!

assets/map.js

We currently only have methods for returning the entity at a given position! We are going to add a method which will return an array containing all entities which are within a given radial distance from the specified center X and Y position. An example of radial distance is:

3333333
3222223
3211123
3210123
3211123
3222223
3333333


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Game.Map.prototype.getEntitiesWithinRadius = function(centerX, centerY, radius) {
    results = [];
    // Determine our bounds
    var leftX = centerX - radius;
    var rightX = centerX + radius;
    var topY = centerY - radius;
    var bottomY = centerY + radius;
    // Iterate through our entities, adding any which are within the bounds
    for (var i = 0; i < this._entities.length; i++) {
        if (this._entities[i].getX() >= leftX &&
            this._entities[i].getX() <= rightX && 
            this._entities[i].getY() >= topY &&
            this._entities[i].getY() <= bottomY) {
            results.push(this._entities[i]);
        }
    }
    return results;
}

assets/entities.js

We are now ready to make a function for sending a message to all entities near a location. This function will be similar to the sendMessage function we previously defined, except it allows us to specify a location rather than a recipient entity. We will send the message to all entities within a given radius from the location (radius is fixed at 5 for now).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Game.sendMessageNearby = function(map, centerX, centerY, message, args) {
    // If args were passed, then we format the message, else
    // no formatting is necessary
    if (args) {
        message = vsprintf(message, args);
    }
    // Get the nearby entities
    entities = map.getEntitiesWithinRadius(centerX, centerY, 5);
    // Iterate through nearby entities, sending the message if
    // they can receive it.
    for (var i = 0; i < entities.length; i++) {
        if (entities[i].hasMixin(Game.Mixins.MessageRecipient)) {
            entities[i].receiveMessage(message);
        }
    }
}

To make sure that this works, lets make it so that when a new fungus grows it will send a message to the entities nearby! This may not be the most efficient code, but it will serve our purposes just fine for now! We will update the FungusActor mixin!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Game.Mixins.FungusActor = {
    // ...
    act: function() { 
        // ...
                    if (this.getMap().isEmptyFloor(this.getX() + xOffset,
                                                   this.getY() + yOffset)) {
                        var entity = new Game.Entity(Game.FungusTemplate);
                        entity.setX(this.getX() + xOffset);
                        entity.setY(this.getY() + yOffset);
                        this.getMap().addEntity(entity);
                        this._growthsRemaining--;

                        // Send a message nearby!
                        Game.sendMessageNearby(this.getMap(),
                            entity.getX(), entity.getY(),
                            'The fungus is spreading!');
                    }
        // ...
    }
}

The last thing we're going to want to do in this file is make it so that it clears the messages after they have been rendered to the screen. While we haven't done the actual rendering yet, we know that it will be done when we call Game.refresh in the PlayerActor mixin. So after we do this call, let's clear our messages!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Main player's actor mixin
Game.Mixins.PlayerActor = {
    name: 'PlayerActor',
    groupName: 'Actor',
    act: function() {
        // Re-render the screen
        Game.refresh();
        // Lock the engine and wait asynchronously
        // for the player to press a key.
        this.getMap().getEngine().lock();        
        // Clear the message queue
        this.clearMessages();
    }
}

assets/screens.js

Now that our messaging system is up and running, let's start rendering them to the screen! For now we will simply render them in the top left corner. We have to make sure to render them after rendering the map! This may not be the most elegant location for them, and we may move them to a centralized message box in the future (or as a challenge try and modify the code to do that now!) but for now this will do. Luckily we can use the drawText function to wrap text and determine how many lines the text spanned across. This will allow us to easily messages that are too long to fit on one line.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Game.Screen.playScreen = {
    // ...
    render: function(display) {
        // ...
        // Get the messages in the player's queue and render them
        var messages = this._player.getMessages();
        var messageY = 0;
        for (var i = 0; i < messages.length; i++) {
            // Draw each message, adding the number of lines
            messageY += display.drawText(
                0, 
                messageY,
                '%c{white}%b{black}' + messages[i]
            );
        }
    },
    // ...
};

Now your hero can go around attacking and killing fungus and seeing feedback right on the screen! One last change I want to make before I finish off this post is to show the player's stats on the last row of the screen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Game.Screen.playScreen = {
    // ...
    render: function(display) {
        // ...
        // Render player HP 
        var stats = '%c{white}%b{black}';
        stats += vsprintf('HP: %d/%d ', [this._player.getHp(), this._player.getMaxHp()]);
        display.drawText(0, screenHeight, stats);
    },
    // ...
};

assets/game.js

As we want to reserve the last line of the display for rendering the stats, we are going to increase the size of the display by 1.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var Game =  {
    // ...
    init: function() {
        // Any necessary initialization will go here.
        this._display = new ROT.Display({width: this._screenWidth,
                                         height: this._screenHeight + 1})
        // ...
    }
    // ...
}

index.html

We have to update our scripts to include the sprintf.js library.

1
2
3
<script src="assets/rot.min.js"></script>
<script src="assets/sprintf.min.js"></script>
<script src="assets/game.js"></script>

Conclusion

This post was pretty significant towards pushing our game closer to a real roguelike game! We now have a more robust combat system as well as a nice way of sending messages to the player! In the next post we will be making our cave have multiple levels, so stick around as it's going to be exciting!

I hope you enjoyed this post! Remember that all the code for this part can be found at the part 6 tag of the jsrogue repository. As always please feel free to post any comments whether questions, clarifications or criticism!

Thanks for reading,

Dominic

Next Part

Part 7 - Deeper Into the Cave