Dead Cells has a Daily Challenge or Daily Run mode, but it is just a minor optional part. Except that is required in order to unlock three weapons (“blueprints”) for the main game. Just by starting a Daily Run on different days unlocks the following blueprints:
Swift Sword (internal name SpeedBlade) the first time
Lacerating Aura (internal name DamageAura) after the fifth time
Meat Skewer (internal name DashSword) after ten starts
However, specifically the Linux version on GOG has an issue where it is impossible to start the Daily Challenge. The Linux version on Steam is not affected. To use anything online-related in Dead Cells – which thankfully is not a lot as it is a local single-player game – the GOG GALAXY client is required. Which does not exist for Linux. Until a GOG GALAXY client for Linux is released, I probably should not buy games from GOG anymore and just go with Steam, because otherwise I cannot trust that all features will work. Which is a pitty because I really like GOG for being DRM-free.
The Dead Cells issue is known for years and by now it seems rather unlikely it will be fixed. At some point a note was added to the store page:
Additional Notes: Galaxy client is required to access Daily Runs and Streaming features.
Let's hack the game
At the point of writing I was using Ubuntu 20.04.2 and the Dead Cells game version 1.14.0 / v24. You will need a hex editor which can handle big files of around 1 GB. I used and can recommend GHex. You can install it with:
This was the 3rd time I participated in the js13kGames game jam, so I was already aware of one thing: The size limit isn't my greatest problem, at least not to the extent of having to do code golfing right out of the gate. Instead I should make sure to have a good code base – multiple files for classes and namespaces as needed, named constants/variables instead of hardcoded values.
One really big advantage was, that I had already implemented things like an input system for keyboard and gamepad, and 2D collision detection in other projects of mine. I could just reuse some code with minor adjustments and save quite some time.
1. Improvement through little things
First some ideas on how to improve the general impression of the game, even though the player may not be actively aware of them.
The game is tile based, so everything – player, monsters, goal – is always positioned at a (x, y) position on a 2D map.
About the background
The background is a single image which is created once right at the beginning. It is drawn on a canvas and each tile is 1px big. In the rendering loop it is then up-scaled to the desired tile size. To avoid a blurry image, it is necessary to disable anti-aliasing.
context.imageSmoothingEnabled = false;
let w = bgCanvas.width * tileWidth;
let h = bgCanvas.height * tileHeight;
function renderLoop() {
context.drawImage( bgCanvas, 0, 0, w, h );
}
About the fog/shadow
The fog/shadow around the player is done in a similar way as the background. The image is pre-rendered with each tile being 1px and then up-scaled in the main loop. But it moves with the player. The darkness is determined by the euclidean distance from the player.
for( let y = 0; y < fogMapHeight; y++ ) {
for( let x = 0; x < fogMapWidth; x++ ) {
// Euclidean distance from origin.
let de = Math.sqrt( x * x + y * y );
// Darkness only starts 2 tiles away from the player.
// f has to be a value between 0 and 1.
let f = ( de < 2 ) ? 0 : Math.min( 1.15 - Math.min( 3 / de, 1 ), 1 );
fogCtx.fillStyle = `rgba(0,0,0,${f})`;
fogCtx.fillRect( x, y, 1, 1 );
}
}
NW.js still provides the Chrome Apps API which has been removed from Chrome, but not ChromeOS. This will allow us to access in a platform-independant manner devices which are connected with the PC per USB.
Without this API, a 3rd party Node.js module like node-hid could be used. This will however come with platform-dependant libraries and will have to be updated or rebuild each time the Node.js version changes.
This article concentrates on sending data to the controller. However it is also possible to retrieve data like pressed buttons using the established connection. Aside from using chrome.hid there is also the Gamepad API for read-only access.
Identifying the controller
First we need a way to identify the DS4. Devices come with a vendor Id and product Id. According to the Gentoo Wiki they are as follows:
Device
Vendor Id
Product Id
DS4 (1st gen)
hex 054C / dec 1356
hex 05C4 / dec 1476
DS4 (2nd gen)
hex 054C / dec 1356
hex 09CC / dec 2508
Having tested with both devices, I can also confirm the Ids.
Get the device
For all communication with the device, we will use the chrome.hid API. First we define a filter using the vendor and product Id, and then query the available devices:
Bei mir läuft immer noch der IBus nebenher, da ich gelegentlich auch mal etwas auf Japanisch eintippen möchte[1]. Dummerweise gibt es scheinbar einen Bug[2] zwischen IBus und gewissen Java-Komponenten, wegen dem dann Tastaturereignisse – sprich: Tastendrücke – nicht an die Java-Anwendung weitergereicht werden. Aufgefallen ist mir das bisher in Minecraft.
Bevor ich zur Lösung komme (wer es eilig hat, liest das hier vermutlich ohnehin nicht und scrollt direkt zum Code), ein paar Hinweise. Zum Einen sollte – auch auf Empfehlung von Notch hin[3] – nicht das standardmäßige OpenJDK benutzt werden, sondern Suns JVM. Im Weiteren setze ich auch voraus, dass diese als Default für den Befehl javagesetzt wurde:
sudo update-java-alternatives -s java-6-sun
Der andere Hinweis ist, IBus aktuell zu halten. Obgleich es bisher nicht zur Behebung des Fehlers beitragen konnte. Wie man stets aktuell bleibt, steht auf der IBus-Projektseite.