Home of Ebyan Alvarez-Buylla Sat, 23 Jan 2021 02:14:14 +0000 en-US hourly 1 Blogs are Back and so is Textmode! Sat, 23 Jan 2021 02:12:49 +0000 Ok. So I lied about blogs. They’re still dead. Also, did you hear about Flash? A shame that. Truly an end to an era.

Textmode, ASCII art, PETSCII art, etc., however, is having a bit of a moment in the sun and you know I’m into it. Let’s catch up on the last couple of years of blog silence.

Flash is Dead for Real This Time

I’m still salty about Flash being killed. It’s highly unlikely I’ll rework all of the projects on this site to JavaScript, though any new projects and demos will be HTML5. I’ll have to make some time to go through the process of deleting (what us in the biz call “sunsetting”) the games that now essentially deliver dead plug-in shells.

Blogs are Dead But You Can Still Read Them

Do you remember when the Internet felt like something everyone owned, and no one owned? This weird, both sprawling and intimate agglomeration of odd sites and fledgling platforms. And now those platforms have fledged and taken flight and we are cast in their shadow. Google decides Reader is no longer a fun project for them, they kill it. Adobe decides Flash is ash, they not only discontinue it, they outright retroactively block it. Who does that? Megalomaniacs, that’s who.


It should come as no surprise that I’m a fan of this ancient art form. Only I didn’t know of its origins or of PETSCIIbased art or what can more generally be described as Textmode art.

I tried to get into doing my own textmode stuff before, but it turns out alls I needed was an editor with the ability to rotate and flip tiles (don’t tell the purists). Enter Playscii, which also has great support for custom tile sets, palettes, animations, and even tile sets with more than 256 glyphs (also don’t tell the purists).

Here are Some Things I’m Working On

This is a thing:

This is another thing:

(more on these soon)


The short of it: paused.

The long of it: I spent quite a long time on world generation, UI design, and even name generation, but never quite got the core loop of the game to be something I was happy with. I started down the path of a large design restructure and simplification but it’s been hard to stay motivated on a long-form project this past year. I did pick up ukulele, electric guitar, and am gardening a lot more!

Nolithius in 2021

Could be an unofficial new year’s resolution or a result of Flash having finally been killed or a growing disillusion with social media or a re-broadening of interests partly brought upon by the pandemic but in any case I have resolved to accomplish the following in 2021:

  • Blog more
  • Release a new game
  • Restructure Guildmaster
  • Get good at music
  • Garden more


]]> 0
World Generation Techniques: Domain Warping Sat, 03 Mar 2018 23:55:35 +0000 Domain Warping, domain distortion, or noise distortion is an effective technique to breathe additional life into noise maps by stretching and twisting them about, adding some excitement to the otherwise fairly uniform underlying noise.

We’ll demonstrate this technique by starting with a vanilla Perlin noise map as the base, and displacing its positions along two sets of Perlin noise maps to achieve a sum greater than its parts. Let’s dive right in:

The Base Map

Start with a Perlin noise map that fits your needs. Play around with the map size and noise parameters until you are satisfied. Due to the nature of the displacement, it is useful to start with a map that is tileable both vertically and horizontally, since points will almost certainly be displaced off of the map boundaries and will need to be wrapped around.

Warp 20: Engage

We’ll need to generate two additional Perlin noise maps for displacement: one for the X values, and one for the Y. These should also be tileable to avoid any obvious seams across map boundaries, and each cell should have a value ranging from -1 to 1. If they have a value from 0 to 255, as in the ones in the demo, map them to the -1 to 1 range with the necessary arithmetic (x / 255 * 2 - 1, in this case).

The one missing ingredient is the amplitude. This describes the maximum displacement that will occur for a value of -1 or 1. Pick a reasonable value for your purposes: from 1 to twice your map dimensions. For this demo I’ve chosen 20.

Now we warp:

  • Iterate through each x/y value.
  • For each position, grab the corresponding value from the X displacement map and multiply it times the amplitude, and do the same for the Y displacement map.
  • You now have two values, one for X, and one for Y, in the range (-amplitude, amplitude).
  • These describe the x/y offset, from the current position, of the cell that will be replacing this cell in the result. So if we’re working with cell 5,10, and the offset values are -2, 9, we’ll write the value in cell 3, 19 to the result, at position 5, 10.
  • We must also ensure that we handle offsets that reach across map boundaries: for our purposes, wrapping works well, though clamping may also give you reasonable results.

Note: I’ve chosen ranges for most values such that the demo produces satisfactory results. I encourage you to tinker with the source code such that you may explore what lies beyond these bounds!

Tidying Things Up

Depending on your particular needs, you may have all that you require as of this point. However, for our world generation purposes, we’ll take some additional steps to round out the edges of the map, pick a water line, and finally, assign some color values to different elevations so that we can better appreciate the difference domain warping makes.

For a quick rounding of the map’s edges, I recommend a simple gradient circle map, which multiplies values within an inner radius times 1, then linearly interpolates to 0 at the outer radius. (The keen observer may have noticed that I have moved away from the more interesting yet radically more expensive particle deposition map from World Generation Breakdown.)

To pick a water line, you may be inclined to take your max elevation after you’ve normalized your maps and multiply it times the desired water cover. However, collecting all of the map’s values in a sorted list, then indexing the value at water_line_percent * list.length will provide more accurate results, since the distribution of the noise values is unlikely to be linear. In the source code, the Map class provides a handy getElevationByPercentage() function for this purpose.

I certainly hope to see more procedurally-generated worlds taking advantage of simple-yet-effective techniques such as domain warping. The source code for all of the demos, and of course, the underlying domain warp logic, can be found on GitHub. Enjoy!

]]> 0
Chronophase Source Code Released! Sat, 29 Apr 2017 18:21:55 +0000 I recently had a request to release the Chronophase source code, which, after many years, still seems to hold up reasonably well!

For the brave of heart, this is what the source code for a 4-day roguelike challenge looks like:

Full disclosure: Chronophase is not a traditional roguelike but rather a brief exploration of what an ASCII turn-based space dogfighting game could be.

The original source was built with Flash and relied on an embedded asset in the FLA, so I took a little bit of time to rework it for IntelliJ IDEA and a standalone AIR SDK compile. It should be straightforward to bring it into Flash Develop or your preferred IDE.


]]> 0 Fall Clean-Up Fri, 14 Oct 2016 23:06:42 +0000 It’s been a while since the site got some love, so I’ve taken a bit of time to make a few enhancements, mostly with the focus of separating evergreen content from timely updates. A rough list of the notable changes includes:

  • New homepage layout.
  • Games section, with game descriptions.
  • About section, with a little information about yours truly.
  • Evergreen content now housed under Articles.
  • Timely posts are now housed under Updates.
  • Removed tags as a method of navigation.
  • Added redirects under the hood so old URLs still reach the expected content.
  • Removed old, less relevant posts.

There are a series of enhancements I cut from this pass to keep on pace with the development of Guildmaster, but which I plan to tackle in the coming months, such as:

  • Widening the readable area for the posts.
  • Moving the Crossword Dungeon microsite to /crossword-dungeon.
  • Adding the Guildmaster microsite to /guildmaster.
  • Cleaning up articles to free them of time-sensitive content
  • Organizing articles in more cohesive series. For example, instead of a monolithic post, splitting the topic of World Generation into a shorter sequence of techniques you might employ together or a-la-carte.

Nice to haves, but not must-haves!

]]> 0
World Generation Breakdown Sun, 20 Mar 2016 05:46:54 +0000 A large part of Dance of Death v0.6.136 was the addition of world generation. Although the world is little more than a large island at the moment, the results are rather satisfying, even considering that the current method of generating terrain is purely based on elevation. After experimenting with a number of techniques, I settled on this simple formula: 1) Generate Fractal Perlin Noise, 2) Multiply noise by a radial gradient, and 3) Apply terrain and water line. Here is the breakdown:

You can click on any of the images to view an interactive demo, and click again on the demo to generate a new map. The images were all generated randomly, so they do not correspond to a progression of the same maps (while Flash’s Perlin noise generator can be seeded, its random function cannot). Source code is available from Google Code, where you can grab it directly from SVN.

Step 1: Generate Perlin Noise

Generate a Fractal Grayscale Perlin Noise with an x-frequency of half the width, a y-frequency of half the height, and 8 octaves. For more loose islands, or for larger maps, you can drop the frequency to 1/4th of the width and height or lower, to fit your purposes. You can normalize the noise from 0-255 if you like, though normalizing now will become redundant later on. This is what we have so far:

Grayscale Fractal Perlin Noise, normalized for contrast. Click to view demo.Grayscale Fractal Perlin Noise, normalized for contrast. Click to view demo.

If we apply the terrain to the tiles at this point, we’ll end up with something like this (jump ahead for the terrain application method):

Terrain applied to noise. Looks pretty good, but it touches the edges and lacks an overall island look. Click to view demo.Terrain applied to noise. Looks pretty good, but it touches the edges and lacks an overall island look. Click to view demo.

Step 2: Generate Rolling Particle Mask

To get the island to be biased towards the center and avoid touching the edges, a straightforward solution is to multiply its values by a radial gradient mask ranging from 1 near the center, to 0 near the edges. A plain radial gradient might do the trick, since the Perlin noise already provides a healthy amount of randomness. However, I went with a modified version of the Rolling Particle algorithm to get some additional roughness. The basic algorithm goes something like this:

  • Start with a blank map, all 0s.
  • Pick a random starting spot for the particle.
  • Increment the value on the map by 1 where the particle is located.
  • Move the particle to a random adjacent position whose value is less than or equal to the current position (imagine the particle rolling downhill).
  • Repeat the last 2 steps as long as the particle is alive.
  • Repeat for a large number of particles.

A particle life of 50, with 3000 particles works well for this map size (which is 88×32, by the way), experiment to get values that work for you. If we normalize, we get this:

Basic Rolling Particle algorithm, 3000 iterations, particle life 50. Click to view demo.Basic Rolling Particle algorithm, 3000 iterations, particle life 50. Click to view demo.

While the algorithm produces inherently center-biased maps, multiplying this across the noise results in islands that are too boxy. To round out the results more, instead of picking a random starting point for particles, let’s ensure that they land closer to the center:

Rolling Particle algorithm, center-biased, particles start 12 tiles away from the edges. Click to view demo.Rolling Particle algorithm, center-biased, particles start 12 tiles away from the edges. Click to view demo.

To absolutely ensure that the islands will not reach the edges, I’ve also multiplied the outermost tiles by 0.75, and the second outermost tiles by 0.88. These tiles already have some of the lowest values of the map, and softening or “blurring” them this way ensures the final map does not touch the edges.

Step 3: Apply Terrain

Once the base noise map and the rolling particle mask have been generated, multiply the noise values times the (mask/255). Dividing by 255 ensures that the mask values will range from 0 to 1 rather than the normalized 0 to 255. This produces the following result:

Combined maps. Click to view demo.Combined maps. Click to view demo.

We’re done with the heightmap generation. Now onto determining the water line and assigning terrain.

I spent some time fussing with linear regression and other estimators to accurately place a water line, but, at the end, directly sampling the values of the map turned out to be the fastest (to program and execute), and most accurate solution. To determine the water line for the default 60% water used in this world generator, step through the map and throw all of the values into an array. Sort this array numerically. The value indexed at (array.length – 1)*0.6 marks your water line. Everything below it is water, everything above is land.

Now that the waterline is set, I pass that value into each tile’s setTypeByElevation() function, which sets the tile type according to its elevation, as follows:

If below the water line:

  • If above waterline-40: shallow water.
  • Else: deep water.

If above the water line:

  • If below waterline+15, coast/beach.
  • If between waterline+15 and waterline+35: plains.
  • If above 255-25: mountain.
  • If between 255-50 and 255-25: hills.
  • Else: forest.

Producing this:

Final result. Click to view demo.Final result. Click to view demo.

Grab the source code from Google Code, or directly from SVN. Feel free to tweak any of the values and modify the algorithms as long as you adhere to the GNU General Public License v3 and provide attribution and all that. Enjoy!

]]> 15
Neural Particle Deposition Wed, 16 Mar 2016 07:28:40 +0000 In developing an algorithm to generate nodes that are guaranteed to be within a certain distance of each other, I stumbled over this interesting distribution of particles which were initially placed randomly then moved near their nearest neighbor. I’ve dubbed this particle system “Neural Particle Deposition” on account of the organic, neuron-like shapes it produces. Take a look:

This algorithm is rather straightforward: after picking the starting point, generate one point at a time within the container’s constraints. If the generated point is too close to its closest point, drop it. Otherwise, move it along the vector to its closest neighbor so it is a certain maximum distance away, then repeat.

The code relies on a container div with the ID of “canvas”, which must have an explicit width and height defined, as well as its position attribute set to “relative”, so that the pips can be absolutely positioned within it. The placed pips are will be given the class of “pip”, with the following initial style:

    width: 8px;
    height: 8px;
    background-color: #000000;
    position: absolute;
    border-radius: 4px;
    opacity: 0.4;

The JavaScript code is as follows:

    var newPipInterval = 8;

    // The farthest
    var maxPipRadius = 6;
    var minPipRadius = maxPipRadius * 0.5;
    var avgPipRadius = maxPipRadius * 0.75;
    var pipCount = 600;

    var center;

    var canvas = $("#canvas");
    var width = canvas.width();
    var height = canvas.height();

    var pips = new Array();


    setInterval(addNewPip, newPipInterval);

    function reset ()
        pips = new Array();
        center = createRandomPip();

    function addNewPip ()
        if (pips.length < pipCount)

    function createPip ()
        var randomPip = createRandomPip();
        var nearestPip = getNearestPip(randomPip);
        var pipDistance = distance(randomPip, nearestPip);

        // Too close, toss
        while (pipDistance < minPipRadius)
            randomPip = createRandomPip();
            nearestPip = getNearestPip(randomPip);
            pipDistance = distance(randomPip, nearestPip);

        // Adjust if we're too far
        if (pipDistance > maxPipRadius)
            // Calculate unit vector
            var unitX = (randomPip.x - nearestPip.x) / pipDistance;
            var unitY = (randomPip.y - nearestPip.y) / pipDistance;

            randomPip.x = avgPipRadius * unitX + nearestPip.x;
            randomPip.y = avgPipRadius * unitY + nearestPip.y;

        return randomPip;

    function getNearestPip (pip)
        var nearestPip = center;
        var nearestDistance = distance(pip, center);

        for (var i = 0; i < pips.length; i++)
            var candidatePip = pips[i];
            var candidateDistance = distance(pip, candidatePip);

            if (candidateDistance < nearestDistance)
                nearestPip = candidatePip;
                nearestDistance = candidateDistance;

        return nearestPip;

    function createRandomPip ()
        return {x: rand(0, width), y: rand(0, height)};

    function addPip (pip)

        var div = document.createElement("div");
        div.setAttribute("class", "pip"); = pip.x + "px"; = pip.y + "px";


        $(div).animate({width: "2px", height: "2px", opacity: 1}, 500);

    function rand(min, max)
        return Math.floor(Math.random()*(max-min+1)+min);

    function clamp (number, min, max)
        return Math.min(Math.max(number, min), max);

    function distance(pip1, pip2)
        var xs = 0;
        var ys = 0;

        xs = pip2.x - pip1.x;
        xs = xs * xs;

        ys = pip2.y - pip1.y;
        ys = ys * ys;

        return Math.sqrt(xs + ys);

You can tame the distribution somewhat by altering the container’s dimensions, as you can see in the header image. To generate a more reliable mountain-range-like distribution, try starting with a narrow container and setting the starting position to one of the ends.

The full source code is available on GitHub as well as Google Code. Enjoy!

]]> 1
World Generation Teaser Tue, 15 Mar 2016 17:05:09 +0000 Happy 2013 everyone! Rather than risking going three months without an update, I figured I’d post some teaser screenshots of the world generation progress on my latest project:

Elevation and water line

Here’s the first pass, mostly focused on getting the land formation looking good with Perlin noise, and sampling the water to ensure a landmass percentage. I’ve also ensured the edges of the map taper off by multiplying the elevation values times a box gradient.

Perlin distortion

A plain Perlin noise elevation is pretty tame, so I figured I’d throw a twist in it this time around and applied a Perlin noise distortion. My distortion implementation involves generating two additional 2D Perlin noise maps, using one to determine the x-offset and another the y. I then sample the original map with these offsets multiplied by a maximum amplitude. It’s worth noting that Ken Perlin’s original C implementation outputs values between -1 and 1, so sampling offsets for a distortion is straightforward. It’s also worth noting that because Objective-C is a superset of C, said original implementation can be used straight out, no porting necessary (although you’ll need to do some work to output the fBm you’re likely expecting).

Floodfilling to identify landmasses

Landmasses have been floodfilled to identify continents and islands (differences not pictured). I’ve picked a somewhat arbitrary minimum area to qualify as a continent (I believe around 4000 tiles), which will determine starting areas for players.

Wind map

Inspired by the [now defunct?] Dungeon League article on Wind Direction, I’ve generated an interpolated wind map, then distorted the result with a Perlin noise distortion. Pictured are the degrees of the wind mapped to 0-255.

Wind vectors over land

Arbitrary wind directions are a good starting point, but the purpose of the wind is to determine humidity distribution by simulating how far moisture would have to travel from water to a given tile, with the added obstructions of mountains that are higher than cloud elevation. To this end, the wind direction on one tile must point towards another tile, so it is necessary to clamp the wind directions to the four cardinal and diagonal directions, resulting in 45° zones. The first pass at clamping had hard boundaries between the zones, which meant the resulting rain shadow had too many straight edges, which then resulted in rather unorganically-bound biomes. To solve this, I set up a random dithering rather than clamping. Pictured are wind directions N, NE, E, SE, S, etc. with a corresponding solid color assigned.

Rain shadow

Also taking a cue from the Dungeon League article on this topic, I then step backwards through the wind vectors, starting at each land tile, and count how many tiles it takes to get to water or to another tile whose humidity has already been set. I set an upper clamp for how long this can go on, in the odd but remotely possible case that you catch a wind loop that never touches water. This max value is also assigned to areas above cloud elevation, which results in mountains creating a rain shadow.

Next steps

I’ve stopped just short of identifying biomes, which is the next step, precluded by a quick temperature gradient generation.

For this particular game, biomes add flavor rather than being a key part of the gameplay, so I’ve shifted gears to building a simple tiling system to represent the terrain. After all, each world tile will be larger than two pixels in the actual game!

]]> 9
WeightedLetter Name Generator Sun, 13 Mar 2016 20:40:28 +0000 This name generator takes a seed of similar sounding names (or any words), and generates a new set of names based on the likelihood that one letter follows another in the original set. It picks the first and last letter, generates the letters in between, best-matches the second-to-last letter, and runs post-generation triple-letter and Damerau–Levenshtein checks to ensure uniformity with the seed.

Originally named the DamerauLevenshteinNamegen, in honor of its last filter step, this generator was first written in PHP, ported to AS3 for use in Dance of Death, and now to Objective C for Crossword Dungeon. The source code is available on GitHub as well as Google Code.

Let’s break it down, step-by-step:

Step 1: Parse the Letter Distribution

This is the largest part and core of this generator: we step through each name in the seed, then through each letter in that name, and keep track of how likely that letter is to follow its prior letter. We also keep track of the sizes of the names, to ensure the generated names are consistently sized. We only need to do this parsing once (or any time the seed changes).

After we’ve got the letter distribution recorded, it’s time to generate some names. We begin by picking a size and a starting letter, then pick following letters based on how likely they are to follow the previous letter in the seed names, until we’ve reached the length of the selected size (let’s call this process the name body generation loop). This is what we end up with (click Generate to generate a new batch of names, or click on the list of seed names to edit them):

Step 2: The Beginning and the End

The generated names are acceptable, but a bit weak, largely because there is no post-processing done to them, but mostly because the first and last letter do not follow the seed distribution. Even though the “Cambridge study” was bogus, there is still an intrinsic importance to the way a word starts and ends. To improve the algorithm, let’s keep track of the likelihood that a letter appears first and the likelihood that it appears last.

Picking the first letter is straightforward: we track it in a weighted array and pick from that; picking the last one is not so trivial. We can pick the last letter before the name body generation loop then best-fit the penultimate letter at the end of the loop (this is the solution I went with), or we can pick the last letter within the name body generation loop by prioritizing letters with higher “lastLetter” weight when picking the last letter (you are welcome to experiment with this approach!).

Thus we achieve:

Step 3: Postprocessing

This is an entirely optional step, since the generator at this point is pretty solid. For some additional refinement, however, we can look to two post-process filters: triple-letter checks, and maximum Damerau–Levenshtein distance from seed names.

The triple letter check is straightforward: step through the word, and ensure no three characters in a row are the same. This is a surprisingly likely occurrence if you have names in your seed with same-letter pairs, since a letter is allowed to follow itself and can do so recursively. In this implementation, this check is being done as a postprocess step, but it can also be integrated into the name body generation loop.

The Damerau–Levenshtein check ensures that the generated word is close enough to at least one of the names in the seed by measuring the Damerau–Levenshtein distance of the generated name to all of the names in the seed until it finds one whose distance is below the threshold (half of the name length works well). What this means is that we want to keep the general structure of half of the generated name, with some room for letter additions, subtractions, replacements, and swaps. This check ends up filtering out names that are very off-the-wall, which you might very well want to keep; it can also be somewhat costly if you have a large number of seed names. I’ve skipped it in the Objective C version without any regrets!

Here is the final algorithm at work:

Grab the source code from GitHub and Google Code. Feel free to tweak any of the values and modify the algorithm; attribution’s always appreciated, but last time I released code under GPLv3 it was met with some discontent, so have fun with it and let me know in what ways you improve the system!

]]> 4
Guildmaster Alpha Progress Wed, 03 Feb 2016 04:27:41 +0000 Guildmaster has progressed rather nicely in the months since the last update, so it is worth taking some time to collate some screenshots showcasing such progress. Most of these were initially posted on Twitter, currently the best avenue to receive bleeding-edge updates on the project. Let’s dive in:

The Party Inventory UI

Taking an experience-first approach, I’ve focused the last chunk of work on the party inventory UI, the primary interface to equip heroes and view party composition at a glance.

This first pass is mostly visual. At the time this screenshot was taken, the equipment system was in place as well as the primary stats directly affecting a character’s expendable attributes (supplies, mana, energy, and health):

It took a bit of layout rejiggering to fit the remaining areas of relevant information. In this next pass, XP, defenses, to-hit, and a hero’s gold crowd the scene a bit, though luckily not much more information was missing:

I then took a brief detour from top-level, high priority implementation to focus on something that’s high on everyone’s coolness list: dual wielding. It was a good thing I tackled this seemingly frivolous feature this early on, because it required a rethinking of the underlying attack system that resulted in a more robust, extensible system that will allow weapon skills to add special attacks, and magic skills to add battle spells to a character’s arsenal. Here’s a dashing rogue brandishing dual swords:

This last UI screenshot reflects the latest and greatest. While visually not much different from the previous one, other than the morale meter, the characters’ special traits are now implemented. Featured in this screen: Curmudgeon, which reduces morale gain by 50% and increases morale loss by 50% for the entire party; Thief, which causes the hero to pocket a percentage of the loot after completing an attack; Shortsight, which reduces the sight range for the party if all members are shortsighted; and Point Blank Shot, which allows a hero without a melee weapon equipped to use a ranged weapon in a front rank:

Guildmaster Website

Also a slight detour from the to-do list, the bug to put together a quick layout for the Guildmaster site bit me; so I mocked it up and begun slicing it and building it out:

That gets us current! I’ll be releasing out Alpha 2 to my internal testers for some early feedback and review shortly, then will move on to the next major areas of missing functionality: XP gaining, leveling up, and city development.

]]> 5
Crossword Dungeon Now Available for Android, Updated to 1.1.3 on iOS Mon, 09 Nov 2015 14:39:42 +0000 Crossword Dungeon is now available on Google Play for Android devices!

Version 1.1.3 (which has enough changes in it to have been 1.2) is also up on the iOS App Store. It’s worth noting this update fixes that nasty intermittent bug that would crash your game and ruin your saves!

This sizable list of changes made it into 1.1.3, along with a few minor details for Android:


  • 300 new words!

New Monsters:

  • Lich: This late-game baddie will raise Skeleton Archers in nearby blank (or already solved) tiles!
  • Raised Skeleton Archer: Same as the Skeleton Archer, but yields no XP.


  • Level Up now shows two stats and two skills, and is better organized.
  • Tip in menu detailing tap-hold to view Monster Info.
  • Swapped order of hints, Across now shows on top of Down.
  • Uncovered locked skill information in Character screen. You can now peek at skills for which you do not yet qualify.
  • Monster Info shows monster XP.
  • Ranged Attacks at a range larger than 1 now move at the same speed.


  • Higher level monsters now give slightly less XP to compensate for the easier Level Up flow.
  • Level panning threshold now slightly bigger, causing less unintended level pans.
  • Time to tap-hold on a monster to show its Monster Info panel slightly lessened.

Bug Fixes:

  • Fixed a fatal crash caused by an improper word entry.
  • Fixed a crash when a ranged attack kills you while descending to the next level.
  • Fixed an issue where a Whirlwind miss would consume your Berserker Rage.
  • Fixed a bug where Multishot could kill a monster twice and yield double XP.

Please let me know if you find any issues or bugs with the game, especially on Android.


]]> 0