import Utils from "/app/js/common/Utils.js"; import ISurface from "/app/js/ifaces/ISurface.js"; import NESTile from "/app/js/models/NESTile.js"; import NESPalette from "/app/js/models/NESPalette.js"; function LRIdx2TileIdxCo(index, lid){ if (isNaN(lid) || lid < 0 || lid > 2){ lid = 2; } var res = { lid: 0, index: 0, x: 0, y: 0 }; var w = (lid == 2) ? 256 : 128; var x = Math.floor(index % w); var y = Math.floor(index / w); if (x < 128){ res.index = (Math.floor(y/8) * 16) + Math.floor(x / 8); if (lid !== 2) res.lid = lid; } else { res.index = (Math.floor(y/8) * 16) + Math.floor((x - 128) / 8); res.lid = 1; } res.x = x % 8; res.y = y % 8; return res; } export default class NESBank extends ISurface{ constructor(){ super(); this.__LP = []; // Left Patterns (Sprites) this.__RP = []; // Right Patterns (Backgrounds) this.__AccessMode = 2; // 0 = Sprites only | 1 = BG only | 2 = Sprites and BG var handle_datachanged = function(side){ if ((side == 0 && (this.__AccessMode == 0 || this.__AccessMode == 2)) || (side == 1 && (this.__AccessMode == 1 || this.__AccessMode == 2))){ this.emit("data_changed"); } } for (var i=0; i < 256; i++){ this.__LP.push(new NESTile()); this.__LP[i].listen("data_changed", handle_datachanged.bind(this, 0)); this.__RP.push(new NESTile()); this.__RP[i].listen("data_changed", handle_datachanged.bind(this, 1)); } this.__palette = null; } get access_mode(){return this.__AccessMode;} set access_mode(m){ if (!Utils.isInt(m)) throw new TypeError("Access mode expected to be integer."); switch(m){ case NESBank.ACCESSMODE_SPRITE: this.__AccessMode = NESBank.ACCESSMODE_SPRITE; break; case NESBank.ACCESSMODE_BACKGROUND: this.__AccessMode = NESBank.ACCESSMODE_BACKGROUND; break; case NESBank.ACCESSMODE_FULL: this.__AccessMode = NESBank.ACCESSMODE_FULL; break; } } get json(){ JSON.stringify({ LP: this.__LP.map(x=>x.base64), RP: this.__RP.map(x=>x.base64) }); } get chr(){ var buff = new Uint8Array(8192); var offset = 0; this.__LP.forEach(function(i){ buff.set(i.chr, offset); offset += 16; }); this.__RP.forEach(function(i){ buff.set(i.chr, offset); offset += 16; }); return buff; } set chr(buff){ if (!(buff instanceof Uint8Array)) throw new TypeError("Expected Uint8Array buffer."); if (buff.length !== 8192) throw new RangeError("Data buffer has invalid byte length."); var offset = 0; this.__LP.forEach((i) => { i.chr = buff.slice(offset, offset+15); offset += 16; }); this.__RP.forEach((i) => { i.chr = buff.slice(offset, offset+15); offset += 16; }); } get palette(){return this.__palette;} set palette(p){ if (p !== null && !(p instanceof NESPalette)) throw new TypeError("Expected null or NESPalette object."); if (p !== this.__palette){ this.__palette = p; } } get width(){return (this.__AccessMode == NESBank.ACCESSMODE_FULL) ? 256 : 128;} get height(){return 128;} get length(){return this.width * this.height;} get coloridx(){ return new Proxy(this, { get:function(obj, prop){ var len = obj.length * 8; if (prop === "length") return len; if (!Utils.isInt(prop)) throw new TypeError("Expected integer index."); prop = parseInt(prop); if (prop < 0 || prop >= len) return NESPalette.Default[4]; var res = LRIdx2TileIdxCo(prop, this.__AccessMode); var list = (res.lid === 0) ? obj.__LP : obj.__RP; return list[res.index].getPixelIndex(res.x, res.y); }, set:function(obj, prop, value){ if (!Utils.isInt(prop)) throw new TypeError("Expected integer index."); if (!Utils.isInt(value)) throw new TypeError("Color expected to be integer."); prop = parseInt(prop); value = parseInt(value); if (prop < 0 || prop >= len) throw new RangeError("Index out of bounds."); if (value < 0 || value >= 4) throw new RangeError("Color index out of bounds."); var res = LRIdx2TileIdxCo(prop, this.__AccessMode); var list = (res.lid === 0) ? obj.__LP : obj.__RP; list[res.index].setPixelIndex(res.x, res.y, value); return true; } }); } get lp(){ return new Proxy(this, { get: function(obj, prop){ if (prop === "length") return obj.__LP.length; if (!Utils.isInt(prop)) throw new TypeError("Expected integer index."); prop = parseInt(prop); if (prop < 0 || prop >= 256) throw new RangeError("Index out of bounds."); return obj.__LP[prop]; }, set: function(obj, prop, value){ if (!Utils.isInt(prop)) throw new TypeError("Expected integer index."); if (!(value instanceof NESTile)) throw new TypeError("Can only assign NESTile objects."); prop = parseInt(prop); if (prop < 0 || prop >= 256) throw new RangeError("Index out of bounds."); obj.__LP[prop].copy(value); return true; } }); } get rp(){ return new Proxy(this, { get: function(obj, prop){ if (prop === "length") return obj.__RP.length; if (!Utils.isInt(prop)) throw new TypeError("Expected integer index."); prop = parseInt(prop); if (prop < 0 || prop >= 256) throw new RangeError("Index out of bounds."); return obj.__RP[prop]; }, set: function(obj, prop, value){ if (!Utils.isInt(prop)) throw new TypeError("Expected integer index."); if (!(value instanceof NESTile)) throw new TypeError("Can only assign NESTile objects."); prop = parseInt(prop); if (prop < 0 || prop >= 256) throw new RangeError("Index out of bounds."); obj.__RP[prop].copy(value); return true; } }); } copy(b){ if (!(b instanceof NESBank)) throw new TypeError("Expected NESBank object."); for (var i=0; i < 256; i++){ this.lp[i] = b.lp[i]; this.rp[i] = b.rp[i]; } return this; } clone(){ return (new NESBank()).copy(this); } getColor(x,y){ if (x < 0 || x >= this.width || y < 0 || y >= this.height) return this.__default_pi[4]; var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode); var list = (res.lid === 0) ? this.__LP : this.__RP; var pi = list[res.index].paletteIndex; var ci = list[res.index].getPixelIndex(res.x, res.y); if (this.__palette !== null){ return this.__palette.get_palette_color(pi, ci); } return NESPalette.Default[ci]; } getColorIndex(x, y){ if (x < 0 || x >= this.width || y < 0 || y >= this.height) return {pi: -1, ci:-1}; var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode); var list = (res.lid === 0) ? this.__LP : this.__RP; return { pi: list[res.index].paletteIndex, ci: list[res.index].getPixelIndex(res.x, res.y) }; } setColorIndex(x, y, ci, pi){ if (x < 0 || x >= this.width || y < 0 || y > this.height) throw new RangeError("Coordinates out of bounds."); if (!Utils.isInt(pi)) pi = -1; if (!Utils.isInt(ci)) ci = 0; if (pi < 0){ this.coloridx[(y*this.width)+x] = ci; } else { var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode); var list = (res.lid === 0) ? this.__LP : this.__RP; list[res.index].paletteIndex = pi; list[res.index].setPixelIndex(res.x, res.y, ci); } return this; } } NESBank.ACCESSMODE_SPRITE = 0; NESBank.ACCESSMODE_BACKGROUND = 1; NESBank.ACCESSMODE_FULL = 2;