Where can I get Quickhack?

Quickhack is available on the Quickhack Ludume Dare page.

title

Quickhack

By: Dr. Dos
Released: Apr. 23, 2017

The Ludum Dare, for those who aren't aware, is a game development competition held every four months with the goal of getting people to finish something, mostly by imposing a strict time limit (48 hours for the official competition, 72 hours for the more casual game jam). Last August I participated in my first (Ludum Dare #36), shortly after launching the Worlds of ZZT project officially. The theme was "Ancient Technology" and I was feeling cheeky and decided to make Ruins of ZZT, fitting the theme by nature of being an MS-DOS game made in 2016.

This past April I gave it another try with Ludum Dare #38 - A Small World. These short time limits lend themselves well to making ZZT games, which can be produced very quickly, and have their own historical precedent throughout the late 90s and early 2000s which had seasonal 24 Hours of ZZT contests. These were later followed by 72 hour Weekend of ZZT events in later years, so the idea of making a ZZT game in such a short timeframe is hardly anything new.

I learned of the event a few days prior to it starting, and decided I'd try to participate in the competition itself this time, leaving me with just 48 hours (which would work well seeing as how I had to work on Sunday which was the last day for the jam) to come up with and create a game based around the "A Small World" theme that had been voted on.

The first idea that I came up with was as you can see from the title, "Armory Crossing". The idea was to create a somewhat persistent world in ZZT where each board was a different day in the same Town of ZZT style armory, with the player's choices influencing characters' attitudes and behaviors over time. The inspiration being Animal Crossing where you have a small town and things to do each day.

If you've ever played an Animal Crossing game, they tend to open with the player character riding on a train towards their new town. They have a conversation with a fellow passenger (a cat named Rover) who asks the player some questions about themselves, their name, gender, etc.

None of that would be relevant for Armory Crossing, but I wanted to keep the intro to the game and use it as a chance to show off a little. One thing I constantly run into when trying to create ZZT games these days is always wanting it to be something fresh. ZZT's full of worlds which work with and around its limitations to do things that somebody with only a basic familiarity with the engine would think were impossible.

So of course, I wondered if I could fake parallax scrolling as a way to convey the train's movement.

And well, the end result isn't the best. The foreground trees move faster than the background trees, but there's not enough detail to really create a good illusion. Each row of trees slowly moves in a large rectangle, first from right to left, and then on hitting the edge of the board, they turn invisible, move up a row, and head back to the right until they can reappear on the right edge of the board to look like they're brand new trees.

The ones in the back do so at a slower rate. There are also trees already heading to the right edges that are invisible to ensure a steady flow of movement, and that's where I ran into my issues.

The amount of trees still appear to be rather sparse, and some rows have to be empty so the the invisible trees can move back into position. This board uses 148/150 stat elements, so the scenery simply can't get any more dense. This effect might work better on a purposely constrained board, but one that's full size is too empty!

I wanted to add a few more details, some form of animation onto the visible rail tracks as well, but was already out of objects. I also planned to indicate time passing by having the sky change from blue to purple to red to black to simulate sunset fading into a night sky. Originally the sky on this board was probably twice as large, but I realized I needed a lot of room just for trees.

The Rover equivalent is still there however, and I realized I didn't have much to say at this point, so I wrote some placeholder "Got any plans?" dialog and that's it. Then I noticed I was wasting way too much time on a cosmetic introduction screen and should be making an actual game.

The Ludum Dare has rules against lifting content from other games, so I redrew a Town of ZZT style armory board by hand, and placed a few buildings around.

I didn't have too much of a plan. The player would live in the yellow bordered house (and be able to pay to repaint it for future boards). There's a bed inside that would be used to end a day and advance to the next, and a few buildings placed.

In keeping with the Animal Crossing theme, the residents of the town would be ZZT creatures. Ty the tiger, Lyle the lion, Barbara the bear, Rufus the ruffian, and Sharon the shark. This conveniently meant I could have one building of each of ZZT's seven default colors, with red being used for the armory walls.

Ty the tiger would take on the Tom Nook role, and be the shopkeeper (hence his location where the Town of ZZT's vendor is). Day one would involve ringing the doorbell on Barbara's blue home to get her to open the door and give the player the key to their home that she was holding onto.

I of course wanted to make a persistent world, in ZZT, and in 48 hours, forcing myself to deal with two extreme restrictions. ZZT's only real ways to store data are with the player's stats, health, ammo, torches, gems, and score, and through ten binary flags with arbitrary names.

Barjesse's Nightmare kept track of the letters collected in the word "consciousness" by splitting the word up into chunks of four letters and managing flag stats there. It would be a lot of code, but it did allow for handling more than 10 bytes of data with only 3 flags. (The extra letter had its own little trick as well.)

Interaction
  •    •    •    •    •    •    •    •    •
#end
:touch
#if cxxx hasC
#if coxx hasC
#if cxnx hasC
#if conx hasC
#if cxxs hasC
#if coxs hasC
#if cons hasC
You don't have the letter C!
#end
:hasC
You do have the letter C!
#end
  •    •    •    •    •    •    •    •    •

A lot of if statements were needed to check for something, and you had to check for each permutation when setting an additional letter, but lots of data could be encoded this way. I figured it would be a possibility because I only really needed to encode and decode the state of the world when the player went to bed. I'd just lock them in place, scan the board as needed, create the flags, and then have the next day begin by checking those flags, updating itself, and then allowing the player to do their thing.

Again though, time would be a major factor. This type of code would be very tedious to write out, easy to make a mistake in, and a nightmare itself to test.

The next issue is one that I dealt with for the actual Ludum Dare entry submitted, ZZT's board size limit. I figured for an example of a persistent world I could have fruit trees like in Animal Crossing. Each day you could shake them for some fruit, chop them down for lots of fruit, or plant a new sapling in a spot where a tree was chopped down.

ZZT has a #bind command which allows an object to point to a different object for code execution, preventing a dozen identical objects from having to have identical code written out and bloating the board size. However because it really is a pointer, if you chopped down a tree, and every tree shared the code, you'd find that every single tree on the board would now believe it was in a chopped down state.

This board has 17 trees on it, each with their own code, and they bring the board size to 10 kilobytes. ZZT gets crashy at 20 kilobytes so half the storage is already being used just for some trees. Avoiding that limit would require massive cuts to what could be done. (Plus those trees don't have code yet to manage a flag for the state of all the trees yet!)

It was a cute idea, but clearly not going to work. I considered just dropping out since I wasn't going to have a lot of time and didn't have any ideas, and of course the time remaining was only going to get smaller and smaller.

I called it a night, not really expecting to do anything the next day.

But of course the next day I wanted to try again! I did have another idea to incorporate the theme, if not much of an idea for gameplay. ZZT's scrolls and objects pop up a blue window when drawing text

Example.
  •    •    •    •    •    •    •    •    •
You know, this thing.

With the white text

And yellow text.

A scroll!
  •    •    •    •    •    •    •    •    •

Somebody (no idea who it was or where it was published to find out) once contributed a little minigame to some ZZT magazine world or other compilation where they had drawn a replica of a scroll's layout on the board itself, making it look as if one was open when there wasn't a scroll anywhere. They placed the player inside, who conveniently matches the colors used in scrolls and made a puzzle where the player had to escape from the scroll.

That minigame has stuck with me, and I thought of making something about a player inside a scroll trying to exit. I had the idea of maybe using the red arrows as a start and end goal, with tiny mazes and enemies inside. So I redrew the scroll in ZZT (which I have probably done a dozen times now for one reason or another over the years) and then called it quits immediately. I didn't have ideas for level design, and didn't want to just slap down some walls and lions and call it a day.

So I waited another hour before coming up with Quickhack, and game whose level design was me slapping down some walls and lions and calling it a day.

"Maybe," I thought to myself, "I can make a game on a single board as my small world."

A dungeon crawl seemed like a good fit. Caves would work for a more natural maze layout, and by having everything on one board, I'd be able to get away with not thinking so much about level design because I'd have time to actually refine the board later.

It would also let me use a game mechanic that though well known as a possibility hadn't actually been used done in ZZT before: setting torch cycles.

Normally a dark room in ZZT requires the player to press the T key to light a torch, which sets your torch cycles to a certain amount, and decreases to zero before requiring the player to light a second torch. This is pretty annoying and combined with the issues of low visibility even in torchlight makes dark rooms relatively unpopular with later ZZT worlds.

Since the torch cycles remaining are stored when a game is saved, and saves and worlds are identical to ZZT, it's possible to set the torch cycles to 32767 and have a single torch which lasts for roughly 3276.7 seconds. This works out to a little under 55 minutes of torchlight without the player having to actually press the T key at any time.

A lot of ZZT games can be finished in an hour, but nobody ever used the technique since the torch cycles deplete on every board, meaning any time not spent in a dark room is wasting torch time. If the player ran out of time, they'd be out of luck since lighting any torches afterwards would only provide the normal amount of torch cycles.

Here however, since the entire game would be confined to a single board the time limit wouldn't be a problem, and there'd be no worries of any of it being wasted. Additionally the limited visibility would be a plus, allowing the player to still explore rather than have everything revealed as soon as the game was loaded.

I called the game Quickhack due to this time limit (even if it was generous) and because it would deliver the hack and slash gameplay of a dungeon crawler on a single board, making it pretty quick to play as well.

I first started by designing a tiny village. I wanted there to be an item shop, an inn, and some sort of hub for quests. The village was originally in the top left corner of the map, but it wasn't long before I realized that having to backtrack to the village for healing would make it more suited towards being in the center of the board.

Another early idea I had was for there to be classes the player could select. I would vary the player's maximum health, attack method, and even speed. The speed adjustment would be cut very early on as I quickly realized how impractical it would be. ZZT doesn't allow you to adjust the player's speed normally. Players always move at the fasted speed of cycle 1, meaning they act on every game tic. Starting a game or going through a passage causes the player on the destination board to be destroyed, and a new one spawned where they're needed, which will always be cycle 1 as well.

By confusing ZZT with bombs and conveyor belts, its possible to physically destroy the player object which is normally not possible.

The player handles all keyboard input, so ZZT will softlock when this happens unless there's another player element on the board (or an object places one). These other players can be of whatever cycle, so it would maybe be possible to engineer a scenario where the player begins the game on a conveyor with a bomb, gets destroyed by it, and then a slower moving player object would get to take over.

Trying to actually do this had its own share of problems. Firstly, the bomb and conveyor trick only seems to work if the player is also trying to move so it would be possible for the player to not be destroyed and break the whole thing. Secondly, the player handles its keyboard input only during its active cycles, which is normally all of them. A player at cycle 3 means input will only be read every 3rd cycle. This means moving, shooting, or even pressing the "S" key to save the game would basically have a 2/3 chance of being a dropped input entirely! Once I realized this would be the case I immediately scrapped the idea of adjusting the player's speed. I don't know if it would be possible to make a ZZT game with input that unresponsive and have it still fun to play.

So let's take a quick look at the three classes as they did get implemented:

Setup
  •    •    •    •    •    •    •    •    •
F I G H T E R
Fighters have trained with swords for
years. As masters of the blade they can
cut down foes who get close effortlessly,
and have the physical strength to stand
strong against powerful enemies.

Maximum Hit Points: 80
Weapon: Sword
  •    •    •    •    •    •    •    •    •

Firstly is the fighter. They use a sword engine that was also used in my previous Ludum Dare ZZT game, "Ruins of ZZT". An object loops checking for a bullet, and when it finds one, it waits a moment, then #changes the bullet into an empty space. The delay determines how far the bullet travels, and at small ranges, gives it similar behavior to stabbing at a close range with a sword.

I did feel a little guilty repeating this exact same mechanic a second time. It has a few drawbacks, since if the bullet is traveling over a fake wall or water when it's erased, the tile underneath is also destroyed, restricting use of these elements. Its biggest drawback, comes from the fact that there's no way with ZZT-OOP to tell if a bullet was shot by the player or an enemy. This prevents any enemies in the game from using bullets themselves as they would cause the timer for erasing bullets to no longer sync to player attacks, causing the sword to randomly have a shorter range than it should or even be erased the moment it is fired.

Fighters gain the benefit though of having nearly endless attacks and infinite use of them. They also have the largest maximum health which means they benefit from the inn's constant price for healing the most.

Setup
  •    •    •    •    •    •    •    •    •
M A G I C I A N
Magicians have dedicated to lives to
learning the secrets of arcane energies.
They are frail, but excel in ranged
magical attacks able to keep foes from
ever getting close to begin with.

Maximum Hit Points: 40
Weapon: Staff of Magic Missiles
  •    •    •    •    •    •    •    •    •

The second class is the magician. They were designed to have the infinite range of regular ZZT shooting, but with a limited amount of ammo to actually shoot with that regenerated slowly over time. I really like how this class came out since balance was pretty much a shot in the dark. They have the most interesting boss fights since they can't always be on the offense, needing to dodge the enemy long enough to recharge their mana/ammo.

Setup
  •    •    •    •    •    •    •    •    •
E L F
Elves are creatures of the forest and are
known for their skills with a longbow.
They are capable of launching a volley of
arrows, provided of course, they have
ammunition to spare.

Maximum Hit Points: 60
Weapon: Longbow
  •    •    •    •    •    •    •    •    •

The final class is obviously "Elf". I am forever amused by that being the class in the original Dungeon and Dragons and not "ranger" or "archer", and I wanted to use slightly more oddball class names, hence "magician" and not "wizard". Elves were more of an afterthought, and weren't added until about half the board was created. They use ZZT's shooting exactly as intended. You shoot arrows as rapidly as you want and as far as they can go, with the only limitation being have ammunition in the first place to be able to fire.

I was quite unsure what my goal with this class was. They were definitely the most difficult to balance, since fighters and magicians could never run out of ammo permanently. If an elf runs out of arrows during a boss fight, they're forced to game over. The need to buy arrows from the village also meant that gems were more essential. I considered just giving them a very large quantity of starting ammo and being a challenge class where they couldn't get more, but felt that wouldn't be all that fun.

The beta testers all had positive things to say about the class system, pointing out how you're not so much picking a weapon, but how to manage your ammo. There's basically a sliding scale of how easy it is to inflict damage with your weapon which is inverse to how limited you are with your ammo.

I did feel a little guilty recycling the sword from before, but would love to use this trifecta of combat styles again as I think it really added to the game.

The classes themselves went through plenty of changes themselves. At the last minute I began worrying about how difficult the game would be for those with little if any experience with ZZT and tweaked the numbers a bit. Every class got a max HP boost, from 60, 30, 40 to the current 80, 40, 60. For nearly all of the game's development, magicians only had 3 mana rather than 5. The regeneration rate also got buffed as well since I'd find myself standing outside a boss door waiting patiently for mana to refill before going in. Lastly the elf had changes to starting ammo. I considered some other potential buffs to the elf such as giving them some starting gold to be more comfortable with buying arrows, as well as giving them dark-vision as a way to automatically reveal any hidden treasures.

Starting with the village I began work on the item shop. I planned to offer healing potions, but also wanted to have other items to purchase that could be beneficial depending on the player's class.

Like the numbers for class stats, the shop's prices and potion effects were repeatedly changed throughout development. I wanted to make sure that healing potions could serve a purpose when the inn to restore health to its maximum was also right there. At 2 gems for 10 health, this made it cheaper to restore to full health by using the inn for all classes, but meant that partially injured players would have to weigh the risk of dying in combat, or spending their gems a little more inefficiently for the safety of topping off their health.

Fighters can more easily get away with not buying health potions, but a magician with 20 out of 40 hp has a tougher decision to make.

An issue I ran into when designing the potion shop, was that since the town was a hub, and the potions would take effect immediately, they had to have a benefit that would work without it mattering if a player half completed a section of the caves and needed to backtrack. The potions needed to provide a benefit that wouldn't be wasted by going through empty sections of the caves again.

I decided on a potion of regeneration which would heal the player over time meaning they could gain health while in combat, and to allow the potion's effects to not check the player's maximum HP when healing. This way a class could heal at the inn, then drink a potion to overheal themselves and be more prepared for their next fight.

The regeneration effect tries to strike a balance between being fast enough that the player does actually benefit from it, but slow enough that they won't want to drink a regen potion, idle in town, and drink some more. I didn't want a game called Quickhack to have a strategy of slowchug. The slow healing also means that fighters benefit from them more reliably, as a magician may get themselves killed before the next health increase with so little HP to spare.

While any class can buy healing or regeneration, the other two items for sale are exclusive to the other classes. Magicians get potions of boost mana which provide 15 ammo at once, letting them stack up before a boss fight and basically fight them like elves would. I think originally they only provided 5 extra mana. I also considered changing it so that they'd boost the regeneration speed of mana for a bit rather than just flatly give ammo, but I think the sudden burst lets the player strategize using them for bosses better.

Elves get to buy arrows. There's not much to that. The price for arrows and amount purchased varied wildly throughout development. If I were to go back I think to add more risk/reward to the elf playstyle I'd have options for larger bundles of arrows that would be more cost effective, but mean risking running out of ammo while you save up for a better priced bundle.

My first Ludum Dare ZZT world, "Ruins of ZZT", had a help board available from the start that explained some basic ZZT concepts to those who had never played any ZZT worlds before. I wanted to avoid having a menu this time to keep the number of boards down to as few as possible. So, in its place is the Tome of Sweeney. Tome Sweeney, a brilliant pun, is a help book. It doesn't really go on to explain what ZZT is and the sorts of things made with it, and just sticks to telling new players the controls and some advice, as well as cheats to turn off darkness as well as some special treasure hidden to make the game very easy to complete.

The tome itself wasn't done until the end of the game, since I wasn't sure how much space would be left. Instead, I opted to use an undocumented feature of ZZT, (though one that has been used by several people before,) which is the ability to have a hyperlink display an external file!

This was never mentioned as a feature in the ZZT help file, but was used in Town of ZZT itself to display an order form during gameplay. Rather than having something like !read;Read the scroll and a :read label to jump to with text afterwards, you can instead say !-scroll.txt;Read it. to tell ZZT to look for scroll.txt and display it instead. This has some use here and there as a way to save space, but does have issues where the file's contents are _not_ parsed as ZZT-OOP so you can't put code in additional files or even other hyperlinks.

I considered at first having more dialog throughout the game which would be displayed using these files, but opted against it since the game's plot wasn't really much of anything to begin with and with what little was there I was able to come in under the 20KB board size limit.

Hidden within a wall as mentioned by the Tome of Sweeney is a little cheat item I made sure to include so that anybody could be confident they could complete the game. The treasure is a ring of djinni summoning, taken from the roguelike ADOM which can be used to grant the player a single wish. While ADOM has the luxury of a text parser for this sort of thing, the possible wishes here are fixed. The player can get 5000 health and their max health limit removed; 5000 gems if they want to play through the game mostly normally, but without fear of getting themselves stuck; or they can take two class specific wishes.

Magicians can wish for infinite mana, which removes the check on maximum mana causing a magician's ammo to never stop rising. Elves can wish for 5000 arrows.

I felt like these kept the flavor of the game in mind as opposed to just giving the player 5000 or so of everything and calling it a day. There's still a choice to be made, though really any of them will radically simplify the game.

Lastly, is the village elder. They're used to explain the player's purpose in the caverns, recovering the mythical amulet of Nibor. As always in these sorts of games, the player first has to prove their worth with a simple quest.

The elder gives a quest to slay an orc chieftain whose tribe has been raiding the village. Your general level 1 adventurer sort of thing.

Okay! That's the village! Now we can get onto the rest of the game which is rather simple and shouldn't take too long!

There's only one place the player can head at the start of the game, and that's through the iron gate south of the village which leads to the orc tribe.

The orcs are just ZZT's lions painted green. I set their intelligence score to the minimum, intending to make them easy enemies and expecting more intelligent orcs to appear later on. In the finished game, all the lions have the same low intelligence, which actually makes them more difficult since they move around randomly rather than having any chance of opting to explicitly move towards the player.

The path here was purposely split into very tiny, but still technically branching paths. I knew with such a small board size things were going to be very linear, and I'm not satisfied with just how linear it came out. There's never a viable alternate route to some point, and any wrong branches which lead to treasure rather than progress pretty much reveal themselves as such almost immediately.

caverns

This branch contains a treasure chest with some gems, as well as a secret cache containing a few more. I was really unsure how many gems I should put in chests or secrets, I erred on the side of caution and I think there's more than enough. One tester with limited ZZT experience found their first playthrough to be very challenging, but then never needed the inn in attempts with the other classes.

There's also a boulder here, which I'd say is foreshadowing, but the player can very easily run into boulders in the village as well if they try to go anywhere else. It's the one point in the game where there's optional backtracking. The boulder-blocked path just has a few more orcs, and more chests with gems and a healing potion inside.

There's a transporter at the end of the branch which leads to the boss chamber. The bosses were really fun to create since most ZZT bosses are pretty bland to begin with. In Quickhack, boss design needs to feel interesting, but also avoid using bullets due to the sword engine. The fights also need to feel fair for every class, and have to work in confined spaces without the bosses overbearing on the player. Lastly, Dosbox not getting along with ZZT's key repeat codes meant that bosses needed to be slowed down so that the play could catch them.

chieftain
  •    •    •    •    •    •    •    •    •
#end
:start
°°°±±±²²²  C H I E F T A I N  ²²²±±±°°°

ORC CHIEFTAIN: YOU DARE TO CHALLENGE ME?
YOU MUST DIE.
/i
:loop
#walk rnd
/i#if contact hit
/i#if contact hit
/i#if contact hit
#if blocked rnd #walk ccw flow
/i#if contact hit
/i#if contact hit
/i#if contact hit
/i#if contact hit
#loop
:thud
#walk cw flow
/i#if contact hit
/i#if contact hit
#loop
:hit
#take health 10 #endgame
#walk opp seek
/i#if contact hit
/i#if contact hit
#loop
:shot
:shot
:shot
:shot
#zap shot
#lock
#char 42
The orc chieftain staggers from your blow!
?opp seek?opp seek?cw opp seek
#char 234
#unlock
#loop
:shot
#lock
#elder:orcdead
#orcexit:start
#give score 100
#if not blocked n #put n red fake
#if not blocked s #put s red fake
#if not blocked e #put e red fake
#if not blocked w #put w red fake
Return to the elder to finish your quest!
#become red fake
  •    •    •    •    •    •    •    •    •

The chieftain begins by walking in a random direction. ZZT's walk command means that they'll automatically continue to move in that direction each cycle. The random direction is translated to a cardinal direction only when the command is first executed, so they'll continue to move in that direction for a few cycles. Each cycle also checks if the player is next to them, and if so the player is damaged. After a few steps the chieftain checks if they're blocked in a random direction, and if so pivots counter clockwise. This adds a small amount of randomness, since it's only going to work if they're next to a wall or the player and they happen to randomly pick a blocked direction. The orc will keep moving in whatever direction they're moving in at that point for a few more cycles before looping and starting over with a new random direction.

ZZT has a special :thud label which is activated when a walking object hits a wall. When this happens, the orc rotates clockwise and continues on in that direction for a few steps before jumping back to the random direction at the start of a loop.

Then there's the :hit label for when the orc chieftain is attacking the player. Here the player loses ten health, and dies if they don't have 10 to spare. The orc then steps away from the player for a few cycles before restarting the loop. This is critical to make sure that the orc doesn't just wail on the player endlessly.

After that, we have several :shot labels. This is how ZZT handles objects that require multiple shots to defeat. The #zap shot command replaces the first instance of :shot in the object's code with 'shot, which ZZT treats as a comment. So objects comment out their own code while executing!

Next up is a #lock command, this tells the object to cut itself off from any external messages. :touch, :shot, and others will no longer work when an object locks itself up from external stimuli. This even includes :thud so there's no issue with hitting a wall from walking while handling the code from being shot.

The orc changes its appearance to a * with #char 42 and staggers by moving away from the player twice if they can, and then clockwise of whatever direction is opposite the player. Much like I don't want the player to be endlessly attacked by a boss, I don't want the boss to be endlessly attacked by the player either. This moves them away and out of alignment, making extra attacks either miss entirely, or hit a locked object and have no effect.

The orc changes its appearance back to normal, unlocks, and restarts the loop.

After being shot 4 times, all those :shots up top will be commented out, and so the 5th bullet will instead defeat the orc. The orc locks up to prevent them from being interrupted mid-death. It tells the elder that they've died, and the hidden object to erase the transporter and let the player leave. Bonus points are awarded, and of course some ZZT blood is splattered by placing red fake walls in any direction the orc wasn't blocked before the object itself turns into a bloodstain on the floor.

Back in the village the player can collect their reward, a hammer to smash up the boulders. This opens up three areas, the one the player would have passed in the orc segment, and two new branches to explore. The elder doesn't give the player any more advice on their quest since I was so reluctant to add any dialog which would use up a lot of space leaving the player to decide what to do next.

Although both paths deeper into the caverns can be taken in any order, one is blocked with two boulders to hopefully suggest it as the more difficult branch. The two boulder path is the second branch to take only in the sense that it was the second branch made.

The first new branch is the western one which is filled with centipedes, not named as such but intended to be wyrms. Centipedes will travel in a straight line with each segment behind following them. If a centipede is split in half, a new head automatically forms on the now headless wyrm and it continues on its way.

The wyrm branch also has regular ZZT boulders. They're never referred to as boulders which works out since the player already got a hammer for smashing boulders! I suppose it could be justified as the hammer being for _large_ boulders. They offer a new mechanic which isn't explained in game but will likely be stumbled on by most players where pushing a boulder into a wyrm will crush that portion of it.

The boulders are used in one spot to block a hidden gem cache until the player pushes them aside. Farther down the path there's also a spot where two chests are both blocked by more boulders, but the player has to block one permanently to acquire the other. Your 100% treasure run is ruined.

Lastly, there's a slider, which opens the path to the wyrm branch's boss fight. This was a boulder at first, but then I realized how easy it would be for a player to push it the wrong way and have some random debris in the boss chamber. I also made a few layout tweaks to make sure the player couldn't render the passage closed with some ill-planned pushing. It's still possible, but you'd really need to go out of your way to do it.

zzt_238

Then we get to the second boss fight: Evilc! That's not Evil C. That's Clive spelled backwards. The Evlic fight was without a doubt the most time consuming part of the game because of how often I had to change the fight to try and make it balanced.

Elvic hints at how to defeat him, his minions need to be defeated first. Sure enough, he opens the fight by having the three ruffian spawners spawn in a ruffian. The player cannot harm Elvic if there are any ruffians alive.

ZZT's ruffians move about erratically, going from pacing in several steps before resting for a few cycles. Their rapid changes from moving to idling can make it difficult to aim at them as well as making it easy to suddenly have a ruffian that was standing around suddenly rush for a player busy fighting other foes.

Evilc
  •    •    •    •    •    •    •    •    •
:loop
#walk rnd
/i/i/i/i
#if blocked rnd #walk rnd
/i/i
#if blocked rnd #walk rnd
/i/i/i/i
:thud
#loop
:shot
:shot
#if any ruffian nah
#zap shot
"Yowch!"
?cw seek
#if not any ruffian #ruff:spawn
#loop
:nah
"Hah! My creatures protect me"
/i#loop
:shot
"N-no!"
#ruff:stop
#ruff2:stop
#boss2:defeat
#become purple key
  •    •    •    •    •    •    •    •    •

Evilc's code is much simpler. It loosely tries to recreate a ruffian's behavior by walking in a random direction for a bit, and then sometimes changing directions if its blocked. There variable length of idles and randomness of whether or not the object is blocked make it unpredictable how long it will move for. And as I write this I realize there's never a moment where it stops walking so it's not very ruffian-like at all actually. Normally checking for being blocked in a random direction wouldn't do much, but Evilc's arena will also have ruffians running around which will make the checks pass more often than only applying when Evilc's against a wall.

The rest of the code handles Evilc only taking damage if there are no ruffians around. Evilc only takes three hits to kill, and there's a special case for magicians where it's merely two hits due to the old rate of mana regeneration and max mana limit.

The battlefield also contains a few chests with healing potions. There are three that can be consumed mid fight, however two of them are replaced with bundles of arrows for elves. This is because of the ruffian spawners meaning the fight can go on indefinitely. The number of spawners ranged from two to four during development, and it quickly became apparently that four ruffians to kill for a magician with three bullets to shoot was incredibly frustrating. The spawn timer as well was originally just a periodic timer that checked every so often if there weren't any ruffians, and if so, spawned more in.

This was brutal for magicians as there was no way to tell if the next check for a spawn was in one cycle or twenty. The strategy that was mandatory was to shoot all the ruffians but one, regenerate ammo, and then shoot the last ruffian and immediately afterwards shoot Evilc. It was a very slow fight!

The final fight instead works by having a primary spawner check every cycle if there are ruffians or not. If there aren't, it spawns one and tells the other spawners to immediately spawn some as well. Then it waits about 9 seconds before returning to its ruffian check loop. This way the player always has ample time to go after Evilc.

Defeating Evilc grants the player a purple key, which is required to get the final red key and obtain the amulet of Nibor.

caverns

There are two purple doors which block the red key, but the first door also opens up to another tiny branch with a few more orcs and some more treasure. This was my sort of difficulty breathing room turned into an actual room. Any player who had consumed all their resources defeating a guardian would get a few healing potions and more gems as a sort of reward for their victory.

caverns

The other path meanwhile takes on a more structured approach that tries to mimic the rectangular rooms of a more traditional roguelike. I wanted it to have trapped tiles, and considered seeing if I could set up something to cause bombs to be activated and explode, but with the scale of the rooms, a bomb going off would easily cover the whole thing and also damage enemies probably more easily than the player.

My next thought was to use spinning guns, but then I remembered I can't use bullets. I had the same issue in "Ruins of ZZT" as well! Then I thought maybe I could use objects as darts that shoot out of the wall, "Mission Enigma" style, but didn't want to waste space on one time traps. Finally I settled on blinkwalls which are basically the theme for the dungeon.

Blinkwalls fire a ray in a specified direction from the origin blinkwall, the ray is a physical tile ZZT places at set intervals according to the blinkwall's starting time, and they stay on based on the blinkwall's period. Tim Sweeney's official ZZT worlds made good use of them with varying starting times and matching periods to create one way passages. They also work well as a puzzle element since they can be blocked with boulders. Dang, I should've made an easter egg if you pushed a boulder from the wyrm cavern to the blinkwall dungeon.

Blinkwalls are good puzzle pieces, but they have a few issues as well. If a blinkwall hits a player and there's no adjacent tile to safely move them to, it's an instant game over which can make using them in tight corridors more dangerous than ideal. Getting hit by a blinkwall tries to put the player north, south, east, and lastly west, but the west check is bugged. ZZT correctly checks if the tile to the west is empty and if it is, it places the player to the east! This lets the player stand on top of things that normally can't be stood on.

That's an odd one, but not something that comes up very often. More pressing is an issue with a blinkwall destroying something with stats causing a temporary off by one error, this can cause some very strange rays to be drawn as the blink wall spends a cycle looking at the wrong element for what color ray to draw and in what direction.

Kevin Vance made a nice GIF demonstrating it!

That one is more of an issue if you're using ZZT's default enemies. Not really trusting myself to be able to come up with an alternative, I took the risk and mixed blink walls and built-ins together. Usually at least one enemy will be zapped by the wall and some some errata to appear.

I opted for the classic ZZT solution to bugs, tell the player things might break so that it's on them to not break them. There's a loose page from the tome of Sweeney warning about the traps and how they may block the path. In all my playtesting I never actually had the path get blocked, but better safe than sorry.

Also, when the invisible walls that fill the dungeon (did I mention some branches are filled with invisible walls at first to keep enemies from wandering around before the player will be there?) are removed, the first blink wall moves a space to the north. I have absolutely no idea why this happens.

The dungeon branch also introduces bears, which are supposed to be bugbears in the context of Quickhack, but that's never actually stated anywhere. Bears idle in place until the player is a certain distance away based on their sensitivity setting. They're mixed in with more orcs and are usually the easiest of ZZT's creatures to fight.

Halfway through the branch is a room with a red door and a cyan key. The red door is the final boss chamber, but the player needs to do a bit more to open it. The cyan key opens a door with a copy of the orc chieftain from earlier who has to be refought to get a yellow key to open another room with another orc chieftain tho drops the blue key to open the dungeon's boss room. These enemies are made slightly easier than the original, only taking 3 shots rather than 5 to be killed.

Once the blue key has been acquired it's time to fight the second guardian, Esina! Esina's code is...

Oh right. It's identical to the orc chieftain's. I was uh, kind of low on time and ideas by the point of development. The fight is made more challenging the first one since the arena has two blinkwalls inside, but there's not much to say about it.

After the player defeats Esina, they receive the final purple key and can acquire the red key to the final chamber. From tester feedback I ended up adding one last chest behind the red key with a massive 30 gems, ensuring the final boss will be one the player is prepared for.

The player is given a message when they open the last chamber warning them that they should be sure they're prepared before actually going inside.

The final chamber has lifeless skeletons in each corner, and a single chest at the very back end of the room. The chest contains the amulet of Nibor, but acquiring it locks the player inside the room.

Examining the newly blocked exit reveals the truth about the amulet. It's a blue collar that says "Meow!" on it and nothing more. The player is given the choice of putting it on or meowing. Meowing has no effect, but wearing it certainly does. The skeletons begin to animate (as skeletons in treasure rooms always do). The chest was a grand illusion created by Nibor's lich who begins his attack.

(I attempted to get a good photograph of my roommates' cat, Robin, with the Meow collar on him, but it turns out he'll keep looking around at your camera when pointing it a few inches from the back of his neck.)

I came up with the idea for this fight pretty early on. Since I knew bullets couldn't be used, I wanted the final boss to use stars, an indestructible projectile enemies can attack with in ZZT that constantly move towards the player. The thing is, stars are really bad. They eventually fizzle out if they don't attack anything, but take way too long to do so. It's also incredibly difficult to get away from several of them at once.

The solution I came up with was in the skeletons. Rather than have stars be shot out at intervals or anything, they became a punishment for the player attacking anything other than Nibor. The skeletons don't harm the player directly, but when shot they crumble in place and throw a star which is supposed to represent the necromantic energy giving them life. After a few seconds any stars are erased from the board as the skeleton reabsorbs the energy and begins to move again.

This makes the lich fight sort of the opposite of Evilc. Here the minions are to be avoided at all costs rather than destroyed before being able to harm the lich himself.

The lich's own code is rather short as well, simply walking towards the player, and rotating when a wall is hit. If he comes in contact with the player, said player is harmed and the lich steps away from the player for a few steps to prevent stunlocking.

Originally the lich was going to move entirely in a pattern, making a threaded loop around the inner walls of the room and retreating to a corner when shot as a trick to allow the pattern to reset since shooting would interrupt it at an unknown point. After scrapping the pattern based movement, I accidentally left what little of the retreating code was started and decided to keep it after seeing how nice it looked to have the lich react so definitively to being harmed.

The lich takes eight hits to defeat, allowing the player to exit the room and causing all the skeletons to turn to ash. I just realized it's possible to have a skeleton block the exit with their erm, corpse. Oops.

Once the player has the amulet and the exit opened they can escape to the surface. All the village services stop working, replaced with messages about escaping to the surface. There's not much of an ascent, but doing so brings the player to the credits/ending screen and ends the game

In the end, the Quickhack caverns took up some 19KB of space. A few times I've had ZZT crash with a runtime error when closing the game, but thankfully nothing during gameplay. There's no explicit amount of memory permitted due to ZZT's memory use during gameplay also needing to allocate a variable amount of space for things like the world list. (If Quickhack is the only ZZT game in a directory I don't think you'll ever see it crash.)

Space was definitely too tight to cram in credits and an ending onto the main board. The credits are mostly the usual ZZT standard, everything by one person, thanks to Sweeney, Janson, and you. I made sure to include Kevin Vance for KevEdit. I don't think the authors of external ZZT editors got the credit they deserved enough back in the day. What was convenience in the early 2000s via ZZTAE and KevEdit are now essential these days. Writing a ZZT game in its default editor is so much more slow, tedious, and prone to issues with code being deleted that I am certain Quickhack couldn't have happened with the aid of one given its extreme time constraints.

Thanks of course also went towards the Worlds of ZZT patrons, with a few specific names of the major backers of the project. Eevee for getting me into making ZZT games for Ludum Dares in the first place, "You" because that's also mandatory, and the various cats whose names I wrote backwards throughout the game.

The epilogue was written without any real thought, just trying to fill things on the board. The name of the cat the player adopts is chosen based on the class they won as as well!

Oh, also the credits board has more objects on it than are used in the caverns board. I was very glad I was able to do that since it shows how strange ZZT's limitations can be. The caverns have 125 stat elements, and the credits have 135. Things ZZT is good at: Cramped dungeon crawlers. Things ZZT is bad at: text that doesn't have a white foreground color.

Making Quickhack was a lot of fun once things got going! I'm very happy with how well the class mechanics worked out, and wouldn't be surprised if I used them once more in whatever Ludum Dare (or otherwise) ZZT worlds I create next!

The Closer Looks series is a part of the Worlds of ZZT project, committed to the preservation of ZZT and its history.
Support Worlds of ZZT on Patreon!