z3r-patch
Version:
A JS module for patching ALTTPR seeds
259 lines (243 loc) • 13.3 kB
JavaScript
// Code mostly copied from aerinon
class SoundEffect {
/** @type {number} */
#set;
/** @type {number} */
#id;
/** @type {number} */
#addr;
/** @type {number[]} */
#chain;
/** @type {boolean} */
#accomp;
/** @type {SoundTransfer} */
#target = {
set: undefined,
id: undefined,
chain: undefined,
};
/**
* @param {number} set
* @param {number} id
* @param {number} addr
* @param {number[]} chain
* @param {boolean} accomp
*/
constructor(set, id, addr, chain = [], accomp = false) {
this.#set = set;
this.#id = id;
this.#addr = addr;
this.#chain = chain;
this.#accomp = accomp;
}
get set() { return this.#set; }
get id() { return this.#id; }
get address() { return this.#addr; }
get chain() { return this.#chain; }
get accomp() { return this.#accomp; }
get target() { return this.#target; }
/** @param {number} a */
set addr(a) { this.#addr = a; }
}
const initSfxDefs = () => [
new SoundEffect(0x02, 0x01, 0x2614), // Slash1
new SoundEffect(0x02, 0x02, 0x2625), // Slash2
new SoundEffect(0x02, 0x03, 0x2634), // Slash3
new SoundEffect(0x02, 0x04, 0x2643), // Slash4
new SoundEffect(0x02, 0x05, 0x25dd), // Wall Clink
new SoundEffect(0x02, 0x06, 0x25d7), // Bombable door clink
new SoundEffect(0x02, 0x07, 0x25b7), // Fwoosh shooting
new SoundEffect(0x02, 0x08, 0x25e3), // Arrow hitting wall
new SoundEffect(0x02, 0x09, 0x25ad), // Boomerand whoosh
new SoundEffect(0x02, 0x0a, 0x25c7), // Hookshot
new SoundEffect(0x02, 0x0b, 0x2478), // Place bomb
new SoundEffect(0x02, 0x0c, 0x269c), // Bomb explosion
new SoundEffect(0x02, 0x0d, 0x2414, [0x3f]), // Magic powder
new SoundEffect(0x02, 0x0e, 0x2404), // Fire rod shot
new SoundEffect(0x02, 0x0f, 0x24c3), // Ice rod shot
new SoundEffect(0x02, 0x10, 0x23fa), // Hammer pound
new SoundEffect(0x02, 0x11, 0x23f0), // hammer peg
new SoundEffect(0x02, 0x12, 0x23cd), // Digging
new SoundEffect(0x02, 0x13, 0x23a0, [0x3e]), // Play flute
new SoundEffect(0x02, 0x14, 0x2380), // Cape on
new SoundEffect(0x02, 0x15, 0x2390), // Cape off/wallmaster grab
new SoundEffect(0x02, 0x16, 0x232c), // Staircase
new SoundEffect(0x02, 0x17, 0x2344), // Staircase
new SoundEffect(0x02, 0x18, 0x2356), // Staircase
new SoundEffect(0x02, 0x19, 0x236e), // Staircase
new SoundEffect(0x02, 0x1a, 0x2316), // Tall grass
new SoundEffect(0x02, 0x1b, 0x2307), // Mire shallow water
new SoundEffect(0x02, 0x1c, 0x2301), // Shallow water
new SoundEffect(0x02, 0x1d, 0x22bb), // Lifting object
new SoundEffect(0x02, 0x1e, 0x2577), // Cut grass
new SoundEffect(0x02, 0x1f, 0x22e9), // Item break
new SoundEffect(0x02, 0x20, 0x22da), // Item fall in pit
new SoundEffect(0x02, 0x21, 0x22cf), // Bomb hit ground
new SoundEffect(0x02, 0x22, 0x2107), // Push obj/Armos bounce
new SoundEffect(0x02, 0x23, 0x22b1), // Boots dust
new SoundEffect(0x02, 0x24, 0x22a5, [0x3d]), // Splash
new SoundEffect(0x02, 0x25, 0x2296), // Mire shallow water 2
new SoundEffect(0x02, 0x26, 0x2844), // Link damaged
new SoundEffect(0x02, 0x27, 0x2252), // Faint
new SoundEffect(0x02, 0x28, 0x2287), // Item splash
new SoundEffect(0x02, 0x29, 0x243f, [0x3b]), // Rupee refill
new SoundEffect(0x02, 0x2a, 0x2033), // Fire rod shot hitting wall/Bombos spell
new SoundEffect(0x02, 0x2b, 0x1ff2), // Heart beep
new SoundEffect(0x02, 0x2c, 0x1fd9, [0x3a]), // Sword up
new SoundEffect(0x02, 0x2d, 0x20a6), // Magic drain
new SoundEffect(0x02, 0x2e, 0x1fca, [0x39]), // GT open
new SoundEffect(0x02, 0x2f, 0x1f47, [0x38]), // GT open/water drain
new SoundEffect(0x02, 0x30, 0x1ef1), // Cucco
new SoundEffect(0x02, 0x31, 0x20ce), // Fairy
new SoundEffect(0x02, 0x32, 0x1d47), // Bug net
new SoundEffect(0x02, 0x33, 0x1cdc, [], true), // Teleport2
new SoundEffect(0x02, 0x34, 0x1f6f, [0x33]), // Teleport1
new SoundEffect(0x02, 0x35, 0x1c67, [0x36]), // Quake/Vitreous/Zora king/Armos/Pyramid/Lanmo
new SoundEffect(0x02, 0x36, 0x1c64, [], true), // Mire open
new SoundEffect(0x02, 0x37, 0x1a43), // Spin charged
new SoundEffect(0x02, 0x38, 0x1f6f, [], true), // Water sound
new SoundEffect(0x02, 0x39, 0x1f9c, [], true), // GT open thunder
new SoundEffect(0x02, 0x3a, 0x1fe7, [], true), // Sword up
new SoundEffect(0x02, 0x3b, 0x2462, [], true), // Quiet rupees
new SoundEffect(0x02, 0x3c, 0x1a37), // Error beep
new SoundEffect(0x02, 0x3d, 0x22ab, [], true), // Big splash
new SoundEffect(0x02, 0x3e, 0x23b5, [], true), // Flute again
new SoundEffect(0x02, 0x3f, 0x2435, [], true), // Powder paired
new SoundEffect(0x03, 0x01, 0x1a18), // Sword beam
new SoundEffect(0x03, 0x02, 0x254e), // TR open
new SoundEffect(0x03, 0x03, 0x224a), // Pyramid hole
new SoundEffect(0x03, 0x04, 0x220e), // Angry solider
new SoundEffect(0x03, 0x05, 0x25b7), // Lynel shot/javelin toss
new SoundEffect(0x03, 0x06, 0x21f5), // BNC swing/Phantom ganon/Helma tail/Arrghus swoosh
new SoundEffect(0x03, 0x07, 0x223d), // Cannon fire
new SoundEffect(0x03, 0x08, 0x21e6), // Damage to enemy; $0BEX.4=1
new SoundEffect(0x03, 0x09, 0x21c1), // Kill enemy
new SoundEffect(0x03, 0x0a, 0x21a9), // Collect rupee
new SoundEffect(0x03, 0x0b, 0x2198), // Collect heart
new SoundEffect(0x03, 0x0c, 0x218e), // Non-blank text character
new SoundEffect(0x03, 0x0d, 0x21b5), // HUD heart (used explicitly by sanc heart?)
new SoundEffect(0x03, 0x0e, 0x2182), // Open chest
new SoundEffect(0x03, 0x0f, 0x24b9, [0x3c, 0x3d, 0x3e, 0x3f]), // Open chest jingle
new SoundEffect(0x03, 0x10, 0x216d, [0x3b]), // Map screen
new SoundEffect(0x03, 0x11, 0x214f), // Open item menu
new SoundEffect(0x03, 0x12, 0x215e), // Close item menu
new SoundEffect(0x03, 0x13, 0x213b), // Throwing object (sprites use it as well)/Stalfos jump
new SoundEffect(0x03, 0x14, 0x246c), // Key door/Trinexx/Dash key landing/Stalfos Knight collapse
new SoundEffect(0x03, 0x15, 0x212f), // Door closing/OW door opening/Chest opening (w/ $29 in $012E)
new SoundEffect(0x03, 0x16, 0x2123), // Armos thud
new SoundEffect(0x03, 0x17, 0x25a6), // Rat squeak
new SoundEffect(0x03, 0x18, 0x20dd), // Dragging/Mantle moving
new SoundEffect(0x03, 0x19, 0x250a), // Fireball/Laser shot; Somehow used by Trinexx???
new SoundEffect(0x03, 0x1a, 0x1e8a, [0x38]), // Chest reveal jingle
new SoundEffect(0x03, 0x1b, 0x20b6, [0x3a]), // Puzzle jingle
new SoundEffect(0x03, 0x1c, 0x1a62), // damage to enemy
new SoundEffect(0x03, 0x1d, 0x20a6), // Potion refill/Magic drain
new SoundEffect(0x03, 0x1e, 0x2091), // Flapping (Duck/Cucco swarm/Ganon bats/Keese/Raven/Vulture)
new SoundEffect(0x03, 0x1f, 0x204b), // Link fall
new SoundEffect(0x03, 0x20, 0x276c), // Menu/Text cursor moved
new SoundEffect(0x03, 0x21, 0x27e2), // Boss hit
new SoundEffect(0x03, 0x22, 0x26cf), // Boss death/delete file
new SoundEffect(0x03, 0x23, 0x2001, [0x39]), // Spin attack/Medallion swoosh
new SoundEffect(0x03, 0x24, 0x2043), // OW map perspective change
new SoundEffect(0x03, 0x25, 0x1e9d), // Pressure switch
new SoundEffect(0x03, 0x26, 0x1e7b), // Lightning/Game over/Laser/Ganon bat/Trinexx lunge
new SoundEffect(0x03, 0x27, 0x1e40), // Aga charge
new SoundEffect(0x03, 0x28, 0x26f7), // Aga/Ganon Teleport
new SoundEffect(0x03, 0x29, 0x1e21), // Aga shot
new SoundEffect(0x03, 0x2a, 0x1e12), // Somaria/Byrna/Ether spell/Helma fire ball
new SoundEffect(0x03, 0x2b, 0x1df3), // Shocked
new SoundEffect(0x03, 0x2c, 0x1dc0), // Bees
new SoundEffect(0x03, 0x2d, 0x1da9, [0x37]), // Milestone, also via text
new SoundEffect(0x03, 0x2e, 0x1d5d, [0x35, 0x34]), // Collecting heart container
new SoundEffect(0x03, 0x2f, 0x1d80, [0x33]), // Collect freestanding/pot key
new SoundEffect(0x03, 0x30, 0x1b53), // Byrna spark/Item plop/Magic bat zap/Blob emerge
new SoundEffect(0x03, 0x31, 0x1aca), // Sprite falling/Moldorm shuffle
new SoundEffect(0x03, 0x32, 0x1a78), // Bumper boing/Somaria punt/Blob transmutation/Sprite boings
new SoundEffect(0x03, 0x33, 0x1d93, [], true), // Jingle (paired $2F→$33)
new SoundEffect(0x03, 0x34, 0x1d66, [], true), // Depressing jingle (paired $2E→$35→$34)
new SoundEffect(0x03, 0x35, 0x1d73, [], true), // Ugly jingle (paired $2E→$35→$34)
new SoundEffect(0x03, 0x36, 0x1aa7), // Wizzrobe shot/Helma fireball split/Mothula beam/Blue balls
new SoundEffect(0x03, 0x37, 0x1db4, [], true), // Dinky jingle (paired $2D→$37)
new SoundEffect(0x03, 0x38, 0x1e93, [], true), // Apathetic jingle (paired $1A→$38)
new SoundEffect(0x03, 0x39, 0x2017, [], true), // Quiet swish (paired $23→$39)
new SoundEffect(0x03, 0x3a, 0x20c0, [], true), // Defective jingle (paired $1B→$3A)
new SoundEffect(0x03, 0x3b, 0x2176, [], true), // Petulant jingle (paired $10→$3B)
new SoundEffect(0x03, 0x3c, 0x248a, [], true), // Triumphant jingle (paired $0F→$3C→$3D→$3E→$3F)
new SoundEffect(0x03, 0x3d, 0x2494, [], true), // Less triumphant jingle ($0F→$3C→$3D→$3E→$3F)
new SoundEffect(0x03, 0x3e, 0x249e, [], true), // "You tried, I guess" jingle (paired $0F→$3C→$3D→$3E→$3F)
new SoundEffect(0x03, 0x3f, 0x2480, [], true), // "You didn't really try" jingle (paired $0F→$3C→$3D→$3E→$3F)
];
/**
* @param {T[]} array
* @template T
*/
const fyRand = array => {
const res = Array.from(array);
for (let i = res.length - 1; i >= 0; --i) {
const sel = Math.floor(Math.random() * (i + 1));
[res[i], res[sel]] = [res[sel], res[i]];
}
return res;
};
export default function shuffleSfx() {
let sfxPool = initSfxDefs();
/** @type {Record<number,Record<number,SoundEffect>>} */
const sfxMap = {
2: {},
3: {},
};
/** @type {Record<number,number[]>} */
const accompMap = {
2: [],
3: [],
};
/** @type {{set:number,id:number}[]} */
let candids = [];
for (const sfx of sfxPool) {
sfxMap[sfx.set][sfx.id] = sfx;
if (sfx.accomp) {
accompMap[sfx.set].push(sfx.id);
} else {
candids.push({
set: sfx.set,
id: sfx.id,
});
}
}
candids = fyRand(candids);
const chainedSfx = fyRand(sfxPool.filter(s => s.chain.length > 0))
.sort((a, b) => b.chain.length - a.chain.length);
for (const chained of chainedSfx) {
const chosen = candids.find(c => accompMap[c.set].length - chained.chain.length >= 0);
if (!chosen) {
throw new Error("SFX chain error");
}
chained.target.set = chosen.set;
chained.target.id = chosen.id;
chained.target.chain = [];
chained.chain.forEach(dstream => {
const next = accompMap[chosen.set].pop();
const dsAcc = sfxMap[chained.set][dstream];
dsAcc.target.set = chosen.set;
dsAcc.target.id = chosen.id;
chained.target.chain.push(next);
});
let ind = candids.indexOf(chosen);
candids.splice(ind, 1);
ind = sfxPool.indexOf(chained);
sfxPool.splice(ind, 1);
}
const unchainedSfx = sfxPool.filter(s => !s.accomp);
for (const s of unchainedSfx) {
const chosen = candids.pop();
s.target.set = chosen.set;
s.target.id = chosen.id;
}
return sfxMap;
}
/**
* @typedef SoundTransfer
* @prop {number} set
* @prop {number} id
* @prop {number[]} chain
*/