Closer Look: The Making of Quickhack

Previous Post
Next Post

Closer Look: The Making of Quickhack

May 10, 2017, 10:49 p.m.

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!
Previous Post
Next Post