├── example ├── assets │ ├── fonts │ │ └── pokefont.ttf │ ├── roms │ │ └── pokemonred.gb │ └── vendors │ │ ├── AddResizeListener.css │ │ ├── AddResizeListener.js │ │ ├── SparkMD5-20140427.min.js │ │ ├── Require-2.1.10.min.js │ │ └── Virtjs-0.0.3.min.js ├── config.js ├── sources │ ├── Keymap.js │ ├── EmulatorTypes.js │ ├── BasicAuth.js │ ├── SizeMonitoring.js │ ├── Emulator.js │ ├── Application.js │ └── save.js ├── startup.js ├── index.html └── stylesheets │ └── application.css ├── library ├── Pokedex.js ├── Opponent.js ├── OpponentTeam.js ├── PlayerTeam.js ├── Item.js ├── Player.js ├── Move.js ├── Species.js ├── utilities.js ├── GrowthRate.js ├── Bag.js ├── Pokelib.js └── TeamPokemon.js ├── README.md └── pokelib.js /example/assets/fonts/pokefont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcanis/pokelib/HEAD/example/assets/fonts/pokefont.ttf -------------------------------------------------------------------------------- /example/assets/roms/pokemonred.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcanis/pokelib/HEAD/example/assets/roms/pokemonred.gb -------------------------------------------------------------------------------- /library/Pokedex.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs' 6 | 7 | ], function ( Virtjs ) { 8 | 9 | return Virtjs.ClassUtil.extend( { 10 | 11 | } ); 12 | 13 | } ); 14 | -------------------------------------------------------------------------------- /example/config.js: -------------------------------------------------------------------------------- 1 | /*global require*/ 2 | 3 | require.config( { 4 | 5 | map : { 6 | '*' : { 7 | 'pokelib' : '../library/Pokelib' 8 | } 9 | } 10 | 11 | } ); 12 | -------------------------------------------------------------------------------- /library/Opponent.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs' 6 | 7 | ], function ( Virtjs ) { 8 | 9 | return Virtjs.ClassUtil.extend( { 10 | 11 | } ); 12 | 13 | } ); 14 | -------------------------------------------------------------------------------- /library/OpponentTeam.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs' 6 | 7 | ], function ( Virtjs ) { 8 | 9 | return Virtjs.ClassUtil.extend( { 10 | 11 | } ); 12 | 13 | } ); 14 | -------------------------------------------------------------------------------- /example/sources/Keymap.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs-gb' 6 | 7 | ], function ( GameBoy ) { 8 | 9 | return { 10 | 81 : GameBoy.A, 87 : GameBoy.B, 11 | 65 : GameBoy.A, 90 : GameBoy.B, 13 : GameBoy.START, 32 : GameBoy.SELECT, 12 | 37 : GameBoy.LEFT, 38 : GameBoy.UP, 39 : GameBoy.RIGHT, 40 : GameBoy.DOWN 13 | }; 14 | 15 | } ); 16 | -------------------------------------------------------------------------------- /example/assets/vendors/AddResizeListener.css: -------------------------------------------------------------------------------- 1 | .resize-triggers { 2 | visibility: hidden; 3 | } 4 | 5 | .resize-triggers, 6 | .resize-triggers > div, 7 | .contract-trigger:before { 8 | content: " "; 9 | display: block; 10 | position: absolute; 11 | left: 0; top: 0; 12 | 13 | width: 100%; 14 | height: 100%; 15 | overflow: hidden; 16 | } 17 | 18 | .contract-trigger:before { 19 | width: 200%; 20 | height: 200%; 21 | } 22 | -------------------------------------------------------------------------------- /example/sources/EmulatorTypes.js: -------------------------------------------------------------------------------- 1 | /*global Virtjs, angular*/ 2 | 3 | angular.module( 'emulatorTypes', [ ] ) 4 | 5 | .service( 'emulatorTypes', function ( ) { 6 | 7 | var GB = Virtjs.engine.GameBoy; 8 | 9 | this.gameboy = this.gb = { 10 | 11 | engine : GB, 12 | 13 | keyboardMap : { 14 | 65 : GB.A, 90 : GB.B, 13 : GB.START, 32 : GB.SELECT 15 | , 37 : GB.LEFT, 38 : GB.UP, 39 : GB.RIGHT, 40 : GB.DOWN 16 | } 17 | 18 | }; 19 | 20 | } ) 21 | 22 | ; 23 | -------------------------------------------------------------------------------- /example/sources/BasicAuth.js: -------------------------------------------------------------------------------- 1 | /*global angular*/ 2 | 3 | angular.module( 'basicAuth', [ ] ) 4 | 5 | .service( 'basicAuth', function ( ) { 6 | 7 | var parser = document.createElement( 'a' ); 8 | 9 | this.authorization = function ( src ) { 10 | 11 | parser.href = src; 12 | 13 | if ( ! parser.username && ! parser.password ) 14 | return undefined; 15 | 16 | return 'Basic ' + window.btoa( parser.username + ':' + parser.password ); 17 | 18 | }; 19 | 20 | } ) 21 | 22 | ; 23 | -------------------------------------------------------------------------------- /example/startup.js: -------------------------------------------------------------------------------- 1 | /*global define, Virtjs, require, angular*/ 2 | 3 | define( 'virtjs', function ( ) { 4 | 5 | return Virtjs; 6 | 7 | } ); 8 | 9 | require( [ 10 | 11 | 'pokelib', 12 | 13 | 'sources/save' 14 | 15 | ], function ( Pokelib, save ) { 16 | 17 | window.Pokelib = Pokelib; 18 | 19 | if ( window.localStorage.getItem( 'main.cartridge.format' ) === null ) { 20 | // These lines define a default save file for every session. It's not the best way, but it works. 21 | window.localStorage.setItem( 'main.cartridge.data.ram', save ); 22 | window.localStorage.setItem( 'main.cartridge.format', 'J{"ram":null}' ); 23 | } 24 | 25 | angular.element( document ).ready( function ( ) { 26 | angular.bootstrap( document, [ 'application' ] ); 27 | } ); 28 | 29 | } ); 30 | -------------------------------------------------------------------------------- /example/sources/SizeMonitoring.js: -------------------------------------------------------------------------------- 1 | /*global angular*/ 2 | 3 | angular.module( 'sizeMonitoring', [ ] ) 4 | 5 | .directive( 'sizeMonitoring', [ '$parse', function ( $parse ) { 6 | 7 | return { 8 | 9 | restrict : 'A', 10 | 11 | link : function ( $scope, $element, $attrs ) { 12 | 13 | var element = $element[ 0 ]; 14 | var target = $parse( $attrs.sizeMonitoring ).assign; 15 | 16 | var onResize = function ( ) { 17 | 18 | $scope.$apply( function ( ) { 19 | 20 | target( $scope, { 21 | width : element.offsetWidth, 22 | height : element.offsetHeight 23 | } ); 24 | 25 | } ); 26 | 27 | }; 28 | 29 | window.addResizeListener( element, onResize ); 30 | 31 | $scope.$on( '$destroy', function ( ) { 32 | window.removeResizeListener( element, onResize ); 33 | } ); 34 | 35 | } 36 | 37 | }; 38 | 39 | } ] ) 40 | 41 | ; 42 | -------------------------------------------------------------------------------- /library/PlayerTeam.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs', 6 | 7 | './TeamPokemon' 8 | 9 | ], function ( Virtjs, TeamPokemon ) { 10 | 11 | return Virtjs.ClassUtil.extend( { 12 | 13 | initialize : function ( pokelib ) { 14 | 15 | this._pokelib = pokelib; 16 | 17 | this[ 0 ] = new TeamPokemon( this._pokelib, 0 ); 18 | this[ 1 ] = new TeamPokemon( this._pokelib, 1 ); 19 | this[ 2 ] = new TeamPokemon( this._pokelib, 2 ); 20 | this[ 3 ] = new TeamPokemon( this._pokelib, 3 ); 21 | this[ 4 ] = new TeamPokemon( this._pokelib, 4 ); 22 | this[ 5 ] = new TeamPokemon( this._pokelib, 5 ); 23 | 24 | }, 25 | 26 | length : function ( ) { 27 | 28 | return this._pokelib.readUint8( 0xD163 ); 29 | 30 | }, 31 | 32 | forEach : function ( fn, context ) { 33 | 34 | for ( var t = 0, T = this.length( ); t < T; ++ t ) { 35 | fn.call( context, this[ t ], t, this ); 36 | } 37 | 38 | }, 39 | 40 | map : function ( fn, context ) { 41 | 42 | var result = [ ]; 43 | 44 | for ( var t = 0, T = this.length( ); t < T; ++ t ) 45 | result.push( fn.call( context, this[ t ], t, this ) ); 46 | 47 | return result; 48 | 49 | }, 50 | 51 | filter : function ( fn, context ) { 52 | 53 | var result = [ ]; 54 | 55 | for ( var t = 0, T = this.length( ); t < T; ++ t ) 56 | if ( fn.call( context, this[ t ], t, this ) ) 57 | result.push( this[ t ] ); 58 | 59 | return result; 60 | 61 | } 62 | 63 | } ); 64 | 65 | } ); 66 | -------------------------------------------------------------------------------- /library/Item.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | 3 | 'virtjs', 4 | 5 | './utilities' 6 | 7 | ], function ( Virtjs, utilities ) { 8 | 9 | /** 10 | * @class Item 11 | * 12 | * A class managing a single item. 13 | * 14 | * Since items are defined in the ROM, and since the ROM is read-only, it is not possible to change these values. 15 | */ 16 | 17 | return Virtjs.ClassUtil.extend( { 18 | 19 | initialize : function ( pokelib, index ) { 20 | 21 | this._pokelib = pokelib; 22 | this._index = index; 23 | 24 | this._nameBank = 0x01; 25 | this._baseNameAddress = 0x472B; 26 | 27 | }, 28 | 29 | /** 30 | */ 31 | 32 | index : function ( ) { 33 | 34 | return this._index; 35 | 36 | }, 37 | 38 | /** 39 | * Return the item's name. 40 | * 41 | * @return {String} The item name. 42 | */ 43 | 44 | name : function ( ) { 45 | if ( this._index >= 0xc4 ) { 46 | // this item is a TM/HM 47 | if ( this._index <= 0xc8 ) { 48 | return "HM0" + ( this._index - 0xc3 ); 49 | } 50 | var machNum = this._index - 0xc8; 51 | if ( machNum < 10 ) return "TM0" + machNum; 52 | return "TM" + machNum; 53 | } 54 | 55 | return this._pokelib.bankSwitch( this._nameBank, function ( ) { 56 | 57 | var address = this._baseNameAddress; 58 | 59 | var realIndex = ( this._index - 1 ) & 0xff; 60 | 61 | for ( var t = 0, T = realIndex; t < T; ++ t ) 62 | address += this._pokelib.readPds( address ).length; 63 | 64 | return utilities.pdsToUtf8( this._pokelib.readPds( address ) ); 65 | 66 | }.bind( this ) ); 67 | 68 | } 69 | 70 | } ); 71 | 72 | } ); 73 | -------------------------------------------------------------------------------- /library/Player.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs', 6 | 7 | './utilities' 8 | 9 | ], function ( Virtjs, utilities ) { 10 | 11 | /** 12 | * @class Player 13 | * 14 | * The class managing the player. 15 | */ 16 | 17 | return Virtjs.ClassUtil.extend( { 18 | 19 | initialize : function ( pokelib ) { 20 | 21 | this._pokelib = pokelib; 22 | 23 | this._nameAddress = 0xD158; 24 | this._goldAddress = 0xD347; 25 | 26 | }, 27 | 28 | /** 29 | * Getter / setter for the trainer name. 30 | * 31 | * The setter version is chainable. 32 | * 33 | * @throws 34 | * This function throws if you try to set a new name, and if this name has a length greater than 10 characters. 35 | * 36 | * @param {Array} [value] The new name. 37 | */ 38 | 39 | name : function ( value ) { 40 | 41 | if ( typeof value === 'undefined' ) { 42 | 43 | return utilities.pdsToUtf8( this._pokelib.readPds( this._nameAddress ) ); 44 | 45 | } else { 46 | 47 | if ( value.length > 10 ) 48 | throw new Error( 'You trainer name cannot have more than 10 characters' ); 49 | 50 | this._pokelib.writePds( this._nameAddress, utilities.utf8ToPds( value ) ); 51 | 52 | return this; 53 | 54 | } 55 | 56 | }, 57 | 58 | /** 59 | * Getter / setter for the trainer gold. 60 | * 61 | * The setter version is chainable. 62 | * 63 | * The gold amount will be clamped into the [0;999999] range. 64 | * 65 | * @param {Number} [value] The new gold amount. 66 | */ 67 | 68 | gold : function ( value ) { 69 | 70 | if ( typeof value === 'undefined' ) { 71 | 72 | return parseInt( this._pokelib.readUint24( this._goldAddress ).toString( 16 ), 10 ); 73 | 74 | } else { 75 | 76 | value = Math.max( 0, Math.min( value, 999999 ) ); 77 | 78 | this._pokelib.writeUint24( this._goldAddress, parseInt( value.toString( 10 ), 16 ) ); 79 | 80 | return this; 81 | 82 | } 83 | 84 | } 85 | 86 | } ); 87 | 88 | } ); 89 | -------------------------------------------------------------------------------- /library/Move.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs' 6 | 7 | ], function ( Virtjs ) { 8 | 9 | /** 10 | * @class Move 11 | * 12 | * A class managing a single move. 13 | * 14 | * Since species are defined in the ROM, and since the ROM is read-only, it is not possible to change these values. 15 | */ 16 | 17 | return Virtjs.ClassUtil.extend( { 18 | 19 | initialize : function ( pokelib, index ) { 20 | 21 | this._pokelib = pokelib; 22 | this._index = index; 23 | 24 | this._dataBank = 0x0E; 25 | this._dataAddress = 0x4000; 26 | 27 | }, 28 | 29 | /** 30 | * Return the move's index. 31 | * 32 | * @return {Number} The move's index. 33 | */ 34 | 35 | index : function ( ) { 36 | 37 | return this._index; 38 | 39 | }, 40 | 41 | /** 42 | * Return the move's animation. 43 | * 44 | * @return {Number} The move's animation. 45 | */ 46 | 47 | animation : function ( ) { 48 | 49 | return this._pokelib.bankSwitch( this._dataBank, this._dataAddress + 0, 8 ); 50 | 51 | }, 52 | 53 | /** 54 | * Return the move's effect. 55 | * 56 | * @return {Number} The move's effect. 57 | */ 58 | 59 | effect : function ( ) { 60 | 61 | return this._pokelib.bankSwitch( this._dataBank, this._dataAddress + 1, 8 ); 62 | 63 | }, 64 | 65 | /** 66 | * Return the move's power. 67 | * 68 | * @return {Number} The move's power. 69 | */ 70 | 71 | power : function ( ) { 72 | 73 | return this._pokelib.bankSwitch( this._dataBank, this._dataAddress + 2, 8 ); 74 | 75 | }, 76 | 77 | /** 78 | * Return the move's type. 79 | * 80 | * @return {Number} The move's type. 81 | */ 82 | 83 | type : function ( ) { 84 | 85 | return this._pokelib.bankSwitch( this._dataBank, this._dataAddress + 3, 8 ); 86 | 87 | }, 88 | 89 | /** 90 | * Return the move's accuracy. 91 | * 92 | * @return {Number} The move's accuracy. 93 | */ 94 | 95 | accuracy : function ( ) { 96 | 97 | return this._pokelib.bankSwitch( this._dataBank, this._dataAddress + 4, 8 ); 98 | 99 | }, 100 | 101 | /** 102 | * Return the move's base PP. 103 | * 104 | * @return {Number} The move's base PP. 105 | */ 106 | 107 | pp : function ( ) { 108 | 109 | return this._pokelib.bankSwitch( this._dataBank, this._dataAddress + 5, 8 ); 110 | 111 | } 112 | 113 | } ); 114 | 115 | } ); 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pokelib 2 | 3 | Pokelib is a Javascript library based on [Virt.js](https://github.com/arcanis/virt.js/). It allows to abstract the inner architecture of a Pokemon Red rom, and offer an API to fetch and manipulate data structures during the runtime. 4 | 5 | **Note** This library is originally a proof of concept. However, it is very usable and pull requests / issues will be looked upon. Please consider updating the README documentation when adding new methods. 6 | 7 | ## ![](http://www.pokemonelite2000.com/sprites/rbspr/rbspr146.png) Usage 8 | 9 | This code assume that you have a Virt.js engine already running. 10 | 11 | ```js 12 | var pokelib = new Pokelib( engine ); 13 | 14 | pokelib.on( 'team.player', function ( e ) { 15 | 16 | var slot = pokelib.teams.player[ 0 ]; 17 | 18 | if ( slot.species( ) === null ) { 19 | console.log( 'There is no more pokemon in the slot #' + e.index ); 20 | } else { 21 | console.log( 'The pokemon in the slot #' + e.index + ' is at level ' + slot.level( ) ); 22 | } 23 | 24 | } ); 25 | ``` 26 | 27 | ## ![](http://www.pokemonelite2000.com/sprites/rbspr/rbspr145.png) Example 28 | 29 | A basic example is shown [here](http://arcanis.github.io/pokelib/example/). It will display your current team and current bag in real time. Pull requests to add new features to this example (such as listing more data, allowing to instantly send pokemon from the PC to the team, ...) are welcomed! 30 | 31 | ## ![](http://www.pokemonelite2000.com/sprites/rbspr/rbspr144.png) Reference 32 | 33 | > *Coming soon* 34 | 35 | ## ![](http://www.pokemonelite2000.com/sprites/rbspr/rbspr150.png) License 36 | 37 | Should someone from Nintendo see this and be offensed by the ROM presence in the example directory (which is only used for demonstration purpose), please feel free to contact me (either by a [regular issue](https://github.com/arcanis/pokelib) on this repository, or by email for more privacy - check [my Github profile](https://github.com/arcanis)), and I will remove the copyrighted material from the repository. 38 | 39 | > The MIT License (MIT) 40 | > 41 | > Copyright © 2014 Maël Nison 42 | > 43 | > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 44 | > 45 | > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 46 | > 47 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | -------------------------------------------------------------------------------- /example/assets/vendors/AddResizeListener.js: -------------------------------------------------------------------------------- 1 | ( function ( ) { 2 | 3 | var requestFrame = window.requestAnimationFrame; 4 | var cancelFrame = window.cancelAnimationFrame; 5 | 6 | var resetTriggers = function ( element ) { 7 | 8 | var triggers = element.__resizeTriggers__, 9 | expand = triggers.firstElementChild, 10 | contract = triggers.lastElementChild, 11 | expandChild = expand.firstElementChild; 12 | 13 | contract.scrollLeft = contract.scrollWidth; 14 | contract.scrollTop = contract.scrollHeight; 15 | 16 | expandChild.style.width = expand.offsetWidth + 1 + 'px'; 17 | expandChild.style.height = expand.offsetHeight + 1 + 'px'; 18 | 19 | expand.scrollLeft = expand.scrollWidth; 20 | expand.scrollTop = expand.scrollHeight; 21 | 22 | }; 23 | 24 | var checkTriggers = function ( element ) { 25 | return element.offsetWidth != element.__resizeLast__.width 26 | || element.offsetHeight != element.__resizeLast__.height 27 | ; 28 | }; 29 | 30 | var scrollListener = function ( e ) { 31 | 32 | resetTriggers( this ); 33 | 34 | if ( this.__resizeRAF__ ) 35 | window.cancelAnimationFrame( this.__resizeRAF__ ); 36 | 37 | this.__resizeRAF__ = window.requestAnimationFrame( function ( ) { 38 | 39 | if ( ! checkTriggers( this ) ) 40 | return ; 41 | 42 | this.__resizeLast__.width = this.offsetWidth; 43 | this.__resizeLast__.height = this.offsetHeight; 44 | this.__resizeListeners__.forEach( function( fn ) { 45 | fn.call( this, e ); 46 | } ); 47 | 48 | }.bind( this ) ); 49 | 50 | }; 51 | 52 | window.addResizeListener = function ( element, fn ) { 53 | 54 | if ( ! element.__resizeTriggers__ ) { 55 | 56 | if ( window.getComputedStyle( element ).position === 'static' ) 57 | element.style.position = 'relative'; 58 | 59 | element.__resizeLast__ = { }; 60 | element.__resizeListeners__ = [ ]; 61 | 62 | element.__resizeTriggers__ = document.createElement( 'div' ); 63 | element.__resizeTriggers__.className = 'resize-triggers'; 64 | element.__resizeTriggers__.innerHTML = [ 65 | '
', 66 | '
' 67 | ].join( '' ); 68 | 69 | element.appendChild( element.__resizeTriggers__ ); 70 | element.addEventListener( 'scroll', scrollListener, true ); 71 | 72 | resetTriggers( element ); 73 | 74 | } 75 | 76 | element.__resizeListeners__.push( fn ); 77 | 78 | }; 79 | 80 | window.removeResizeListener = function ( element, fn ) { 81 | 82 | element.__resizeListeners__.splice( element.__resizeListeners__.indexOf( fn ), 1 ); 83 | 84 | if ( element.__resizeListeners__.length ) 85 | return ; 86 | 87 | element.removeEventListener( 'scroll', scrollListener ); 88 | element.__resizeTriggers__ = ! element.removeChild( element.__resizeTriggers__ ); 89 | 90 | }; 91 | 92 | } )( ); 93 | -------------------------------------------------------------------------------- /library/Species.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs' 6 | 7 | ], function ( Virtjs ) { 8 | 9 | /** 10 | * @class Species 11 | * 12 | * A class managing a single species. 13 | * 14 | * Since species are defined in the ROM, and since the ROM is read-only, it is not possible to change these values. 15 | */ 16 | 17 | return Virtjs.ClassUtil.extend( { 18 | 19 | initialize : function ( pokelib, index ) { 20 | 21 | this._pokelib = pokelib; 22 | this._index = ( index - 1 ) & 0xff; 23 | 24 | this._pokedexIdBank = 0x10; 25 | this._pokedexIdAddress = 0x5024 + this._index; 26 | 27 | this._speciesDataBank = 0x0E; 28 | this._speciesDataAddress = 0x43DE + this.pokedexId( ) * 28; 29 | 30 | }, 31 | 32 | /** 33 | * Return the species' index, which may not be the pokedex number. 34 | * 35 | * @return {Number} The species index. 36 | */ 37 | 38 | index : function ( ) { 39 | 40 | return this._index; 41 | 42 | }, 43 | 44 | /** 45 | * Return the species' pokedex number. 46 | * 47 | * @return {Number} The pokedex number. 48 | */ 49 | 50 | pokedexId: function ( ) { 51 | 52 | return this._pokelib.bankSwitch( this._pokedexIdBank, this._pokedexIdAddress, 8 ); 53 | 54 | }, 55 | 56 | /** 57 | * Return the species' base HP stat. 58 | * 59 | * @return {Number} The base HP stat. 60 | */ 61 | 62 | baseHp : function ( ) { 63 | 64 | return this._pokelib.bankSwitch( this._speciesDataBank, this._speciesDataAddress + 1, 8 ); 65 | 66 | }, 67 | 68 | /** 69 | * Return the species' base Attack stat. 70 | * 71 | * @return {Number} The base Attack stat. 72 | */ 73 | 74 | baseAttack : function ( ) { 75 | 76 | return this._pokelib.bankSwitch( this._speciesDataBank, this._speciesDataAddress + 2, 8 ); 77 | 78 | }, 79 | 80 | /** 81 | * Return the species' base Defense stat. 82 | * 83 | * @return {Number} The base Defense stat. 84 | */ 85 | 86 | baseDefense : function ( ) { 87 | 88 | return this._pokelib.bankSwitch( this._speciesDataBank, this._speciesDataAddress + 3, 8 ); 89 | 90 | }, 91 | 92 | /** 93 | * Return the species' base Speed stat. 94 | * 95 | * @return {Number} The base Speed stat. 96 | */ 97 | 98 | baseSpeed : function ( ) { 99 | 100 | return this._pokelib.bankSwitch( this._speciesDataBank, this._speciesDataAddress + 4, 8 ); 101 | 102 | }, 103 | 104 | /** 105 | * Return the species' base Special stat. 106 | * 107 | * @return {Number} The base Special stat. 108 | */ 109 | 110 | baseSpecial : function ( value ) { 111 | 112 | return this._pokelib.bankSwitch( this._speciesDataBank, this._speciesDataAddress + 5, 8 ); 113 | 114 | }, 115 | 116 | /** 117 | * Return the species' growth rate. 118 | * 119 | * @return {GrowthRate} The growth rate. 120 | */ 121 | 122 | growthRate : function ( ) { 123 | 124 | return this._pokelib.growthRates[ this._pokelib.bankSwitch( this._speciesDataBank, this._speciesDataAddress + 19, 8 ) ]; 125 | 126 | } 127 | 128 | } ); 129 | 130 | } ); 131 | -------------------------------------------------------------------------------- /library/utilities.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs' 6 | 7 | ], function ( Virtjs ) { 8 | 9 | var pdsSpecialCharacters = [ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xE1, 0xE2, 0xE4, 0xE5, 0xF0 ]; 10 | var pdsRegularCharacters = '�������������:ぃぅ‘’“”·⋯ぁぇえ╔═╗║╚╝ ABCDEFGHIJKLMNOPQRSTUVWXYZ():;[]abcdefghijklmnopqrstuvwxyzé����� \'��-��?!.ァゥェ▷▶▼♂$×./,♀0123456789'; 11 | 12 | return { 13 | 14 | /** 15 | * Convert a Pokemon Data String into UTF-8. You should probably not use this method manually, since most of this library's methods are already using it internally. 16 | * 17 | * Note that some characters (such as PkMn) cannot be represented with the UTF-8 encoding. In such case, the result will depends on the optional parameter. If the `allowPrivateSpaceUnicode` is falsy, these characters will be replaced by the unicode character \uFFFD. In any other case, they will be replaced by their private-space unicode counterparts (ie. that the 0xF0 character will become \uE0F0, and similar). 18 | * 19 | * @private 20 | * 21 | * @param {Array} value A Pokemon Data String. 22 | * @param {Boolean} [allowPrivateSpaceUnicode=false] An option affecting the output. 23 | * 24 | * @return {String} An UTF-8 string. 25 | */ 26 | 27 | pdsToUtf8 : function ( value, allowPrivateSpaceUnicode ) { 28 | 29 | if ( typeof allowPrivateSpaceUnicode === 'undefined' ) 30 | allowPrivateSpaceUnicode = false; 31 | 32 | // 0x50 is a End-Of-Text character 33 | // We have to ignore any following character 34 | 35 | var end = value.indexOf( 0x50 ); 36 | 37 | if ( end !== - 1 ) 38 | value = value.slice( 0, end ); 39 | 40 | return String.fromCharCode.apply( String, value.map( function ( n ) { 41 | 42 | if ( allowPrivateSpaceUnicode && pdsSpecialCharacters.indexOf( n ) !== - 1 ) 43 | return 0xE000 + pdsSpecialCharacters; 44 | 45 | if ( n >= 0x60 && n < 0x60 + pdsRegularCharacters.length ) 46 | return pdsRegularCharacters.charCodeAt( n - 0x60 ); 47 | 48 | return null; 49 | 50 | } ).filter( function ( n ) { 51 | 52 | return ! isNaN( n ); 53 | 54 | } ) ); 55 | 56 | }, 57 | 58 | /** 59 | * Convert UTF-8 into a Pokemon Data String. You should probably not use this method manually, since most of this library's methods are already using it internally. 60 | * 61 | * @private 62 | * 63 | * @param {String} utf8String An UTF-8 string. 64 | * 65 | * @return {Array} A Pokemon Data String. 66 | */ 67 | 68 | utf8ToPds : function ( utf8String ) { 69 | 70 | return utf8String.split( '' ).map( function ( character ) { 71 | 72 | var charCode = character.charCodeAt( 0 ); 73 | 74 | if ( charCode === 0xFFFD ) 75 | // The 'Unknown Character' symbol should never match 76 | return null; 77 | 78 | if ( ( charCode & 0xFF00 ) === 0xE000 && pdsSpecialCharacters.indexOf( charCode & 0x00FF ) ) 79 | return charCode & 0x00FF; 80 | 81 | var pdsCharCode = pdsRegularCharacters.indexOf( character ); 82 | 83 | if ( pdsCharCode !== - 1 ) 84 | return 0x60 + pdsCharCode; 85 | 86 | return null; 87 | 88 | } ).filter( function ( n ) { 89 | 90 | return ! isNaN( n ); 91 | 92 | } ).concat( [ 0x50 ] ); 93 | 94 | } 95 | 96 | }; 97 | 98 | } ); 99 | -------------------------------------------------------------------------------- /library/GrowthRate.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs' 6 | 7 | ], function ( Virtjs ) { 8 | 9 | /** 10 | * @class GrowthRate 11 | * 12 | * A class used to compute the level of a Pokemon according to its experience. 13 | * 14 | * Internally, it is a third-degree polynomial function (ax³+bx²+cx+d). 15 | */ 16 | 17 | return Virtjs.ClassUtil.extend( { 18 | 19 | initialize : function ( pokelib, index ) { 20 | 21 | this._pokelib = pokelib; 22 | this._index = index; 23 | 24 | this._bank = 0x16; 25 | this._address = 0x501D + index * 4; 26 | 27 | }, 28 | 29 | /** 30 | * Return the A, B, C and D values used in the polynomial expression. 31 | * 32 | * @return {Object} The polynomial values: 33 | * @return {Number} return.a 34 | * @return {Number} return.b 35 | * @return {Number} return.c 36 | * @return {Number} return.d 37 | */ 38 | 39 | parameters : function ( ) { 40 | 41 | return this._pokelib.bankSwitch( this._bank, function ( ) { 42 | 43 | var w = this._pokelib.readUint8( this._address + 0 ); 44 | var x = this._pokelib.readUint8( this._address + 1 ); 45 | var y = this._pokelib.readUint8( this._address + 2 ); 46 | var z = this._pokelib.readUint8( this._address + 3 ); 47 | 48 | return { 49 | a : ( ( w & 0xF0 ) >> 4 ) / ( ( w & 0x0F ) >> 0 ), 50 | b : ( x & 0x7F ) * ( x & 0x80 ? - 1 : 1 ), 51 | c : y, d : - z 52 | }; 53 | 54 | }.bind( this ) ); 55 | 56 | }, 57 | 58 | /** 59 | * Compute the level given by a specific experience amount. 60 | * 61 | * Please note that calling this function is pretty expensive; a lot of calculations are made in order to prevent rounding issues. 62 | * 63 | * @param {Number} experience The experience amount. 64 | * 65 | * @return {Number} The related level. 66 | */ 67 | 68 | experienceToLevel : function ( experience ) { 69 | 70 | // I'm gonna be clear here : I have no idea how it works exactly (well, I learned it in high school but ... long time no see). 71 | // The formula comes from http://www3.telus.net/thothworks/Quad3Deg.html and seems right. 72 | 73 | var p = this.parameters( ); 74 | var a = p.a, b = p.b, c = p.c, d = p.d - experience; 75 | 76 | b /= a; c /= a; d /= a; 77 | 78 | var q = ( 3 * c - Math.pow( b, 2 ) ) / 9; 79 | var r = ( - 27 * d + b * ( 9 * c - 2 * Math.pow( b, 2 ) ) ) / 54; 80 | 81 | var discrim = Math.pow( q, 3 ) + Math.pow( r, 2 ); 82 | 83 | if ( discrim <= 0 ) // more than one real solution. That should never happen (assertion). 84 | throw new Error( 'The growth rate can have multiple levels for a same experience amount. Wtf?' ); 85 | 86 | var s = r + Math.sqrt( discrim ); 87 | s = s < 0 ? - Math.pow( - s, 1 / 3 ) : Math.pow( s, 1 / 3 ); 88 | 89 | var t = r - Math.sqrt( discrim ); 90 | t = t < 0 ? - Math.pow( - t, 1 / 3 ) : Math.pow( t, 1 / 3 ); 91 | 92 | var root = - ( b / 3 ) + s + t; 93 | 94 | var up = Math.ceil( root ); 95 | if ( experience >= this.levelToExperience( up ) ) 96 | return up; 97 | 98 | return Math.floor( root ); 99 | 100 | }, 101 | 102 | /** 103 | * Compute the minimal experience amount required to get to a specific level. 104 | * 105 | * @param {Number} level The level. 106 | * 107 | * @return {Number} The experience amount. 108 | */ 109 | 110 | levelToExperience : function ( level ) { 111 | 112 | var p = this.parameters( ); 113 | 114 | var a = p.a, b = p.b, c = p.c, d = p.d; 115 | var n = level; 116 | 117 | return Math.floor( a * Math.pow( n, 3 ) + b * Math.pow( n, 2 ) + c * n + d ); 118 | 119 | } 120 | 121 | } ); 122 | 123 | } ); 124 | -------------------------------------------------------------------------------- /example/sources/Emulator.js: -------------------------------------------------------------------------------- 1 | /*global angular, Virtjs, SparkMD5*/ 2 | 3 | angular.module( 'emulator', [ 'basicAuth', 'emulatorTypes' ] ) 4 | 5 | .directive( 'emulator', [ '$http', 'basicAuth', 'emulatorTypes', function ( $http, basicAuth, emulatorTypes ) { 6 | 7 | var engines = [ ]; 8 | 9 | window.addEventListener( 'unload', function ( ) { 10 | engines.forEach( function ( engine ) { 11 | engine._options.devices.data.requestSave( ); 12 | } ); 13 | } ); 14 | 15 | return { 16 | 17 | restrict : 'E', 18 | template : '', 19 | 20 | scope : { 21 | type : '@', 22 | rom : '@', 23 | width : '@', 24 | height : '@', 25 | ident : '@', 26 | engine : '=?' 27 | }, 28 | 29 | link : function ( $scope, $element, $attrs ) { 30 | 31 | var element = $element[ 0 ]; 32 | var canvas = $element.find( 'canvas' )[ 0 ]; 33 | 34 | var screen = new Virtjs.screen.WebGL( { element : canvas } ); 35 | var timer = new Virtjs.timer.RAFrame( ); 36 | var data = new Virtjs.data.LocalStorage( ); 37 | 38 | // Those two components depends on the Virtjs engine type 39 | var input, engine; 40 | 41 | var buildEngine = function ( type ) { 42 | 43 | if ( ! type ) 44 | return ; 45 | 46 | if ( ! emulatorTypes[ type ] ) 47 | throw new Error( 'Unknown emulator type: ' + type ); 48 | 49 | if ( input ) 50 | input.destroy( ); 51 | 52 | if ( engine ) 53 | engine.pause( ); 54 | 55 | input = new Virtjs.input.Keyboard( { 56 | 57 | element : canvas, 58 | 59 | map : emulatorTypes[ type ].keyboardMap 60 | 61 | } ); 62 | 63 | $scope.engine = engine = window.engine = Virtjs.create( emulatorTypes[ type ].engine, { 64 | 65 | devices : { 66 | screen : screen, 67 | input : input, 68 | timer : timer, 69 | data : data 70 | }, 71 | 72 | skipBios : true, 73 | 74 | events : [ 'load', 'post-write' ], 75 | 76 | iterationCountPerFrame : 1 77 | 78 | } ); 79 | 80 | engines.push( engine ); 81 | 82 | loadRom( $scope.rom ); 83 | resizeOutput( $scope.width, $scope.height ); 84 | 85 | }; 86 | 87 | var loadRom = function ( rom ) { 88 | 89 | if ( ! rom ) 90 | return ; 91 | 92 | var authorization = basicAuth.authorization( rom ); 93 | 94 | $http.get( rom, { 95 | 96 | responseType : 'arraybuffer', 97 | headers : { authorization : authorization } 98 | 99 | } ).success( function ( data ) { 100 | 101 | if ( rom !== $scope.rom ) 102 | return ; 103 | 104 | var ident = $scope.ident || SparkMD5.ArrayBuffer.hash( data ); 105 | 106 | engine.load( data, { ident : ident } ); 107 | 108 | } ); 109 | 110 | }; 111 | 112 | var resizeOutput = function ( width, height ) { 113 | 114 | if ( ! width || ! height ) 115 | return ; 116 | 117 | screen.setOutputSize( width, height ); 118 | 119 | }; 120 | 121 | $scope.$watch( 'type', function ( ) { 122 | 123 | buildEngine( $scope.type.toLowerCase( ) ); 124 | 125 | } ); 126 | 127 | $scope.$watch( 'rom', function ( src ) { 128 | 129 | loadRom( $scope.src ); 130 | 131 | } ); 132 | 133 | $scope.$watchCollection( '[ width, height ]', function ( ) { 134 | 135 | resizeOutput( $scope.width, $scope.height ); 136 | 137 | } ); 138 | 139 | $scope.$on( '$destroy', function ( ) { 140 | 141 | if ( ! engine ) 142 | return ; 143 | 144 | engine._options.data.requestSave( ); 145 | engines.splice( engines.indexOf( engine ), 1 ); 146 | 147 | engine.pause( ); 148 | 149 | } ); 150 | 151 | } 152 | 153 | }; 154 | 155 | } ] ) 156 | 157 | ; 158 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Pokelib Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |
38 | 39 |
40 | 41 |
42 | {{ name }}${{ gold }} 43 |
44 | 45 |
46 | 47 |
48 | 49 |
50 | 51 |
52 | 53 |
54 |
55 | 56 |
57 | 58 |
59 | {{ name }}L {{ level }} 60 |
61 | 62 |
63 | 64 |
65 | 66 |
67 | 68 |
69 | 70 |
71 | {{ item.item.name() }} 72 | x{{ item.count }} 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 |
81 | 82 |
83 | 84 | 85 | Fork me on GitHub 86 | 87 | 88 | 89 | 90 | 91 |
92 | 93 |
?
94 |
95 |
96 |

Click on the screen, then control the emulator using the following keys :

97 |
    98 |
  • A / Q : A 99 |
  • Z / W : B 100 |
  • Enter : Start 101 |
  • Space : Select 102 |
103 |

You will see the left panel fill in with the runtime data extracted from the running emulator - click to edit them.

104 |

The emulator is powered by Virt.js.

105 |
106 |
107 | 108 |
109 | 110 |
111 | 112 |
113 | 114 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /example/assets/vendors/SparkMD5-20140427.min.js: -------------------------------------------------------------------------------- 1 | (function(a){if(typeof exports==="object"){module.exports=a()}else{if(typeof define==="function"&&define.amd){define(a)}else{var c;try{c=window}catch(b){c=self}c.SparkMD5=a()}}}(function(c){var e=function(s,r){return(s+r)&4294967295},n=function(z,v,u,r,y,w){v=e(e(v,z),e(r,w));return e((v<>>(32-y)),u)},a=function(v,u,A,z,r,y,w){return n((u&A)|((~u)&z),v,u,r,y,w)},k=function(v,u,A,z,r,y,w){return n((u&z)|(A&(~z)),v,u,r,y,w)},f=function(v,u,A,z,r,y,w){return n(u^A^z,v,u,r,y,w)},p=function(v,u,A,z,r,y,w){return n(A^(u|(~z)),v,u,r,y,w)},d=function(s,u){var t=s[0],r=s[1],w=s[2],v=s[3];t=a(t,r,w,v,u[0],7,-680876936);v=a(v,t,r,w,u[1],12,-389564586);w=a(w,v,t,r,u[2],17,606105819);r=a(r,w,v,t,u[3],22,-1044525330);t=a(t,r,w,v,u[4],7,-176418897);v=a(v,t,r,w,u[5],12,1200080426);w=a(w,v,t,r,u[6],17,-1473231341);r=a(r,w,v,t,u[7],22,-45705983);t=a(t,r,w,v,u[8],7,1770035416);v=a(v,t,r,w,u[9],12,-1958414417);w=a(w,v,t,r,u[10],17,-42063);r=a(r,w,v,t,u[11],22,-1990404162);t=a(t,r,w,v,u[12],7,1804603682);v=a(v,t,r,w,u[13],12,-40341101);w=a(w,v,t,r,u[14],17,-1502002290);r=a(r,w,v,t,u[15],22,1236535329);t=k(t,r,w,v,u[1],5,-165796510);v=k(v,t,r,w,u[6],9,-1069501632);w=k(w,v,t,r,u[11],14,643717713);r=k(r,w,v,t,u[0],20,-373897302);t=k(t,r,w,v,u[5],5,-701558691);v=k(v,t,r,w,u[10],9,38016083);w=k(w,v,t,r,u[15],14,-660478335);r=k(r,w,v,t,u[4],20,-405537848);t=k(t,r,w,v,u[9],5,568446438);v=k(v,t,r,w,u[14],9,-1019803690);w=k(w,v,t,r,u[3],14,-187363961);r=k(r,w,v,t,u[8],20,1163531501);t=k(t,r,w,v,u[13],5,-1444681467);v=k(v,t,r,w,u[2],9,-51403784);w=k(w,v,t,r,u[7],14,1735328473);r=k(r,w,v,t,u[12],20,-1926607734);t=f(t,r,w,v,u[5],4,-378558);v=f(v,t,r,w,u[8],11,-2022574463);w=f(w,v,t,r,u[11],16,1839030562);r=f(r,w,v,t,u[14],23,-35309556);t=f(t,r,w,v,u[1],4,-1530992060);v=f(v,t,r,w,u[4],11,1272893353);w=f(w,v,t,r,u[7],16,-155497632);r=f(r,w,v,t,u[10],23,-1094730640);t=f(t,r,w,v,u[13],4,681279174);v=f(v,t,r,w,u[0],11,-358537222);w=f(w,v,t,r,u[3],16,-722521979);r=f(r,w,v,t,u[6],23,76029189);t=f(t,r,w,v,u[9],4,-640364487);v=f(v,t,r,w,u[12],11,-421815835);w=f(w,v,t,r,u[15],16,530742520);r=f(r,w,v,t,u[2],23,-995338651);t=p(t,r,w,v,u[0],6,-198630844);v=p(v,t,r,w,u[7],10,1126891415);w=p(w,v,t,r,u[14],15,-1416354905);r=p(r,w,v,t,u[5],21,-57434055);t=p(t,r,w,v,u[12],6,1700485571);v=p(v,t,r,w,u[3],10,-1894986606);w=p(w,v,t,r,u[10],15,-1051523);r=p(r,w,v,t,u[1],21,-2054922799);t=p(t,r,w,v,u[8],6,1873313359);v=p(v,t,r,w,u[15],10,-30611744);w=p(w,v,t,r,u[6],15,-1560198380);r=p(r,w,v,t,u[13],21,1309151649);t=p(t,r,w,v,u[4],6,-145523070);v=p(v,t,r,w,u[11],10,-1120210379);w=p(w,v,t,r,u[2],15,718787259);r=p(r,w,v,t,u[9],21,-343485551);s[0]=e(t,s[0]);s[1]=e(r,s[1]);s[2]=e(w,s[2]);s[3]=e(v,s[3])},q=function(t){var u=[],r;for(r=0;r<64;r+=4){u[r>>2]=t.charCodeAt(r)+(t.charCodeAt(r+1)<<8)+(t.charCodeAt(r+2)<<16)+(t.charCodeAt(r+3)<<24)}return u},m=function(r){var t=[],s;for(s=0;s<64;s+=4){t[s>>2]=r[s]+(r[s+1]<<8)+(r[s+2]<<16)+(r[s+3]<<24)}return t},l=function(A){var u=A.length,r=[1732584193,-271733879,-1732584194,271733878],w,t,z,x,y,v;for(w=64;w<=u;w+=64){d(r,q(A.substring(w-64,w)))}A=A.substring(w-64);t=A.length;z=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(w=0;w>2]|=A.charCodeAt(w)<<((w%4)<<3)}z[w>>2]|=128<<((w%4)<<3);if(w>55){d(r,z);for(w=0;w<16;w+=1){z[w]=0}}x=u*8;x=x.toString(16).match(/(.*?)(.{0,8})$/);y=parseInt(x[2],16);v=parseInt(x[1],16)||0;z[14]=y;z[15]=v;d(r,z);return r},o=function(z){var t=z.length,r=[1732584193,-271733879,-1732584194,271733878],v,s,y,w,x,u;for(v=64;v<=t;v+=64){d(r,m(z.subarray(v-64,v)))}z=(v-64)>2]|=z[v]<<((v%4)<<3)}y[v>>2]|=128<<((v%4)<<3);if(v>55){d(r,y);for(v=0;v<16;v+=1){y[v]=0}}w=t*8;w=w.toString(16).match(/(.*?)(.{0,8})$/);x=parseInt(w[2],16);u=parseInt(w[1],16)||0;y[14]=x;y[15]=u;d(r,y);return r},j=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"],h=function(u){var t="",r;for(r=0;r<4;r+=1){t+=j[(u>>(r*8+4))&15]+j[(u>>(r*8))&15]}return t},b=function(r){var s;for(s=0;s>16)+(u>>16)+(t>>16);return(s<<16)|(t&65535)}}g.prototype.append=function(r){if(/[\u0080-\uFFFF]/.test(r)){r=unescape(encodeURIComponent(r))}this.appendBinary(r);return this};g.prototype.appendBinary=function(t){this._buff+=t;this._length+=t.length;var s=this._buff.length,r;for(r=64;r<=s;r+=64){d(this._state,q(this._buff.substring(r-64,r)))}this._buff=this._buff.substr(r-64);return this};g.prototype.end=function(t){var w=this._buff,v=w.length,u,s=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],r;for(u=0;u>2]|=w.charCodeAt(u)<<((u%4)<<3)}this._finish(s,v);r=!!t?this._state:b(this._state);this.reset();return r};g.prototype._finish=function(s,w){var u=w,t,v,r;s[u>>2]|=128<<((u%4)<<3);if(u>55){d(this._state,s);for(u=0;u<16;u+=1){s[u]=0}}t=this._length*8;t=t.toString(16).match(/(.*?)(.{0,8})$/);v=parseInt(t[2],16);r=parseInt(t[1],16)||0;s[14]=v;s[15]=r;d(this._state,s)};g.prototype.reset=function(){this._buff="";this._length=0;this._state=[1732584193,-271733879,-1732584194,271733878];return this};g.prototype.destroy=function(){delete this._state;delete this._buff;delete this._length};g.hash=function(t,r){if(/[\u0080-\uFFFF]/.test(t)){t=unescape(encodeURIComponent(t))}var s=l(t);return !!r?s:b(s)};g.hashBinary=function(s,r){var t=l(s);return !!r?t:b(t)};g.ArrayBuffer=function(){this.reset()};g.ArrayBuffer.prototype.append=function(r){var u=this._concatArrayBuffer(this._buff,r),t=u.length,s;this._length+=r.byteLength;for(s=64;s<=t;s+=64){d(this._state,m(u.subarray(s-64,s)))}this._buff=(s-64)>2]|=w[u]<<((u%4)<<3)}this._finish(s,v);r=!!t?this._state:b(this._state);this.reset();return r};g.ArrayBuffer.prototype._finish=g.prototype._finish;g.ArrayBuffer.prototype.reset=function(){this._buff=new Uint8Array(0);this._length=0;this._state=[1732584193,-271733879,-1732584194,271733878];return this};g.ArrayBuffer.prototype.destroy=g.prototype.destroy;g.ArrayBuffer.prototype._concatArrayBuffer=function(u,s){var t=u.length,r=new Uint8Array(t+s.byteLength);r.set(u);r.set(new Uint8Array(s),t);return r};g.ArrayBuffer.hash=function(r,s){var t=o(new Uint8Array(r));return !!s?t:b(t)};return g})); 2 | -------------------------------------------------------------------------------- /pokelib.js: -------------------------------------------------------------------------------- 1 | /*global window, module, define, Virtjs*/ 2 | 3 | ( function ( factory ) { 4 | 5 | var library = factory( ); 6 | 7 | if ( typeof window !== 'undefined' ) 8 | window.Pokelib = library; 9 | 10 | if ( typeof module !== 'undefined' ) 11 | module.exports = library; 12 | 13 | if ( typeof define === 'function' && define.amd ) { 14 | define( [ ], function ( ) { 15 | return library; 16 | } ); 17 | } 18 | 19 | } )( function ( ) { 20 | 21 | var unserializeString = function ( value ) { 22 | 23 | var end = value.indexOf( 0x50 ); 24 | 25 | if ( end !== - 1 ) 26 | value = value.slice( 0, end ); 27 | 28 | return String.fromCharCode.apply( String, value.map( function ( n ) { 29 | 30 | if ( n === 0x7F ) 31 | return 32; 32 | 33 | if ( n >= 0x80 && n <= 0x99 ) 34 | return 65 + n - 0x80; 35 | 36 | if ( n >= 0x9A && n <= 0x9F ) 37 | return '():;[]'.charCodeAt( n - 0x9A ); 38 | 39 | if ( n >= 0xA0 && n <= 0xB9 ) 40 | return 97 + n - 0xA0; 41 | 42 | if ( n >= 0xF6 && n <= 0xFF ) 43 | return 48 + n - 0xF6; 44 | 45 | return null; 46 | 47 | } ).filter( function ( n ) { return ! isNaN( n ); } ) ); 48 | 49 | }; 50 | 51 | var serializeString = function ( value ) { 52 | 53 | }; 54 | 55 | var readUint8 = function ( engine, address ) { 56 | return engine.mmu.readUint8( address ); }; 57 | 58 | var readUint16 = function ( engine, address ) { 59 | return ( readUint8( engine, address + 0 ) << 8 ) 60 | | ( readUint8( engine, address + 1 ) << 0 ); }; 61 | 62 | var readUint24 = function ( engine, address ) { 63 | return ( readUint16( engine, address + 0 ) << 8 ) 64 | | ( readUint8( engine, address + 2 ) << 0 ); }; 65 | 66 | var readUint32 = function ( engine, address ) { 67 | return ( readUint16( engine, address + 0 ) << 16 ) 68 | | ( readUint16( engine, address + 2 ) << 0 ); }; 69 | 70 | var readString = function ( engine, index ) { 71 | for ( var serialized = [ ]; readUint8( engine, index ) !== 0x50; ++ index ) 72 | serialized.push( readUint8( engine, index ) ); 73 | return unserializeString( serialized ); 74 | }; 75 | 76 | var writeUint8 = function ( engine, address, value ) { 77 | engine.mmu.writeUint8( address, value ); }; 78 | 79 | var writeUint16 = function ( engine, address, value ) { 80 | writeUint8( engine, address + 0, ( value & 0xFF00 ) >> 8 ); 81 | writeUint8( engine, address + 1, ( value & 0x00FF ) >> 0 ); 82 | }; 83 | 84 | var writeUint24 = function ( engine, address, value ) { 85 | writeUint16( engine, address + 0, ( value & 0xFFFF00 ) >> 8 ); 86 | writeUint16( engine, address + 2, ( value & 0x0000FF ) >> 0 ); 87 | }; 88 | 89 | var writeUint32 = function ( engine, address, value ) { 90 | writeUint16( engine, address + 0, ( value & 0xFFFF0000 ) >> 16 ); 91 | writeUint16( engine, address + 2, ( value & 0x0000FFFF ) >> 0 ); 92 | }; 93 | 94 | var executeShellcode = function ( engine, shellcode ) { 95 | 96 | if ( typeof engine._shellCodeAddress === 'undefined' ) 97 | engine._shellCodeAddress = 0x0154; 98 | 99 | for ( var t = 0, T = shellcode.length; t < T; ++ t ) 100 | engine.environment.rom[ engine._shellCodeAddress + t ] = shellcode[ t ]; 101 | 102 | engine._shellCodeAddress += shellcode.length; 103 | 104 | }; 105 | 106 | var getPokemonNumberFromId = function ( engine, id ) { 107 | 108 | if ( id === 0 ) 109 | return null; 110 | 111 | var currentBank = engine.environment.mbc3RomBank; 112 | 113 | engine.mmu.writeUint8( 0x2000, 0x10 ); 114 | var number = engine.mmu.readUint8( 0x5024 + id - 1 ); 115 | engine.mmu.writeUint8( 0x2000, currentBank ); 116 | 117 | return number; 118 | 119 | }; 120 | 121 | return Virtjs.ClassUtil.extend( [ 122 | 123 | Virtjs.EmitterMixin 124 | 125 | ], { 126 | 127 | initialize : function ( engine ) { 128 | 129 | this._engine = engine; 130 | 131 | this.bag = new Bag( this._engine ); 132 | 133 | this.items = [ ]; 134 | for ( var t = 0; t < 0xFF; ++ t ) 135 | this.items.push( new Item( this._engine, t ) ); 136 | 137 | this.teamslots = [ ]; 138 | for ( var t = 0; t < 6; ++ t ) 139 | this.teamslots.push( new TeamPokemon( this._engine, t ) ); 140 | 141 | engine.mmu.on( 'post-write', function ( e ) { 142 | this._checkMemoryChange( e.address ); 143 | }.bind( this ) ); 144 | 145 | }, 146 | 147 | _checkMemoryChange : function ( address ) { 148 | 149 | if ( address >= 0xD31E && address <= 0xD345 ) 150 | this.emit( 'bag' ); 151 | 152 | if ( address >= 0xD16B && address <= 0xD196 ) 153 | this.emit( 'teamslot', { slot : 0 } ); 154 | if ( address >= 0xD2B5 && address <= 0xD2BF ) 155 | this.emit( 'teamslot', { slot : 0 } ); 156 | 157 | if ( address >= 0xD197 && address <= 0xD1C2 ) 158 | this.emit( 'teamslot', { slot : 1 } ); 159 | if ( address >= 0xD2C0 && address <= 0xD2CA ) 160 | this.emit( 'teamslot', { slot : 1 } ); 161 | 162 | if ( address >= 0xD1C3 && address <= 0xD1EE ) 163 | this.emit( 'teamslot', { slot : 2 } ); 164 | if ( address >= 0xD2CB && address <= 0xD2D5 ) 165 | this.emit( 'teamslot', { slot : 2 } ); 166 | 167 | if ( address >= 0xD1EF && address <= 0xD21A ) 168 | this.emit( 'teamslot', { slot : 3 } ); 169 | if ( address >= 0xD2D6 && address <= 0xD2E1 ) 170 | this.emit( 'teamslot', { slot : 3 } ); 171 | 172 | if ( address >= 0xD21B && address <= 0xD246 ) 173 | this.emit( 'teamslot', { slot : 4 } ); 174 | if ( address >= 0xD2E2 && address <= 0xD2EB ) 175 | this.emit( 'teamslot', { slot : 4 } ); 176 | 177 | if ( address >= 0xD247 && address <= 0xD272 ) 178 | this.emit( 'teamslot', { slot : 5 } ); 179 | if ( address >= 0xD2EC && address <= 0xD2F6 ) 180 | this.emit( 'teamslot', { slot : 5 } ); 181 | 182 | return ; 183 | 184 | } 185 | 186 | } ); 187 | 188 | } ); 189 | -------------------------------------------------------------------------------- /library/Bag.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs', 6 | 7 | './Item', 8 | './utilities' 9 | 10 | ], function ( Virtjs, Item, utilities ) { 11 | 12 | /** 13 | * @class Bag 14 | * 15 | * The class managing the player bag. 16 | */ 17 | 18 | return Virtjs.ClassUtil.extend( [ 19 | 20 | Virtjs.IterableMixin 21 | 22 | ], { 23 | 24 | initialize : function ( pokelib ) { 25 | 26 | this._pokelib = pokelib; 27 | 28 | this._firstAddress = 0xD31E; 29 | this._lastAddress = 0xD344; 30 | 31 | this._maxCount = ( this._lastAddress - this._firstAddress ) / 2 + 1; 32 | 33 | }, 34 | 35 | /** 36 | * Fetch the slot index of the specified item type in the bag, or return -1 if there is none. 37 | * 38 | * @param {Item} item The item type. 39 | * 40 | * @return {Number} The slot index. 41 | */ 42 | 43 | indexOf : function ( item ) { 44 | 45 | if ( item instanceof Item ) 46 | item = item.index( ); 47 | 48 | for ( var index = 0, count = this.length( ); index < count; ++ index ) 49 | if ( this._pokelib.readUint8( this._firstAddress + index * 2 + 0 ) === item ) 50 | return index; 51 | 52 | return - 1; 53 | 54 | }, 55 | 56 | /** 57 | * Tells how many items of a specific type contains the bag. 58 | * 59 | * @param {Item} item The item type. 60 | * 61 | * @return {Number} The item count. 62 | */ 63 | 64 | countOf : function ( item ) { 65 | 66 | var index = this.indexOf( item ); 67 | 68 | if ( index === - 1 ) 69 | return 0; 70 | 71 | return this.at( index ).count; 72 | 73 | }, 74 | 75 | /** 76 | * Get the item in the specified slot from the bag. 77 | * 78 | * @param {Number} index The slot index. 79 | * 80 | * @return {Object} An object describing the slot: 81 | * @return {Item} return.item The item type. 82 | * @return {Number} return.count The item count. 83 | */ 84 | 85 | at : function ( index ) { 86 | 87 | if ( index >= this.length( ) ) 88 | return null; 89 | 90 | return { 91 | item : this._pokelib.items[ this._pokelib.readUint8( this._firstAddress + index * 2 + 0 ) ], 92 | count : this._pokelib.readUint8( this._firstAddress + index * 2 + 1 ) 93 | }; 94 | 95 | }, 96 | 97 | /** 98 | * Get the size of the bag. The bag size is the item type count, not the total item count. 99 | * 100 | * @return {Number} The bag size. 101 | */ 102 | 103 | length : function ( ) { 104 | 105 | return this._pokelib.readUint8( this._firstAddress - 1 ); 106 | 107 | }, 108 | 109 | /** 110 | * Adds a new item in the bag. You can add multiple instances of the same object at once, but the final result will be capped to 99. 111 | * 112 | * @param {Item} item The item type. 113 | * @param {Number} [howMany=1] The item count. 114 | * 115 | * @throws 116 | * This function will throw if the bag is full (ie. if it cannot accept any more item type), and you try to add a new item type. 117 | * 118 | * @chainable 119 | */ 120 | 121 | add : function ( item, howMany ) { 122 | 123 | if ( item instanceof Item ) 124 | item = item.index( ); 125 | 126 | if ( typeof howMany === 'undefined' ) 127 | howMany = 1; 128 | 129 | var index = this.indexOf( item ); 130 | 131 | if ( index === this._maxCount ) 132 | throw new Error( 'Bag full; empty a slot before adding a new item type' ); 133 | 134 | if ( index === - 1 ) { 135 | // If the item type is not in the bag yet, we add a slot 136 | index = this.length( ); 137 | this._pokelib.writeUint8( this._firstAddress - 1, index + 1 ); 138 | this._pokelib.writeUint8( this._firstAddress + index * 2 + 0, item ); 139 | this._pokelib.writeUint8( this._firstAddress + index * 2 + 1, Math.min( howMany, 0xFF ) ); 140 | this._pokelib.writeUint8( this._firstAddress + ( index + 1 ) * 2 + 0, 0xFF ); 141 | } else { 142 | // Otherwise, we can just expand the item count from the previous slot 143 | this._pokelib.writeUint8( this._firstAddress + index * 2 + 1, Math.min( this._pokelib.readUint8( this._firstAddress + index * 2 + 1 ) + howMany, 0xFF ) ); 144 | } 145 | 146 | return this; 147 | 148 | }, 149 | 150 | /** 151 | * Removes items from the bag. You can remove multiple instances of the same item type at once (actually, the default is to remove every instance). 152 | * 153 | * @param {Item} item The item type. 154 | * @param {Number} [howMany=Infinity] The item count. 155 | * 156 | * @chainable 157 | */ 158 | 159 | remove : function ( item, howMany ) { 160 | 161 | if ( item instanceof Item ) 162 | item = item.index( ); 163 | 164 | if ( typeof howMany === 'undefined' ) 165 | howMany = Infinity; 166 | 167 | var index = this.indexOf( item ); 168 | 169 | if ( index === - 1 ) 170 | return this; 171 | 172 | var newCount = Math.max( 0, this._pokelib.readUint8( this._firstAddress + index * 2 + 1 ) - howMany ); 173 | 174 | if ( newCount > 0 ) { 175 | 176 | // Even when removing these items, some will still be here 177 | 178 | this._pokelib.writeUint8( this._firstAddress + index * 2 + 1, newCount ); 179 | 180 | return this; 181 | 182 | } else { 183 | 184 | // These items are the last of this item type, and their slot will be fully removed 185 | 186 | var length = this.length( ); 187 | 188 | this._pokelib.writeUint8( this._firstAddress - 1, length - 1 ); 189 | 190 | for ( ; index < length; ++ index ) { 191 | 192 | var next = index + 1; 193 | 194 | this._pokelib.writeUint8( this._firstAddress + index * 2 + 0, this._pokelib.readUint8( this._firstAddress + next * 2 + 0 ) ); 195 | this._pokelib.writeUint8( this._firstAddress + index * 2 + 1, this._pokelib.readUint8( this._firstAddress + next * 2 + 1 ) ); 196 | 197 | } 198 | 199 | return this; 200 | 201 | } 202 | 203 | } 204 | 205 | } ); 206 | 207 | } ); 208 | -------------------------------------------------------------------------------- /example/sources/Application.js: -------------------------------------------------------------------------------- 1 | /*global angular, Pokelib*/ 2 | 3 | angular.module( 'application', [ 'emulator', 'sizeMonitoring' ] ) 4 | 5 | .controller( 'root', [ '$scope', function ( $scope ) { 6 | 7 | $scope.$watch( 'engine', function ( ) { 8 | 9 | if ( ! $scope.engine ) 10 | return ; 11 | 12 | $scope.engine.on( 'load', function ( ) { 13 | 14 | $scope.pokelib = window.pokelib = new Pokelib( $scope.engine, { delayEvents : true } ); 15 | $scope.ready = $scope.pokelib.ready( ); 16 | 17 | if ( ! $scope.ready ) { 18 | $scope.pokelib.on( 'ready', function ( ) { 19 | $scope.ready = true; 20 | $scope.$apply( ); 21 | } ); 22 | } 23 | 24 | } ); 25 | 26 | } ); 27 | 28 | } ] ) 29 | 30 | .controller( 'help', [ '$scope', function ( $scope ) { 31 | 32 | var focus = function ( ) { 33 | document.querySelector( 'canvas' ).focus( ); 34 | }; 35 | 36 | $scope.active = true; 37 | 38 | $scope.hide = function ( e ) { 39 | $scope.active = false; 40 | focus( ); 41 | }; 42 | 43 | $scope.toggle = function ( ) { 44 | $scope.active = ! $scope.active; 45 | if ( ! $scope.active ) { 46 | focus( ); 47 | } 48 | }; 49 | 50 | } ] ) 51 | 52 | .controller( 'bag', [ '$scope', function ( $scope ) { 53 | 54 | var reset = function ( ) { 55 | 56 | $scope.items = [ ]; 57 | 58 | }; 59 | 60 | var update = function ( ) { 61 | 62 | $scope.items = $scope.pokelib.bag.slice( ); 63 | 64 | $scope.changeCount = function ( n ) { 65 | 66 | var type = $scope.items[ n ].item; 67 | var oldCount = $scope.items[ n ].count; 68 | var newCount = parseInt( prompt( 'How many instance of this object do you wish to have?', oldCount ), 10 ); 69 | 70 | if ( newCount === null || isNaN( newCount ) || oldCount === newCount ) 71 | return ; 72 | 73 | if ( newCount > oldCount ) { 74 | $scope.pokelib.bag.add( type, newCount - oldCount ); 75 | } else { 76 | $scope.pokelib.bag.remove( type, oldCount - newCount ); 77 | } 78 | 79 | }; 80 | 81 | }; 82 | 83 | var attach = function ( ) { 84 | $scope.pokelib.on( 'bag', function ( ) { 85 | $scope.$apply( update ); 86 | } ); 87 | }; 88 | 89 | $scope.$watch( 'pokelib', function ( ) { 90 | if ( ! $scope.pokelib ) { 91 | reset( ); 92 | } else { 93 | attach( ); 94 | update( ); 95 | } 96 | } ); 97 | 98 | } ] ) 99 | 100 | .controller( 'trainer', [ '$scope', function ( $scope ) { 101 | 102 | var reset = function ( ) { 103 | 104 | $scope.name = null; 105 | 106 | $scope.gold = 0; 107 | 108 | }; 109 | 110 | var update = function ( ) { 111 | 112 | $scope.name = $scope.pokelib.player.name( ); 113 | $scope.gold = $scope.pokelib.player.gold( ); 114 | 115 | $scope.changeName = function ( ) { 116 | 117 | var newName = prompt( 'Which name do you wish to have?', $scope.name ); 118 | 119 | if ( ! newName ) 120 | return ; 121 | 122 | try { 123 | $scope.pokelib.player.name( newName ); 124 | } catch ( e ) { 125 | alert( 'Error: ' + e.message ); 126 | } 127 | 128 | }; 129 | 130 | $scope.changeGold = function ( ) { 131 | 132 | var newLevel = prompt( 'How many gold do you wish to have?', $scope.gold ); 133 | 134 | if ( ! newLevel ) 135 | return ; 136 | 137 | $scope.pokelib.player.gold( parseInt( newLevel, 10 ) ); 138 | 139 | }; 140 | 141 | }; 142 | 143 | var attach = function ( ) { 144 | $scope.pokelib.on( 'player', function ( ) { 145 | $scope.$apply( update ); 146 | } ); 147 | }; 148 | 149 | $scope.$watch( 'pokelib', function ( ) { 150 | if ( ! $scope.pokelib ) { 151 | reset( ); 152 | } else { 153 | attach( ); 154 | update( ); 155 | } 156 | } ); 157 | 158 | } ] ) 159 | 160 | .controller( 'team-member', [ '$scope', function ( $scope ) { 161 | 162 | var reset = function ( ) { 163 | 164 | $scope.empty = true; 165 | 166 | $scope.species = null; 167 | 168 | $scope.experience = 0; 169 | $scope.life = 0; 170 | 171 | }; 172 | 173 | var update = function ( ) { 174 | 175 | var slot = $scope.pokelib.teams.player[ $scope.index ]; 176 | $scope.empty = slot.species( ) === null; 177 | 178 | if ( $scope.empty ) 179 | return ; 180 | 181 | $scope.name = slot.name( ); 182 | $scope.species = slot.species( ); 183 | $scope.level = slot.level( ); 184 | 185 | $scope.pokedexId = $scope.species.pokedexId( ); 186 | 187 | var growthRate = $scope.species.growthRate( ); 188 | var baseExp = growthRate.levelToExperience( $scope.level ); 189 | var requiredExp = growthRate.levelToExperience( $scope.level + 1 ); 190 | 191 | $scope.hpPercent = slot.currentHp( ) / slot.maxHp( ); 192 | $scope.expPercent = ( slot.experience( ) - baseExp ) / ( requiredExp - baseExp ); 193 | 194 | $scope.changeName = function ( ) { 195 | 196 | var newName = prompt( 'Which name do you wish to give to this Pokemon?', $scope.name ); 197 | 198 | if ( ! newName ) 199 | return ; 200 | 201 | try { 202 | slot.name( newName ); 203 | } catch ( e ) { 204 | alert( 'Error: ' + e.message ); 205 | } 206 | 207 | }; 208 | 209 | $scope.changeLevel = function ( ) { 210 | 211 | var newLevel = prompt( 'Which level do you wish to give to this Pokemon?', $scope.level ); 212 | 213 | if ( ! newLevel ) 214 | return ; 215 | 216 | slot.level( parseInt( newLevel, 10 ) ); 217 | 218 | }; 219 | 220 | }; 221 | 222 | var attach = function ( ) { 223 | $scope.pokelib.on( 'team.player', function ( e ) { 224 | if ( e.slot !== $scope.index ) return ; 225 | $scope.$apply( update ); 226 | } ); 227 | }; 228 | 229 | $scope.$watch( 'pokelib', function ( ) { 230 | if ( ! $scope.pokelib ) { 231 | reset( ); 232 | } else { 233 | attach( ); 234 | update( ); 235 | } 236 | } ); 237 | 238 | } ] ) 239 | 240 | ; 241 | -------------------------------------------------------------------------------- /example/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Montserrat); 2 | @import url(//fonts.googleapis.com/css?family=Anaheim); 3 | 4 | * { 5 | box-sizing: border-box; 6 | } 7 | 8 | html, body { 9 | margin: 0; 10 | 11 | padding: 0; 12 | 13 | width: 100%; 14 | height: 100%; 15 | 16 | font-family: Anaheim; 17 | 18 | background: #000; 19 | } 20 | 21 | canvas { 22 | display: block; 23 | } 24 | 25 | [ng-click] { 26 | cursor: pointer; 27 | } 28 | 29 | .interface { 30 | display: flex; 31 | 32 | width: 100%; 33 | height: 100%; 34 | 35 | } 36 | 37 | .side { 38 | position: relative; 39 | z-index: 2; 40 | 41 | flex: none; 42 | width: 360px; 43 | overflow-y: scroll; 44 | 45 | background: #C12026; 46 | box-shadow: 1px 0 13px 0px rgba(0, 0, 0, 0.7); 47 | } 48 | 49 | .side::-webkit-scrollbar { 50 | width: 25px; 51 | height: 25px; 52 | 53 | background: #F0F0F0; 54 | } 55 | 56 | .side::-webkit-scrollbar-thumb { 57 | border-radius: 20px; 58 | border: 7px solid transparent; 59 | 60 | background: #D1D1D1; 61 | background-clip: padding-box; 62 | } 63 | 64 | .side .trainer { 65 | margin-bottom: 20px; 66 | 67 | text-align: center; 68 | 69 | background: #F0F0F0; 70 | box-shadow: 0 5px 13px 0px rgba(0, 0, 0, 0.7); 71 | } 72 | 73 | .side .trainer .content { 74 | position: relative; 75 | display: inline-block; 76 | 77 | padding: 15px 20px; 78 | } 79 | 80 | .side .trainer .content::before { 81 | position: absolute; 82 | right: 100%; bottom: 0; 83 | content: ''; 84 | 85 | width: 28px; 86 | height: 100%; 87 | 88 | background-image: url(http://i.imgur.com/LhmnXct.png); 89 | background-repeat: no-repeat; 90 | background-position: bottom left; 91 | } 92 | 93 | .trainer .name { 94 | font-weight: bold; 95 | } 96 | 97 | .trainer .gold { 98 | font-style: italic; 99 | } 100 | 101 | .side .team { 102 | margin: 10px 0; 103 | padding-left: 10px; 104 | } 105 | 106 | .team .slot { 107 | height: 100px; 108 | } 109 | 110 | .team .slot::before { 111 | display: block; 112 | content: ''; 113 | 114 | height: 20px; 115 | } 116 | 117 | .team .slot:first-child { 118 | height: 80px; 119 | } 120 | 121 | .team .slot:first-child::before { 122 | display: none; 123 | } 124 | 125 | .team .slot.empty { 126 | height: 0; 127 | overflow: hidden; 128 | 129 | transition-delay: .3s; 130 | transition: height .3s; 131 | } 132 | 133 | .team .slot .pokemon { 134 | position: relative; 135 | display: flex; 136 | 137 | border-top-left-radius: 10px; 138 | border-bottom-left-radius: 10px; 139 | 140 | height: 80px; 141 | 142 | padding-top: 10px; 143 | padding-left: 10px; 144 | 145 | line-height: 70px; 146 | 147 | background: #F0F0F0; 148 | box-shadow: 0 5px 13px 0px rgba(0, 0, 0, 0.7); 149 | 150 | transition: all .3s; 151 | } 152 | 153 | .team .slot.empty .pokemon { 154 | -webkit-transform: rotateX(-90deg); 155 | } 156 | 157 | .team .pokemon .life { 158 | position: absolute; 159 | top: 0; left: 0; 160 | 161 | width: 100%; 162 | height: 10px; 163 | 164 | border-top-left-radius: 10px; 165 | 166 | /* Mask fix for Chrome - http://stackoverflow.com/questions/5736503/how-to-make-css3-rounded-corners-hide-overflow-in-chrome-opera */ 167 | -webkit-mask-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC"); 168 | } 169 | 170 | .team .pokemon .life .bar { 171 | height: 100%; 172 | 173 | border-bottom-right-radius: 10px; 174 | 175 | background: #0A810A; 176 | 177 | transition: width .3s; 178 | } 179 | 180 | .team .pokemon .experience { 181 | position: absolute; 182 | top: 0; left: 0; 183 | 184 | width: 10px; 185 | height: 100%; 186 | 187 | border-bottom-left-radius: 10px; 188 | 189 | padding-top: 10px; 190 | 191 | /* Mask fix for Chrome - http://stackoverflow.com/questions/5736503/how-to-make-css3-rounded-corners-hide-overflow-in-chrome-opera */ 192 | -webkit-mask-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC"); 193 | } 194 | 195 | .team .pokemon .experience .bar { 196 | width: 100%; 197 | 198 | border-bottom-right-radius: 10px; 199 | 200 | background: #BB6D31; 201 | 202 | transition: height .3s; 203 | } 204 | 205 | .team .pokemon .preview { 206 | margin: 2px 5px; 207 | 208 | width: 96px; 209 | 210 | background-size: contain; 211 | background-position: center center; 212 | background-repeat: no-repeat; 213 | } 214 | 215 | .team .pokemon .infos { 216 | padding: 0 20px; 217 | } 218 | 219 | .team .pokemon .infos a { 220 | text-decoration: none; 221 | 222 | color: #126CDF; 223 | } 224 | 225 | .bag { 226 | margin: 10px; 227 | } 228 | 229 | .bag .item { 230 | display: flex; 231 | 232 | margin-top: 20px; 233 | 234 | border-radius: 5px; 235 | 236 | padding: 7px 14px; 237 | 238 | background: #F0F0F0; 239 | box-shadow: 0 5px 13px 0px rgba(0, 0, 0, 0.7); 240 | } 241 | 242 | .bag .item .name { 243 | display: block; 244 | 245 | flex: auto; 246 | overflow: hidden; 247 | 248 | text-overflow: ellipsis; 249 | white-space: nowrap; 250 | } 251 | 252 | .bag .item .count { 253 | display: block; 254 | 255 | margin-left: 10px; 256 | 257 | flex: none; 258 | } 259 | 260 | .bag .item a { 261 | text-decoration: none; 262 | 263 | color: #126CDF; 264 | } 265 | 266 | .separator { 267 | display: none; 268 | position: relative; 269 | z-index: 1; 270 | 271 | flex: none; 272 | width: 20px; 273 | 274 | background: #F0F0F0; 275 | box-shadow: 1px 0 13px 0px rgba(0, 0, 0, 0.7); 276 | } 277 | 278 | .screen { 279 | position: relative; 280 | display: flex; 281 | 282 | flex: auto; 283 | 284 | padding: 0 15px; 285 | 286 | background: #FFFFFF; 287 | } 288 | 289 | .screen .emulator { 290 | flex: auto; 291 | } 292 | 293 | .screen .emulator canvas { 294 | outline: none; 295 | } 296 | 297 | .screen .help { 298 | position: absolute; 299 | left: 0; bottom: 0; 300 | } 301 | 302 | .screen .help.button { 303 | display: flex; 304 | align-items: center; 305 | justify-content: center; 306 | z-index: 3; 307 | 308 | margin: 20px; 309 | 310 | width: 70px; 311 | height: 70px; 312 | 313 | border: 2px solid #000000; 314 | border-radius: 100px; 315 | 316 | font-size: 56px; 317 | 318 | background: #FFFFFF; 319 | 320 | cursor: pointer; 321 | } 322 | 323 | .screen .help.overlay { 324 | display: flex; 325 | right: 0; top: 0; 326 | z-index: 1; 327 | 328 | background: rgba(0, 0, 0, .9); 329 | opacity: 0; 330 | 331 | pointer-events: none; 332 | 333 | transition: opacity .3s; 334 | } 335 | 336 | .screen .help.button:hover { 337 | background: #D1D1D1; 338 | } 339 | 340 | /*.screen .help.button:hover + .help.overlay,*/ 341 | .help.overlay.active { 342 | opacity: 1; 343 | 344 | pointer-events: auto; 345 | } 346 | 347 | 348 | .screen .help.overlay .window { 349 | margin: auto; 350 | 351 | max-width: 550px; 352 | 353 | border-radius: 25px; 354 | 355 | padding: 0 25px; 356 | 357 | color: #FFFFFF; 358 | background: #343434; 359 | } 360 | 361 | .screen .help.overlay .window p, 362 | .screen .help.overlay .window ul { 363 | margin: 25px 0; 364 | } 365 | 366 | .screen .help.overlay .window a { 367 | color: #9292EE; 368 | } 369 | 370 | .screen .help.overlay .window li { 371 | margin-top: 10px; 372 | margin-bottom: 10px; 373 | 374 | list-style-type: none; 375 | } 376 | 377 | .screen .help.overlay .window .key { 378 | display: inline-block; 379 | 380 | border: #101010; 381 | border-radius: 5px; 382 | 383 | padding: 5px; 384 | 385 | font-variant: small-caps; 386 | font-weight: bold; 387 | 388 | color: #101010; 389 | background: #F0F0F0; 390 | } 391 | -------------------------------------------------------------------------------- /library/Pokelib.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs', 6 | 7 | './Bag', 8 | './GrowthRate', 9 | './Item', 10 | './Move', 11 | './OpponentTeam', 12 | './Opponent', 13 | './PlayerTeam', 14 | './Player', 15 | './Pokedex', 16 | './Species', 17 | './utilities' 18 | 19 | ], function ( Virtjs, Bag, GrowthRate, Item, Move, OpponentTeam, Opponent, PlayerTeam, Player, Pokedex, Species, utilities ) { 20 | 21 | /** 22 | * @class Pokelib 23 | * 24 | * The main class of the library. It contains a handful of utilities, and all the root references to the ROM / RAM elements. 25 | */ 26 | 27 | return Virtjs.ClassUtil.extend( [ 28 | 29 | Virtjs.EmitterMixin 30 | 31 | ], { 32 | 33 | /** 34 | * @property {Player} player 35 | * 36 | * A property containing a reference toward the player interface. 37 | */ 38 | 39 | /** 40 | * @property {Opponent} opponent 41 | * 42 | * A property containing a reference toward the opponent interface. 43 | */ 44 | 45 | /** 46 | * @property {Object} teams 47 | * 48 | * A property containing the current Pokemon teams. 49 | * 50 | * It's an object containing the following fields: 51 | * 52 | * @property {PlayerTeam} teams.player The player team interface. 53 | * @property {OpponentTeam} teams.opponent The opponent team interface. 54 | */ 55 | 56 | /** 57 | * @property {Pokedex} pokedex 58 | * 59 | * A property containing a reference toward the pokedex interface. 60 | */ 61 | 62 | /** 63 | * @property {Bag} bag 64 | * 65 | * A property containing a reference toward the bag interface. 66 | */ 67 | 68 | /** 69 | * @property {Item[]} items 70 | * 71 | * An array containing the list of all items defined in the ROM. 72 | */ 73 | 74 | /** 75 | * @property {GrowthRate[]} growthRates 76 | * 77 | * An array containing the list of all growth rates defined in the ROM. 78 | */ 79 | 80 | /** 81 | * @property {Species[]} species 82 | * 83 | * An array containing the list of all species defined in the ROM. 84 | */ 85 | 86 | /** 87 | * @property {Move[]} moves 88 | * 89 | * An array containing the list of all moves defined in the ROM. 90 | */ 91 | 92 | initialize : function ( engine ) { 93 | 94 | this._engine = engine; 95 | 96 | this.player = new Player( this ); 97 | this.opponent = new Opponent( this ); 98 | 99 | this.teams = { }; 100 | this.teams.player = new PlayerTeam( this ); 101 | this.teams.opponent = new OpponentTeam( this ); 102 | 103 | this.bag = new Bag( this ); 104 | 105 | this.items = [ ]; 106 | for ( var t = 0; t <= 255; t++ ) 107 | this.items[ t ] = new Item( this, t ); 108 | 109 | this.growthRates = [ ]; 110 | for ( var t = 0; t < 5; t++ ) 111 | this.growthRates[ t ] = new GrowthRate( this, t ); 112 | 113 | this.species = [ ]; 114 | for ( var t = 0; t <= 255; t++ ) 115 | this.species[ t ] = new Species( this, t ); 116 | 117 | this.moves = [ ]; 118 | for ( var t = 0; t <= 255; t++ ) 119 | this.moves[ t ] = new Move( this, t ); 120 | 121 | this.pokedex = new Pokedex( this ); 122 | 123 | this._events = { }; 124 | 125 | this._events.ready = [ 'ready' ]; 126 | 127 | this._events.player = [ 'player' ]; 128 | 129 | this._events.bag = [ 'bag' ]; 130 | 131 | this._events.teams = { }; 132 | 133 | this._events.teams.player = [ ]; 134 | this._events.teams.player[ 0 ] = [ 'team.player', { slot : 0 } ]; 135 | this._events.teams.player[ 1 ] = [ 'team.player', { slot : 1 } ]; 136 | this._events.teams.player[ 2 ] = [ 'team.player', { slot : 2 } ]; 137 | this._events.teams.player[ 3 ] = [ 'team.player', { slot : 3 } ]; 138 | this._events.teams.player[ 4 ] = [ 'team.player', { slot : 4 } ]; 139 | this._events.teams.player[ 5 ] = [ 'team.player', { slot : 5 } ]; 140 | 141 | this._events.teams.opponent = [ ]; 142 | this._events.teams.opponent[ 0 ] = [ 'team.opponent', { slot : 0 } ]; 143 | this._events.teams.opponent[ 1 ] = [ 'team.opponent', { slot : 1 } ]; 144 | this._events.teams.opponent[ 2 ] = [ 'team.opponent', { slot : 2 } ]; 145 | this._events.teams.opponent[ 3 ] = [ 'team.opponent', { slot : 3 } ]; 146 | this._events.teams.opponent[ 4 ] = [ 'team.opponent', { slot : 4 } ]; 147 | this._events.teams.opponent[ 5 ] = [ 'team.opponent', { slot : 5 } ]; 148 | 149 | this._pendingEvents = [ ]; 150 | 151 | this._engine.mmu.on( 'post-write', function ( e ) { 152 | this._checkMemoryWrite( e.address ); 153 | }.bind( this ) ); 154 | 155 | }, 156 | 157 | /** 158 | * Return true if the game contains a valid data save. 159 | * 160 | * However, please note that the game may be still writing the save in the RAM. You probably should wait a bit (for example by using the delayEvent option ?) to be sure. 161 | */ 162 | 163 | ready : function ( ) { 164 | 165 | // According to my tests, this value will only be equal to 0xFF when a valid save game is being loaded. 166 | // However, you may have to wait for the save to be fully loaded (the byte is wrote in the first instructions, when there is still other memory space to fill). 167 | 168 | return this._engine.mmu.readUint8( 0xD71B ) === 0xFF; 169 | 170 | }, 171 | 172 | /** 173 | * Read a single byte of data from the current memory space. 174 | * 175 | * @param {Number} address The memory address. 176 | * 177 | * @return {Number} The byte at the specified address. 178 | */ 179 | 180 | readUint8 : function ( address ) { 181 | 182 | return this._engine.mmu.readUint8( address ); 183 | 184 | }, 185 | 186 | /** 187 | * Read two bytes of data from the current memory space, using big endian. 188 | * 189 | * @param {Number} address The memory address. 190 | * 191 | * @return {Number} The word at the specified address. 192 | */ 193 | 194 | readUint16 : function ( address ) { 195 | 196 | return ( this.readUint8( address + 0 ) << 8 ) 197 | | ( this.readUint8( address + 1 ) << 0 ); 198 | 199 | }, 200 | 201 | /** 202 | * Read three bytes of data from the current memory space, using big endian. 203 | * 204 | * @param {Number} address The memory address. 205 | * 206 | * @return {Number} The dword at the specified address. 207 | */ 208 | 209 | readUint24 : function ( address ) { 210 | 211 | return ( this.readUint16( address + 0 ) << 8 ) 212 | | ( this.readUint8( address + 2 ) << 0 ); 213 | 214 | }, 215 | 216 | /** 217 | * Read four bytes of data from the current memory space, using big endian. 218 | * 219 | * @param {Number} address The memory address. 220 | * 221 | * @return {Number} The dword at the specified address. 222 | */ 223 | 224 | readUint32 : function ( address ) { 225 | 226 | return ( this.readUint16( address + 0 ) << 16 ) 227 | | ( this.readUint16( address + 2 ) << 0 ); 228 | 229 | }, 230 | 231 | /** 232 | * Read a Pokemon Data String from the current memory space. Runs up to the next 0x50 character. 233 | * 234 | * @param {Number} address The memory address. 235 | * 236 | * @return {Array} A Pokemon Data String. 237 | */ 238 | 239 | readPds : function ( address ) { 240 | 241 | var pds = [ ]; 242 | 243 | do { 244 | 245 | var character = this.readUint8( address ); 246 | address += 1; 247 | 248 | pds.push( character ); 249 | 250 | } while ( character !== 0x50 ); 251 | 252 | return pds; 253 | 254 | }, 255 | 256 | /** 257 | * Write a single byte of data into the current memory space. 258 | * 259 | * @param {Number} address The memory address. 260 | * @param {Number} value The new value. 261 | */ 262 | 263 | writeUint8 : function ( address, value ) { 264 | 265 | this._engine.mmu.writeUint8( address, value ); 266 | 267 | }, 268 | 269 | /** 270 | * Write two bytes of data into the current memory space, using big endian. 271 | * 272 | * @param {Number} address The memory address. 273 | * @param {Number} value The new value. 274 | */ 275 | 276 | writeUint16 : function ( address, value ) { 277 | 278 | this.writeUint8( address + 0, ( value & 0xFF00 ) >> 8 ); 279 | this.writeUint8( address + 1, ( value & 0x00FF ) >> 0 ); 280 | 281 | }, 282 | 283 | /** 284 | * Write three bytes of data into the current memory space, using big endian. 285 | * 286 | * @param {Number} address The memory address. 287 | * @param {Number} value The new value. 288 | */ 289 | 290 | writeUint24 : function ( address, value ) { 291 | 292 | this.writeUint16( address + 0, ( value & 0xFFFF00 ) >> 8 ); 293 | this.writeUint8( address + 2, ( value & 0x0000FF ) >> 0 ); 294 | 295 | }, 296 | 297 | /** 298 | * Write four bytes of data into the current memory space, using big endian. 299 | * 300 | * @param {Number} address The memory address. 301 | * @param {Number} value The new value. 302 | */ 303 | 304 | writeUint32 : function ( address, value ) { 305 | 306 | this.writeUint16( address + 0, ( value & 0xFFFF0000 ) >> 16 ); 307 | this.writeUint16( address + 2, ( value & 0x0000FFFF ) >> 0 ); 308 | 309 | }, 310 | 311 | /** 312 | * Write a Pokemon Data String into the current memory space. 313 | * 314 | * @param {Number} address The memory address. 315 | * @param {Number} pds The Pokemon Data String. 316 | */ 317 | 318 | writePds : function ( address, pds ) { 319 | 320 | var length = pds.indexOf( 0x50 ); 321 | 322 | if ( length === - 1 ) { 323 | length = pds.length; 324 | } else { 325 | length += 1; 326 | } 327 | 328 | for ( var t = 0; t < length; ++ t ) { 329 | this.writeUint8( address + t, pds[ t ] ); 330 | } 331 | 332 | }, 333 | 334 | /** 335 | * Switch the current ROM bank, then instantly call the `fn` callback. When this function returns, the original ROM bank is restaured. This function is synchronous. 336 | * 337 | * @param {Number} bank A ROM bank number. 338 | * @param {Function} fn A callback called when the ROM bank is changed. 339 | * @param {Number} [size] The address size. Used only when `fn` is an address. 340 | * 341 | * @return {*} The valued returned by the callback. 342 | */ 343 | 344 | bankSwitch : function ( bank, fn, size ) { 345 | 346 | var result; 347 | var currentBank = this._engine.environment.mbc3RomBank; 348 | 349 | this._engine.mmu.writeUint8( 0x2000, bank ); 350 | 351 | if ( typeof fn === 'function' ) { 352 | result = fn( ); 353 | } else { 354 | result = this[ 'readUint' + size ]( fn ); 355 | } 356 | 357 | this._engine.mmu.writeUint8( 0x2000, currentBank ); 358 | 359 | return result; 360 | 361 | }, 362 | 363 | _checkMemoryWrite : function ( address ) { 364 | 365 | if ( address === 0xD71B && this.ready( ) ) 366 | return this._scheduleEvent( this._events.ready ); 367 | 368 | if ( address >= 0xD31E && address <= 0xD345 ) 369 | return this._scheduleEvent( this._events.bag ); 370 | 371 | if ( address >= 0xD16B && address <= 0xD272 ) 372 | return this._scheduleEvent( this._events.teams.player[ Math.floor( ( address - 0xD16B ) / 44 ) ] ); 373 | 374 | if ( address >= 0xD2B5 && address <= 0xD2F6 ) 375 | return this._scheduleEvent( this._events.teams.player[ Math.floor( ( address - 0xD2B5 ) / 11 ) ] ); 376 | 377 | if ( address >= 0xD158 && address <= 0xD162 ) 378 | return this._scheduleEvent( this._events.player ); 379 | if ( address >= 0xD347 && address <= 0xD349 ) 380 | return this._scheduleEvent( this._events.player ); 381 | 382 | return void 0; 383 | 384 | }, 385 | 386 | _scheduleEvent : function ( eventDescriptor ) { 387 | 388 | if ( this._options.delayEvents ) { 389 | 390 | var offset = this._pendingEvents.indexOf( eventDescriptor ); 391 | 392 | if ( offset !== - 1 ) 393 | this._pendingEvents.splice( offset, 1 ); 394 | 395 | this._pendingEvents.push( eventDescriptor ); 396 | 397 | if ( ! this._eventEmitterDelay ) { 398 | 399 | this._eventEmitterDelay = window.setTimeout( function ( ) { 400 | this._eventEmitterDelay = undefined; 401 | this._dispatchEvents( ); 402 | }.bind( this ), 0 ); 403 | 404 | } 405 | 406 | } else { 407 | 408 | this.emit( eventDescriptor[ 0 ], eventDescriptor[ 1 ] ); 409 | 410 | } 411 | 412 | }, 413 | 414 | _dispatchEvents : function ( ) { 415 | 416 | var events = this._pendingEvents; 417 | this._pendingEvents = [ ]; 418 | 419 | for ( var t = 0, T = events.length; t < T; ++ t ) { 420 | this.emit( events[ t ][ 0 ], events[ t ][ 1 ] ); 421 | } 422 | 423 | } 424 | 425 | }, { 426 | 427 | utilities : utilities 428 | 429 | } ); 430 | 431 | } ); 432 | -------------------------------------------------------------------------------- /example/assets/vendors/Require-2.1.10.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.1.10 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(ca){function G(b){return"[object Function]"===N.call(b)}function H(b){return"[object Array]"===N.call(b)}function v(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(G(c)){if(this.events.error&&this.map.isDefine||h.onError!==da)try{f=i.execCb(b,c,e,f)}catch(d){a=d}else f=i.execCb(b,c,e,f);this.map.isDefine&&void 0===f&&((e=this.module)?f=e.exports:this.usingExports&& 19 | (f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=c;this.exports=f;if(this.map.isDefine&&!this.ignore&&(p[b]=f,h.onResourceLoad))h.onResourceLoad(i,this.map,this.depMaps);y(b);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a= 20 | this.map,b=a.id,d=m(a.prefix);this.depMaps.push(d);r(d,"defined",t(this,function(f){var d,g;g=j(ba,this.map.id);var J=this.map.name,u=this.map.parentMap?this.map.parentMap.name:null,p=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(J=f.normalize(J,function(a){return c(a,u,!0)})||""),f=m(a.prefix+"!"+J,this.map.parentMap),r(f,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),g=j(k,f.id)){this.depMaps.push(f); 21 | if(this.events.error)g.on("error",t(this,function(a){this.emit("error",a)}));g.enable()}}else g?(this.map.url=i.nameToUrl(g),this.load()):(d=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),d.error=t(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];B(k,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),d.fromText=t(this,function(f,c){var g=a.name,J=m(g),k=O;c&&(f=c);k&&(O=!1);q(J);s(l.config,b)&&(l.config[g]=l.config[b]);try{h.exec(f)}catch(j){return w(C("fromtexteval", 22 | "fromText eval for "+b+" failed: "+j,j,[b]))}k&&(O=!0);this.depMaps.push(J);i.completeLoad(g);p([g],d)}),f.load(a.name,p,d,l))}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){W[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,t(this,function(a,b){var c,f;if("string"===typeof a){a=m(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=j(K,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;r(a,"defined",t(this,function(a){this.defineDep(b, 23 | a);this.check()}));this.errback&&r(a,"error",t(this,this.errback))}c=a.id;f=k[c];!s(K,c)&&(f&&!f.enabled)&&i.enable(a,this)}));B(this.pluginMaps,t(this,function(a){var b=j(k,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:l,contextName:b,registry:k,defined:p,urlFetched:T,defQueue:A,Module:$,makeModuleMap:m, 24 | nextTick:h.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=l.shim,c={paths:!0,bundles:!0,config:!0,map:!0};B(a,function(a,b){c[b]?(l[b]||(l[b]={}),V(l[b],a,!0,!0)):l[b]=a});a.bundles&&B(a.bundles,function(a,b){v(a,function(a){a!==b&&(ba[a]=b)})});a.shim&&(B(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);b[c]=a}),l.shim=b);a.packages&&v(a.packages,function(a){var b, 25 | a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(l.paths[b]=a.location);l.pkgs[b]=a.name+"/"+(a.main||"main").replace(ja,"").replace(R,"")});B(k,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=m(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ca,arguments));return b||a.exports&&ea(a.exports)}},makeRequire:function(a,e){function g(f,c,d){var j,l;e.enableBuildCallback&&(c&&G(c))&&(c.__requireJsBuild= 26 | !0);if("string"===typeof f){if(G(c))return w(C("requireargs","Invalid require call"),d);if(a&&s(K,f))return K[f](k[a.id]);if(h.get)return h.get(i,f,a,g);j=m(f,a,!1,!0);j=j.id;return!s(p,j)?w(C("notloaded",'Module name "'+j+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[j]}M();i.nextTick(function(){M();l=q(m(null,a));l.skipMap=e.skipMap;l.init(f,c,d,{enabled:!0});D()});return g}e=e||{};V(g,{isBrowser:z,toUrl:function(b){var e,d=b.lastIndexOf("."),g=b.split("/")[0];if(-1!== 27 | d&&(!("."===g||".."===g)||1g.attachEvent.toString().indexOf("[native code"))&&!Z?(O=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)): 34 | (g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=d,M=g,D?y.insertBefore(g,D):y.appendChild(g),M=null,g;if(fa)try{importScripts(d),b.completeLoad(c)}catch(j){b.onError(C("importscripts","importScripts failed for "+c+" at "+d,j,[c]))}};z&&!r.skipDataMain&&U(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(L=b.getAttribute("data-main"))return q=L,r.baseUrl||(E=q.split("/"),q=E.pop(),Q=E.length?E.join("/")+"/":"./",r.baseUrl= 35 | Q),q=q.replace(R,""),h.jsExtRegExp.test(q)&&(q=L),r.deps=r.deps?r.deps.concat(q):[q],!0});define=function(b,c,d){var g,h;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(g=M))P&&"interactive"===P.readyState||U(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),g=P;g&&(b|| 36 | (b=g.getAttribute("data-requiremodule")),h=F[g.getAttribute("data-requirecontext")])}(h?h.defQueue:S).push([b,c,d])};define.amd={jQuery:!0};h.exec=function(b){return eval(b)};h(r)}})(this); 37 | -------------------------------------------------------------------------------- /library/TeamPokemon.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define( [ 4 | 5 | 'virtjs', 6 | 7 | './Species', 8 | './utilities' 9 | 10 | ], function ( Virtjs, Species, utilities ) { 11 | 12 | /** 13 | * @class TeamPokemon 14 | * 15 | * A class representing a Pokemon from the player team. 16 | */ 17 | 18 | return Virtjs.ClassUtil.extend( { 19 | 20 | initialize : function ( pokelib, index ) { 21 | 22 | this._pokelib = pokelib; 23 | this._index = index; 24 | 25 | this._speciesAddress = 0xD164 + index * 1; 26 | this._dataAddress = 0xD16B + index * 44; 27 | this._nameAddress = 0xD2B5 + index * 11; 28 | 29 | }, 30 | 31 | /** 32 | * Getter / setter for the Pokemon name. 33 | * 34 | * The setter version is chainable. 35 | * 36 | * @throws 37 | * This function throws if you try to set a new name, and if this name has a length greater than 10 characters. 38 | * 39 | * @param {Array} [value] The new name. 40 | */ 41 | 42 | name : function ( value ) { 43 | 44 | if ( typeof value === 'undefined' ) { 45 | 46 | return utilities.pdsToUtf8( this._pokelib.readPds( this._nameAddress ) ); 47 | 48 | } else { 49 | 50 | if ( value.length > 10 ) 51 | throw new Error( 'A pokemon name cannot have a length greater than 10 characters' ); 52 | 53 | this._pokelib.writePds( this._nameAddress, utilities.utf8ToPds( value ) ); 54 | 55 | return this; 56 | 57 | } 58 | 59 | }, 60 | 61 | /** 62 | * Getter / setter for the Pokemon species. 63 | * 64 | * The setter version is chainable. 65 | * 66 | * @param {Species} [value] The new species. 67 | */ 68 | 69 | species : function ( value ) { 70 | 71 | if ( typeof value === 'undefined' ) { 72 | // do NOT rely on the species being 0x00 or 0xff! 73 | // instead, get the number of pokemon from D163 (in English R/B)! 74 | if ( this._pokelib.readUint8( 0xd163 ) < ( this._index + 1 ) ) return null; 75 | 76 | var speciesIndex = this._pokelib.readUint8( this._speciesAddress ); 77 | 78 | return this._pokelib.species[ speciesIndex ]; 79 | 80 | } else { 81 | 82 | var speciesIndex = value !== null ? value.index( ) + 1 : 0; 83 | 84 | this._pokelib.writeUint8( this._speciesAddress, speciesIndex ); 85 | this._pokelib.writeUint8( this._dataAddress + 0, speciesIndex ); 86 | 87 | return this; 88 | 89 | } 90 | 91 | }, 92 | 93 | /** 94 | * Getter / setter for the Pokemon maximal HP. Note that when the Pokemon stats will be computed again by the game (for example during a level up), any change to this value will be overriden as well. 95 | * 96 | * Setting this property to a lower value than the current HP will also lower the current HP (so that the two values does not conflict with each other). 97 | * 98 | * The setter version is chainable. 99 | * 100 | * @param {Number} [value] The new maximal HP. 101 | */ 102 | 103 | maxHp : function ( value ) { 104 | 105 | if ( typeof value === 'undefined' ) { 106 | 107 | return this._pokelib.readUint16( this._dataAddress + 34 ); 108 | 109 | } else { 110 | 111 | this._pokelib.writeUint16( this._dataAddress + 34, value ); 112 | 113 | if ( this.currentHP( ) > value ) 114 | this.currentHP( value ); 115 | 116 | return this; 117 | 118 | } 119 | 120 | }, 121 | 122 | /** 123 | * Getter / setter for the Pokemon current HP. Note that when the Pokemon stats will be computed again by the game (for example during a level up), any change to this value will be overriden as well. 124 | * 125 | * Setting this property to a greater value than the maximal HP will also increase the maximal HP (so that the two values does not conflict with each other). 126 | * 127 | * The setter version is chainable. 128 | * 129 | * @param {Number} [value] The new current HP. 130 | */ 131 | 132 | currentHp : function ( value ) { 133 | 134 | if ( typeof value === 'undefined' ) { 135 | 136 | return this._pokelib.readUint16( this._dataAddress + 1 ); 137 | 138 | } else { 139 | 140 | if ( this.maxHP( ) < value ) 141 | this.maxHP( value ); 142 | 143 | this._pokelib.writeUint16( this._dataAddress + 1, value ); 144 | 145 | return this; 146 | 147 | } 148 | 149 | }, 150 | 151 | /** 152 | * Getter / setter for the Pokemon level. 153 | * 154 | * Setting this property will also affect the Pokemon experience (so that the two values does not conflict with each other). 155 | * 156 | * The setter version is chainable. 157 | * 158 | * @param {Number} [value] The new level. 159 | */ 160 | 161 | level : function ( value ) { 162 | 163 | if ( typeof value === 'undefined' ) { 164 | 165 | return this._pokelib.readUint8( this._dataAddress + 33 ); 166 | 167 | } else { 168 | 169 | if ( value === this.level( ) ) 170 | return this; 171 | 172 | this._pokelib.writeUint8( this._dataAddress + 33, value ); 173 | 174 | this.experience( this.species( ).growthRate( ).levelToExperience( value ) ); 175 | 176 | return this; 177 | 178 | } 179 | 180 | }, 181 | 182 | /** 183 | * Getter / setter for the Pokemon moveset. 184 | * 185 | * The setter version is chainable. 186 | * 187 | * @param {Array} [value] The new moveset. 188 | */ 189 | 190 | moveset : function ( value ) { 191 | 192 | if ( typeof value === 'undefined' ) { 193 | 194 | var moveset = [ 195 | this._pokelib.readUint8( this._dataAddress + 8 ), 196 | this._pokelib.readUint8( this._dataAddress + 9 ), 197 | this._pokelib.readUint8( this._dataAddress + 10 ), 198 | this._pokelib.readUint8( this._dataAddress + 11 ) 199 | ]; 200 | 201 | moveset[ 0 ] = moveset[ 0 ] !== 0 ? this._pokelib.moves[ moveset[ 0 ] - 1 ] : null; 202 | moveset[ 1 ] = moveset[ 1 ] !== 0 ? this._pokelib.moves[ moveset[ 1 ] - 1 ] : null; 203 | moveset[ 2 ] = moveset[ 2 ] !== 0 ? this._pokelib.moves[ moveset[ 2 ] - 1 ] : null; 204 | moveset[ 3 ] = moveset[ 3 ] !== 0 ? this._pokelib.moves[ moveset[ 3 ] - 1 ] : null; 205 | 206 | return moveset; 207 | 208 | } else { 209 | 210 | this._pokelib.writeUint8( this._dataAddress + 8, value[ 0 ] ? value[ 0 ].index( ) + 1 : 0 ); 211 | this._pokelib.writeUint8( this._dataAddress + 9, value[ 1 ] ? value[ 1 ].index( ) + 1 : 0 ); 212 | this._pokelib.writeUint8( this._dataAddress + 10, value[ 2 ] ? value[ 2 ].index( ) + 1 : 0 ); 213 | this._pokelib.writeUint8( this._dataAddress + 11, value[ 3 ] ? value[ 3 ].index( ) + 1 : 0 ); 214 | 215 | return this; 216 | 217 | } 218 | 219 | }, 220 | 221 | /** 222 | * Getter / setter for the Pokemon PPs. 223 | * 224 | * The setter version is chainable. 225 | * 226 | * @param {Array} [value] The new PPs. 227 | */ 228 | 229 | pps : function ( value ) { 230 | 231 | if ( typeof value === 'undefined' ) { 232 | 233 | return [ 234 | this._pokelib.readUint8( this._dataAddress + 29 ), 235 | this._pokelib.readUint8( this._dataAddress + 30 ), 236 | this._pokelib.readUint8( this._dataAddress + 31 ), 237 | this._pokelib.readUint8( this._dataAddress + 32 ) 238 | ]; 239 | 240 | } else { 241 | 242 | this._pokelib.writeUint8( this._dataAddress + 29, value[ 0 ] ); 243 | this._pokelib.writeUint8( this._dataAddress + 30, value[ 1 ] ); 244 | this._pokelib.writeUint8( this._dataAddress + 31, value[ 2 ] ); 245 | this._pokelib.writeUint8( this._dataAddress + 32, value[ 3 ] ); 246 | 247 | return this; 248 | 249 | } 250 | 251 | }, 252 | 253 | /** 254 | * Getter / setter for the Pokemon EVs. 255 | * 256 | * The setter version is chainable. 257 | * 258 | * @param {Array} [value] The new EVs. 259 | * @param {Number} [value.hp] The HP EV. 260 | * @param {Number} [value.attack] The attack EV. 261 | * @param {Number} [value.defense] The defense EV. 262 | * @param {Number} [value.speed] The speed EV. 263 | * @param {Number} [value.special] The special EV. 264 | */ 265 | 266 | evs : function ( value ) { 267 | 268 | if ( typeof value === 'undefined' ) { 269 | 270 | return { 271 | hp : this._pokelib.readUint16( this._dataAddress + 17 ), 272 | attack : this._pokelib.readUint16( this._dataAddress + 19 ), 273 | defense : this._pokelib.readUint16( this._dataAddress + 21 ), 274 | speed : this._pokelib.readUint16( this._dataAddress + 23 ), 275 | special : this._pokelib.readUint16( this._dataAddress + 25 ) 276 | }; 277 | 278 | } else { 279 | 280 | if ( typeof value.hp !== 'undefined' ) 281 | this._pokelib.writeUint16( this._dataAddress + 17, value.hp ); 282 | 283 | if ( typeof value.attack !== 'undefined' ) 284 | this._pokelib.writeUint16( this._dataAddress + 19, value.attack ); 285 | 286 | if ( typeof value.defense !== 'undefined' ) 287 | this._pokelib.writeUint16( this._dataAddress + 21, value.defense ); 288 | 289 | if ( typeof value.speed !== 'undefined' ) 290 | this._pokelib.writeUint16( this._dataAddress + 23, value.speed ); 291 | 292 | if ( typeof value.special !== 'undefined' ) 293 | this._pokelib.writeUint16( this._dataAddress + 25, value.special ); 294 | 295 | return this; 296 | 297 | } 298 | 299 | }, 300 | 301 | /** 302 | * Getter / setter for the Pokemon IVs. 303 | * 304 | * Remember that in Gen I, there is no HP IV. The HP IV is computed from the other IVs (the last bit of each IV is used to compute a fourth one). Pokelib offers a getter / setter on the HP IV too, but modifying it will alter the other IVs. Be careful. 305 | * 306 | * The setter version is chainable. 307 | * 308 | * @param {Array} [value] The new IVs. 309 | * @param {Number} [value.hp] The HP IV. 310 | * @param {Number} [value.attack] The attack IV. 311 | * @param {Number} [value.defense] The defense IV. 312 | * @param {Number} [value.speed] The speed IV. 313 | * @param {Number} [value.special] The special IV. 314 | */ 315 | 316 | ivs : function ( value ) { 317 | 318 | if ( typeof value === 'undefined' ) { 319 | 320 | var flag = this._pokelib.readUint16( this._dataAddress + 27 ); 321 | 322 | var ivs = { 323 | attack : ( flag >> 0 ) & 0x0F, 324 | defense : ( flag >> 4 ) & 0x0F, 325 | speed : ( flag >> 8 ) & 0x0F, 326 | special : ( flag >> 12 ) & 0x0F 327 | }; 328 | 329 | ivs.hp = 330 | ( ( ivs.attack & 1 ) << 0 ) | 331 | ( ( ivs.defense & 1 ) << 1 ) | 332 | ( ( ivs.speed & 1 ) << 2 ) | 333 | ( ( ivs.special & 1 ) << 3 ) 334 | ; 335 | 336 | return ivs; 337 | 338 | } else { 339 | 340 | var flag = this._pokelib.readUint16( this._dataAddress + 27 ); 341 | 342 | if ( typeof value.attack !== 'undefined' ) 343 | flag = ( flag & 0xFFF0 ) | ( ( value.attack & 0x0F ) << 0 ); 344 | 345 | if ( typeof value.defense !== 'undefined' ) 346 | flag = ( flag & 0xFF0F ) | ( ( value.defense & 0x0F ) << 4 ); 347 | 348 | if ( typeof value.speed !== 'undefined' ) 349 | flag = ( flag & 0xF0FF ) | ( ( value.speed & 0x0F ) << 8 ); 350 | 351 | if ( typeof value.special !== 'undefined' ) 352 | flag = ( flag & 0x0FFF ) | ( ( value.special & 0x0F ) << 12 ); 353 | 354 | if ( typeof value.hp !== 'undefined' ) { 355 | flag &= flag & 0xEEEE; 356 | flag |= ( ( value.attack >> 0 ) & 1 ) << 0; 357 | flag |= ( ( value.defense >> 1 ) & 1 ) << 4; 358 | flag |= ( ( value.speed >> 2 ) & 1 ) << 8; 359 | flag |= ( ( value.special >> 3 ) & 1 ) << 12; 360 | } 361 | 362 | return this; 363 | 364 | } 365 | 366 | }, 367 | 368 | /** 369 | * Getter / setter for the Pokemon attack. Note that when the Pokemon stats will be computed again by the game (for example during a level up), any change to this value will be overriden as well. 370 | * 371 | * The setter version is chainable. 372 | * 373 | * @param {Number} [value] The new Attack stat. 374 | */ 375 | 376 | attack : function ( value ) { 377 | 378 | if ( typeof value === 'undefined' ) { 379 | return this._pokelib.readUint16( this._dataAddress + 36 ); 380 | } else { 381 | this._pokelib.writeUint16( this._dataAddress + 36, value ); 382 | return this; 383 | } 384 | 385 | }, 386 | 387 | /** 388 | * Getter / setter for the Pokemon defense. Note that when the Pokemon stats will be computed again by the game (for example during a level up), any change to this value will be overriden as well. 389 | * 390 | * The setter version is chainable. 391 | * 392 | * @param {Number} [value] The new Defense stat. 393 | */ 394 | 395 | defense : function ( value ) { 396 | 397 | if ( typeof value === 'undefined' ) { 398 | return this._pokelib.readUint16( this._dataAddress + 38 ); 399 | } else { 400 | this._pokelib.writeUint16( this._dataAddress + 38, value ); 401 | return this; 402 | } 403 | 404 | }, 405 | 406 | /** 407 | * Getter / setter for the Pokemon speed. Note that when the Pokemon stats will be computed again by the game (for example during a level up), any change to this value will be overriden as well. 408 | * 409 | * The setter version is chainable. 410 | * 411 | * @param {Number} [value] The new Speed stat. 412 | */ 413 | 414 | speed : function ( value ) { 415 | 416 | if ( typeof value === 'undefined' ) { 417 | return this._pokelib.readUint16( this._dataAddress + 40 ); 418 | } else { 419 | this._pokelib.writeUint16( this._dataAddress + 40, value ); 420 | return this; 421 | } 422 | 423 | }, 424 | 425 | /** 426 | * Getter / setter for the Pokemon special. Note that when the Pokemon stats will be computed again by the game (for example during a level up), any change to this value will be overriden as well. 427 | * 428 | * The setter version is chainable. 429 | * 430 | * @param {Number} [value] The new Special stat. 431 | */ 432 | 433 | special : function ( value ) { 434 | 435 | if ( typeof value === 'undefined' ) { 436 | return this._pokelib.readUint16( this._dataAddress + 42 ); 437 | } else { 438 | this._pokelib.writeUint16( this._dataAddress + 42, value ); 439 | return this; 440 | } 441 | 442 | }, 443 | 444 | /** 445 | * Getter / setter for the Pokemon experience. 446 | * 447 | * Setting this property may also affect the Pokemon level (so that the two values does not conflict with each other). 448 | * 449 | * The setter version is chainable. 450 | * 451 | * @param {Number} [value] The new level. 452 | */ 453 | 454 | experience : function ( value ) { 455 | 456 | if ( typeof value === 'undefined' ) { 457 | 458 | return this._pokelib.readUint24( this._dataAddress + 14 ); 459 | 460 | } else { 461 | 462 | this._pokelib.writeUint24( this._dataAddress + 14, value ); 463 | 464 | var newLevel = this.species( ).growthRate( ).experienceToLevel( value ); 465 | if ( newLevel !== this.level( ) ) this.level( newLevel ); 466 | 467 | return this; 468 | 469 | } 470 | 471 | } 472 | 473 | } ); 474 | 475 | } ); 476 | -------------------------------------------------------------------------------- /example/assets/vendors/Virtjs-0.0.3.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/almond for details 5 | */ 6 | 7 | (function(e){var t=e();typeof window!="undefined"&&(window.Virtjs=t),typeof module!="undefined"&&(module.exports=t),typeof define=="function"&&define.amd&&define([],function(){return t})})(function(){var requirejs,require,define;return function(e){function h(e,t){return f.call(e,t)}function p(e,t){var n,r,i,s,o,a,f,l,h,p,d,v=t&&t.split("/"),m=u.map,g=m&&m["*"]||{};if(e&&e.charAt(0)===".")if(t){v=v.slice(0,v.length-1),e=e.split("/"),o=e.length-1,u.nodeIdCompat&&c.test(e[o])&&(e[o]=e[o].replace(c,"")),e=v.concat(e);for(h=0;h0&&(e.splice(h-1,2),h-=2)}}e=e.join("/")}else e.indexOf("./")===0&&(e=e.substring(2));if((v||g)&&m){n=e.split("/");for(h=n.length;h>0;h-=1){r=n.slice(0,h).join("/");if(v)for(p=v.length;p>0;p-=1){i=m[v.slice(0,p).join("/")];if(i){i=i[r];if(i){s=i,a=h;break}}}if(s)break;!f&&g&&g[r]&&(f=g[r],l=h)}!s&&f&&(s=f,a=l),s&&(n.splice(0,a,s),e=n.join("/"))}return e}function d(t,r){return function(){return n.apply(e,l.call(arguments,0).concat([t,r]))}}function v(e){return function(t){return p(t,e)}}function m(e){return function(t){s[e]=t}}function g(n){if(h(o,n)){var r=o[n];delete o[n],a[n]=!0,t.apply(e,r)}if(!h(s,n)&&!h(a,n))throw new Error("No "+n);return s[n]}function y(e){var t,n=e?e.indexOf("!"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function b(e){return function(){return u&&u.config&&u.config[e]||{}}}var t,n,r,i,s={},o={},u={},a={},f=Object.prototype.hasOwnProperty,l=[].slice,c=/\.js$/;r=function(e,t){var n,r=y(e),i=r[0];return e=r[1],i&&(i=p(i,t),n=g(i)),i?n&&n.normalize?e=n.normalize(e,v(t)):e=p(e,t):(e=p(e,t),r=y(e),i=r[0],e=r[1],i&&(n=g(i))),{f:i?i+"!"+e:e,n:e,pr:i,p:n}},i={require:function(e){return d(e)},exports:function(e){var t=s[e];return typeof t!="undefined"?t:s[e]={}},module:function(e){return{id:e,uri:"",exports:s[e],config:b(e)}}},t=function(t,n,u,f){var l,c,p,v,y,b=[],w=typeof u,E;f=f||t;if(w==="undefined"||w==="function"){n=!n.length&&u.length?["require","exports","module"]:n;for(y=0;y0&&!this._byAddress[e])e-=1;if(!this._byAddress[e])throw new Error("Instruction not found");return this._byAddress[e]},_dummifyInstruction:function(e){var t=e.address,n=this._instructions.indexOf(e);delete this._byAddress[t],this._instructions.splice(n,1);for(var r=0;r0&&!--this._breakDelay;this._skipBreakpoint&&(this._skipBreakpoint=!1);if(!t&&!n)return;e.break(),this.enableDOM()},_updateAddress:function(e){if(!this._domEnabled)return;var t=this._instructions.indexOf(this._byAddress[e]);this._invalidateRow(t)},_updateCurrent:function(e){var t=this._currentAddress;this._currentAddress=e.address;if(!this._domEnabled)return;this._updateAddress(t),this._updateAddress(this._currentAddress),this._render(),this._focusCurrent()},_focusCurrent:function(){var e=this._instructions.indexOf(this._byAddress[this._currentAddress]);this._grid.scrollRowIntoView(e)}})}),define("../../libraries/base/./devices/inputs/../../utils/./Object",[],function(){return{extend:function(){var e=arguments[0];for(var t=1,n=arguments.length;t