Skip to content

Instantly share code, notes, and snippets.

@rafalberezin
Last active February 4, 2025 20:24
Sixty Four Mods

Sixty Four Mods

A collection of my mods for game Sixty Four.

Important

These require the Mod Autoloader.

You can find more information on modding this game HERE.

My Mods

Note

These are links to the gist mod files.
Read more detailed descriptions in the Github Repo.

Note

Didn't find what you're looking for? Check out the modding wiki to find other mods.

Mods Installation guide

/*
* Sixty-Four Mod: Dynamic Background
*
* https://sixtyfour.game-vault.net/wiki/Modding:Index
*
* ----------------------------------------------
*
* REQUIRES THE MOD AUTOLOADER
* See https://gist.github.com/NamelessCoder/26be6b5db7480de09f9dfb9e80dee3fe#file-_readme-md
*
* REQUIRES THE SPRITE REPLACEMENTS TO GET THE CHANGED BACKGROUND UNDER MACHINES
* https://github.com/RafalBerezin/Sixty_Four_Mods/blob/master/Dynamic_Background/dynamic_background_sprite_overrides.zip
* Extract the contents of this zip archive into the mods folder.
* The folder structure should look like this:
*
* mods/
* ├── dynamic_background.js
* └── db_sprite_overrides/
* └── [.png files]
*
* ----------------------------------------------
*
* Allows to change the background color.
* Additionally, allows for easy replacement of the machine sprites.
*/
const _rgbHexPattern = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
function _sanitizeRbgHex(val, def) {
if (!("" + val).startsWith("#")) val = "#" + val
if (!_rgbHexPattern.test(val)) return def;
if (val.length == 7) return val;
const r = val.charAt(1);
const g = val.charAt(2);
const b = val.charAt(3);
return `#${r + r + g + g + b + b}`;
}
module.exports = class DynamicBackground extends Mod {
label = "Dynamic Background";
description = "Modify background color.";
version = "1.0.1";
replacementsDir = "mods/db_sprite_overrides/";
settings = {
backgroundColor: {
default: "#24242c",
label: "Background Color",
description: "Rgb hex for background color.",
sanitize: _sanitizeRbgHex,
},
replaceSprites: {
default: true,
label: "Replace Sprites",
description: 'Replace default sprites with the modified versions.\nYou need to download these with the mod and place them in \'' + this.replacementsDir + '\'.',
},
};
spriteReplacements = {};
getMethodReplacements() {
const self = this;
const options = this.getOptions();
const methods = [
{
class: Game,
method: "renderloop",
replacement: function () {
requestAnimationFrame(_ => this.renderloop());
this.unit = this.solidUnit * this.zoom;
if (this.halt) return;
const now = performance.now();
this.renderTime.dt = now - this.renderTime.lt;
this.renderTime.lt = now;
if (this.slowdown.state) this.renderTime.dt *= (1 * (1 - this.slowdown.f) + this.slowdown.multiplyer * this.slowdown.f);
this.ctx.fillStyle = this.plane === 1 ? "#000" : options.backgroundColor;
this.ctx.fillRect(0, 0, this.w, this.h);
this.ctx.save();
this.ctx.translate(this.w2, this.h2);
this.renderConductors(this.renderTime.dt);
this.ctx.translate(-this.w2, -this.h2);
this.renderChasmVFX();
this.ctx.translate(this.w2, this.h2);
this.renderEntities(this.renderTime.dt);
if (this.altActive && !this.plane) {
this.ctx.fillStyle = `${options.backgroundColor}cc`;
this.ctx.fillRect(-this.w2, -this.h2, this.w, this.h);
if (this.hoveredEntity && !(this.hoveredEntity instanceof Cube)) {
this.renderSOI(this.hoveredEntity);
this.hoveredEntity.render(0);
this.renderAffected(this.hoveredEntity.name);
}
}
if (this.hoveredCell) {
if (this.itemInHand) {
this.renderAvailability();
this.renderSOI(this.hoveredCell);
}
this.renderHoveredCell();
if (this.itemInHand && !this.itemInHand.eraser) {
this.ctx.save();
this.ctx.globalAlpha = .5;
this.itemInHand.render(0, this.hoveredCell);
this.ctx.restore();
this.renderAffected(this.itemInHand.name);
}
}
if (this.pinhole) {
const radius = this.unit * .01 + this.unit * 2 * this.pinhole.f;
const time = performance.now() / 1000;
const noise = (Math.sin(time * 37) * .6 + Math.sin(time * 1913.2) * .4) * this.unit * .08;
const ctx = this.ctx;
const xy = this.uvToXY(this.pinhole.position);
ctx.save();
ctx.translate(xy[0], xy[1] - this.unit);
ctx.fillStyle = this.plane ? options.backgroundColor : "#000";
ctx.beginPath();
ctx.arc(0, 0, Math.max(0, radius + noise), 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ctx.restore();
}
this.ctx.restore();
this.renderVFX();
if (!this.plane) {
if (this.chasm) this.renderChasm();
this.renderResources();
if (!this.chillMode) this.renderHollowEvents();
this.renderSlowdown();
} else {
if (this.entitiesInGame.pinhole > 0) this.renderResources();
else this.renderDarkResources();
if (!this.chillMode) this.renderDarkHollowEvents();
}
if (this.mouse.cursorVisible) this.renderCursor();
if (this.currentHint.element) {
this.currentHint.element.style.left = this.mouse.offsetxy[0] + "px";
this.currentHint.element.style.top = this.mouse.offsetxy[1] + "px";
}
if (!self.darkMode && this.photofobia && this.flashlight && !this.plane) {
this.ctx.fillStyle = this.flashlight;
this.ctx.fillRect(0, 0, this.w, this.h);
}
}
},
];
if (options.replaceSprites) methods.push(
{
class: Sprite,
method: "switchSequence",
replacement: function (n) {
self.replaceSpriteSource(this);
self.originalMethods.Sprite.switchSequence.call(this, n);
}
}
);
return methods;
};
sanitizeOptions() {
const options = this.getOptions();
const settings = this.getSettings();
for (const key in options) {
const option = this.settings[key];
if (option.sanitize) options[key] = option.sanitize(options[key], settings[key].default);
}
return options;
}
calculateBrightness(color) {
const hex = color.substring(1);
const rgb = parseInt(hex, 16);
const r = (rgb >> 16) & 0xff;
const g = (rgb >> 8) & 0xff;
const b = (rgb >> 0) & 0xff;
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709
this.darkMode = luma < 128;
}
registerSpriteReplacements() {
const self = this;
const original_abstract_getCodex = abstract_getCodex;
abstract_getCodex = function () {
const codex = original_abstract_getCodex();
const preloads = codex.preload;
const fs = require("fs");
const dir = __dirname.endsWith("mods") ? __dirname.substring(0, __dirname.length - 4) : __dirname + "/";
fs.readdirSync(dir + self.replacementsDir).filter(e => e.endsWith(".png")).forEach(entry => {
const original = "img/" + entry;
const replacement = self.replacementsDir + entry;
const preloadIndex = preloads.indexOf(original);
if (preloadIndex == -1) preloads.push(replacement);
else preloads[preloadIndex] = replacement;
self.spriteReplacements[original] = replacement;
});
return codex;
};
};
replaceSpriteSource(sprite) {
if (sprite.replacementDone) return;
for (const key in this.spriteReplacements) {
if (!sprite.img.src.endsWith(key)) continue;
const preloaded = sprite.master.images[key];
if (preloaded) sprite.img = preloaded;
else sprite.img.src = this.spriteReplacements[key];
break;
}
sprite.replacementDone = true;
}
init() {
const options = this.sanitizeOptions();
this.calculateBrightness(options.backgroundColor);
if (options.replaceSprites) this.registerSpriteReplacements();
console.log("Dynamic Background was initialized.");
};
};
/*
* Sixty-Four Mod: Dynamic Prices
*
* https://sixtyfour.game-vault.net/wiki/Modding:Index
*
* ----------------------------------------------
*
* REQUIRES THE MOD AUTOLOADER
* See https://gist.github.com/NamelessCoder/26be6b5db7480de09f9dfb9e80dee3fe#file-_readme-md
*
* ----------------------------------------------
*
* Scales the base and increase rate of build prices.
*/
function _sanitizeNonNegative(val, def) {
if (typeof val !== "number") return def;
return val < 0 ? 0 : val;
}
module.exports = class DynamicPrices extends Mod {
label = "Dynamic Prices";
description = "Modify build prices.";
version = "1.1.0";
settings = {
priceBaseMultiplier: {
default: 1,
label: "Base Multiplier",
description: "Scale the base build price.",
sanitize: _sanitizeNonNegative,
},
priceExponentMultiplier: {
default: 0.5,
label: "Scaling Multiplier",
description: "Scale the build price increse rate.\nSet to 0 for constant prices.",
sanitize: _sanitizeNonNegative,
},
};
initializeOptions() {
super.initializeOptions();
this.sanitizeOptions();
};
sanitizeOptions() {
const options = this.getOptions();
const settings = this.getSettings();
for (const key in options) {
const setting = this.settings[key];
if (setting?.sanitize) options[key] = setting.sanitize(options[key], settings[key].default);
}
}
updateCodex() {
const self = this;
const original_abstract_getCodex = abstract_getCodex;
abstract_getCodex = function () {
const codex = original_abstract_getCodex();
for (const key in codex.entities) {
self.applyMultipliers(codex.entities[key]);
}
return codex;
};
}
applyMultipliers(entity) {
if (entity.dynamicPricesApplied) return;
const options = this.getOptions();
if (entity.priceExponent !== 1 && options.priceExponentMultiplier !== 1) entity.priceExponent = (entity.priceExponent - 1) * options.priceExponentMultiplier + 1;
if (entity.price && options.priceBaseMultiplier !== 1) entity.price = entity.price.map(n => n * options.priceBaseMultiplier);
entity.dynamicPricesApplied = true;
}
init() {
this.updateCodex();
console.log("Dynamic Prices was initialized.");
};
};
/*
* Sixty-Four Mod: Industrial Furnace
*
* https://sixtyfour.game-vault.net/wiki/Modding:Index
*
* ----------------------------------------------
*
* REQUIRES THE MOD AUTOLOADER
* See https://gist.github.com/NamelessCoder/26be6b5db7480de09f9dfb9e80dee3fe#file-_readme-md
*
* REQUIRES CUSTOM SPRITES
* https://github.com/RafalBerezin/Sixty_Four_Mods/blob/master/Industrial_Furnace/industrial_furnace_sprites.zip
* Extract the contents of this zip archive into the mods folder.
* The folder structure shuold look like this:
*
* mods/
* ├── industrial_furnace.js
* └── img/
* ├── industrial_furnace.png
* └── shop/
* └── industrial_furnace.jpg
*
* ----------------------------------------------
*
* Adds an Industrial Furnace, a more efficient version of Beta-Pylene Oxidizer.
* It's unlocked after placing a Celestial Reactor.
*
* IMPORTANT
* All Industrial Furnaces will disappear without returning the resources if you start the game without this mod enabled.
*/
const registryName = "industrial_furnace";
const entitySpriteSrc = `mods/img/${registryName}.png`;
class IndustrialFurnace extends Entity {
name = registryName;
fill = 0;
conversion = 0;
baseConversionSpeed = 25e-6;
state = 0;
fuel = [0, 0, 0, 65536, 0, 1024, 16384];
result = [524288, 65536, 32768];
soulPower = 128;
src = entitySpriteSrc;
constructor(master) {
super(master);
this.sprite = new Sprite({
master: this.master,
src: this.src,
mask: [0, 0, 455, 730],
frames: [[0, 0, 455, 730], [455, 0, 455, 730]],
origins: [227, 600],
scale: 1,
sequences: [0, 1],
intervals: 100
});
this.initHint();
this.initSellHint();
}
getConversionOutput() {
return this.result;
}
update(dt) {
this.checkHarvest(dt);
this.updateConversionProgress(dt);
if (this.conversion > 0 && this.sprite.currentSequence === 0) this.sprite.switchSequence(1);
}
checkHarvest(dt) {
if (!this.timeToHarvest) return;
this.timeToHarvest -= dt;
if (this.timeToHarvest > 0) return;
this.harvest();
delete this.timeToHarvest;
}
harvest() {
this.shootExhaust();
const screenxy = this.master.uvToXYUntranslated(this.position);
const pan = this.master.getPanValueFromX(screenxy[0]);
const loudness = this.master.getLoudnessFromXY(screenxy);
this.master.createResourceTransfer(this.getConversionOutput(), screenxy);
this.master.playSound("break", pan, loudness);
this.master.playSound("tap1", pan, loudness);
}
updateConversionProgress(dt) {
if (this.state !== 2) return;
const multiplicator = this.preheaters.reduce((acc, preheater) => acc + preheater.tap(), 1);
this.conversion += this.baseConversionSpeed * dt * multiplicator;
if (this.conversion < 1) return;
this.state = 0;
this.conversion = 0;
this.fill = 0;
this.master.activeConverters.delete(this);
this.sprite.switchSequence(0);
this.timeToHarvest = Math.random() * 512;
}
refill() {
if (this.state !== 0) return;
const resources = this.master.requestResources(this.fuel, this.position, _ => this.activate());
if (resources) this.state = 1;
}
activate() {
this.fill = 1;
this.state = 2;
this.sprite.switchSequence(1);
this.master.activeConverters.add(this);
}
onDelete() {
this.master.activeConverters.delete(this);
}
onmousedown() {
this.refill();
}
init() {
this.preheaters = [];
this.isNextToSilo = false;
this.loopSoi(cell => {
if (cell instanceof Preheater) return this.preheaters.push(cell);
if (cell instanceof Silo) this.isNextToSilo = true;
});
}
loopSoi(callback) {
const x = this.position[0];
const y = this.position[1];
for (let i = 0; i < this.soi.length; i++) {
const dx = this.soi[i][0];
const dy = this.soi[i][1];
const pos = [x + dx, y + dy];
const cell = this.master.entityAtCoordinates(pos);
if (cell) callback(cell);
}
}
render(_, vposition) {
const position = vposition ? vposition : this.position;
this.sprite.renderState(position, 0);
if (!this.conversion) return;
const ctx = this.master.ctx;
ctx.save();
ctx.globalAlpha = Math.min(this.conversion * 10, 1);
this.sprite.renderState(position, 1);
ctx.restore();
}
}
const codexEntry = {
class: IndustrialFurnace,
price: [4096, 0, 0, 524288, 1024, 4096, 1048576],
priceExponent: 1.8,
canPurchase: true,
isUpgradeTo: "converter41",
affected: { silo: true, silo2: true, preheater: true },
shouldUnlock: m => m.entitiesInGame.converter64 > 0,
merge: true,
modded: {
shopImageSrc: "mods/img/shop/industrial_furnace.jpg",
}
}
const wordsEntry = {
name: "Industrial Furnace",
description: "Effitiently burns huge amounts of Beta-Pylene with Celestial Foam to produce Charonite and trace amounts of other elements."
}
module.exports = class IndustrialFurnaceMod extends Mod {
label = 'Industrial Furnace';
description = 'Adds a more efficient way to convert Beta-Pylene to Charonite';
version = '1.0.0';
getMethodReplacements() {
const self = this;
const methods = [
{
class: Shop,
method: "addItem",
replacement: function (params) {
const entity = this.master.codex.entities[params.id];
if (entity && entity.modded) return self.addModdedShopItem(this, entity, params);
self.originalMethods.Shop.addItem.call(this, params);
}
}
];
return methods;
};
addModdedShopItem(shop, entity, params) {
const { item, imageVessel, image, header, description, price, counter, existed } = this.createShopItemContainer();
image.src = entity.modded.shopImageSrc;
header.innerText = params.name;
description.innerText = params.description;
existed.innerText = shop.master.words.random.existed;
this.addShopItemUpgradeInfo(shop, imageVessel, entity);
params.vessel.appendChild(item);
item.onmousedown = _ => {
shop.master.pickupItem(params.id);
shop.master.processMousemove();
}
shop.items.push({
html: item,
pack: params.vessel.classList.contains("shopPack") ? params.vessel : false,
priceHtml: price,
price: params.price,
priceExponent: params.priceExponent,
name: params.id,
counter,
existed
})
}
createShopItemContainer() {
const item = document.createElement("div");
item.classList.add("shopItem", "hidden");
const imageVessel = document.createElement("div");
imageVessel.classList.add("imageVessel")
item.appendChild(imageVessel);
const image = document.createElement("img");
imageVessel.appendChild(image);
const header = document.createElement("div");
header.classList.add("itemHeader");
item.appendChild(header);
const description = document.createElement("div");
description.classList.add("itemDescription");
item.appendChild(description);
const price = document.createElement("div");
price.classList.add("itemPrice");
item.appendChild(price);
const counter = document.createElement("div");
counter.classList.add("itemCounter");
item.appendChild(counter);
const existed = document.createElement("div");
existed.classList.add("existed");
item.appendChild(existed);
return { item, imageVessel, image, header, description, price, counter, existed };
}
addShopItemUpgradeInfo(shop, imageVessel, entity) {
const baseEntityId = entity.isUpgradeTo;
if (!baseEntityId) return;
const baseEntity = shop.master.codex.entities[baseEntityId];
if (!baseEntity) return;
const upBox = document.createElement("div");
upBox.classList.add("upgradeFrom");
imageVessel.appendChild(upBox);
const src = baseEntity.modded?.shopImageSrc ?? `${baseEntity.modded ? "mods/" : ""}img/shop/${baseEntityId}.jpg`;
upBox.style.backgroundImage = `url("${src}")`;
}
registerEntity() {
const original_abstract_getCodex = abstract_getCodex;
abstract_getCodex = function () {
const codex = original_abstract_getCodex();
codex.entities[registryName] = codexEntry;
codex.preload.push(entitySpriteSrc);
return codex;
};
const original_abstract_getWords = abstract_getWords;
abstract_getWords = function () {
const words = original_abstract_getWords();
words.en.entities[registryName] = wordsEntry;
return words;
}
}
handleDynamicPrices() {
const dp = this.mods.dynamic_prices;
if (!dp || !dp.enabled) return;
const options = dp.settings;
if (options.priceExponentMultiplier !== 1) codexEntry.priceExponent = (codexEntry.priceExponent - 1) * options.priceExponentMultiplier + 1;
if (options.priceBaseMultiplier !== 1) codexEntry.price = codexEntry.price.map(n => n * options.priceBaseMultiplier);
codexEntry.dynamicPricesApplied = true;
}
init() {
this.registerEntity();
this.handleDynamicPrices();
};
};
/*
* Sixty-Four Mod: No HUD
*
* https://sixtyfour.game-vault.net/wiki/Modding:Index
*
* ----------------------------------------------
*
* REQUIRES THE MOD AUTOLOADER
* See https://gist.github.com/NamelessCoder/26be6b5db7480de09f9dfb9e80dee3fe#file-_readme-md
*
* ----------------------------------------------
*
* Allows to hide the HUD.
*/
module.exports = class NoHUD extends Mod {
label = "No HUD";
description = "Press [H] to hide the HUD";
version = "1.0.0";
settings = {
hideResources: {
default: true,
label: "Hide Resources",
description: "Hide the Resource Bar, Resource Transfer and Chasm vfx."
},
hideCursor: {
default: false,
label: "Hide Cursor",
description: "Hide the Cursor and related visuals."
}
};
hiddenClass = "no-hud";
styles = `
.${this.hiddenClass} {
visibility: hidden;
pointer-events: none;
}
`;
toggleKey = "h";
hidden = false;
htmlHUDElements = undefined;
htmlHUDElementSelectors = [".shop", ".shopToggle", ".chatIcon", ".messenger"];
getMethodReplacements() {
const self = this;
const options = this.getOptions();
const methods = [
{
class: Game,
method: "setListeners",
replacement: function () {
self.game = this;
self.addListeners();
self.originalMethods.Game.setListeners.call(this);
}
}
];
if (options.hideResources) {
methods.push(
{
class: Game,
method: "renderChasm",
replacement: function () {
if (self.hidden) return;
self.originalMethods.Game.renderChasm.call(this);
}
},
{
class: Game,
method: "renderResources",
replacement: function () {
if (self.hidden) return;
self.originalMethods.Game.renderResources.call(this);
}
},
{
class: Game,
method: "renderVFX",
replacement: function () {
if (self.hidden) return;
self.originalMethods.Game.renderVFX.call(this);
}
}
);
}
if (options.hideCursor) {
methods.push(
{
class: Game,
method: "renderCursor",
replacement: function () {
if (self.hidden) return;
self.originalMethods.Game.renderCursor.call(this);
}
},
{
class: Game,
method: "renderSOI",
replacement: function (entity) {
if (self.hidden) return;
self.originalMethods.Game.renderSOI.call(this, entity);
}
},
{
class: Game,
method: "renderAffected",
replacement: function (name) {
if (self.hidden) return;
self.originalMethods.Game.renderAffected.call(this, name);
}
}
);
}
return methods;
};
addListeners() {
window.addEventListener("keyup", e => this.toggleHUDEvent(e));
}
toggleHUDEvent(e) {
if (e.key.toLowerCase() !== this.toggleKey || this.game.splash.isShown ) return;
this.hidden = !this.hidden;
this.getHUDHtmlElements().forEach(element => {
element.classList.toggle(this.hiddenClass, this.hidden);
});
if (this.hidden) this.game.removeHint();
}
getHUDHtmlElements() {
if (this.htmlHUDElements) return this.htmlHUDElements;
this.htmlHUDElements = this.htmlHUDElementSelectors.map(selector => document.querySelector(selector)).filter(e => e);
return this.htmlHUDElements;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment