This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
viluon's profile picture

Zombease (beta)

Started by viluon, 19 December 2016 - 02:27 PM
viluon #1
Posted 19 December 2016 - 03:27 PM

A Christmas Horror

Hello, glorious people of ComputerCraft!
For months I've been silent, but now comes a day which I'm sure all of you have anticipated greatly: today, I'm releasing an open beta of Zombease, a top-down zombie survival shooter for your favourite Minecraft mod. Merry Christmas!

Controls
WASD to walk around, click/hold/drag to shoot or punch in that direction. Zombies occasionally drop items, so go ahead and pick them up, before they perish! White ones are weapons, light grey items are ammo. Press 1 to 9 (on the top row, not the num pad) or use your mouse wheel to select a weapon (you will at first only have your fists). Press R to reload a gun. Should you get bored, Q quits the game in any situation.
In the Armoury, you can select a different weapon by scrolling, and drag the model around with your mouse.

A Word of Warning
This is beta. That means it's pretty stable, probably kinda usable and maybe a bit fun, but definitely not perfect. There's a lot more to come for Zombease, and you will run into bugs. So please report them! Whether here, through PM, on Gitter, on GitHub, doesn't matter. Just do!

Also, you'll notice that you can go outside of the world borders (bordered by asterisks). While this might save your life, it isn't recommended: no bullets spawn in "the void", so attacks will have no effect. Shooting will still consume ammo, however.

Installation
Luckily, installing Zombease is as simple as running
pastebin run SNnkfxnx
Note that you will need an advanced computer and ComputerCraft version 1.76 or later to run the game properly. The UI should scale up rather well, but the opposite will not be true.
To launch the game, run menu.lua

Updating
To update Zombease, simply run pastebin run snnkFXNX again. An installation/update will not overwrite existing saves (if the game crashes, try a full reinstall, however), nor settings, nor any other files.

Screenshots

MoreImgur album






Enemies
Currently, there are four types of zombies. First there is the generic kind, the and symbols (&). They're just regular infected people with average speed, damage, and health. The second kind are runners (dollar signs $). They once were able athletes and now try to outrun each other in the race for your blood. With the third wave, you'll be introduced to the heavy-duties, the men of steel who lived the lives of police officers and soldiers. Their skull-shattering damage and (almost-)impenetrable armour are balanced by slow speed. There is also one more zombie type, which you'll meet in wave #5 and beyond. I don't want to spoil its abilities, but good luck fighting that one!

Hint: Runners ($) are the only kind which drops health packs.

Roadmap
The number one on the to-do list is the implementation of a GUI for weapon attachments. Their functionality (and some models) are technically already implemented in the game. Next up is geometry, levels, and generally more content. You might have noticed a contrast between the dark theme of the arena and the snow storm of the main menu. This isn't accidental, as players will be able to fight in various different environments. If you have ideas on what you'd like to see in the game, please share! I'm always looking for ways to improve this project.

Performance issues
UPDATE: Zombease 0.1.2-beta added an option to limit the FPS to the precision of the system clock (20 FPS on vanilla-like platforms), which is enabled by default. This seems to fix CCEmuX performance issues and improves responsiveness of other platforms as well.

Yes, Zombease is quite heavy on your platform. This is why I recommend using CCEmuRedux (Lignumm's CCEmuX unfortunately has performance issues itself at the time of writing) instead of the vanilla game, for the best experience. Zombease won't display correctly on CCLite, as it requires the new character set support.

Credits
Thanks to [member='Bomb Bloke'] for his BLittle API and [member='nitrogenfingers'] for Micropaint! The installer is powered by [member='apemanzilla']'s flawless gitget.
Many people shared ideas and improvements to the game before the beta release, namely CrazedProgrammer, [member='SquidDev'], demhydraz, daelvn, InternetUnexplorer, Lignumm, [member='Exerro'], and [member='apemanzilla'] (if I forgot any of you guys, I'm very sorry! Please contact me to correct that). They have all been a great deal of help, and made me look at the design from a different perspective. For their never-dying support, I promised to make an easter egg for each one of them. The easter eggs aren't present in this beta, but will definitely come in a later release!
Last but not least, many thanks to [member='Oddstr13'] for his cc-require. A standards-compatible package and require should be in CC by default!
Edited on 21 December 2016 - 07:36 PM
Bomb Bloke #2
Posted 20 December 2016 - 10:47 PM
0.1.4-beta crashes on line 18 with an attempt to call nil. For me a fix was to change init.lua to read:

shell.run( fs.combine( fs.getDir( shell.getRunningProgram and shell.getRunningProgram() or shell.dir() ), "require.lua" ) )

… though frankly I don't see any reason to stick such a simple bit of code in its own file.

Once I got it running, I found the game and interface quite solid in terms of design. :)/>

But some other issues:

After dying, the game simply exited without a visible error, leaving the end-game stats on the display above the cursor.

Having the "bullet" counter up the top of the screen show "bullets on screen" is less intuitive / useful than "bullets available" would be.

Why is the frame rate so high even when the display has nothing to update? If the screen is static for a second, there's no reason to go above 0FPS.
viluon #3
Posted 21 December 2016 - 11:02 AM
Thank you for the feedback, [member='Bomb Bloke']!

You didn't specify which file did the error originate in, but for 0.1.4-beta, the line 18 in both menu.lua and main.lua call require. Since main.lua would exit differently was it not run from the menu, I can only assume that the error came from menu.lua itself - which is rather odd. That would mean require was not loaded as expected, which should happen on line 14 in the same file:

if not require then shell.run "/desktox/init.lua" end
I must therefore suspect that something is wrong with your setup - a fresh install of Zombease 0.1.4-beta runs without issues. It is also interesting that such a change to (I suppose) /desktox/init.lua fixed the issue. The only thing actually changed is the use of shell.run instead of dofile to execute the file, both functions use the global environment and should (in this case) produce identical results. Are you sure you didn't tinker with the files in any way? I don't see how could 0.1.4-beta have these issues. What platform was this on?

/desktox/init.lua is (as the location implies) a part of Desktox, on which Zombease is built. Its purpose is to prepare the environment so that Desktox can be loaded and utilised, whatever that involves. At the moment, its only task is to add a standards-compatible require.

After dying, the game simply exited without a visible error, leaving the end-game stats on the display above the cursor.
Yes, that is not a crash but intended behaviour. I plan to change this, though I should probably give it higher priority. Thanks for the reminder!

Having the "bullet" counter up the top of the screen show "bullets on screen" is less intuitive / useful than "bullets available" would be.
Umm, the counter is really just a remnant of on-screen debug information - I'll remove that. What do you mean by "bullets available"? Your ammunition count is shown in the bottom right corner…

Why is the frame rate so high even when the display has nothing to update? If the screen is static for a second, there's no reason to go above 0FPS.
Oh, I'm surprised you ask! The game loop needs to run continuously to update things that move independently on player input events, such as bullets or zombies (actually, even the player character moves on a timed basis, rather than whenever a relevant key event is caught). Bullet positions, for example, are updated with every frame based on that bullet's speed. The position is not a vector of natural numbers, instead, it is precise, but rendered at the rounded position (to simulate actual physics). What you are suggesting is to check whether any of the rounded values changed from that of the last frame, adding two checks for each object on each frame, and essentially storing twice the amount of data. And that's not even all of the overhead that would need to be introduced! The game would also need to check if anything displayed on-screen changed, such as the countdown till next wave, or your weapon selection, or anything else. The code would be a mess, frames would take longer to process and memory usage would increase as well. Clearly, the costs outweigh the benefits greatly.

If you're interested, the game runs on Desktox (mentioned above) and relies on it for rendering. desktox.buffer grants Zombease UI the transparency capabilities - essentially all of the UI is in a separate transparent buffer that is rendered to the main buffer after "background" stuff finished rendering. Bullets are also rendered to this overlay buffer, and (like the UI text) have their background colour set to be transparent to the foreground colour of the pixel beneath them. You can see this easily in action, just position yourself so that some parts of the UI appear above a background pixel. You'll see the UI pixel's background to change. I decided for this set up to better utilise the screen space available on ComputerCraft computers: UI doesn't "block" your view. Desktox resolves transparency for all pixels on-screen (the overlay buffer must span the whole screen, because bullets can be anywhere) with each frame. While this full render-time transparency is one of its native tricks and involves no function calls in the render method itself, the maximum FPS would be tripled was there no transparency involved.

ComputerCraft clock precision is, unfortunately, 50ms. That's terrible, compared to "real-life" platforms. Moreover, ComputerCraft only really updates the terminal 20 times per second. However, some emulators (such as CCEmuRedux or CCLite) can update the screen more frequently. I've developed a prototype of a software clock, a utility that simulates a higher-precision clock by estimating how many times per second can its parent coroutine yield. This functionality, paired with the better update frequencies on some emulators, can make for smoother game experience with higher effective FPS. While some people have complained about performance issues, having this as an optional feature would be rather interesting (the occasional wind howls in the menu of 0.1.4-beta would certainly benefit from this!), so stay tuned!

Thanks again for your feedback, [member='Bomb Bloke']! I'll look into the issues you mentioned.
Bomb Bloke #4
Posted 22 December 2016 - 03:04 AM
You didn't specify which file did the error originate in

Ah, sorry. It was in menu.lua, the file the installer told me to run.

What platform was this on?

A fresh CC 1.79 advanced system.

Given that shell.run() yielded a quick solution, I would guess changes to dofile() between the version used by 1.76 and the version used by 1.79 have something to do with it.

What do you mean by "bullets available"? Your ammunition count is shown in the bottom right corner…

You need to switch weapons to get it to appear there, and I seem to be stuck with melee most of the time. It'd be handy to know when it's worth switching without actually having to switch, see.

The game loop needs to run continuously to update things that move independently on player input events, such as bullets or zombies

Yeah but your counter is telling me that it's updating the display even when nothing is moving within the display area. In order to achieve the 20FPS it usually ticks along at, I'm assuming you're yielding 20 times a second - but if nothing's actually moved, I don't get why your coroutine would be resuming that often… unless you're doing it purely so you can update the FPS counter more often, which'd mean you're doing redundant screen writes just so you can report how many redundant screen writes you're doing. :/

Bullets are also rendered to this overlay buffer, and (like the UI text) have their background colour set to be transparent to the foreground colour of the pixel beneath them.

I noticed that, it's a pretty cool effect.

While some people have complained about performance issues, having this as an optional feature would be rather interesting (the occasional wind howls in the menu of 0.1.4-beta would certainly benefit from this!), so stay tuned!

Looking at your buffer, some minor tweaks which may or may not scrape a bit more performance out of it:
  • Don't use table.insert/remove unless you require their ability to shuffle other indexes around for you. If you want to shuffle over and over again within a loop, consider building a new table instead and getting it all done in one iteration.
  • Don't use ipairs, full stop.
  • Don't work in tables when you don't need to. You'd be better off creating three locals here (as opposed to three table keys), and then making a table out of them later.
  • Sometimes tables can be faster - say you made those three vars each point to new tables, you could then place chars into those instead of repetitively concatenating here. All the values could be combined later with a single table.concat() call.
Edited on 22 December 2016 - 02:07 AM
viluon #5
Posted 25 December 2016 - 01:16 PM
Hello again, [member='Bomb Bloke']! Merry Christmas!

Please pardon me, given the time of the year, I wasn't able to respond instantly.

Given that shell.run() yielded a quick solution, I would guess changes to dofile() between the version used by 1.76 and the version used by 1.79 have something to do with it.
Absolutely not. If that's the code behind dofile, then the function should logically work as I expected it to. I tested on four different platforms: CCEmuX, CCEmuRedux, the SwitchCraft modpack, and (of course) vanilla ComputerCraft 1.79 on MC 1.8.9 with recommended Forge and no other mods installed. In all cases, I was able to install, launch and play the game with no issues at all. Not to mention I had no other reports of this problem. Clearly, this is not a Zombease-side issue.

You need to switch weapons to get it to appear there, and I seem to be stuck with melee most of the time. It'd be handy to know when it's worth switching without actually having to switch, see.
Oh, well, that's an easy thing to do - I'll make it an optional feature :)/>

Yeah but your counter is telling me that it's updating the display even when nothing is moving within the display area. In order to achieve the 20FPS it usually ticks along at, I'm assuming you're yielding 20 times a second - but if nothing's actually moved, I don't get why your coroutine would be resuming that often… unless you're doing it purely so you can update the FPS counter more often, which'd mean you're doing redundant screen writes just so you can report how many redundant screen writes you're doing. :/
Wow, I thought I explained in thorough enough detail. I'll make one last attempt at clearing this up for you here, if you'd have further questions, please contact me via a PM.

Before going any further, let me get one thing straight: Zombease framerate isn't tied to the yield rate. On my system, Zombease usually yields about 1400 times per second, but only renders at 20 FPS. The yield rate depends on the platform, but the point is that Zombease yields as frequently as possible. It does this by queueing a custom event marking the end of the event queue. Whenever it catches that event, it queues it again. This ensures that when CC's hardware clock (os.clock()) changes, Zombease gets a non-zero delta time as quickly as possible, minimising framerate fluctuations (this set up also opens the door for a future software clock implementation). I call this The Glorious Game Loop.

You see, it's harder to state when not to update the terminal than it is to state when to update it. Simply put, you can just draw everything each frame, and it'll work well. Or you can clog the memory with historical information about everything in the scene, and the CPU with comparisons to a previous state of the world, models, UI and all that and only update when something changes. Whichever path you choose, ComputerCraft will send the terminal contents over the network 20 times per second (note: I'm not sure about this. I think CC just sends all the data, but it might skip doing this if the terminal didn't update, or only send the changes. So there's a chance that your way of thinking might lower network bandwitdth). As I said, to render only on changes, you would need to compare the game state to that of the last frame. That means keeping twice the amount of data in memory. There's no other way for Zombease to say whether anything has moved. There could be shortcuts - for example, if there are no bullets and no zombies in the scene, there's way less stuff to check. But how do you know whether the player has moved or not? You need to compare his or her current position to the previous position, or keep a flag for it around and handle that in the player movement functionality. So the player didn't move, there are no bullets or zombies - we can skip a frame, right? Wait, what if the weapon selection changed? What if there's some effect on-screen that needs updating? An animation in the UI itself? Countdown to the next wave?

(There are a few cases in which keeping a single needs_redraw flag would be enough, with no extra checks, but only for actions that result from the event system.)

Games aren't movies. Since you don't know what the next frame will look like, you always have to construct it, and then check for changes. Most of the overhead of the game itself is already used up on constructing the next frame, so why complicate things even more with checks for changes? Rendering the frame is easy, isn't game-object-specific (like the checks are) and can be kept in a single function (such as redraw()). With all the computational expenses of your approach, [member='Bomb Bloke'], it is hardly ever worth implementing.

This isn't just my opinion or my stupidity of not seeing a better approach. If you launch a computer game such as, let's say, Minecraft, and stare at something static (such as a stone wall) so that nothing in your view is moving, and look at the FPS, you will notice that the game renders at the same (or possibly higher) framerate as when you're actually looking at dynamic stuffs - even though every frame is identical (this isn't just the monitor's need to update at a constant rate, as it can pull the last frame from video card memory). Games do this for the same reasons I do: because it's efficient, clean, and organised. Minecraft is even more of an argument than my previous explanation using Zombease. On each frame, MC needs to access all triangles of every chunk (stored in vertex array objects) (which it sends over the (relatively) slow PCIe connection to your GPU on every block update), run the shaders on all of the triangles and finally output the resultant image. It does all this even though you're looking at a single block of a stone wall. Checks for changes aren't worth it.

On to the Desktox optimisations. I opened issues (#4 and #5) to track the optimisation progress. Feel free to PR or open more issues, should you think more parts of Desktox need attention!

Don't use table.insert/remove unless you require their ability to shuffle other indexes around for you.
Check. Those two are only used in the :scroll() method, which needs to shift the buffer contents.

If you want to shuffle over and over again within a loop, consider building a new table instead and getting it all done in one iteration.
Now this is an interesting insight - I should give that approach a go. Probably won't be efficient for cases where you :scroll( 1 ) and similar, but might scale better. Thanks for the idea!

  • Don't use ipairs, full stop.
  • Don't work in tables when you don't need to. You'd be better off creating three locals here (as opposed to three table keys), and then making a table out of them later.
  • Sometimes tables can be faster - say you made those three vars each point to new tables, you could then place chars into those instead of repetitively concatenating here. All the values could be combined later with a single table.concat() call.
Oh yes, the awful :render_to_window() - the ugliest part of Desktox. As you can see, the code of :cook_lines() is a shameless copypasta of the :render() method, hacked here and there to produce blit-friendly output, which is then utilised in the most painful of ways in :render_to_window() itself. Chances are, this code is the real bottleneck of desktox.buffer's performance. Thanks for pointing that out! Oh and thanks once more for reminding me of table.concat - I still forget that (tables scale better in these cases) from time to time.

Thanks for your input, [member='Bomb Bloke']!
Edited on 25 December 2016 - 12:18 PM
bobasrty #6
Posted 09 August 2017 - 08:07 PM
I installed the game, then I ran menu.lua but then after it installed blittle, it says "blittle:595: Doesn't appear to be a BLT file."
Bomb Bloke #7
Posted 10 August 2017 - 03:00 PM
I've tweaked BLittle's file format a couple of times since this was written - I suggest implementing the old loading code directly: Nevermind, I've made my loader backwards compatible. Should've done it to begin with.

This commit to ComputerCraft itself is also noteworthy (require's now in, but since it's shell-specific you won't find it in _G - you'll also have a harder time getting multiple scripts to share the same global environment), as is this one (if you want to define a window, the parent must offer getPaletteColour).
Edited on 28 August 2017 - 12:03 PM