|
|
@@ -4,6 +4,26 @@ import NESBank from "/app/js/models/NESBank.js"; |
|
|
|
import NESPalette from "/app/js/models/NESPalette.js"; |
|
|
|
|
|
|
|
|
|
|
|
function NumToHex(n){ |
|
|
|
var h = n.toString(16); |
|
|
|
if (h.length %2) |
|
|
|
h = '0' + h; |
|
|
|
return '$' + h; |
|
|
|
} |
|
|
|
|
|
|
|
function CompileAttribs(attrib){ |
|
|
|
return (attrib[3] << 6) | (attrib[2] << 4) | (attrib[1] << 2) | (attrib[0] << 0) |
|
|
|
} |
|
|
|
|
|
|
|
function DecompileAttribs(v){ |
|
|
|
return [ |
|
|
|
v & 0x00000011, |
|
|
|
(v & 0x00001100) >> 2, |
|
|
|
(v & 0x00110000) >> 4, |
|
|
|
(v & 0x11000000) >> 6 |
|
|
|
]; |
|
|
|
} |
|
|
|
|
|
|
|
export default class NESNameTable extends ISurface{ |
|
|
|
constructor(){ |
|
|
|
super(); |
|
|
@@ -12,18 +32,49 @@ export default class NESNameTable extends ISurface{ |
|
|
|
this.__tiles = []; |
|
|
|
this.__attribs = []; |
|
|
|
|
|
|
|
this.__undos = []; |
|
|
|
this.__redos = []; |
|
|
|
|
|
|
|
for (let i=0; i < 960; i++) |
|
|
|
this.__tiles[i] = 0; |
|
|
|
for (let i=0; i < 64; i++) |
|
|
|
this.__attribs[i] = [0,0,0,0]; |
|
|
|
} |
|
|
|
|
|
|
|
get base64(){ |
|
|
|
var b = ""; |
|
|
|
for (let i = 0; i < this.__tiles.length; i++) |
|
|
|
b += String.fromCharCode(this.__tiles[i]); |
|
|
|
for (let i = 0; i < this.__attribs.length; i++) |
|
|
|
b += String.fromCharCode(CompileAttribs(this.__attribs[i])); |
|
|
|
return window.btoa(b); |
|
|
|
} |
|
|
|
|
|
|
|
set base64(s){ |
|
|
|
var b = window.atob(s); |
|
|
|
var len = b.length; |
|
|
|
if (b.length !== 1024){ |
|
|
|
throw new Error("Base64 string contains invalid byte count."); |
|
|
|
} |
|
|
|
b = new Uint8Array(b.split("").map(function(c){ |
|
|
|
return c.charCodeAt(0); |
|
|
|
})); |
|
|
|
for (let i=0; i < b.length; i++){ |
|
|
|
if (i < 960){ |
|
|
|
this.__tiles[i] = b[i]; |
|
|
|
} else { |
|
|
|
this.__attribs[i-960] = DecompileAttribs(b[i]); |
|
|
|
} |
|
|
|
} |
|
|
|
this.emit("data_changed"); |
|
|
|
} |
|
|
|
|
|
|
|
get bank(){return this.__bank;} |
|
|
|
set bank(b){ |
|
|
|
if (b !== null and !(b instanceof NESBank)) |
|
|
|
throw new TypeError("Expected a NESBank object."); |
|
|
|
this.__bank = b; |
|
|
|
this.emit("data_changed"); |
|
|
|
} |
|
|
|
|
|
|
|
get palette(){return this.__palette;} |
|
|
@@ -31,13 +82,14 @@ export default class NESNameTable extends ISurface{ |
|
|
|
if (p !== null && !(p instanceof NESPalette)) |
|
|
|
throw new TypeError("Expected a NESPalette object."); |
|
|
|
this.__palette = p; |
|
|
|
this.emit("data_changed"); |
|
|
|
} |
|
|
|
|
|
|
|
get width(){return 256;} |
|
|
|
get height(){return 240;} |
|
|
|
get length(){return 0;} |
|
|
|
get undos(){return 0;} |
|
|
|
get redos(){return 0;} |
|
|
|
get length(){return this.width * this.height;} |
|
|
|
get undos(){return this.__undos.length;} |
|
|
|
get redos(){return this.__redos.length;} |
|
|
|
|
|
|
|
|
|
|
|
copy(b){ |
|
|
@@ -53,6 +105,7 @@ export default class NESNameTable extends ISurface{ |
|
|
|
b.__attribs[i][3] |
|
|
|
]; |
|
|
|
} |
|
|
|
this.emit("data_changed"); |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
@@ -60,13 +113,52 @@ export default class NESNameTable extends ISurface{ |
|
|
|
return (new NESNameTable()).clone(this); |
|
|
|
} |
|
|
|
|
|
|
|
snapshot(){return this;} |
|
|
|
undo(){return this;} |
|
|
|
redo(){return this;} |
|
|
|
clearUndos(){return this;} |
|
|
|
clearRedos(){return this;} |
|
|
|
clearHistory(){ |
|
|
|
return this.clearUndos().clearRedos(); |
|
|
|
snapshot(){ |
|
|
|
if (this.__redos.length > 0) // Remove the redo history. We're adding a new snapshot. |
|
|
|
this.__redos = []; |
|
|
|
|
|
|
|
var snap = this.base64; |
|
|
|
if (this.__undos.length === this.__historyLength){ |
|
|
|
this.__undos.pop(); |
|
|
|
} |
|
|
|
this.__undos.splice(0,0,snap); |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
undo(){ |
|
|
|
if (this.__undos.length > 0){ |
|
|
|
var usnap = this.__undos.splice(0, 1)[0]; |
|
|
|
var rsnap = this.base64; |
|
|
|
this.base64 = usnap; |
|
|
|
if (this.__redos.length === this.__historyLength){ |
|
|
|
this.__redos.pop(); |
|
|
|
} |
|
|
|
this.__redos.splice(0,0,rsnap); |
|
|
|
} |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
redo(){ |
|
|
|
if (this.__redos.length > 0){ |
|
|
|
var rsnap = this.__redos.splice(0,1)[0]; |
|
|
|
var usnap = this.base64; |
|
|
|
this.base64 = rsnap; |
|
|
|
if (this.__undos.length === this.__historyLength){ |
|
|
|
this.__undos.pop(); |
|
|
|
} |
|
|
|
this.__undos.splice(0,0,usnap); |
|
|
|
} |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
clearUndos(){ |
|
|
|
this.__undos = []; |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
clearRedos(){ |
|
|
|
this.__redos = []; |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
getColor(x, y){ |
|
|
@@ -96,9 +188,8 @@ export default class NESNameTable extends ISurface{ |
|
|
|
|
|
|
|
var tileX = Math.floor(x / 32); |
|
|
|
var tileY = Math.floor(y / 32); |
|
|
|
var tileIndex = 256 + this.__tiles[(tileY * 32) + tileX]; |
|
|
|
|
|
|
|
ci = this.__bank.rp[tileIndex].getPixelIndex(_x, _y); |
|
|
|
ci = this.__bank.rp[this.__tiles[(tileY * 32) + tileX]].getPixelIndex(_x, _y); |
|
|
|
pi = this._PaletteFromCoords(x, y); |
|
|
|
} |
|
|
|
return {pi:pi, ci:ci}; |
|
|
@@ -106,22 +197,111 @@ export default class NESNameTable extends ISurface{ |
|
|
|
|
|
|
|
|
|
|
|
setColorIndex(x, y, ci, pi){ |
|
|
|
if (x < 0 || x >= this.width || y < 0 || y >= this.height) |
|
|
|
throw new RangeError("Coordinates are out of bounds."); |
|
|
|
if (pi < 0 || pi >= 4) |
|
|
|
throw new RangeError("Palette index is out of bounds."); |
|
|
|
|
|
|
|
// NOTE: This method (setColorIndex) is called by CTRLPainter, which doesn't know about painting tile |
|
|
|
// indicies... therefore, CTRLPainter will still call this method for NESNameTable surfaces, but all it |
|
|
|
// will do is change palette indicies. |
|
|
|
// To paint the actual tile index, however, we'll use this emit that will be watched by the CTRLNameTable class |
|
|
|
// and call this class's setTileIndex() method for tile painting. |
|
|
|
// YAY to cheating!! |
|
|
|
this.emit("paint_nametable"); |
|
|
|
|
|
|
|
// Then business as usual! |
|
|
|
var bp = this._GetAttribBlockPalette(x,y); |
|
|
|
if (this.__attribs[bp.bindex][bp.pindex] !== pi){ |
|
|
|
this.__attribs[bp.bindex][bp.pindex] = pi; |
|
|
|
this.emit("data_changed"); |
|
|
|
} |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
setTileIndex(x, y, ti){ |
|
|
|
if (ti < 0 || ti >= 256) |
|
|
|
throw new RangeError("Tile index is out of bounds."); |
|
|
|
if (x < 0 || x >= this.width || y < 0 || y >= this.height) |
|
|
|
throw new RangeError("Coordinates are out of bounds."); |
|
|
|
|
|
|
|
var _x = Math.floor(x / 8); |
|
|
|
var _y = Math.floor(y / 8); |
|
|
|
var tindex = (_y * 32) + _x; |
|
|
|
if (this.__tiles[tindex] !== ti){ |
|
|
|
this.__tiles[tindex] = ti; |
|
|
|
this.emit("data_changed"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Generates a small 6502 assembly block string containing the current nametable data. |
|
|
|
* @param {string} [ntname="NameTableData"] The label name under which to store the data. |
|
|
|
* @returns {string} |
|
|
|
*/ |
|
|
|
nametable_asm(ntname="NameTableData"){ |
|
|
|
var s = ntname + ":"; |
|
|
|
|
|
|
|
for (let i=0; i < this.__tiles.length; i++){ |
|
|
|
if (i % 32 === 0) |
|
|
|
s += "\n\t.db"; |
|
|
|
s += " " + NumToHex(this.__tiles[i]); |
|
|
|
} |
|
|
|
|
|
|
|
return s; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Generates a small 6502 assembly block string containing the current attribute table data. |
|
|
|
* @param {string} [atname="AttribTableData"] The label name under which to store the data. |
|
|
|
* @returns {string} |
|
|
|
*/ |
|
|
|
attribtable_asm(atname="AttribTableData"){ |
|
|
|
var s = atname + ":"; |
|
|
|
|
|
|
|
for (let i=0; i < this.__attribs.length; i++){ |
|
|
|
if (i % 32 === 0) |
|
|
|
s += "\n\t.db"; |
|
|
|
s += " " + NumToHex(CompileAttribs(this.__attribs[i])); |
|
|
|
} |
|
|
|
|
|
|
|
return s; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Generates a small 6502 assembly block string containing the current name and attribute table data. |
|
|
|
* @param {string} [ntname="NameTableData"] The label name under which to store the Nametable data. |
|
|
|
* @param {string} [atname="AttribTableData"] the label name under which to store the Attribute table data. |
|
|
|
* @returns {string} |
|
|
|
*/ |
|
|
|
to_asm(ntname="NameTableData", atname="AttribTableData"){ |
|
|
|
return this.nametable_asm(ntname) + "\n\n" + this.attribtable_asm(atname); |
|
|
|
} |
|
|
|
|
|
|
|
_GetAttribBlockPalette(x,y){ |
|
|
|
var bp = {bindex:0, pindex:0}; |
|
|
|
|
|
|
|
_PaletteFromCoords(x,y){ |
|
|
|
var blockX = Math.floor(x / 32); |
|
|
|
var blockY = Math.floor(y / 32); |
|
|
|
var bIndex = (blockY * 8) + blockX; |
|
|
|
bp.bindex = (blockY * 8) + blockX; |
|
|
|
|
|
|
|
var palX = Math.floor(x % 16); |
|
|
|
var palY = Math.floor(y % 16); |
|
|
|
var pIndex = ((palX < 8) ? 0 : 1) + ((palY >= 8) ? 2 : 0); |
|
|
|
bp.pindex = ((palX < 8) ? 0 : 1) + ((palY >= 8) ? 2 : 0); |
|
|
|
|
|
|
|
return this.__attribs[bIndex][pIndex]; |
|
|
|
return bp; |
|
|
|
} |
|
|
|
|
|
|
|
_PaletteFromCoords(x,y){ |
|
|
|
var bp = this._GetAttribBlockPalette(x,y); |
|
|
|
return this.__attribs[bp.bindex][bp.pindex]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|