├── data.ods ├── stores ├── itchio.png ├── itchio.xcf ├── screenshot-1.PNG ├── screenshot-2.PNG ├── screenshot-3.PNG └── screenshot-4.PNG ├── credits.txt ├── moved └── index.html ├── static-lib └── unicodetiles │ ├── DejaVuSansMono.eot │ ├── DejaVuSansMono.ttf │ ├── DejaVuSansMono.woff │ ├── unicodetiles.css │ ├── ut.DOMRenderer.js │ ├── ut.CanvasRenderer.js │ ├── input.js │ ├── ut.WebGLRenderer.js │ ├── unicodetiles.js │ └── unicodetiles-lcd.js ├── src ├── js │ ├── Item.class.js │ ├── Distance.js │ ├── monster │ │ ├── Status.enum.js │ │ ├── Stats.enum.js │ │ ├── Types.enum.js │ │ ├── ModifyStatEffect.js │ │ ├── Effects.enum.js │ │ ├── Race.class.js │ │ ├── Effectiveness.js │ │ ├── DamageEffect.js │ │ └── Skills.enum.js │ ├── ui │ │ ├── Box.class.js │ │ └── TextBox.class.js │ ├── ca │ │ ├── CA.js │ │ ├── Rule.class.js │ │ └── Matrix.class.js │ ├── Game.js │ ├── util │ │ └── Direction.js │ ├── tests │ │ ├── TestRouteData.js │ │ ├── TestMonsterData.js │ │ └── TestSkills.js │ ├── Random.js │ ├── World.js │ ├── procgen │ │ └── TownNames.js │ ├── Scenario.js │ ├── NPCRaces.enum.js │ ├── Stat.class.js │ ├── Tiles.enum.js │ ├── ItemType.enum.js │ ├── Items.enum.js │ ├── ai │ │ └── Trainer.js │ ├── Level.class.js │ ├── Input.js │ ├── MasterPlans.js │ ├── Display.js │ ├── MetadataGenerator.js │ ├── LevelGenerator.js │ ├── Player.js │ └── Being.class.js └── html │ ├── lcd.html │ └── index.html └── README /data.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/data.ods -------------------------------------------------------------------------------- /stores/itchio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/stores/itchio.png -------------------------------------------------------------------------------- /stores/itchio.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/stores/itchio.xcf -------------------------------------------------------------------------------- /credits.txt: -------------------------------------------------------------------------------- 1 | Credits: 2 | * https://www.gamefaqs.com/gameboy/367023-pokemon-red-version/faqs/5764 3 | * Bulbapedia -------------------------------------------------------------------------------- /moved/index.html: -------------------------------------------------------------------------------- 1 | This website has moved to the MonsterTrainerRL portal -------------------------------------------------------------------------------- /stores/screenshot-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/stores/screenshot-1.PNG -------------------------------------------------------------------------------- /stores/screenshot-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/stores/screenshot-2.PNG -------------------------------------------------------------------------------- /stores/screenshot-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/stores/screenshot-3.PNG -------------------------------------------------------------------------------- /stores/screenshot-4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/stores/screenshot-4.PNG -------------------------------------------------------------------------------- /static-lib/unicodetiles/DejaVuSansMono.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/static-lib/unicodetiles/DejaVuSansMono.eot -------------------------------------------------------------------------------- /static-lib/unicodetiles/DejaVuSansMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/static-lib/unicodetiles/DejaVuSansMono.ttf -------------------------------------------------------------------------------- /static-lib/unicodetiles/DejaVuSansMono.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/monster-trainer-rl/HEAD/static-lib/unicodetiles/DejaVuSansMono.woff -------------------------------------------------------------------------------- /src/js/Item.class.js: -------------------------------------------------------------------------------- 1 | function Item(def, forSale){ 2 | this.def = def; 3 | this.forSale = forSale; 4 | } 5 | 6 | Item.prototype = { 7 | 8 | } 9 | 10 | module.exports = Item; -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | An open source monster training roguelike game 2 | 3 | A procedurally generated world where you have to catch and train your monsters to battle the 8 gym leaders and become a monster master. 4 | 5 | You can play online here: http://slashie.net/mtrl 6 | 7 | Contributions are welcome -------------------------------------------------------------------------------- /src/js/Distance.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | distance: function (x1, y1, x2, y2) { 3 | return Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); 4 | }, 5 | flatDistance: function (x1, y1, x2, y2){ 6 | var xDist = Math.abs(x1 - x2); 7 | var yDist = Math.abs(y1 - y2); 8 | if (xDist === yDist) 9 | return xDist; 10 | else 11 | return xDist + yDist; 12 | } 13 | } -------------------------------------------------------------------------------- /src/js/monster/Status.enum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | BURN: { 3 | id: 'burn' 4 | }, 5 | CONFUSION: { 6 | id: 'confusion' 7 | }, 8 | FLINCH: { 9 | id: 'flinch' 10 | }, 11 | FREEZE: { 12 | id: 'freeze' 13 | }, 14 | PARALYZE: { 15 | id: 'paralyze' 16 | }, 17 | POISON: { 18 | id: 'poison' 19 | }, 20 | RAGE: { 21 | id: 'rage' 22 | }, 23 | FOCUSED: { 24 | id: 'focused', 25 | onSetMessage: "focuses on the battle!" 26 | } 27 | } -------------------------------------------------------------------------------- /src/js/monster/Stats.enum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ATK: { 3 | id: "ATK", 4 | name: "Attack" 5 | }, 6 | DEF: { 7 | id: "DEF", 8 | name: "Defense" 9 | }, 10 | SPD: { 11 | id: "SPD", 12 | name: "Speed" 13 | }, 14 | SP_ATK: { 15 | id: "SP_ATK", 16 | name: "Special Attack" 17 | }, 18 | SP_DEF: { 19 | id: "SP_DEF", 20 | name: "Special Defense" 21 | }, 22 | ACC: { 23 | id: "ACC", 24 | name: "Accuracy" 25 | }, 26 | EVA: { 27 | id: "EVA", 28 | name: "Evasion" 29 | } 30 | } -------------------------------------------------------------------------------- /src/js/ui/Box.class.js: -------------------------------------------------------------------------------- 1 | function Box (term, height, width, position) { 2 | this.term = term; 3 | this.height = height; 4 | this.width = width; 5 | this.position = position; 6 | this.spaces = ""; 7 | for (var i = 0; i < width; i++){ 8 | this.spaces += " "; 9 | } 10 | } 11 | 12 | Box.prototype.draw = function(){ 13 | for (var i = 0; i < this.height; i++) { 14 | this.term.putString(this.spaces, this.position.x, this.position.y + i, 255, 255, 255); 15 | } 16 | }; 17 | 18 | module.exports = Box; -------------------------------------------------------------------------------- /src/js/ca/CA.js: -------------------------------------------------------------------------------- 1 | var Random = require('../Random'); 2 | var Matrix = require('./Matrix.class'); 3 | 4 | module.exports = { 5 | runCA: function(map, rules, generations, wrap){ 6 | var matrix = new Matrix(map) 7 | for (var i = 0; i < generations; i++) 8 | this.step(matrix, rules, wrap); 9 | }, 10 | step: function(matrix, rules, wrap){ 11 | for (var x = 0; x < matrix.getWidth(); x++){ 12 | for (var y = 0; y < matrix.getHeight(); y++){ 13 | for (var i = 0; i < rules.length; i++){ 14 | rules[i].apply(x,y, matrix, wrap); 15 | } 16 | } 17 | } 18 | matrix.advance(); 19 | } 20 | } -------------------------------------------------------------------------------- /src/js/Game.js: -------------------------------------------------------------------------------- 1 | var Display = require('./Display'); 2 | var World = require('./World'); 3 | var Player = require('./Player'); 4 | var Input = require('./Input'); 5 | var Scenario = require('./Scenario'); 6 | 7 | var Game = { 8 | start: function(){ 9 | this.display = Display; 10 | this.world = World; 11 | this.player = Player; 12 | this.input = Input; 13 | Display.init(this); 14 | Player.init(this); 15 | World.init(this); 16 | Input.init(this); 17 | Scenario.start(this); 18 | this.player.updateFOV(); 19 | Display.refresh(); 20 | Display.showScene("WELCOME"); 21 | }, 22 | version: "1.2" 23 | } 24 | 25 | window.Game = Game; 26 | 27 | module.exports = Game; -------------------------------------------------------------------------------- /static-lib/unicodetiles/unicodetiles.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @font-face { 4 | font-family: 'DejaVuSansMono'; 5 | src: url('DejaVuSansMono.eot'); 6 | src: url('DejaVuSansMono.eot?#iefix') format('eot'), 7 | url('DejaVuSansMono.woff') format('woff'), 8 | url('DejaVuSansMono.ttf') format('truetype'); 9 | } 10 | 11 | .unicodetiles { 12 | font-family: "DejaVuSansMono", "DejaVu Sans Mono", monospace; 13 | white-space: pre; 14 | text-align: center; 15 | line-height: 1; 16 | letter-spacing: 0px; 17 | display: inline-block; 18 | } 19 | 20 | .unicodetiles div { 21 | float: left; 22 | height: 1em; 23 | } 24 | 25 | .unicodetiles br { 26 | clear: both; 27 | } 28 | -------------------------------------------------------------------------------- /src/js/util/Direction.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | direction: function(x1, y1, x2, y2){ 3 | return {x: this.sign(x2 - x1), y: this.sign(y2 - y1)}; 4 | }, 5 | sign: function(a){ 6 | if (a > 0) 7 | return 1; 8 | else if (a < 0) 9 | return -1; 10 | else 11 | return 0; 12 | }, 13 | dxMap: { 14 | UP: { 15 | x: 0, 16 | y: -1 17 | }, 18 | DOWN: { 19 | x: 0, 20 | y: 1 21 | }, 22 | LEFT: { 23 | x: -1, 24 | y: 0 25 | }, 26 | RIGHT: { 27 | x: 1, 28 | y: 0 29 | } 30 | }, 31 | opposite: { 32 | UP: "DOWN", 33 | DOWN: "UP", 34 | LEFT: "RIGHT", 35 | RIGHT: "LEFT" 36 | }, 37 | CARDINALS: ["UP", "DOWN", "LEFT", "RIGHT"] 38 | } 39 | -------------------------------------------------------------------------------- /src/js/monster/Types.enum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NORMAL: { 3 | id: 'NORMAL' 4 | }, 5 | FIGHTING: { 6 | id: 'FIGHTING' 7 | }, 8 | FLYING: { 9 | id: 'FLYING' 10 | }, 11 | POISON: { 12 | id: 'POISON' 13 | }, 14 | GROUND: { 15 | id: 'GROUND' 16 | }, 17 | ROCK: { 18 | id: 'ROCK' 19 | }, 20 | BUG: { 21 | id: 'BUG' 22 | }, 23 | GHOST: { 24 | id: 'GHOST' 25 | }, 26 | FIRE: { 27 | id: 'FIRE' 28 | }, 29 | WATER: { 30 | id: 'WATER' 31 | }, 32 | GRASS: { 33 | id: 'GRASS' 34 | }, 35 | ELECTRIC: { 36 | id: 'ELECTRIC' 37 | }, 38 | PSYCHIC: { 39 | id: 'PSYCHIC' 40 | }, 41 | ICE: { 42 | id: 'ICE' 43 | }, 44 | DRAGON: { 45 | id: 'DRAGON' 46 | } 47 | } -------------------------------------------------------------------------------- /src/js/tests/TestRouteData.js: -------------------------------------------------------------------------------- 1 | // This test validates all races defined on the route stereotypes are correct 2 | 3 | 4 | global.ut = { 5 | Tile: function(){} 6 | } 7 | 8 | function assert(expression){ 9 | if (!expression){ 10 | throw new Error("Assertion failed"); 11 | } 12 | } 13 | 14 | var MasterPlans = require('../MasterPlans'); 15 | 16 | MasterPlans.routeStereotypes.forEach((route) => {route.monsters.forEach((def) => assert(def.race != null))}); 17 | 18 | // Verify trainer specs are ok 19 | for (var gymKey in MasterPlans.gymStereotypes){ 20 | var gym = MasterPlans.gymStereotypes[gymKey]; 21 | console.log("gymKey: "+gymKey); 22 | gym.monsters.forEach((def) => assert(def.race != null)); 23 | } -------------------------------------------------------------------------------- /src/js/tests/TestMonsterData.js: -------------------------------------------------------------------------------- 1 | global.ut = { 2 | Tile: function(){} 3 | } 4 | var Races = require('../monster/Races.enum') 5 | var Being = require('../Being.class') 6 | 7 | /* 8 | * Make sure all monsters can evolve 9 | */ 10 | var mockLevel = { 11 | game: { 12 | display: { 13 | message: function(m){ 14 | 15 | } 16 | } 17 | } 18 | } 19 | 20 | 21 | for (var r in Races){ 22 | r = Races[r]; 23 | var being = new Being(mockLevel.game, mockLevel, r, 1); 24 | for (var i = 0; i < 100; i++){ 25 | being.levelUp(); 26 | } 27 | if (being.skills.length === 0){ 28 | console.log("**** "+r.name+ " has no skills"); 29 | } else { 30 | console.log(r.name+ " ("+being.race.name+")"); 31 | console.log(""+being.skills.map((skill) => " "+skill.skill.name)); 32 | } 33 | } -------------------------------------------------------------------------------- /src/js/monster/ModifyStatEffect.js: -------------------------------------------------------------------------------- 1 | var Stats = require('./Stats.enum'); 2 | 3 | var MODIFIER_MESSAGES = { 4 | "-2": "greatly fell!", 5 | "-1": "fell!", 6 | "1": "rose!", 7 | "2": "greatly rose!", 8 | }; 9 | 10 | module.exports = { 11 | effect: function(monster, skill, sign){ 12 | var level = skill.params.level ? skill.params.level : 1; 13 | if (sign > 0){ 14 | monster.game.display.message(monster.race.name+" uses "+skill.name); 15 | target = monster; 16 | } else { 17 | var enemy = monster.getNearestEnemy(); 18 | monster.game.display.message(monster.race.name+" uses "+skill.name+" on the "+enemy.race.name+"."); 19 | target = enemy; 20 | } 21 | monster.game.display.message("The "+target.race.name+" "+Stats[skill.params.stat].name+" "+MODIFIER_MESSAGES[(level*sign)]); 22 | target.changeStat(skill.params.stat, 5, level * sign); 23 | } 24 | } -------------------------------------------------------------------------------- /src/js/Random.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | n: function(a, b){ 3 | return Math.floor(Math.random() * (b - a + 1))+a; 4 | }, 5 | chance: function(c){ 6 | return this.n(0,100) <= c; 7 | }, 8 | from: function(array, remove){ 9 | var index = this.n(0, array.length-1); 10 | var value = array[index]; 11 | if (remove){ 12 | array.splice(index, 1); 13 | } 14 | return value; 15 | }, 16 | fromObject: function(object, remove){ 17 | var keys = Object.keys(object) 18 | var key = keys[this.n(0, keys.length-1)]; 19 | var value = object[key]; 20 | if (remove){ 21 | delete object[key]; 22 | } 23 | return value; 24 | }, 25 | fromWeighted: function(array, totalWeight){ 26 | var totalWeight = 0; 27 | for (var i = 0; i < array.length; i++){ 28 | totalWeight += array[i].weight; 29 | } 30 | var pivot = this.n(0, totalWeight); 31 | var acum = 0; 32 | for (var i = 0; i < array.length; i++){ 33 | acum += array[i].weight; 34 | if (pivot <= acum){ 35 | return array[i]; 36 | } 37 | } 38 | return false; // Should never happen 39 | } 40 | } -------------------------------------------------------------------------------- /src/js/World.js: -------------------------------------------------------------------------------- 1 | var Level = require('./Level.class'); 2 | var MetadataGenerator = require('./MetadataGenerator'); 3 | var LevelGenerator = require('./LevelGenerator'); 4 | 5 | var Random = require('./Random'); 6 | 7 | module.exports = { 8 | levels: {}, 9 | metadata: {}, 10 | init: function(game){ 11 | this.game = game; 12 | this.player = game.player; 13 | this.metadata = MetadataGenerator.generateMetadata(); 14 | this.loadLevel(this.metadata._startingLevelId); 15 | }, 16 | loadLevel: function(levelId){ 17 | this.player.pullBackAll(); 18 | var oldLevel = this.game.world.level; 19 | if (this.levels[levelId]){ 20 | this.level = this.levels[levelId]; 21 | } else { 22 | this.level = new Level(this.game, levelId); 23 | LevelGenerator.generateTestLevel(this.level, this.metadata[levelId]); 24 | this.levels[levelId] = this.level; 25 | } 26 | if (!this.metadata[levelId].startPosition){ 27 | // Look for the exit from whence we came 28 | var exit = this.level.getExit(oldLevel.id); 29 | this.player.x = exit.x; 30 | this.player.y = exit.y; 31 | } else { 32 | this.metadata[levelId].startPosition = false; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/js/monster/Effects.enum.js: -------------------------------------------------------------------------------- 1 | var Random = require('../Random'); 2 | var DamageEffect = require('./DamageEffect'); 3 | var ModifyStatEffect = require('./ModifyStatEffect'); 4 | var Status = require('./Status.enum'); 5 | 6 | module.exports = { 7 | DAMAGE: function(monster, skill){ 8 | return DamageEffect.effect(monster, skill); 9 | }, 10 | LOWER_STAT: function(monster, skill){ 11 | ModifyStatEffect.effect(monster, skill, -1); 12 | }, 13 | RAISE_STAT: function(monster, skill){ 14 | ModifyStatEffect.effect(monster, skill, 1); 15 | }, 16 | NOTHING: function(monster, skill){ 17 | monster.game.display.message(monster.race.name+" uses "+skill.name); 18 | monster.game.display.message("Nothing happens"); 19 | }, 20 | SET_STATUS: function(monster, skill){ 21 | monster.game.display.message(monster.race.name+" uses "+skill.name); 22 | var status = Status[skill.params.status]; 23 | monster.inflictStatus(status, 5); 24 | monster.game.display.message(monster.race.name+" "+status.onSetMessage); 25 | 26 | }, 27 | skipTarget: function(skill){ 28 | return skill.effect === this.RAISE_STAT || skill.effect === this.NOTHING || skill.effect === this.SET_STATUS; 29 | } 30 | } -------------------------------------------------------------------------------- /src/js/monster/Race.class.js: -------------------------------------------------------------------------------- 1 | function Race(name, chara, r, g, b, aggressive, type, typeb, hp, attack, defense, spAttack, spDefense, speed, xp){ 2 | this.name = name; 3 | this.tile = new ut.Tile(chara, r, g, b); 4 | this.aggressive = aggressive; 5 | this.type = type; 6 | this.typeb = typeb; 7 | this.hp = hp; 8 | this.attack = attack; 9 | this.defense = defense; 10 | this.spAttack = spAttack; 11 | this.spDefense = spDefense; 12 | this.speed = speed; 13 | this.xp = xp; 14 | 15 | this.skills = []; 16 | } 17 | 18 | Race.prototype = { 19 | setMainEvolution: function(level, race){ 20 | this.evolution = {minLevel: level, race: race}; 21 | race.setPreevolution(level, this); 22 | }, 23 | setPreevolution: function(level, race){ 24 | // Add the skills from the preevolution 25 | var originalSkills = this.skills; 26 | this.skills = this.skills.concat(race.skills.filter((skill) => { 27 | if (originalSkills.find((originalSkill) => { return originalSkill.skill === skill.skill })) 28 | return false; 29 | return skill.level <= level; 30 | })); 31 | }, 32 | setStoneEvolution: function(itemId, fromRace){ 33 | this.parentRace = fromRace; 34 | } 35 | } 36 | 37 | module.exports = Race; -------------------------------------------------------------------------------- /src/js/tests/TestSkills.js: -------------------------------------------------------------------------------- 1 | // Test all skills can be executed against random targets 2 | global.ut = { 3 | Tile: function(){} 4 | } 5 | 6 | var Races = require('../monster/Races.enum') 7 | var Skills = require('../monster/Skills.enum') 8 | var Being = require('../Being.class') 9 | var Random = require('../Random') 10 | 11 | // Mock data 12 | var mockGame = { 13 | display: { 14 | message: function(m){ 15 | console.log(m) 16 | } 17 | }, 18 | world: mockWorld 19 | }; 20 | var mockLevel = { 21 | game: mockGame, 22 | removeBeing: function(){} 23 | } 24 | var mockWorld = { 25 | level: mockLevel 26 | } 27 | mockGame.world = mockWorld; 28 | 29 | // Tests 30 | for (var s in Skills){ 31 | s = Skills[s]; 32 | var enemy = new Being(mockGame, mockLevel, Random.fromObject(Races), Random.n(1,100)); 33 | var monster = new Being(mockGame, mockLevel, Random.fromObject(Races), Random.n(1,100)); 34 | monster.getNearestEnemy = function(){ 35 | return enemy; 36 | } 37 | console.log("Testing skill "+s.name); 38 | var effect = s.effect; 39 | if (effect === null || effect === undefined){ 40 | console.log("Skill has no effect."); 41 | //TODO: Assert false when all skill effects implemented 42 | continue; 43 | } 44 | effect(monster, s); 45 | } -------------------------------------------------------------------------------- /src/js/procgen/TownNames.js: -------------------------------------------------------------------------------- 1 | var Random = require('../Random'); 2 | 3 | var TownNames = { 4 | generateName: function(){ 5 | return Random.from(this.COLORS, true); 6 | }, 7 | COLORS: ["Alabaster", 8 | "Alizarin", 9 | "Almond", 10 | "Amaranth", 11 | "Amber", 12 | "Apricot", 13 | "Artichoke", 14 | "Arylide", 15 | "Ash", 16 | "Auburn", 17 | "Azure", 18 | "Begonia", 19 | "Beige", 20 | "Bisque", 21 | "Bistre", 22 | "Blush", 23 | "Bole", 24 | "Brass", 25 | "Brunswick", 26 | "Burgundy", 27 | "Burlywood", 28 | "Cadet", 29 | "Cadmium", 30 | "Capri", 31 | "Carmine", 32 | "Carnation", 33 | "Carnelian", 34 | "Catawba", 35 | "Celeste", 36 | "Cerise", 37 | "Cerulean", 38 | "Chamoisee", 39 | "Chartreuse", 40 | "Cinereous", 41 | "Cinnabar", 42 | "Cinnamon", 43 | "Citrine", 44 | "Citron", 45 | "Claret", 46 | "Cobalt", 47 | "Coquelicot", 48 | "Coral", 49 | "Cordovan", 50 | "Crimson", 51 | "Cyclamen", 52 | "Daffodil", 53 | "Dandelion", 54 | "Drab", 55 | "Ebony", 56 | "Lavender", 57 | "Lilac", 58 | "Maroon", 59 | "Peach", 60 | "Scarlet", 61 | "Sienna", 62 | "Sienna", 63 | "Taupe", 64 | "Ube", 65 | "Umber" 66 | ] 67 | } 68 | 69 | 70 | 71 | module.exports = TownNames; 72 | -------------------------------------------------------------------------------- /src/js/Scenario.js: -------------------------------------------------------------------------------- 1 | var Display = require('./Display'); 2 | var Races = require('./monster/Races.enum'); 3 | var Being = require('./Being.class'); 4 | var Item = require('./Item.class'); 5 | var Items = require('./Items.enum'); 6 | var Stat = require('./Stat.class'); 7 | 8 | module.exports = { 9 | start: function(game){ 10 | game.player.hp = new Stat(20); 11 | game.player.money = 1000; 12 | /*var charmander = new Being(game, false, Races.CHARMANDER, 16); 13 | game.player.addMonster(charmander); 14 | charmander = new Being(game, false, Races.RATTATA, 2); 15 | game.player.addMonster(charmander); 16 | charmander = new Being(game, false, Races.RATTATA, 3); 17 | game.player.addMonster(charmander); 18 | charmander = new Being(game, false, Races.PIDGEY, 4); 19 | game.player.addMonster(charmander); 20 | charmander = new Being(game, false, Races.CHARMANDER, 2); 21 | game.player.addMonster(charmander);*/ 22 | 23 | game.player.addItem(new Item(Items.CATCHBALL)); 24 | game.player.addItem(new Item(Items.CATCHBALL)); 25 | game.player.addItem(new Item(Items.GREATBALL)); 26 | game.player.addItem(new Item(Items.GREATBALL)); 27 | game.player.addItem(new Item(Items.POTION)); 28 | game.player.addItem(new Item(Items.SUPER_POTION)); 29 | game.player.addItem(new Item(Items.POTION)); 30 | game.player.addItem(new Item(Items.SUPER_POTION)); 31 | } 32 | } -------------------------------------------------------------------------------- /src/js/NPCRaces.enum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | STORE_CLERK: { 3 | name: "Store Clerk", 4 | tile: new ut.Tile('@', 0, 255, 0), 5 | hp: 20, 6 | speed: 50, 7 | interact: function(player){ 8 | player.game.display.message("Welcome! please grab the items you would be interested on!"); 9 | } 10 | }, 11 | NURSE: { 12 | name: "Nurse", 13 | tile: new ut.Tile('@', 255, 85, 255), 14 | hp: 20, 15 | speed: 50, 16 | interact: function(player){ 17 | player.game.display.message("Would you like me to heal your monsters back to perfect health? [Y/N]"); 18 | player.game.input.promptFunction = function(confirm){ 19 | if (confirm){ 20 | player.game.display.message("Ok!"); 21 | player.healAll(); 22 | } else { 23 | player.game.display.message("Ok."); 24 | } 25 | player.endTurn(); 26 | player.game.input.mode = "MOVEMENT"; 27 | } 28 | player.game.input.mode = "PROMPT"; 29 | } 30 | }, 31 | PROFFESSOR: { 32 | name: "Proffessor", 33 | tile: new ut.Tile('@', 0, 255, 255), 34 | hp: 20, 35 | speed: 50, 36 | trainer: true, 37 | interact: function(player){ 38 | if (player.pickedStarter){ 39 | player.game.display.message("Good luck in your quest!"); 40 | } else { 41 | player.game.display.message("Pick one of the starter monsters."); 42 | } 43 | } 44 | }, 45 | GYM_LEADER: { 46 | name: "Gym Leader", 47 | tile: new ut.Tile('@', 255, 255, 0), 48 | hp: 20, 49 | speed: 50, 50 | trainer: true 51 | } 52 | } -------------------------------------------------------------------------------- /src/js/ca/Rule.class.js: -------------------------------------------------------------------------------- 1 | var Random = require('../Random'); 2 | 3 | /** 4 | * Stores a rule of the form: 5 | * if {baseCell} has {condType} {cellQuant} {cellParam} around, turn into {destinationCell} 6 | * 7 | * Example 8 | * when 1 has morethan 4 2 around turn into 0 9 | */ 10 | 11 | function Rule(baseCell, condType, cellQuant, cellParam, destinationCell, chance){ 12 | this.baseCell = baseCell; 13 | this.condType = condType; 14 | this.cellQuant = cellQuant; 15 | this.cellParam = cellParam; 16 | this.destinationCell = destinationCell; 17 | this.chance = chance ? chance : 100; 18 | } 19 | 20 | Rule.prototype = { 21 | apply: function (x, y, m, wrap){ 22 | if (m.get(x,y) == this.baseCell){ 23 | var surroundingCount = 0; 24 | if (wrap) 25 | surroundingCount = m.getSurroundingCount(x,y,this.cellParam); 26 | else 27 | surroundingCount = m.getSurroundingCountNoWrap(x,y,this.cellParam); 28 | 29 | switch (this.condType){ 30 | case Rule.HAS: 31 | if (surroundingCount === this.cellQuant && Random.chance(this.chance)){ 32 | m.setFuture(this.destinationCell, x, y); 33 | } 34 | break; 35 | case Rule.MORE_THAN: 36 | if (surroundingCount > this.cellQuant && Random.chance(this.chance)){ 37 | m.setFuture(this.destinationCell, x, y); 38 | } 39 | break; 40 | case Rule.LESS_THAN: 41 | if (surroundingCount < this.cellQuant && Random.chance(this.chance)){ 42 | m.setFuture(this.destinationCell, x, y); 43 | } 44 | break; 45 | } 46 | 47 | } 48 | } 49 | } 50 | 51 | Rule.HAS = 0; 52 | Rule.MORE_THAN = 1; 53 | Rule.LESS_THAN = 2, 54 | 55 | module.exports = Rule; -------------------------------------------------------------------------------- /src/js/Stat.class.js: -------------------------------------------------------------------------------- 1 | function Stat(max){ 2 | this.max = max; 3 | this.current = max; 4 | } 5 | 6 | module.exports = Stat; 7 | 8 | Stat.prototype.replenish = function(){ 9 | this.current = this.max; 10 | }; 11 | 12 | Stat.prototype.regenerate = function(){ 13 | if (this.current < this.max) 14 | this.current++; 15 | }; 16 | 17 | Stat.prototype.increase = function(value){ 18 | if (this.current < this.max) 19 | this.current += value; 20 | if (this.current > this.max) 21 | this.current = this.max; 22 | }; 23 | 24 | Stat.prototype.increaseOpenly = function(value){ 25 | this.current += value; 26 | }; 27 | 28 | Stat.prototype.recoverProportion = function(proportion){ 29 | var missing = this.max - this.current; 30 | this.increase(Math.round(missing*proportion)); 31 | }; 32 | 33 | Stat.prototype.recoverProportionOfTotal = function(proportion){ 34 | this.increase(Math.round(this.max*proportion)); 35 | }; 36 | 37 | Stat.prototype.reduce = function(value){ 38 | if (this.current > 0) 39 | this.current-= value; 40 | if (this.current < 0) 41 | this.current = 0; 42 | }; 43 | 44 | Stat.prototype.extend = function(value){ 45 | this.max += value; 46 | this.current += value; 47 | }; 48 | 49 | Stat.prototype.multiply = function(value){ 50 | this.max *= value; 51 | this.current *= value; 52 | }; 53 | 54 | Stat.prototype.contract = function(value){ 55 | this.max -= value; 56 | if (this.max < 0) 57 | this.max = 0; 58 | if (this.current > this.max) 59 | this.current = this.max; 60 | }; 61 | 62 | Stat.prototype.getText = function(){ 63 | return this.current + "/" + this.max; 64 | }; 65 | 66 | Stat.prototype.getProportion = function(){ 67 | return this.current / this.max; 68 | }; 69 | 70 | Stat.prototype.notFull = function(value){ 71 | return this.current !== this.max; 72 | }; 73 | 74 | Stat.prototype.empty = function(){ 75 | return this.current <= 0; 76 | }; 77 | 78 | Stat.prototype.getRemaining = function(){ 79 | return this.max - this.current; 80 | }; -------------------------------------------------------------------------------- /src/js/Tiles.enum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | FENCE: { 3 | tile: new ut.Tile('+', 255, 255, 85), 4 | darkTile: new ut.Tile('+', 128, 128, 128), 5 | solid: true, 6 | opaque: false 7 | }, 8 | GRASS: { 9 | tile: new ut.Tile(',', 0, 128, 0), 10 | darkTile: new ut.Tile(',', 128, 128, 128), 11 | solid: false, 12 | opaque: false 13 | }, 14 | STAIRS_DOWN: { 15 | tile: new ut.Tile('>', 255, 255, 255), 16 | darkTile: new ut.Tile('>', 128, 128, 128), 17 | solid: false, 18 | opaque: false 19 | }, 20 | STAIRS_UP: { 21 | tile: new ut.Tile('<', 255, 255, 255), 22 | darkTile: new ut.Tile('<', 128, 128, 128), 23 | solid: false, 24 | opaque: false 25 | }, 26 | BUSH: { 27 | tile: new ut.Tile('&', 0, 128, 0), 28 | darkTile: new ut.Tile('&', 128, 128, 128), 29 | solid: false, 30 | opaque: true 31 | }, 32 | WATER: { 33 | tile: new ut.Tile('~', 0, 0, 255), 34 | darkTile: new ut.Tile('~', 128, 128, 128), 35 | solid: true, 36 | opaque: false 37 | }, 38 | WALL: { 39 | tile: new ut.Tile('#', 128, 128, 128), 40 | darkTile: new ut.Tile('#', 128, 128, 128), 41 | solid: true, 42 | opaque: true 43 | }, 44 | FLOOR: { 45 | tile: new ut.Tile('.', 0, 0, 255), 46 | darkTile: new ut.Tile('.', 128, 128, 128), 47 | solid: false, 48 | opaque: false 49 | }, 50 | ROAD: { 51 | tile: new ut.Tile('.', 128, 128, 128), 52 | darkTile: new ut.Tile('.', 128, 128, 128), 53 | solid: false, 54 | opaque: false 55 | }, 56 | TALL_GRASS: { 57 | tile: new ut.Tile('|', 0, 255, 128), 58 | darkTile: new ut.Tile('|', 128, 128, 128), 59 | semiopaque: true 60 | }, 61 | H_COUNTER: { 62 | tile: new ut.Tile('=', 255, 255, 255), 63 | darkTile: new ut.Tile('=', 128, 128, 128), 64 | isCounter: true, 65 | solid: true 66 | }, 67 | V_COUNTER: { 68 | tile: new ut.Tile('|', 255, 255, 255), 69 | darkTile: new ut.Tile('|', 128, 128, 128), 70 | isCounter: true, 71 | solid: true 72 | }, 73 | GYM_ENTRANCE: { 74 | tile: new ut.Tile('/', 255, 255, 255), 75 | darkTile: new ut.Tile('/', 128, 128, 128) 76 | } 77 | } -------------------------------------------------------------------------------- /src/js/ItemType.enum.js: -------------------------------------------------------------------------------- 1 | var Random = require('./Random'); 2 | 3 | module.exports = { 4 | LOADED_CATCHBALL: { 5 | name: 'Loaded Catchball', 6 | pickupFunction: function(game, item){ 7 | game.player.getMonster(item.def.race); 8 | } 9 | }, 10 | CATCHBALL: { 11 | name: 'Catchball', 12 | useFunction: function(game, item, dx, dy){ 13 | if (game.player.getAvailableSlotNumber() === false){ 14 | game.display.message("You don't have any monster slots available"); 15 | return; 16 | } 17 | game.display.message("You throw the "+item.def.name+"!"); 18 | game.player.removeItem(item); 19 | for (var i = 1; i < 5; i++){ 20 | var monster = game.world.level.getBeing(game.player.x+i*dx, game.player.y+i*dy); 21 | if (monster){ 22 | if (monster.slotNumber !== undefined){ 23 | game.display.message(monster.race.name+" is already in your team."); 24 | } else { 25 | var catchChance = 100 - monster.hp.getProportion() * 100 + 5 + (item.def.catchBoost ? item.def.catchBoost : 0); 26 | if (Random.chance(catchChance)){ 27 | game.display.message("You capture the "+monster.race.name+"!"); 28 | game.player.addMonster(monster); 29 | game.world.level.removeBeing(monster); 30 | } else { 31 | game.display.message("The "+monster.race.name+" breaks free!"); 32 | } 33 | } 34 | game.player.endTurn(); 35 | return; 36 | } 37 | if (!game.world.level.canWalkTo(game.player.x+i*dx, game.player.y+i*dy)){ 38 | break; 39 | } 40 | } 41 | i--; 42 | game.world.level.addItem(item, game.player.x+i*dx, game.player.y+i*dy); 43 | game.player.endTurn(); 44 | }, 45 | targetted: true 46 | }, 47 | POTION: { 48 | name: 'Potion', 49 | useFunction: function(game, item, dx, dy){ 50 | var monster = game.world.level.getBeing(game.player.x+dx, game.player.y+dy); 51 | if (monster){ 52 | game.display.message("The "+monster.race.name+" recovers "+item.def.points+" HP."); 53 | game.player.removeItem(item); 54 | monster.recoverHP(item.def.points); 55 | game.player.endTurn(); 56 | } else { 57 | game.display.message("There's no monster there"); 58 | } 59 | }, 60 | targetted: true 61 | }, 62 | BADGE: { 63 | name: "Badge", 64 | useFunction: function(game, item){ 65 | game.display.message("You proudly show off the "+item.def.name+"."); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/js/ui/TextBox.class.js: -------------------------------------------------------------------------------- 1 | /** Represents a text box which can be updated to 2 | * show a new text along with the old one until 3 | * it is full, in which case it shows a [More] 4 | * prompt, waits for a player keypress, erases it 5 | * selfs and continues exhibiting the same behaviour. 6 | * @author Santiago Zapata 7 | * @author Eben Howard 8 | */ 9 | 10 | function TextBox (term, height, width, position, display) { 11 | this.display = display; 12 | this.term = term; 13 | this.curx = 0; 14 | this.cury = 0; 15 | this.height = height; 16 | this.width = width; 17 | 18 | this.lines = new Array(); 19 | this.position = position; 20 | for (var i = 0; i < height; i++){ 21 | this.lines[i] = ""; 22 | } 23 | this.spaces = ""; 24 | for (var i = 0; i < width; i++){ 25 | this.spaces += " "; 26 | } 27 | this.lastUpdateMillis = 0; 28 | } 29 | 30 | TextBox.prototype.draw = function(){ 31 | for (var i = 0; i < this.lines.length; i++) { 32 | this.term.putString(this.lines[i], this.position.x, this.position.y + i, 255, 255, 255); 33 | } 34 | }; 35 | 36 | TextBox.prototype.overgrown = function(){ 37 | return this.lines.length > this.height; 38 | } 39 | 40 | TextBox.prototype.checkFaint = function(){ 41 | var currentTime = new Date().getTime(); 42 | if (this.overgrown() && currentTime - this.lastUpdateMillis > 200){ 43 | this.clear(); 44 | this.display.refresh(); 45 | } 46 | } 47 | 48 | TextBox.prototype.addText = function(text){ 49 | var currentTime = new Date().getTime(); 50 | if (this.cury >= this.height - 1 && currentTime - this.lastUpdateMillis > 200){ 51 | this.clear(); 52 | this.display.refresh(); 53 | } 54 | this.lastUpdateMillis = currentTime; 55 | 56 | var tokens = text.split(" "); 57 | for (var i = 0; i < tokens.length; i++) { 58 | var distance = this.width - this.curx; 59 | if (distance < tokens[i].length + 1) { 60 | this.curx = 0; 61 | this.cury++; 62 | } 63 | if (!this.lines[this.cury]){ 64 | this.lines[this.cury] = []; 65 | } 66 | this.lines[this.cury] += tokens[i] + " "; 67 | this.curx += tokens[i].length + 1; 68 | } 69 | }; 70 | 71 | TextBox.prototype.setText = function(text) { 72 | this.clear(); 73 | this.addText(text); 74 | this.draw(); 75 | this.term.render(); 76 | }; 77 | 78 | TextBox.prototype.clear = function() { 79 | for (var i = 0; i < this.lines.length; i++) { 80 | this.lines[i] = ""; 81 | this.term.putString(this.spaces, this.position.x, this.position.y + i, 255, 255, 255); 82 | } 83 | this.lines.length = this.height; 84 | this.curx = 0; 85 | this.cury = 0; 86 | }; 87 | 88 | module.exports = TextBox; -------------------------------------------------------------------------------- /src/js/monster/Effectiveness.js: -------------------------------------------------------------------------------- 1 | module.exports = function(skill, monster, target){ 2 | if (modifiers[skill.type.id]){ 3 | var modifier = 1; 4 | if (modifiers[skill.type.id][target.race.type.id] !== undefined){ 5 | if (modifiers[skill.type.id][target.race.type.id] === 0){ 6 | return 0; 7 | } 8 | modifier *= modifiers[skill.type.id][target.race.type.id]; 9 | } 10 | if (target.race.typeb && modifiers[skill.type.id][target.race.typeb.id] !== undefined){ 11 | if (modifiers[skill.type.id][target.race.typeb.id] === 0){ 12 | return 0; 13 | } 14 | modifier *= modifiers[skill.type.id][target.race.typeb.id]; 15 | } 16 | return modifier; 17 | } else { 18 | return 1; 19 | } 20 | } 21 | 22 | var modifiers = { 23 | NORMAL: { 24 | ROCK: 0.5, 25 | GHOST: 0 26 | }, 27 | FIGHTING: { 28 | NORMAL: 2, 29 | FLYING: 0.5, 30 | POISON: 0.5, 31 | ROCK: 2, 32 | BUG: 0.5, 33 | GHOST: 0, 34 | PSYCHIC: 0.5, 35 | ICE: 2 36 | }, 37 | FLYING: { 38 | FIGHTING: 2, 39 | ROCK: 0.5, 40 | BUG: 2, 41 | GRASS: 2, 42 | ELECTRIC: 0.5 43 | }, 44 | POISON: { 45 | POISON: 0.5, 46 | GROUND: 0.5, 47 | ROCK: 0.5, 48 | BUG: 2, 49 | GHOST: 0.5, 50 | GRASS: 2 51 | }, 52 | GROUND: { 53 | FLYING: 0, 54 | POISON: 2, 55 | ROCK: 2, 56 | BUG: 0.5, 57 | FIRE: 2, 58 | GRASS: 0.5, 59 | ELECTRIC: 2 60 | }, 61 | ROCK: { 62 | FIGHTING: 0.5, 63 | FLYING: 2, 64 | GROUND: 0.5, 65 | BUG: 2, 66 | FIRE: 2, 67 | ICE: 2 68 | }, 69 | BUG: { 70 | FIGHTING: 0.5, 71 | FLYING: 0.5, 72 | POISON: 2, 73 | GHOST: 0.5, 74 | FIRE: 0.5, 75 | GRASS: 2, 76 | PSYCHIC: 2 77 | }, 78 | GHOST: { 79 | NORMAL: 0, 80 | GHOST: 2, 81 | PSYCHIC: 0 82 | }, 83 | FIRE: { 84 | ROCK: 0.5, 85 | BUG: 2, 86 | FIRE: 0.5, 87 | WATER: 0.5, 88 | GRASS: 2, 89 | ICE: 2, 90 | DRAGON: 0.5 91 | }, 92 | WATER: { 93 | GROUND: 2, 94 | ROCK: 2, 95 | FIRE: 2, 96 | WATER: 0.5, 97 | GRASS: 0.5, 98 | DRAGON: 0.5 99 | }, 100 | GRASS: { 101 | FLYING: 0.5, 102 | POISON: 0.5, 103 | GROUND: 2, 104 | ROCK: 2, 105 | BUG: 0.5, 106 | FIRE: 0.5, 107 | WATER: 2, 108 | GRASS: 0.5, 109 | DRAGON: 0.5 110 | }, 111 | ELECTRIC: { 112 | FLYING: 2, 113 | GROUND: 0, 114 | WATER: 2, 115 | GRASS: 0.5, 116 | ELECTRIC: 0.5, 117 | DRAGON: 0.5 118 | }, 119 | PSYCHIC: { 120 | FIGHTING: 2, 121 | POISON: 2, 122 | PSYCHIC: 0.5, 123 | }, 124 | ICE: { 125 | FLYING: 2, 126 | GROUND: 2, 127 | WATER: 0.5, 128 | GRASS: 2, 129 | ICE: 0.5, 130 | DRAGON: 2 131 | }, 132 | DRAGON: { 133 | DRAGON: 2 134 | } 135 | } -------------------------------------------------------------------------------- /static-lib/unicodetiles/ut.DOMRenderer.js: -------------------------------------------------------------------------------- 1 | /*global ut */ 2 | 3 | /// Class: DOMRenderer 4 | /// Renders the into DOM elements. 5 | /// 6 | /// *Note:* This is an internal class used by 7 | ut.DOMRenderer = function(view) { 8 | "use strict"; 9 | this.view = view; 10 | 11 | // Create a matrix of elements, cache references 12 | this.spans = new Array(view.h); 13 | this.colors = new Array(view.h); 14 | for (var j = 0; j < view.h; ++j) { 15 | this.spans[j] = new Array(view.w); 16 | this.colors[j] = new Array(view.w); 17 | for (var i = 0; i < view.w; ++i) { 18 | this.spans[j][i] = document.createElement("div"); 19 | view.elem.appendChild(this.spans[j][i]); 20 | } 21 | // Line break 22 | this.spans[j].push(document.createElement("br")); 23 | view.elem.appendChild(this.spans[j][view.w]); 24 | } 25 | ut.viewportStyleUpdaterHack = this; 26 | setTimeout(function() { ut.viewportStyleUpdaterHack.updateStyle(); }, 0); 27 | }; 28 | 29 | ut.DOMRenderer.prototype.updateStyle = function(s) { 30 | "use strict"; 31 | s = window.getComputedStyle(this.spans[0][0], null); 32 | this.tw = parseInt(s.width, 10); 33 | if (this.tw === 0 || isNaN(this.tw)) return; // Nothing to do, exit 34 | this.th = parseInt(s.height, 10); 35 | if (this.view.squarify) this.tw = this.th; 36 | var w = this.view.w, h = this.view.h; 37 | for (var j = 0; j < h; ++j) { 38 | for (var i = 0; i < w; ++i) { 39 | this.spans[j][i].style.width = this.tw + "px"; 40 | } 41 | } 42 | }; 43 | 44 | ut.DOMRenderer.prototype.clear = function() { 45 | "use strict"; 46 | for (var j = 0; j < this.view.h; ++j) { 47 | for (var i = 0; i < this.view.w; ++i) { 48 | this.colors[j][i] = ""; 49 | } 50 | } 51 | }; 52 | 53 | ut.DOMRenderer.prototype.render = function() { 54 | "use strict"; 55 | var w = this.view.w, h = this.view.h; 56 | var buffer = this.view.buffer; 57 | var defaultColor = this.view.defaultColor; 58 | var defaultBackground = this.view.defaultBackground; 59 | for (var j = 0; j < h; ++j) { 60 | for (var i = 0; i < w; ++i) { 61 | var tile = buffer[j][i]; 62 | var span = this.spans[j][i]; 63 | // Check and update colors 64 | var fg = tile.r === undefined ? defaultColor : tile.getColorRGB(); 65 | var bg = tile.br === undefined ? defaultBackground : tile.getBackgroundRGB(); 66 | var colorHash = fg + bg; 67 | if (colorHash !== this.colors[j][i]) { 68 | this.colors[j][i] = colorHash; 69 | span.style.color = fg; 70 | span.style.backgroundColor = bg; 71 | } 72 | // Check and update character 73 | var ch = tile.getChar(); 74 | if (ch !== span.innerHTML) 75 | span.innerHTML = ch; 76 | } 77 | } 78 | }; 79 | 80 | ut.viewportStyleUpdaterHack = null; 81 | -------------------------------------------------------------------------------- /src/js/Items.enum.js: -------------------------------------------------------------------------------- 1 | var ItemType = require('./ItemType.enum'); 2 | var Races = require('./monster/Races.enum'); 3 | 4 | module.exports = { 5 | CATCHBALL: { 6 | type: ItemType.CATCHBALL, 7 | name: 'Catchball', 8 | tile: new ut.Tile('*', 255, 0, 0), 9 | cost: 100 10 | }, 11 | GREATBALL: { 12 | type: ItemType.CATCHBALL, 13 | name: 'Great Ball', 14 | tile: new ut.Tile('*', 0, 255, 0), 15 | catchBoost: 10, 16 | cost: 200 17 | }, 18 | ULTRABALL: { 19 | type: ItemType.CATCHBALL, 20 | name: 'Ultra Ball', 21 | tile: new ut.Tile('*', 0, 255, 0), 22 | catchBoost: 20, 23 | cost: 500 24 | }, 25 | POTION: { 26 | type: ItemType.POTION, 27 | name: 'Potion', 28 | tile: new ut.Tile('!', 255, 255, 0), 29 | points: 50, 30 | cost: 50 31 | }, 32 | SUPER_POTION: { 33 | type: ItemType.POTION, 34 | name: 'Super Potion', 35 | tile: new ut.Tile('!', 0, 255, 255), 36 | points: 150, 37 | cost: 100 38 | }, 39 | HYPER_POTION: { 40 | type: ItemType.POTION, 41 | name: 'Hyper Potion', 42 | tile: new ut.Tile('!', 0, 255, 255), 43 | points: 400, 44 | cost: 100 45 | }, 46 | MAX_POTION: { 47 | type: ItemType.POTION, 48 | name: 'Max Potion', 49 | tile: new ut.Tile('!', 0, 255, 255), 50 | points: 1000, 51 | cost: 100 52 | }, 53 | 54 | BOULDER_BADGE: { 55 | type: ItemType.BADGE, 56 | name: 'Boulder Badge', 57 | tile: new ut.Tile('O', 255, 255, 255) 58 | }, 59 | CASCADE_BADGE: { 60 | type: ItemType.BADGE, 61 | name: 'Cascade Badge', 62 | tile: new ut.Tile('O', 255, 255, 255) 63 | }, 64 | THUNDER_BADGE: { 65 | type: ItemType.BADGE, 66 | name: 'Thunder Badge', 67 | tile: new ut.Tile('O', 255, 255, 255) 68 | }, 69 | RAINBOW_BADGE: { 70 | type: ItemType.BADGE, 71 | name: 'Rainbow Badge', 72 | tile: new ut.Tile('O', 255, 255, 255) 73 | }, 74 | MARSH_BADGE: { 75 | type: ItemType.BADGE, 76 | name: 'Marsh Badge', 77 | tile: new ut.Tile('O', 255, 255, 255) 78 | }, 79 | 80 | CHARMANDER_CATCHBALL: { 81 | type: ItemType.LOADED_CATCHBALL, 82 | name: 'Charmander', 83 | tile: new ut.Tile('*', 255, 0, 0), 84 | race: Races.CHARMANDER 85 | }, 86 | BULBASAUR_CATCHBALL: { 87 | type: ItemType.LOADED_CATCHBALL, 88 | name: 'Bulbasaur', 89 | tile: new ut.Tile('*', 0, 128, 0), 90 | race: Races.BULBASAUR 91 | }, 92 | SQUIRTLE_CATCHBALL: { 93 | type: ItemType.LOADED_CATCHBALL, 94 | name: 'Squirtle', 95 | tile: new ut.Tile('*', 83, 255, 255), 96 | race: Races.SQUIRTLE 97 | }, 98 | PIKACHU_CATCHBALL: { 99 | type: ItemType.LOADED_CATCHBALL, 100 | name: 'Pikachu', 101 | tile: new ut.Tile('*', 255, 255, 83), 102 | race: Races.PIKACHU 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /src/js/ai/Trainer.js: -------------------------------------------------------------------------------- 1 | var Random = require('../Random'); 2 | 3 | module.exports = { 4 | act: function(being){ 5 | this.being = being; 6 | if (being.monsters.length == 0){ 7 | // Defeated! 8 | if (!being.gavePrize){ 9 | being.givePrize(); 10 | } 11 | // Stand by 12 | return; 13 | } 14 | if (!being.game.player.gymTown){ 15 | // Player gave up 16 | if (being.monsterDeployed){ 17 | being.pullBackMonster(); 18 | } 19 | being.healAll(); 20 | return; 21 | } 22 | if (being.monsterDeployed){ 23 | if (this.pullBackMonster()){ 24 | being.pullBackMonster(); 25 | } else if (this.useItem()){ 26 | being.useItem(this.selectItem()); 27 | } else if (this.issuePositionalOrder()){ 28 | being.issueOrder(this.selectPositionalOrder()); 29 | } else if (this.issueSkillOrder()){ 30 | being.issueOrder(this.selectSkillOrder()); 31 | } else { 32 | //Stand by 33 | } 34 | } else { 35 | if (this.releaseMonster()){ 36 | being.releaseMonster(this.selectMonster()); 37 | } else { 38 | // Stand by 39 | } 40 | } 41 | }, 42 | pullBackMonster: function(){ 43 | // Define if should call back the current monster 44 | var hasMoreMonster = this.being.monsters.length > 1; 45 | var tooInjured = this.being.monsterDeployed.hp.getProportion() <= 0.3 && Random.chance(60); 46 | var whatever = Random.chance(1); 47 | return hasMoreMonster && (tooInjured || whatever); 48 | }, 49 | useItem: function(){ 50 | // Define if should use an item 51 | return false; 52 | }, 53 | selectItem: function(){ 54 | // Select an item to use from inventory 55 | return false; 56 | }, 57 | issuePositionalOrder: function(){ 58 | // Define if should issue a positional order 59 | return false; 60 | }, 61 | selectPositionalOrder: function(){ 62 | // Select what positional order to give 63 | return false; 64 | }, 65 | issueSkillOrder: function(){ 66 | // Define if should issue a skill order 67 | return false; 68 | }, 69 | selectSkillOrder: function(){ 70 | // Select what skill order to give 71 | return false; 72 | }, 73 | releaseMonster: function(){ 74 | // Define is should release a monster 75 | return this.being.monsters.length > 0 && Random.chance(30); 76 | }, 77 | selectMonster: function(){ 78 | // Define what monster to release 79 | if (this.being.enemyMonster){ 80 | // Evaluate all available monsters to see which one is the best 81 | var evals = this.being.monsters.map(function(monster){ 82 | return this.evaluate(monster); 83 | }, this); 84 | evals.sort(); 85 | return evals[evals.length - 1]; 86 | } else { 87 | // Select a random monster from available 88 | return Random.from(this.being.monsters); 89 | } 90 | }, 91 | evaluate: function(monster){ 92 | if (this.being.monsterDeployed.hp.getProportion() <= 0.3) 93 | return 1; 94 | return 5; 95 | } 96 | } -------------------------------------------------------------------------------- /src/html/lcd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Monster Trainer RL 1.3 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 |

A production of Slashie, powered by unicodetiles.js

36 |
37 |
38 |

Switch to Terminal mode (Resets game)

39 |

How to play

40 | 41 |

Your goal is to become a Monster master by defeating the gym leaders of the 8 cities in your region.

42 |

Pick your starter monster (using ,). Then find the exit to route 1 from your hometown.

43 |

When you find wild monster, use "r" to release your selected monster in a given direction. He will automatically fight nearby 44 | enemies with his default skill

45 |

If you want to use a different skill, press the key as indicated on the right bar

46 |

To call back your monster, use "P"

47 |

To use items go to the "I"nventory and select a direction

48 | 49 |

Keyboard Commands

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
Arrow keysMove around.
QWE
ASD
ZXC
Numpad
IShow Inventory.
,Pick up items.
1 to 6Select monster slot.
RRelease selected monster.
PPull back selected monster.
V,B,N,MUse monster skills.
79 |
80 | 81 | -------------------------------------------------------------------------------- /static-lib/unicodetiles/ut.CanvasRenderer.js: -------------------------------------------------------------------------------- 1 | /*global ut */ 2 | 3 | /// Class: CanvasRenderer 4 | /// Renders the into an HTML5 element. 5 | /// 6 | /// *Note:* This is an internal class used by 7 | ut.CanvasRenderer = function(view) { 8 | "use strict"; 9 | this.view = view; 10 | this.canvas = document.createElement("canvas"); 11 | if (!this.canvas.getContext) throw("Canvas not supported"); 12 | this.ctx2 = this.canvas.getContext("2d"); 13 | if (!this.ctx2 || !this.ctx2.fillText) throw("Canvas not supported"); 14 | view.elem.appendChild(this.canvas); 15 | 16 | // Create an offscreen canvas for rendering 17 | this.offscreen = document.createElement("canvas"); 18 | this.ctx = this.offscreen.getContext("2d"); 19 | this.updateStyle(); 20 | this.canvas.width = (view.squarify ? this.th : this.tw) * view.w; 21 | this.canvas.height = this.th * view.h; 22 | this.offscreen.width = this.canvas.width; 23 | this.offscreen.height = this.canvas.height; 24 | // Doing this again since setting canvas w/h resets the state 25 | this.updateStyle(); 26 | }; 27 | 28 | ut.CanvasRenderer.prototype.updateStyle = function(s) { 29 | "use strict"; 30 | s = s || window.getComputedStyle(this.view.elem, null); 31 | this.ctx.font = s.fontSize + "/" + s.lineHeight + " " + s.fontFamily; 32 | this.ctx.textBaseline = "middle"; 33 | this.tw = this.ctx.measureText("M").width; 34 | this.th = parseInt(s.fontSize, 10); 35 | this.gap = this.view.squarify ? (this.th - this.tw) : 0; 36 | if (this.view.squarify) this.tw = this.th; 37 | }; 38 | 39 | ut.CanvasRenderer.prototype.clear = function() { /* No op */ }; 40 | 41 | ut.CanvasRenderer.prototype.render = function() { 42 | "use strict"; 43 | var tile, ch, fg, bg, x, y; 44 | var view = this.view, buffer = this.view.buffer; 45 | var w = view.w, h = view.h; 46 | var hth = 0.5 * this.th; 47 | var hgap = 0.5 * this.gap; // Squarification 48 | // Clearing with one big rect is much faster than with individual char rects 49 | this.ctx.fillStyle = view.defaultBackground; 50 | this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); 51 | y = hth; // half because textBaseline is middle 52 | for (var j = 0; j < h; ++j) { 53 | x = 0; 54 | for (var i = 0; i < w; ++i) { 55 | tile = buffer[j][i]; 56 | ch = tile.ch; 57 | fg = tile.getColorRGB(); 58 | bg = tile.getBackgroundRGB(); 59 | // Only render background if the color is non-default 60 | if (bg.length && bg !== view.defaultBackground) { 61 | this.ctx.fillStyle = bg; 62 | this.ctx.fillRect(x, y-hth, this.tw, this.th); 63 | } 64 | // Do not attempt to render empty char 65 | if (ch.length) { 66 | if (!fg.length) fg = view.defaultColor; 67 | this.ctx.fillStyle = fg; 68 | this.ctx.fillText(ch, x+hgap, y); 69 | } 70 | x += this.tw; 71 | } 72 | y += this.th; 73 | } 74 | this.ctx2.drawImage(this.offscreen, 0, 0); 75 | }; 76 | -------------------------------------------------------------------------------- /src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Monster Trainer RL 1.3 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 33 | 34 | 35 | 36 |
37 |
38 |
39 |
40 |

A production of Slashie, powered by unicodetiles.js

41 |
42 |
43 |

Switch to LCD mode (Resets game)

44 |

Note to 7DRL reviewers: this is the post 7DRL version, please check here for the 7drl edition

45 |

How to play

46 | 47 |

Your goal is to become a Monster master by defeating the gym leaders of the 8 cities in your region.

48 |

Pick your starter monster (using ,). Then find the exit to route 1 from your hometown.

49 |

When you find wild monster, use "r" to release your selected monster in a given direction. He will automatically fight nearby 50 | enemies with his default skill

51 |

If you want to use a different skill, press the key as indicated on the right bar

52 |

To call back your monster, use "P"

53 |

To use catchballs or potions, go to the "I"nventory and select a direction

54 | 55 |

Keyboard Commands

56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
Arrow keysMove around.
QWE
ASD
ZXC
Numpad
IShow Inventory.
,Pick up items.
1 to 6Select monster slot.
RRelease selected monster.
PPull back selected monster.
V,B,N,MUse monster skills.
85 |
86 | 87 | -------------------------------------------------------------------------------- /src/js/ca/Matrix.class.js: -------------------------------------------------------------------------------- 1 | function Matrix(map, w, h){ 2 | if (map){ 3 | this.values = map; 4 | this.futureValues = map; 5 | } else { 6 | this.values = []; 7 | this.futureValues = []; 8 | } 9 | } 10 | 11 | Matrix.prototype = { 12 | setFuture: function(value, x, y){ 13 | this.futureValues[x][y] = value; 14 | }, 15 | setPresent: function (value, x, y){ 16 | this.values[x][y] = value; 17 | }, 18 | get: function(x, y){ 19 | return this.values[x][y]; 20 | }, 21 | getWidth: function(){ 22 | return this.values.length; 23 | }, 24 | getHeight: function(){ 25 | return this.values[0].length; 26 | }, 27 | advance: function(){ 28 | for (var x = 0; x < this.values.length; x++){ 29 | for (var y = 0; y < this.values[0].length; y++){ 30 | this.values[x][y] = this.futureValues[x][y]; 31 | } 32 | } 33 | }, 34 | getSurroundingCount: function(x, y, type){ 35 | var upIndex = (y == 0 ? this.getHeight()-1 : y-1); 36 | var downIndex = (y == this.getHeight()-1 ? 0 : y+1); 37 | var rightIndex = (x == this.getWidth()-1 ? 0 : x+1); 38 | var leftIndex = (x == 0 ? this.getWidth()-1 : x-1); 39 | var count = 40 | (this.values[leftIndex][upIndex] == type ? 1 : 0) + 41 | (this.values[leftIndex][y] == type ? 1 : 0) + 42 | (this.values[leftIndex][downIndex] == type ? 1 : 0) + 43 | (this.values[rightIndex][upIndex] == type ? 1 : 0) + 44 | (this.values[rightIndex][y] == type ? 1 : 0) + 45 | (this.values[rightIndex][downIndex] == type ? 1 : 0) + 46 | (this.values[x][downIndex] == type ? 1 : 0) + 47 | (this.values[x][upIndex] == type ? 1 : 0) ; 48 | return count; 49 | }, 50 | getSurroundingCountNoWrap: function(x, y, type){ 51 | var count = 52 | (y==0||x==0?0:(this.values[x-1][y-1] == type ? 1 : 0)) + 53 | (x==0?0:(this.values[x-1][y] == type ? 1 : 0)) + 54 | (x==0||y==this.getHeight()-1?0:(this.values[x-1][y+1] == type ? 1 : 0)) + 55 | (y==0||x==this.getWidth()-1?0:(this.values[x+1][y-1] == type ? 1 : 0)) + 56 | (x==this.getWidth()-1?0:(this.values[x+1][y] == type ? 1 : 0)) + 57 | (x==this.getWidth()-1||y==this.getHeight()-1?0:(this.values[x+1][y+1] == type ? 1 : 0)) + 58 | (y==this.getHeight()-1?0:(this.values[x][y+1] == type ? 1 : 0)) + 59 | (y==0?0:(this.values[x][y-1] == type ? 1 : 0)) ; 60 | return count; 61 | }, 62 | clean: function(){ 63 | this.values = []; 64 | this.futureValues = []; 65 | }, 66 | getArrays: function(){ 67 | return this.values; 68 | }, 69 | addHotSpot: function(value, x, y){ 70 | if (!x) x = Random.n(5,this.getWidth()-5); 71 | if (!y) y = Random.n(5,this.getHeight()-5); 72 | this.values[x][y] = value; 73 | this.futureValues[x][y] = value; 74 | }, 75 | addShoweredHotSpot: function(value, shower, showers, maxDist, x, y){ 76 | if (!x) x = Random.n(5,this.getWidth()-5); 77 | if (!y) y = Random.n(5,this.getHeight()-5); 78 | var xs = showers; 79 | for (var i = 0; i < xs; i++){ 80 | var xdif = maxDist - Random.n(0,maxDist*2); 81 | var ydif = maxDist - Random.n(0,maxDist*2); 82 | if (this.isValid(x+xdif, y+ydif)) 83 | this.futureValues[x+xdif][y+ydif] = shower; 84 | } 85 | this.futureValues[x][y] = value; 86 | }, 87 | isValid: function(x, y){ 88 | return x>=0 && y >= 0 && x < this.getWidth() && y < this.getHeight(); 89 | } 90 | } 91 | 92 | module.exports = Matrix; -------------------------------------------------------------------------------- /src/js/Level.class.js: -------------------------------------------------------------------------------- 1 | var Distance = require('./Distance'); 2 | var Random = require('./Random'); 3 | var Being = require('./Being.class'); 4 | 5 | var Level = function(game, id){ 6 | this.init(game, id); 7 | } 8 | 9 | Level.prototype = { 10 | init: function(game, id){ 11 | this.map = []; 12 | this.beings = []; 13 | this.beingsList = []; 14 | this.exits = []; 15 | this.exitsMap = []; 16 | this.items = []; 17 | this.spawnPositions = []; // For respawning 18 | this.spawnCounter = 200; 19 | 20 | this.storePlaces = []; // For restocking 21 | 22 | this.game = game; 23 | this.id = id; 24 | this.player = game.player; 25 | }, 26 | getBeing: function(x, y){ 27 | if (!this.beings[x]) 28 | return false; 29 | return this.beings[x][y]; 30 | }, 31 | respawnMonsters: function(){ 32 | var initialPopulation = this.initialPopulation + Random.n(0,5); 33 | var currentMonsters = this.beingsList.length; 34 | initialPopulation -= currentMonsters; 35 | var level = this; 36 | for (var i = 0; i < initialPopulation; i++){ 37 | var spawnPosition = level.spawnPositions.length > 0 ? Random.from(level.spawnPositions) : false; 38 | if (spawnPosition){ 39 | var x = spawnPosition.x; 40 | var y = spawnPosition.y; 41 | } 42 | if (!spawnPosition || level.getBeing(x, y)){ 43 | // Place somewhere else 44 | x = Random.n(0,this.map.length-1); 45 | y = Random.n(0,this.map[0].length-1); 46 | } 47 | if (Distance.distance(x,y,this.game.player.x, this.game.player.y) < 12){ 48 | continue; 49 | } 50 | var wm = Random.fromWeighted(this.wildMonsters); 51 | var being = new Being(level.game, level, wm.race, wm.level); 52 | level.addBeing(being, x, y); 53 | if (wm.race.aggressive){ 54 | being.intent = 'CHASE'; 55 | } else if (wm.race.trainer){ 56 | being.intent = 'TRAINER'; 57 | } else { 58 | being.intent = 'STILL'; 59 | } 60 | } 61 | }, 62 | beingsTurn: function(){ 63 | this.spawnCounter--; 64 | if (this.spawnCounter === 0){ 65 | this.respawnMonsters(); 66 | } 67 | this.beingsList.sort(this.speedSorter); 68 | /* 69 | this.beingsList.map(function(monster, i){ 70 | console.log(i+": "+monster.race.name+" "+monster.getEffectiveSpeed()); 71 | }) 72 | */ 73 | for (var i = 0; i < this.beingsList.length; i++){ 74 | this.beingsList[i].endTurn(); 75 | this.beingsList[i].act(); 76 | } 77 | this.player.updateFOV(); 78 | this.game.display.refresh(); 79 | this.game.input.inputEnabled = true; 80 | }, 81 | speedSorter: function(a, b){ 82 | return b.getEffectiveSpeed() - a.getEffectiveSpeed(); 83 | }, 84 | addBeing: function(being, x, y){ 85 | this.beingsList.push(being); 86 | if (!this.beings[x]) 87 | this.beings[x] = []; 88 | being.x = x; 89 | being.y = y; 90 | this.beings[x][y] = being; 91 | }, 92 | removeBeing: function(being){ 93 | this.beings[being.x][being.y] = false; 94 | this.beingsList.splice(this.beingsList.indexOf(being), 1); 95 | }, 96 | getCell: function(x, y){ 97 | try { 98 | return this.map[x][y]; 99 | } catch (e){ 100 | // Catch OOB 101 | return false; 102 | } 103 | }, 104 | canWalkTo: function(x, y){ 105 | try { 106 | if (this.map[x][y].solid){ 107 | return false; 108 | } 109 | } catch (e){ 110 | // Catch OOB 111 | return false; 112 | } 113 | if (this.beings[x] && this.beings[x][y]){ 114 | return false; 115 | } 116 | return true; 117 | }, 118 | addExit: function(x,y, levelId, tile){ 119 | if (!this.exits[x]) 120 | this.exits[x] = []; 121 | this.exits[x][y] = levelId; 122 | this.exitsMap[levelId] = { 123 | x: x, 124 | y: y 125 | }; 126 | }, 127 | getExit: function(levelId){ 128 | return this.exitsMap[levelId]; 129 | }, 130 | addItem: function(item, x, y){ 131 | if (!this.items[x]) 132 | this.items[x] = []; 133 | this.items[x][y] = item; 134 | }, 135 | getItem: function(x, y){ 136 | if (!this.items[x]) 137 | return false; 138 | return this.items[x][y]; 139 | }, 140 | removeItem: function(x, y){ 141 | if (!this.items[x]) 142 | this.items[x] = []; 143 | this.items[x][y] = false; 144 | }, 145 | } 146 | 147 | module.exports = Level; -------------------------------------------------------------------------------- /static-lib/unicodetiles/input.js: -------------------------------------------------------------------------------- 1 | /// File: input.js 2 | /// This file contains a very simple input system. 3 | 4 | /*jshint browser:true */ 5 | 6 | /// Namespace: ut 7 | /// Container namespace. 8 | var ut = ut || {}; 9 | 10 | /// Constants: Keycodes 11 | /// KEY_BACKSPACE - 8 12 | /// KEY_TAB - 9 13 | /// KEY_ENTER - 13 14 | /// KEY_SHIFT - 16 15 | /// KEY_CTRL - 17 16 | /// KEY_ALT - 18 17 | /// KEY_ESCAPE - 27 18 | /// KEY_SPACE - 32 19 | /// KEY_LEFT - 37 20 | /// KEY_UP - 38 21 | /// KEY_RIGHT - 39 22 | /// KEY_DOWN - 40 23 | /// KEY_0 - 48 24 | /// KEY_1 - 49 25 | /// KEY_2 - 50 26 | /// KEY_3 - 51 27 | /// KEY_4 - 52 28 | /// KEY_5 - 53 29 | /// KEY_6 - 54 30 | /// KEY_7 - 55 31 | /// KEY_8 - 56 32 | /// KEY_9 - 57 33 | /// KEY_A - 65 34 | /// KEY_B - 66 35 | /// KEY_C - 67 36 | /// KEY_D - 68 37 | /// KEY_E - 69 38 | /// KEY_F - 70 39 | /// KEY_G - 71 40 | /// KEY_H - 72 41 | /// KEY_I - 73 42 | /// KEY_J - 74 43 | /// KEY_K - 75 44 | /// KEY_L - 76 45 | /// KEY_M - 77 46 | /// KEY_N - 78 47 | /// KEY_O - 79 48 | /// KEY_P - 80 49 | /// KEY_Q - 81 50 | /// KEY_R - 82 51 | /// KEY_S - 83 52 | /// KEY_T - 84 53 | /// KEY_U - 85 54 | /// KEY_V - 86 55 | /// KEY_W - 87 56 | /// KEY_X - 88 57 | /// KEY_Y - 89 58 | /// KEY_Z - 90 59 | /// KEY_NUMPAD0 - 96 60 | /// KEY_NUMPAD1 - 97 61 | /// KEY_NUMPAD2 - 98 62 | /// KEY_NUMPAD3 - 99 63 | /// KEY_NUMPAD4 - 100 64 | /// KEY_NUMPAD5 - 101 65 | /// KEY_NUMPAD6 - 102 66 | /// KEY_NUMPAD7 - 103 67 | /// KEY_NUMPAD8 - 104 68 | /// KEY_NUMPAD9 - 105 69 | /// KEY_F1 - 112 70 | /// KEY_F2 - 113 71 | /// KEY_F3 - 114 72 | /// KEY_F4 - 115 73 | /// KEY_F5 - 116 74 | /// KEY_F6 - 117 75 | /// KEY_F7 - 118 76 | /// KEY_F8 - 119 77 | /// KEY_F9 - 120 78 | /// KEY_F10 - 121 79 | /// KEY_F11 - 122 80 | /// KEY_F12 - 123 81 | /// KEY_COMMA - 188 82 | /// KEY_DASH - 189 83 | /// KEY_PERIOD - 190 84 | 85 | ut.KEY_BACKSPACE = 8; 86 | ut.KEY_TAB = 9; 87 | ut.KEY_ENTER = 13; 88 | ut.KEY_SHIFT = 16; 89 | ut.KEY_CTRL = 17; 90 | ut.KEY_ALT = 18; 91 | ut.KEY_ESCAPE = 27; 92 | ut.KEY_SPACE = 32; 93 | ut.KEY_LEFT = 37; 94 | ut.KEY_UP = 38; 95 | ut.KEY_RIGHT = 39; 96 | ut.KEY_DOWN = 40; 97 | 98 | ut.KEY_0 = 48; 99 | ut.KEY_1 = 49; 100 | ut.KEY_2 = 50; 101 | ut.KEY_3 = 51; 102 | ut.KEY_4 = 52; 103 | ut.KEY_5 = 53; 104 | ut.KEY_6 = 54; 105 | ut.KEY_7 = 55; 106 | ut.KEY_8 = 56; 107 | ut.KEY_9 = 57; 108 | ut.KEY_A = 65; 109 | ut.KEY_B = 66; 110 | ut.KEY_C = 67; 111 | ut.KEY_D = 68; 112 | ut.KEY_E = 69; 113 | ut.KEY_F = 70; 114 | ut.KEY_G = 71; 115 | ut.KEY_H = 72; 116 | ut.KEY_I = 73; 117 | ut.KEY_J = 74; 118 | ut.KEY_K = 75; 119 | ut.KEY_L = 76; 120 | ut.KEY_M = 77; 121 | ut.KEY_N = 78; 122 | ut.KEY_O = 79; 123 | ut.KEY_P = 80; 124 | ut.KEY_Q = 81; 125 | ut.KEY_R = 82; 126 | ut.KEY_S = 83; 127 | ut.KEY_T = 84; 128 | ut.KEY_U = 85; 129 | ut.KEY_V = 86; 130 | ut.KEY_W = 87; 131 | ut.KEY_X = 88; 132 | ut.KEY_Y = 89; 133 | ut.KEY_Z = 90; 134 | ut.KEY_NUMPAD0 = 96; 135 | ut.KEY_NUMPAD1 = 97; 136 | ut.KEY_NUMPAD2 = 98; 137 | ut.KEY_NUMPAD3 = 99; 138 | ut.KEY_NUMPAD4 = 100; 139 | ut.KEY_NUMPAD5 = 101; 140 | ut.KEY_NUMPAD6 = 102; 141 | ut.KEY_NUMPAD7 = 103; 142 | ut.KEY_NUMPAD8 = 104; 143 | ut.KEY_NUMPAD9 = 105; 144 | ut.KEY_F1 = 112; 145 | ut.KEY_F2 = 113; 146 | ut.KEY_F3 = 114; 147 | ut.KEY_F4 = 115; 148 | ut.KEY_F5 = 116; 149 | ut.KEY_F6 = 117; 150 | ut.KEY_F7 = 118; 151 | ut.KEY_F8 = 119; 152 | ut.KEY_F9 = 120; 153 | ut.KEY_F10 = 121; 154 | ut.KEY_F11 = 122; 155 | ut.KEY_F12 = 123; 156 | 157 | ut.KEY_COMMA = 188; 158 | ut.KEY_DASH = 189; 159 | ut.KEY_PERIOD = 190; 160 | 161 | 162 | ut.pressedKeys = {}; 163 | ut.keyRepeatDelay = 150; 164 | 165 | /// Function: isKeyPressed 166 | /// Checks if given key is pressed down. You must call first. 167 | /// 168 | /// Parameters: 169 | /// key - key code to check 170 | /// 171 | /// Returns: 172 | /// True if the key is pressed down, false otherwise. 173 | ut.isKeyPressed = function(key) { 174 | "use strict"; 175 | if (ut.pressedKeys[key]) return true; 176 | else return false; 177 | }; 178 | 179 | /// Function: setKeyRepeatInterval 180 | /// Sets the interval when user's onKeyDown handler is called when a key is held down. 181 | /// must be called with a handler for this to work. 182 | /// 183 | /// Parameters: 184 | /// milliseconds - the interval delay in milliseconds (1 second = 1000 milliseconds) 185 | ut.setKeyRepeatInterval = function(milliseconds) { 186 | "use strict"; 187 | ut.keyRepeatDelay = milliseconds; 188 | }; 189 | 190 | /// Function: initInput 191 | /// Initilizes input by assigning default key handlers and optional user's handlers. 192 | /// This must be called in order to to work. 193 | /// 194 | /// Parameters: 195 | /// onkeydown - (optional) function(keyCode) for key down event handler 196 | /// onkeyup - (optional) function(keyCode) for key up event handler 197 | ut.initInput = function(onKeyDown, onKeyUp) { 198 | ut.onkeydown = onKeyDown; 199 | ut.onkeyup = onKeyUp; 200 | // Attach default onkeydown handler that updates pressedKeys 201 | document.onkeydown = function(event) { 202 | "use strict"; 203 | var k = event.keyCode; 204 | if (ut.pressedKeys[k] !== null && ut.pressedKeys[k] !== undefined) return false; 205 | ut.pressedKeys[k] = true; 206 | if (ut.onkeydown) { 207 | ut.onkeydown(k); // User event handler 208 | // Setup keyrepeat 209 | ut.pressedKeys[k] = setInterval("ut.onkeydown("+k+")", ut.keyRepeatDelay); 210 | } 211 | if (ut.pressedKeys[ut.KEY_CTRL] || ut.pressedKeys[ut.KEY_ALT]) 212 | return true; // CTRL/ALT for browser hotkeys 213 | else return false; 214 | }; 215 | // Attach default onkeyup handler that updates pressedKeys 216 | document.onkeyup = function(event) { 217 | "use strict"; 218 | var k = event.keyCode; 219 | if (ut.onkeydown && ut.pressedKeys[k] !== null && ut.pressedKeys[k] !== undefined) 220 | clearInterval(ut.pressedKeys[k]); 221 | ut.pressedKeys[k] = null; 222 | if (ut.onkeyup) ut.onkeyup(k); // User event handler 223 | return false; 224 | }; 225 | // Avoid keys getting stuck at down 226 | window.onblur = function() { 227 | "use strict"; 228 | for (var k in ut.pressedKeys) 229 | if (ut.onkeydown && ut.pressedKeys[k] !== null) 230 | clearInterval(ut.pressedKeys[k]); 231 | ut.pressedKeys = {}; 232 | }; 233 | }; 234 | 235 | -------------------------------------------------------------------------------- /src/js/Input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | inputEnabled: true, 3 | init: function(game){ 4 | this.game = game; 5 | ut.initInput(this.onKeyDown.bind(this)); 6 | this.mode = 'MOVEMENT'; 7 | this.SKILL_KEYS = {}; 8 | this.SKILL_KEYS[ut.KEY_V] = 0; 9 | this.SKILL_KEYS[ut.KEY_B] = 1; 10 | this.SKILL_KEYS[ut.KEY_N] = 2; 11 | this.SKILL_KEYS[ut.KEY_M] = 3; 12 | }, 13 | movedir: { x: 0, y: 0 }, 14 | selectAvailableMonsterSlot: function(){ 15 | this.selectedMonsterSlot = false; 16 | for (var i = 0; i < this.game.player.monsterSlots.length; i++){ 17 | if (this.game.player.monsterSlots[i]){ 18 | this.selectedMonsterSlot = i; 19 | return; 20 | } 21 | } 22 | }, 23 | isLeftish: function(k){ 24 | return k === ut.KEY_LEFT || 25 | k === ut.KEY_H || 26 | k === ut.KEY_NUMPAD4 || k === ut.KEY_NUMPAD1 || k === ut.KEY_NUMPAD7 || 27 | k === ut.KEY_Q || k === ut.KEY_A || k === ut.KEY_Z; 28 | }, 29 | isRightish: function(k){ 30 | return k === ut.KEY_RIGHT || 31 | k === ut.KEY_L || 32 | k === ut.KEY_NUMPAD6 || k === ut.KEY_NUMPAD9 || k === ut.KEY_NUMPAD3 || 33 | k === ut.KEY_E || k === ut.KEY_D || k === ut.KEY_C; 34 | 35 | }, 36 | isUppish: function(k){ 37 | return k === ut.KEY_UP || 38 | k === ut.KEY_K || 39 | k === ut.KEY_NUMPAD8 || k === ut.KEY_NUMPAD7 || k === ut.KEY_NUMPAD9 || 40 | k === ut.KEY_Q || k === ut.KEY_W || k === ut.KEY_E; 41 | }, 42 | isDownish: function(k){ 43 | return k === ut.KEY_DOWN || 44 | k === ut.KEY_J || 45 | k === ut.KEY_NUMPAD2 || k === ut.KEY_NUMPAD1 || k === ut.KEY_NUMPAD3 || 46 | k === ut.KEY_Z || k === ut.KEY_X || k === ut.KEY_C; 47 | }, 48 | setMoveDir: function(k){ 49 | if (this.isLeftish(k)) { 50 | this.movedir.x = -1; 51 | } 52 | if (this.isRightish(k)) { 53 | this.movedir.x = 1; 54 | } 55 | if (this.isUppish(k)) { 56 | this.movedir.y = -1; 57 | } 58 | if (this.isDownish(k)) { 59 | this.movedir.y = 1; 60 | } 61 | }, 62 | onKeyDown: function(k){ 63 | if (!this.inputEnabled) 64 | return; 65 | if (this.mode === "SCENE"){ 66 | if (k === ut.KEY_ENTER){ 67 | this.game.display.hideScene(); 68 | this.mode = "MOVEMENT"; 69 | } 70 | } else if (this.mode === "PROMPT"){ 71 | if (k === ut.KEY_Y){ 72 | this.promptFunction(true); 73 | } else if (k === ut.KEY_N){ 74 | this.promptFunction(false); 75 | } 76 | } else if (this.mode === 'MOVEMENT'){ 77 | if (k === ut.KEY_COMMA){ 78 | this.game.player.tryPickup(); 79 | return; 80 | } 81 | if (k === ut.KEY_I){ 82 | if (this.game.player.items.length === 0){ 83 | this.game.display.message("You don't have any items"); 84 | return; 85 | } 86 | this.mode = 'INVENTORY'; 87 | this.selectedItemIndex = 0; 88 | this.selectedItem = this.game.player.items[0]; 89 | this.game.display.showInventory(); 90 | return; 91 | } 92 | // Monster slots 93 | if (k >= ut.KEY_1 && k <= ut.KEY_6){ 94 | var slot = k-ut.KEY_1; 95 | if (this.game.player.monsterSlots[slot]){ 96 | this.selectedMonsterSlot = slot; 97 | this.game.display.refresh(); 98 | } 99 | return; 100 | } 101 | if (k === ut.KEY_R){ 102 | //Release 103 | var slot = this.game.player.monsterSlots[this.selectedMonsterSlot]; 104 | if (slot && slot.onPocket){ 105 | this.game.display.message("Select a direction."); 106 | this.mode = 'SELECT_DIRECTION'; 107 | this.directionAction = 'RELEASE_MONSTER'; 108 | } 109 | return; 110 | } 111 | if (k === ut.KEY_G){ 112 | // Give up 113 | this.game.player.giveUpGymBattle(); 114 | return; 115 | } 116 | if (k === ut.KEY_P){ 117 | // Pull back 118 | var slot = this.game.player.monsterSlots[this.selectedMonsterSlot]; 119 | if (slot && !slot.onPocket){ 120 | this.game.player.pullBack(); 121 | } 122 | return; 123 | } 124 | if (k === ut.KEY_V || k === ut.KEY_B || k === ut.KEY_N || k === ut.KEY_M){ 125 | var slot = this.game.player.monsterSlots[this.selectedMonsterSlot]; 126 | if (slot && !slot.onPocket){ 127 | var index = this.SKILL_KEYS[k]; 128 | slot.being.useSkill(index); 129 | } 130 | return; 131 | } 132 | 133 | // Movement 134 | this.movedir.x = 0; 135 | this.movedir.y = 0; 136 | this.setMoveDir(k); 137 | if (this.movedir.x === 0 && this.movedir.y === 0){ 138 | return; 139 | } 140 | this.inputEnabled = false; 141 | this.game.player.tryMove(this.movedir); 142 | } else if (this.mode === 'INVENTORY'){ 143 | if (k === ut.KEY_ESCAPE){ 144 | this.game.display.hideInventory(); 145 | this.mode = 'MOVEMENT'; 146 | } else if (this.isUppish(k)){ 147 | if (this.selectedItemIndex > 0){ 148 | this.selectedItemIndex --; 149 | } 150 | this.selectedItem = this.game.player.items[this.selectedItemIndex]; 151 | this.game.display.showInventory(); 152 | } else if (this.isDownish(k)){ 153 | if (this.selectedItemIndex < this.game.player.items.length - 1){ 154 | this.selectedItemIndex ++; 155 | } 156 | this.selectedItem = this.game.player.items[this.selectedItemIndex]; 157 | this.game.display.showInventory(); 158 | } else if (k === ut.KEY_D){ 159 | this.game.player.tryDrop(this.selectedItem); 160 | this.game.display.hideInventory(); 161 | this.mode = 'MOVEMENT'; 162 | } else if (k === ut.KEY_ENTER || k === ut.KEY_U){ 163 | if (this.selectedItem.def.targetted || this.selectedItem.def.type.targetted){ 164 | this.game.display.message("Select a direction."); 165 | this.game.display.hideInventory(); 166 | this.mode = 'SELECT_DIRECTION'; 167 | this.directionAction = 'USE_ITEM'; 168 | } else { 169 | this.game.player.tryUse(this.selectedItem); 170 | this.game.display.hideInventory(); 171 | this.mode = 'MOVEMENT'; 172 | } 173 | } 174 | } else if (this.mode === 'SELECT_DIRECTION'){ 175 | if (k === ut.KEY_ESCAPE){ 176 | if (this.directionAction === 'USE_ITEM'){ 177 | this.mode = 'INVENTORY'; 178 | this.game.display.showInventory(); 179 | } else if (this.directionAction === 'RELEASE_MONSTER'){ 180 | this.mode = 'MOVEMENT'; 181 | } 182 | this.game.display.message("Cancelled."); 183 | return; 184 | } 185 | this.movedir.x = 0; 186 | this.movedir.y = 0; 187 | this.setMoveDir(k); 188 | if (this.movedir.x === 0 && this.movedir.y === 0){ 189 | return; 190 | } 191 | if (this.directionAction === 'USE_ITEM'){ 192 | this.game.player.tryUse(this.selectedItem, this.movedir.x, this.movedir.y); 193 | } else if (this.directionAction === 'RELEASE_MONSTER'){ 194 | this.game.player.releaseMonster(this.movedir); 195 | } 196 | this.mode = 'MOVEMENT'; 197 | } 198 | } 199 | } -------------------------------------------------------------------------------- /src/js/MasterPlans.js: -------------------------------------------------------------------------------- 1 | var Races = require('./monster/Races.enum'); 2 | var Items = require('./Items.enum'); 3 | 4 | module.exports = { 5 | cities: 4, 6 | towns: 1, 7 | startingMonsters: [ 8 | Items.CHARMANDER_CATCHBALL, 9 | Items.BULBASAUR_CATCHBALL, 10 | Items.SQUIRTLE_CATCHBALL, 11 | Items.PIKACHU_CATCHBALL 12 | ], 13 | items: [ 14 | { 15 | item: Items.CATCHBALL, 16 | weight: 100, 17 | tier: 1 18 | }, 19 | { 20 | item: Items.GREATBALL, 21 | weight: 20, 22 | tier: 2 23 | }, 24 | { 25 | item: Items.ULTRABALL, 26 | weight: 20, 27 | tier: 3 28 | }, 29 | { 30 | item: Items.POTION, 31 | weight: 100, 32 | tier: 1 33 | }, 34 | { 35 | item: Items.SUPER_POTION, 36 | weight: 5, 37 | tier: 2 38 | }, 39 | { 40 | item: Items.HYPER_POTION, 41 | weight: 5, 42 | tier: 3 43 | }, 44 | { 45 | item: Items.MAX_POTION, 46 | weight: 5, 47 | tier: 4 48 | } 49 | ], 50 | routeStereotypes: [ 51 | { 52 | monsters: [ 53 | { 54 | race: Races.RATTATA, 55 | weight: 45 56 | }, 57 | { 58 | race: Races.PIDGEY, 59 | levelBoost: 1, 60 | weight: 55 61 | } 62 | ] 63 | }, 64 | { 65 | monsters: [ 66 | { 67 | race: Races.CATERPIE, 68 | weight: 15 69 | }, 70 | { 71 | race: Races.WEEDLE, 72 | weight: 15 73 | }, 74 | { 75 | race: Races.PIDGEY, 76 | weight: 40 77 | }, 78 | { 79 | race: Races.RATTATA, 80 | weight: 45 81 | } 82 | ] 83 | }, 84 | { 85 | monsters: [ 86 | { 87 | race: Races.PIDGEY, 88 | levelBoost: 1, 89 | weight: 50 90 | }, 91 | { 92 | race: Races.SPEAROW, 93 | levelBoost: 1, 94 | weight: 40 95 | }, 96 | { 97 | race: Races.JIGGLYPUFF, 98 | weight: 10 99 | } 100 | ] 101 | }, 102 | { 103 | monsters: [ 104 | { 105 | race: Races.RATTATA, 106 | levelBoost: 1, 107 | weight: 45 108 | }, 109 | { 110 | race: Races.SPEAROW, 111 | levelBoost: 1, 112 | weight: 30 113 | }, 114 | { 115 | race: Races.EKANS, 116 | weight: 25 117 | }, 118 | { 119 | race: Races.SANDSHREW, 120 | weight: 25 121 | } 122 | ] 123 | }, 124 | { 125 | monsters: [ 126 | { 127 | race: Races.RATTATA, 128 | weight: 45 129 | }, 130 | { 131 | race: Races.SPEAROW, 132 | weight: 30 133 | }, 134 | { 135 | race: Races.EKANS, 136 | weight: 25 137 | }, 138 | { 139 | race: Races.SANDSHREW, 140 | weight: 25 141 | } 142 | ] 143 | }, 144 | { 145 | monsters: [ 146 | { 147 | race: Races.PIDGEY, 148 | weight: 35 149 | }, 150 | { 151 | race: Races.ODDISH, 152 | weight: 40 153 | }, 154 | { 155 | race: Races.MEOWTH, 156 | weight: 25 157 | }, 158 | { 159 | race: Races.MANKEY, 160 | weight: 25 161 | }, 162 | { 163 | race: Races.BELLSPROUT, 164 | weight: 40 165 | } 166 | ] 167 | }, 168 | { 169 | monsters: [ 170 | { 171 | race: Races.PIDGEY, 172 | weight: 35 173 | }, 174 | { 175 | race: Races.VULPIX, 176 | weight: 10 177 | }, 178 | { 179 | race: Races.ODDISH, 180 | weight: 25 181 | }, 182 | { 183 | race: Races.MEOWTH, 184 | weight: 30 185 | }, 186 | { 187 | race: Races.MANKEY, 188 | weight: 30 189 | }, 190 | { 191 | race: Races.GROWLITHE, 192 | weight: 30 193 | }, 194 | { 195 | race: Races.BELLSPROUT, 196 | weight: 25 197 | } 198 | ] 199 | }, 200 | { 201 | monsters: [ 202 | { 203 | race: Races.PIDGEY, 204 | weight: 35 205 | }, 206 | { 207 | race: Races.EKANS, 208 | weight: 20 209 | }, 210 | { 211 | race: Races.SANDSHREW, 212 | weight: 20 213 | }, 214 | { 215 | race: Races.VULPIX, 216 | weight: 20 217 | }, 218 | { 219 | race: Races.MEOWTH, 220 | weight: 25 221 | }, 222 | { 223 | race: Races.GROWLITHE, 224 | weight: 20 225 | }, 226 | { 227 | race: Races.MANKEY, 228 | weight: 25 229 | } 230 | ] 231 | }, 232 | { 233 | monsters: [ 234 | { 235 | race: Races.SPEAROW, 236 | weight: 30 237 | }, 238 | { 239 | race: Races.EKANS, 240 | weight: 25 241 | }, 242 | { 243 | race: Races.SANDSHREW, 244 | weight: 25 245 | }, 246 | { 247 | race: Races.VOLTORB, 248 | weight: 45 249 | } 250 | ] 251 | }, 252 | { 253 | monsters: [ 254 | { 255 | race: Races.SPEAROW, 256 | weight: 30 257 | }, 258 | { 259 | race: Races.EKANS, 260 | weight: 45 261 | }, 262 | { 263 | race: Races.SANDSHREW, 264 | weight: 45 265 | }, 266 | { 267 | race: Races.DROWZEE, 268 | weight: 25 269 | } 270 | ] 271 | }, 272 | { 273 | monsters: [ 274 | { 275 | race: Races.PIDGEY, 276 | weight: 35 277 | }, 278 | { 279 | race: Races.ODDISH, 280 | weight: 40 281 | }, 282 | { 283 | race: Races.GLOOM, 284 | weight: 5 285 | }, 286 | { 287 | race: Races.VENONAT, 288 | weight: 20 289 | }, 290 | { 291 | race: Races.BELLSPROUT, 292 | weight: 40 293 | }, 294 | { 295 | race: Races.WEEPINBELL, 296 | weight: 5 297 | } 298 | ] 299 | }, 300 | ], 301 | gymStereotypes: { 302 | ROCK: { 303 | monsters: [ 304 | { 305 | race: Races.ONIX, 306 | levelBoost: 5, 307 | weight: 60 308 | }, 309 | { 310 | race: Races.GEODUDE, 311 | weight: 60 312 | }, 313 | { 314 | race: Races.GRAVELER, 315 | weight: 20 316 | } 317 | ], 318 | badge: Items.BOULDER_BADGE 319 | }, 320 | WATER: { 321 | monsters: [ 322 | { 323 | race: Races.STARMIE, 324 | levelBoost: 5, 325 | weight: 60 326 | }, 327 | { 328 | race: Races.STARYU, 329 | weight: 20 330 | } 331 | ], 332 | badge: Items.CASCADE_BADGE 333 | }, 334 | ELECTRIC: { 335 | monsters: [ 336 | { 337 | race: Races.VOLTORB, 338 | weight: 20 339 | }, 340 | { 341 | race: Races.PIKACHU, 342 | weight: 60 343 | }, 344 | { 345 | race: Races.RAICHU, 346 | levelBoost: 5, 347 | weight: 60 348 | } 349 | ], 350 | badge: Items.THUNDER_BADGE 351 | }, 352 | GRASS: { 353 | monsters: [ 354 | { 355 | race: Races.VOLTORB, 356 | weight: 20 357 | }, 358 | { 359 | race: Races.PIKACHU, 360 | weight: 60 361 | }, 362 | { 363 | race: Races.RAICHU, 364 | levelBoost: 5, 365 | weight: 60 366 | } 367 | ], 368 | badge: Items.RAINBOW_BADGE 369 | }, 370 | PSYCHIC: { 371 | monsters: [ 372 | { 373 | race: Races.VOLTORB, 374 | weight: 20 375 | }, 376 | { 377 | race: Races.PIKACHU, 378 | weight: 60 379 | }, 380 | { 381 | race: Races.RAICHU, 382 | levelBoost: 5, 383 | weight: 60 384 | } 385 | ], 386 | badge: Items.MARSH_BADGE 387 | } 388 | } 389 | } -------------------------------------------------------------------------------- /src/js/Display.js: -------------------------------------------------------------------------------- 1 | var TextBox = require('./ui/TextBox.class'); 2 | var Box = require('./ui/Box.class'); 3 | 4 | module.exports = { 5 | BLANK_TILE: new ut.Tile(' ', 255, 255, 255), 6 | CURSOR_TILE: new ut.Tile('*', 255, 255, 255), 7 | init: function(game){ 8 | this.game = game; 9 | this.term = new ut.Viewport(document.getElementById("game"), 80, 25); 10 | this.eng = new ut.Engine(this.term, this.getDisplayedTile.bind(this), 80, 25); 11 | this.textBox = new TextBox(this.term, 2, 80, {x:0, y:0}, this); 12 | this.inventoryBox = new Box(this.term, 25, 40, {x:19, y:0}); 13 | this.sceneBox = new TextBox(this.term, 10, 45, {x:20, y:3}, this); 14 | }, 15 | getDisplayedTile: function(x,y){ 16 | var level = this.game.world.level; 17 | if (x === level.player.x && y === level.player.y){ 18 | return level.player.tile; 19 | } 20 | var xr = x - level.player.x; 21 | var yr = y - level.player.y; 22 | if (level.player.canSee(xr, yr)){ 23 | if (level.beings[x] && level.beings[x][y]){ 24 | return level.beings[x][y].tile; 25 | } else if (level.items[x] && level.items[x][y]){ 26 | return level.items[x][y].def.tile; 27 | } else if (level.map[x] && level.map[x][y]){ 28 | return level.map[x][y].tile; 29 | } else { 30 | return ut.NULLTILE; 31 | } 32 | } else if (level.player.remembers(x, y)){ 33 | if (level.map[x] && level.map[x][y]){ 34 | return level.map[x][y].darkTile; 35 | } else { 36 | return ut.NULLTILE; 37 | } 38 | } else { 39 | return ut.NULLTILE; 40 | } 41 | }, 42 | refresh: function(){ 43 | this.eng.update(this.game.player.x, this.game.player.y); 44 | this.updateStatus(); 45 | this.textBox.draw(); 46 | this.term.render(); 47 | if (this.currentScene){ 48 | this.showScene(this.currentScene); 49 | } 50 | }, 51 | updateStatus: function(){ 52 | this.term.putString("HP "+this.game.player.hp.getText(), 2, 3, 255, 255, 255); 53 | this.term.putString("$"+this.game.player.money, 2, 4, 255, 255, 255); 54 | this.term.putString(this.game.world.level.name, 2, 5, 255, 255, 255); 55 | // Monster list 56 | var baseX = 3; 57 | var baseY = 7; 58 | for (var i = 0; i < this.game.player.monsterSlots.length; i++){ 59 | var slot = this.game.player.monsterSlots[i]; 60 | if (!slot){ 61 | continue; 62 | } 63 | if (i == this.game.input.selectedMonsterSlot){ 64 | this.term.putString(">"+(i+1)+" "+slot.being.race.name, baseX-1, baseY + i*3, 255, 0, 0); 65 | } else { 66 | this.term.putString((i+1)+" "+slot.being.race.name, baseX, baseY + i*3, 255, 255, 255); 67 | } 68 | this.term.put(slot.being.race.tile, baseX + 2, baseY + i*3); 69 | this.term.putString(" Lv"+slot.being.xpLevel+" HP "+slot.being.hp.getText(), baseX, baseY + i*3 + 1, 255, 255, 255); 70 | this.term.putString(slot.onPocket ? " On Pocket" : " Released", baseX, baseY + i*3 + 2, 255, 255, 255); 71 | } 72 | // Monster actions 73 | baseX = 60; 74 | baseY = 3; 75 | var actions = []; //TODO: Cache this 76 | if (this.game.player.gymTown){ 77 | actions.push({ 78 | key: 'G', 79 | name: 'Give up' 80 | }); 81 | } 82 | if (this.game.input.selectedMonsterSlot !== undefined && this.game.input.selectedMonsterSlot !== false){ 83 | var slot = this.game.player.monsterSlots[this.game.input.selectedMonsterSlot]; 84 | if (slot.onPocket){ 85 | actions.push({ 86 | key: 'R', 87 | name: 'Release' 88 | }); 89 | 90 | } else { 91 | actions.push({ 92 | key: 'P', 93 | name: 'Pull back' 94 | }); 95 | } 96 | for (var i = 0; i < slot.being.skills.length; i++){ 97 | actions.push({ 98 | key: slot.onPocket ? '*' : this.SKILL_KEYS[i].key, 99 | name: slot.being.skills[i].skill.name, 100 | info: "["+slot.being.skills[i].pp.getText()+"]" 101 | }) 102 | } 103 | this.term.putString(slot.being.race.name, baseX + 2, baseY, 255, 255, 255); 104 | this.term.put(slot.being.race.tile, baseX, baseY); 105 | this.term.putString("HP "+slot.being.hp.getText()+" Lv"+slot.being.xpLevel, baseX, baseY + 1, 255, 255, 255); 106 | this.term.putString("("+slot.being.xp+"/"+slot.being.nextLevelXP+")", baseX, baseY + 2, 255, 255, 255); 107 | this.term.putString("ATK "+slot.being.attack.current, baseX, baseY + 3, 255, 255, 255); 108 | this.term.putString("DEF "+slot.being.defense.current, baseX+8, baseY + 3, 255, 255, 255); 109 | this.term.putString("SPA "+slot.being.spAttack.current, baseX, baseY + 4, 255, 255, 255); 110 | this.term.putString("SPD "+slot.being.spDefense.current, baseX+8, baseY + 4, 255, 255, 255); 111 | 112 | this.term.putString(slot.being.race.name, baseX + 2, baseY, 255, 255, 255); 113 | this.term.putString(slot.being.race.name, baseX + 2, baseY, 255, 255, 255); 114 | 115 | } 116 | for (var i = 0; i < actions.length && i < 5; i++){ 117 | this.term.putString("("+actions[i].key + ")", baseX, baseY + (i*2) + 6, 255, 0, 0); 118 | this.term.putString(actions[i].name, baseX+4, baseY + (i*2) + 6, 255, 255, 255); 119 | if (actions[i].info){ 120 | this.term.putString(actions[i].info, baseX+4, baseY + (i*2)+1 + 6, 255, 255, 255); 121 | } 122 | } 123 | 124 | // Show nearby monsters 125 | baseX = 19; 126 | baseY = 23; 127 | for (var i = 0; i < this.game.player.observedMonsters.length && i < 4; i++){ 128 | var monster = this.game.player.observedMonsters[i]; 129 | this.term.putString(" "+monster.race.name, baseX + i * 15, baseY, 255, 255, 255); 130 | this.term.put(monster.race.tile, baseX + i * 15, baseY); 131 | if (monster.hp){ 132 | this.term.putString("Lv"+monster.xpLevel+" HP "+Math.round(monster.hp.getProportion()*100)+"%", baseX + i * 15, baseY + 1, 255, 255, 255); 133 | } 134 | } 135 | }, 136 | SKILL_KEYS: [ 137 | { 138 | key: "V" 139 | }, 140 | { 141 | key: "B" 142 | }, 143 | { 144 | key: "N" 145 | }, 146 | { 147 | key: "M" 148 | } 149 | ], 150 | showInventory: function(){ 151 | this.inventoryBox.draw(); 152 | var xBase = 20; 153 | var yBase = 0; 154 | this.term.putString("Inventory", xBase, yBase, 255, 0, 0); 155 | for (var i = 0; i < this.game.player.items.length; i++){ 156 | var item = this.game.player.items[i]; 157 | if (item == this.game.input.selectedItem){ 158 | this.term.put(this.CURSOR_TILE, xBase, yBase+1+i); 159 | } else { 160 | this.term.put(this.BLANK_TILE, xBase, yBase+1+i); 161 | } 162 | this.term.put(item.def.tile, xBase+2, yBase+1+i); 163 | this.term.put(item.def.tile, xBase+2, yBase+1+i); 164 | this.term.putString(item.def.name, xBase + 4, yBase+1+i, 255, 255, 255); 165 | } 166 | this.term.render(); 167 | }, 168 | hideInventory: function(){ 169 | this.term.clear(); 170 | this.refresh(); 171 | }, 172 | message: function(str){ 173 | this.textBox.addText(str+" "); 174 | this.textBox.draw(); 175 | this.term.render(); 176 | }, 177 | SCENES: { 178 | WELCOME: "Welcome to Monster Trainer RL! Select your starter monster using ,", 179 | VICTORY: "Congratulations, you have become a Monster Master!" 180 | }, 181 | showScene: function(id){ 182 | this.currentScene = id; 183 | this.sceneBox.setText(this.SCENES[id]); 184 | this.game.input.mode = 'SCENE'; 185 | this.sceneBox.draw(); 186 | this.term.putString("Press Enter to continue...", 30, 11, 255, 255, 255); 187 | this.term.render(); 188 | }, 189 | hideScene: function(){ 190 | this.currentScene = false; 191 | this.refresh(); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/js/monster/DamageEffect.js: -------------------------------------------------------------------------------- 1 | var Status = require('./Status.enum'); 2 | var Random = require('../Random'); 3 | var Effectiveness = require('./Effectiveness'); 4 | 5 | module.exports = { 6 | hits: function(monster, enemy, skill){ 7 | if (hitChance === 0){ 8 | return true; 9 | } 10 | if (skill.params.alwaysHit) 11 | return true; 12 | var hitChance = skill.accuracy * (monster.getEffectiveAccuracy() / enemy.getEffectiveEvasion()); 13 | return Random.chance(hitChance); 14 | }, 15 | getCriticalHitChance: function(monster, skill){ 16 | var t = monster.speed.current / 2; // This uses the base speed, not affected by modifiers 17 | if (skill.params.critBoost){ 18 | t *= 8; 19 | } 20 | if (monster.hasStatus(Status.FOCUSED)){ 21 | t *= 4; 22 | } 23 | return t / 256; 24 | }, 25 | calculateDamage: function(monster, enemy, skill){ 26 | if (skill.params.fixedDamage){ 27 | if (skill.params.fixedDamage === "Level"){ 28 | return monster.xpLevel; 29 | } else { 30 | return skill.params.fixedDamage; 31 | } 32 | } else if (skill.params.fixedDamagePercentage){ 33 | var damage = Math.floor(enemy.hp.current * (skill.params.fixedDamagePercentage/100)); 34 | if (damage === 0) 35 | damage = 1; 36 | return damage; 37 | } else if (skill.params.specialDamage){ 38 | if (skill.params.specialDamage === "psywave"){ 39 | return Random.n(1, Math.floor(monster.xpLevel * 1.5)); 40 | } 41 | } 42 | var criticalHitChance = this.getCriticalHitChance(monster, skill) * 100; 43 | var criticalHit = Random.chance(criticalHitChance); 44 | if (criticalHit){ 45 | monster.game.display.message("A Critical Hit!"); 46 | } 47 | var level = criticalHit ? monster.xpLevel * 2 : monster.xpLevel; 48 | var a = skill.damageType = 'PHYS' ? monster.getEffectiveAttack() : monster.getEffectiveSpecialAttack(); 49 | var d = skill.damageType = 'PHYS' ? enemy.getEffectiveDefense() : enemy.getEffectiveSpecialDefense(); 50 | var power = skill.power; 51 | var targets = 1; // Changes if multitargets 52 | var weather = 1; // Changes based on environment 53 | var random = Random.n(85,100) / 100; 54 | var stab = skill.type === monster.type ? 1.5 : 1; 55 | var type = Effectiveness(skill, monster, enemy); 56 | switch(type){ 57 | case 2: 58 | monster.game.display.message("It's super effective!"); 59 | break; 60 | case 0.5: 61 | monster.game.display.message("It's not very effective..."); 62 | break; 63 | } 64 | var modifier = targets * weather * random * stab * type; 65 | var damage = modifier * ( 66 | ( 67 | ( 68 | ( 69 | ( 70 | ( 71 | 2 * level 72 | ) / 5 73 | ) + 2 74 | ) * power * (a/d) 75 | )/50 76 | )+2); 77 | damage = Math.floor(damage); 78 | if (skill.params.undergroundBonus && enemy.isUnderground()){ 79 | damage *= 2; 80 | } 81 | return damage; 82 | }, 83 | effect: function(monster, skill){ 84 | var enemy = monster.getNearestEnemy(); 85 | monster.game.display.message(monster.race.name+" uses "+skill.name+" on the "+enemy.race.name+"."); 86 | 87 | if (skill.params.suicide){ 88 | monster.game.display.message("The "+monster.race.name+" sacrifices himself!"); 89 | } 90 | 91 | if (!this.hits(monster, enemy, skill)){ 92 | monster.game.display.message("The "+monster.race.name+" misses."); 93 | // Missed?? 94 | if (skill.params.damageOnMiss){ 95 | monster.game.display.message("The "+monster.race.name+" takes "+skill.params.damageOnMiss+" miss damage."); 96 | monster.takeDamage(skill.params.damageOnMiss); 97 | } 98 | return; 99 | } 100 | 101 | if (enemy.intent === 'STILL'){ 102 | // No longer!!! 103 | monster.game.display.message("The "+enemy.race.name+" becomes hostile!"); 104 | enemy.intent = 'CHASE'; 105 | } 106 | 107 | var damage = this.calculateDamage(monster, enemy, skill); 108 | 109 | 110 | if (damage > 0){ 111 | monster.game.display.message("The "+enemy.race.name+" is hit for "+damage+" damage."); 112 | enemy.recordHitBy(monster); 113 | enemy.takeDamage(damage); 114 | if (skill.params.absorbHPPercentage){ 115 | var absorbedHP = Math.floor(damage * (skill.params.absorbHPPercentage / 100)); 116 | if (absorbedHP === 0){ 117 | absorbedHP = 1; 118 | } 119 | monster.game.display.message("The "+monster.race.name+" absorbs "+absorbedHP+" damage."); 120 | monster.recoverHP(absorbedHP); 121 | } 122 | if (skill.params.recoil){ 123 | var recoilDamage = Math.floor(damage * (skill.params.recoil / 100)); 124 | monster.game.display.message("The "+monster.race.name+" takes "+recoilDamage+" recoil damage."); 125 | monster.takeDamage(recoilDamage); 126 | } 127 | 128 | if (skill.params.burnChance && Random.chance(skill.params.burnChance)){ 129 | monster.game.display.message("The "+enemy.race.name+" is burned!"); 130 | enemy.inflictStatus(Status.BURN, 5); 131 | } 132 | 133 | if (skill.params.confuseChance && Random.chance(skill.params.confuseChance)){ 134 | monster.game.display.message("The "+enemy.race.name+" is confused!"); 135 | enemy.inflictStatus(Status.CONFUSION, 5); 136 | } 137 | 138 | if (skill.params.flinchChance && Random.chance(skill.params.flinchChance)){ 139 | monster.game.display.message("The "+enemy.race.name+" flinches!"); 140 | enemy.inflictStatus(Status.FLINCH, 1); 141 | } 142 | 143 | if (skill.params.freezeChance && Random.chance(skill.params.freezeChance)){ 144 | monster.game.display.message("The "+enemy.race.name+" is frozen!"); 145 | enemy.inflictStatus(Status.FREEZE, 20); 146 | } 147 | 148 | if (skill.params.paralyzeChance && Random.chance(skill.params.paralyzeChance)){ 149 | monster.game.display.message("The "+enemy.race.name+" is paralyzed!"); 150 | enemy.inflictStatus(Status.PARALYZE, 5); 151 | } 152 | 153 | if (skill.params.poisonChance && Random.chance(skill.params.poisonChance)){ 154 | monster.game.display.message("The "+enemy.race.name+" is poisoned!"); 155 | enemy.inflictStatus(Status.POISON, 10); 156 | } 157 | 158 | if (skill.params.lowerChance && Random.chance(skill.params.lowerChance)){ 159 | monster.game.display.message("The "+enemy.race.name+"'s "+skill.params.lowerStat.name+" is lowered!"); 160 | enemy.changeStat(skill.params.lowerStat.id, 5, -1); 161 | } 162 | 163 | if (skill.params.suicide){ 164 | monster.die(); 165 | } 166 | 167 | // afterHits - Status to set after ALL hits (when multiHit) 168 | // assault - not here but previous, make the monster get next to enemy 169 | // buildUpTurns - not here? turns required to execute the move 170 | // rageTurns - Add a counter which would repeat the same skill until over. 171 | // raiseAttackWhileRaging - not here, if current skill has this param and monster is raging, raise attack temporarily. 172 | // dropCoins - Drop money when hits 173 | // hideTurns - not here? hide for x turns before executing 174 | // multihit - not here? execute attack multiple times 175 | 176 | // recoverTurns - not here? Counter during which cannot do anything 177 | // requireStatus - Validation, target should have this status for the attack to be done 178 | // splashRange - not here, affect multiple targets 179 | // trap - target can't move (nor attack? for 4-5 turns), damage is caused every turn 180 | } else { 181 | monster.game.display.message("The "+enemy.race.name+" is not affected."); 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /src/js/MetadataGenerator.js: -------------------------------------------------------------------------------- 1 | var NPCRaces = require('./NPCRaces.enum'); 2 | var Items = require('./Items.enum'); 3 | var Direction = require('./util/Direction') 4 | var Random = require('./Random'); 5 | var MasterPlans = require('./MasterPlans'); 6 | var TownNameGen = require('./procgen/TownNames'); 7 | 8 | module.exports = { 9 | plansComplete: function(){ 10 | return this.remainingTowns === 0 && this.remainingCities === 0; 11 | }, 12 | generateMetadata: function(){ 13 | this.exits = []; 14 | this.metadata = {}; 15 | this.currentRouteId = 0; 16 | this.currentTownId = 0; 17 | this.currentGymId = 0; 18 | this.masterPlans = MasterPlans; 19 | this.supermap = { 20 | x: 0, 21 | y: 0, 22 | minx: 0, 23 | miny: 0, 24 | maxx: 0, 25 | maxy: 0, 26 | map: [] 27 | }; 28 | 29 | if (Object.keys(this.masterPlans.gymStereotypes).length < this.masterPlans.cities){ 30 | throw "Not enough gym stereotypes"; 31 | } 32 | 33 | this.remainingTowns = this.masterPlans.towns; 34 | this.remainingCities = this.masterPlans.cities; 35 | this.placeTown({starting: true}); 36 | while (!this.plansComplete()){ 37 | if (Random.chance(50) && this.remainingTowns > 0){ 38 | this.placeTown({}); 39 | this.remainingTowns--; 40 | } 41 | if (Random.chance(50) && this.remainingCities > 0){ 42 | this.placeTown({hasGym: true}); 43 | this.remainingCities--; 44 | } 45 | } 46 | return this.metadata; 47 | }, 48 | showSupermap: function(){ 49 | // Normalize 50 | for (var y = this.supermap.miny; y <= this.supermap.maxy; y++){ 51 | var str = ""; 52 | for (var x = this.supermap.minx; x <= this.supermap.maxx; x++){ 53 | if (this.supermap.map[x] && this.supermap.map[x][y]){ 54 | str += (this.supermap.map[x][y]+" ").substring(0,8); 55 | } else { 56 | str += " "; 57 | } 58 | } 59 | console.log((y-this.supermap.miny)+":"+str); 60 | } 61 | console.log("---------"); 62 | }, 63 | addToSuperMap: function(id){ 64 | if (!this.supermap.map[this.supermap.x]){ 65 | this.supermap.map[this.supermap.x] = []; 66 | } 67 | this.supermap.map[this.supermap.x][this.supermap.y] = id; 68 | if (this.supermap.x < this.supermap.minx){ 69 | this.supermap.minx = this.supermap.x; 70 | } 71 | if (this.supermap.y < this.supermap.miny){ 72 | this.supermap.miny = this.supermap.y; 73 | } 74 | if (this.supermap.x > this.supermap.maxx){ 75 | this.supermap.maxx = this.supermap.x; 76 | } 77 | if (this.supermap.y > this.supermap.maxy){ 78 | this.supermap.maxy = this.supermap.y; 79 | } 80 | }, 81 | isClear: function(x, y, dir){ 82 | return !(this.supermap.map[x+dir.x] && this.supermap.map[x+dir.x][y+dir.y]) && 83 | !(this.supermap.map[x+dir.x*2] && this.supermap.map[x+dir.x*2][y+dir.y*2]) 84 | }, 85 | placeTown: function(specs){ 86 | if (this.exits.length === 0){ 87 | // First town 88 | this.supermap.x = 0; 89 | this.supermap.y = 0; 90 | var townId = this.createTown(specs); 91 | this.metadata._startingLevelId = townId; 92 | this.addToSuperMap(townId); 93 | 94 | } else { 95 | while (true){ 96 | var exit = Random.from(this.exits); 97 | var dx = Direction.dxMap[exit.direction]; 98 | if (this.isClear(exit.x, exit.y, dx)){ 99 | break; 100 | } 101 | } 102 | 103 | this.exits.splice(this.exits.indexOf(exit), 1); 104 | 105 | this.supermap.x = exit.x; 106 | this.supermap.y = exit.y; 107 | 108 | var currentMetadata = this.metadata[exit.fromId]; 109 | this.currentRouteId++; 110 | var routeId = "ROUTE_"+this.currentRouteId; 111 | currentMetadata.exits.push({ 112 | dir: exit.direction, 113 | toId: routeId 114 | }); 115 | var orientation = dx.x === 0 ? "VERTICAL" : "HORIZONTAL"; 116 | this.supermap.x += dx.x; 117 | this.supermap.y += dx.y; 118 | this.addToSuperMap(routeId); 119 | 120 | var routeStereotype = Random.from(this.masterPlans.routeStereotypes, true); 121 | 122 | this.metadata[routeId] = { 123 | type: 'ROUTE', 124 | orientation: orientation, 125 | name: 'Route '+this.currentRouteId, 126 | width: orientation === "VERTICAL" ? 32 : 64, 127 | height: orientation === "VERTICAL" ? 64 : 32, 128 | initialPopulation: 5, 129 | wildMonsters: routeStereotype.monsters, 130 | exits: [ 131 | { 132 | dir: Direction.opposite[exit.direction], 133 | toId: exit.fromId 134 | } 135 | ] 136 | } 137 | this.metadata[routeId].wildMonsters = this.metadata[routeId].wildMonsters.map(function(spec){ 138 | return { 139 | race: spec.race, 140 | level: this.currentRouteId * 3 + (spec.levelBoost ? spec.levelBoost : 0), 141 | weight: spec.weight 142 | } 143 | }, this); 144 | this.supermap.x += dx.x; 145 | this.supermap.y += dx.y; 146 | var townId = this.createTown(specs, Direction.opposite[exit.direction], routeId); 147 | this.metadata[routeId].exits.push({ 148 | dir: exit.direction, 149 | toId: townId 150 | }); 151 | this.addToSuperMap(townId); 152 | } 153 | }, 154 | createTown: function(specs, fromDir, fromId){ 155 | // Creates a town and puts this.exits 156 | this.currentTownId++; 157 | var townId = "TOWN_"+this.currentTownId; 158 | var townName = TownNameGen.generateName()+(specs.hasGym?" City":" Town");; 159 | var metadata = { 160 | type: 'TOWN', 161 | name: townName, 162 | width: 48, 163 | height: 48, 164 | exits: [] 165 | } 166 | this.metadata[townId] = metadata; 167 | metadata.features = []; 168 | if (specs.starting){ 169 | metadata.startPosition = { 170 | x: 16, 171 | y: 16 172 | }; 173 | metadata.features.push( 174 | { // Hero's house 175 | type: 'myHouse' 176 | } 177 | ); 178 | metadata.features.push( 179 | { // Oak's Lab 180 | type: 'lab', 181 | monsters: this.masterPlans.startingMonsters, 182 | x: 1, 183 | y: 1 184 | } 185 | ); 186 | metadata.features.push( 187 | { 188 | // Rival's house (empty) 189 | type: 'house' 190 | } 191 | ); 192 | } 193 | var houses = Random.n(0,3); 194 | for (var i = 0; i < houses; i++){ 195 | metadata.features.push( 196 | { 197 | type: 'house' 198 | } 199 | ); 200 | } 201 | if (specs.hasGym){ //TODO: Separate hasMart 202 | var maxTier = Math.floor(this.currentTownId / (this.masterPlans.cities+this.masterPlans.towns)) * 4; 203 | if (maxTier < 1) 204 | maxTier = 1; 205 | metadata.features.push( 206 | { 207 | type: 'mart', 208 | items: this.masterPlans.items.filter(function(itemDef){return itemDef.tier <= maxTier;}) 209 | } 210 | ); 211 | metadata.features.push( 212 | { 213 | type: 'hospital' 214 | } 215 | ); 216 | } 217 | if (Random.chance(10)){ 218 | metadata.features.push( 219 | { 220 | type: 'pond' 221 | } 222 | ); 223 | } 224 | if (specs.starting){ 225 | // A single exit for the starting town 226 | this.exits.push({ 227 | x: this.supermap.x, 228 | y: this.supermap.y, 229 | direction: "UP", 230 | fromId: townId 231 | }); 232 | } else { 233 | for (var i = 0; i < Direction.CARDINALS.length; i++){ 234 | if (fromDir === Direction.CARDINALS[i]){ 235 | continue; 236 | } 237 | this.exits.push({ 238 | x: this.supermap.x, 239 | y: this.supermap.y, 240 | direction: Direction.CARDINALS[i], 241 | fromId: townId 242 | }); 243 | } 244 | } 245 | if (fromDir){ 246 | metadata.exits.push({ 247 | dir: fromDir, 248 | toId: fromId 249 | }); 250 | } 251 | 252 | if (specs.hasGym){ 253 | this.currentGymId++; 254 | var gymId = "GYM" + this.currentGymId; 255 | metadata.features.push( 256 | { 257 | type: 'gym', 258 | name: townName+" Gym", 259 | toId: gymId 260 | } 261 | ); 262 | var gymStereotype = Random.fromObject(this.masterPlans.gymStereotypes, true); 263 | var numberOfMonsters = Random.n(3,4); 264 | var gymMonsters = []; 265 | for (var i = 0; i < numberOfMonsters; i++){ 266 | var monsterSpec = Random.fromWeighted(gymStereotype.monsters); 267 | var monsterRace = monsterSpec.race; 268 | var levelBoost = monsterSpec.levelBoost ? monsterSpec.levelBoost : 0; 269 | levelBoost += Random.n(0,3); 270 | gymMonsters.push({ 271 | race: monsterRace, 272 | level: this.currentGymId * 5 + 5 + levelBoost 273 | }); 274 | } 275 | 276 | var gymMetadata = { 277 | type: 'GYM', 278 | name: townName+" Gym", 279 | width: 24, 280 | height: 24, 281 | exits: [ 282 | { 283 | dir: "DOWN", 284 | toId: townId 285 | } 286 | ], 287 | trainer: { 288 | race: NPCRaces.GYM_LEADER, 289 | monsters: gymMonsters 290 | }, 291 | gymNumber: this.currentGymId, 292 | badge: gymStereotype.badge 293 | } 294 | this.metadata[gymId] = gymMetadata; 295 | } 296 | return townId; 297 | } 298 | } -------------------------------------------------------------------------------- /src/js/LevelGenerator.js: -------------------------------------------------------------------------------- 1 | var Tiles = require('./Tiles.enum'); 2 | var NPCRaces = require('./NPCRaces.enum'); 3 | var Items = require('./Items.enum'); 4 | var Being = require('./Being.class'); 5 | var Item = require('./Item.class'); 6 | var CA = require('./ca/CA'); 7 | var Rule = require('./ca/Rule.class'); 8 | var Random = require('./Random'); 9 | var Direction = require('./util/Direction'); 10 | 11 | module.exports = { 12 | generateTestLevel: function(level, specs){ 13 | this.level = level; 14 | this.level.name = specs.name; 15 | switch (specs.type){ 16 | case "TOWN": 17 | this.basicFill(level, specs); 18 | this.buildTown(level, specs); 19 | break; 20 | case "ROUTE": 21 | this.basicFill(level, specs); 22 | this.buildRoute(level, specs); 23 | break; 24 | case "GYM": 25 | this.buildGym(level, specs); 26 | break; 27 | } 28 | if (specs.wildMonsters){ 29 | level.initialPopulation = specs.initialPopulation; 30 | level.wildMonsters = specs.wildMonsters; 31 | level.respawnMonsters(); 32 | } 33 | for (var i = 0; i < specs.exits.length; i++){ 34 | var xs = Math.round(specs.width/2); 35 | var ys = Math.round(specs.height/2); 36 | var exit = specs.exits[i]; 37 | switch (exit.dir){ 38 | case 'UP': 39 | ys = 0; 40 | break; 41 | case 'DOWN': 42 | ys = specs.height-1; 43 | break; 44 | case 'LEFT': 45 | xs = 0; 46 | break; 47 | case 'RIGHT': 48 | xs = specs.width-1; 49 | break; 50 | } 51 | var dx = Direction.dxMap[Direction.opposite[exit.dir]]; 52 | switch (specs.type){ 53 | case "TOWN": 54 | level.map[xs][ys] = Tiles.GRASS; 55 | level.map[xs+dx.x][ys+dx.y] = Tiles.GRASS; 56 | if (dx.x == 0){ 57 | level.map[xs+1][ys] = Tiles.FENCE; 58 | level.map[xs-1][ys] = Tiles.FENCE; 59 | } else { 60 | level.map[xs][ys+1] = Tiles.FENCE; 61 | level.map[xs][ys-1] = Tiles.FENCE; 62 | } 63 | break; 64 | case "ROUTE": 65 | case "GYM": 66 | level.map[xs][ys] = Tiles.STAIRS_DOWN; 67 | break; 68 | } 69 | 70 | 71 | level.addExit(xs, ys, exit.toId); 72 | } 73 | if (specs.startPosition){ 74 | level.player.x = specs.startPosition.x; 75 | level.player.y = specs.startPosition.y; 76 | } 77 | }, 78 | CA_GRASS_RULES: [ 79 | new Rule(Tiles.GRASS, Rule.MORE_THAN, 2, Tiles.TALL_GRASS, Tiles.TALL_GRASS, 50) 80 | ], 81 | basicFill: function(level, specs){ 82 | for (var x = 0; x < specs.width; x++){ 83 | level.map[x] = []; 84 | for (var y = 0; y < specs.height; y++){ 85 | level.map[x][y] = Tiles.GRASS; 86 | } 87 | } 88 | for (var i = 0; i < 40; i++){ 89 | level.map[Random.n(0,specs.width-1)][Random.n(0,specs.height-1)] = Tiles.BUSH; 90 | } 91 | }, 92 | buildRoute: function(level, specs){ 93 | // Place a road based on the orientation and some patches of grass 94 | switch (specs.orientation){ 95 | case "HORIZONTAL": 96 | var midy = Math.round(specs.height/2); 97 | for (var x = 0; x < specs.width; x++){ 98 | for (var y = midy-5; y < midy + 2; y++){ 99 | this.level.map[x][y] = Tiles.ROAD; 100 | } 101 | } 102 | break; 103 | case "VERTICAL": 104 | var midx = Math.round(specs.width/2); 105 | for (var x = midx-5; x < midx+5; x++){ 106 | for (var y = 0; y < specs.height; y++){ 107 | this.level.map[x][y] = Tiles.ROAD; 108 | } 109 | } 110 | break; 111 | } 112 | var patches = Random.n(3,10); 113 | for (var i = 0; i < patches; i++){ 114 | var w = Random.n(8,10); 115 | var h = Random.n(8,10); 116 | var x = Random.n(0,specs.width - w - 1); 117 | var y = Random.n(0,specs.height - h - 1); 118 | for (var xx = x; xx < x + w; xx++){ 119 | for (var yy = y; yy < y + h; yy++){ 120 | if (Random.chance(80)){ 121 | this.level.map[xx][yy] = Tiles.TALL_GRASS; 122 | } 123 | if (xx > x + 3 && xx < x + w - 3 && yy > y + 3 && yy < y + h - 3 && Random.chance(30)){ 124 | this.level.spawnPositions.push({x: xx, y: yy}); 125 | } 126 | } 127 | } 128 | } 129 | // Run a Non deterministic CA to smooth patches of grass 130 | CA.runCA(level.map, this.CA_GRASS_RULES, 4); 131 | }, 132 | buildTown: function(level, specs){ 133 | // Place the borders 134 | for (var xx = 1; xx < specs.width - 1; xx++){ 135 | this.level.map[xx][1] = Tiles.FENCE; 136 | this.level.map[xx][specs.height - 2] = Tiles.FENCE; 137 | } 138 | for (var yy = 1; yy < specs.height - 1; yy++){ 139 | this.level.map[1][yy] = Tiles.FENCE; 140 | this.level.map[specs.width - 2][yy] = Tiles.FENCE; 141 | } 142 | 143 | // Place houses based on specs on a grid 144 | var grid = []; 145 | var gridx = Math.floor((specs.width - 6) / 10); 146 | var gridy = Math.floor((specs.height - 6) / 10); 147 | for (var i = 0; i < gridx; i++){ 148 | grid[i] = []; 149 | } 150 | for (var i = 0; i < specs.features.length; i++){ 151 | var feature = specs.features[i]; 152 | var x = feature.x; 153 | var y = feature.y; 154 | var w = feature.w; 155 | var h = feature.h; 156 | if (!w) 157 | w = Random.n(7,9); 158 | if (!h) 159 | h = Random.n(7,9); 160 | if (!x){ 161 | x = Random.n(0,gridx-1); 162 | } 163 | if (!y){ 164 | y = Random.n(0,gridy-1); 165 | } 166 | while (grid[x][y]){ 167 | x = Random.n(0,gridx-1); 168 | y = Random.n(0,gridy-1); 169 | } 170 | grid[x][y] = true; 171 | x = x * 10 + Random.n(0,10-w-1) + 3; 172 | y = y * 10 + Random.n(0,10-h-1) + 3; 173 | this.fillFeature(feature, x, y, w, h); 174 | } 175 | }, 176 | fillFeature: function(feature, x, y, w, h){ 177 | switch (feature.type){ 178 | case 'myHouse': 179 | this.fillMyHouse(x,y,w,h); 180 | break; 181 | case 'lab': 182 | this.fillLab(x,y,w,h, feature.monsters); 183 | break; 184 | case 'house': 185 | this.fillHouse(x,y,w,h); 186 | break; 187 | case 'pond': 188 | this.fillPond(x,y,w,h); 189 | break; 190 | case 'mart': 191 | this.fillMart(x,y,w,h, feature.items); 192 | break; 193 | case 'hospital': 194 | this.fillHospital(x,y,w,h); 195 | break; 196 | case 'gym': 197 | this.fillGym(x,y,w,h, feature); 198 | break; 199 | } 200 | }, 201 | fillHouse: function(x, y, w, h){ 202 | for (var xx = x; xx < x + w; xx++){ 203 | for (var yy = y; yy < y + h; yy++){ 204 | if (xx === x || xx === x + w - 1 || yy === y || yy === y + h - 1){ 205 | this.level.map[xx][yy] = Tiles.WALL; 206 | } else { 207 | this.level.map[xx][yy] = Tiles.FLOOR; 208 | } 209 | } 210 | } 211 | // Place door 212 | var xd = x + 2 + Random.n(0, w - 4); 213 | var yd = y + 2 + Random.n(0, h - 4); 214 | switch (Random.n(0,3)){ 215 | case 0: 216 | yd = y; 217 | break; 218 | case 1: 219 | yd = y+h-1; 220 | break; 221 | case 2: 222 | xd = x; 223 | break; 224 | case 3: 225 | xd = x+w-1; 226 | break; 227 | } 228 | this.level.map[xd][yd] = Tiles.FLOOR; 229 | }, 230 | fillLab: function(x, y, w, h, monsters){ 231 | this.fillHouse(x,y,w,h); 232 | monsters.forEach(function(monster){ 233 | while (true){ 234 | var xx = Random.n(x+1, x + w -2); 235 | var yy = Random.n(y+1, y + h -2); 236 | if (!this.level.getBeing(xx, yy) && !this.level.getItem(xx, yy)){ 237 | break; 238 | } 239 | } 240 | this.level.addItem(new Item(monster), xx, yy); 241 | }, this); 242 | while (true){ 243 | var xx = Random.n(x+1, x + w -2); 244 | var yy = Random.n(y+1, y + h -2); 245 | if (!this.level.getItem(xx, yy)){ 246 | break; 247 | } 248 | } 249 | var proffessor = new Being(this.level.game, this.level, NPCRaces.PROFFESSOR); 250 | this.level.addBeing(proffessor, xx, yy); 251 | proffessor.isTrainer = true; 252 | proffessor.intent = 'STILL'; 253 | }, 254 | fillMyHouse: function(x, y, w, h){ 255 | this.fillHouse(x,y,w,h); 256 | }, 257 | fillPond: function(x, y, w, h){ 258 | for (var xx = x; xx < x + w; xx++){ 259 | for (var yy = y; yy < y + h; yy++){ 260 | this.level.map[xx][yy] = Tiles.WATER; 261 | } 262 | } 263 | }, 264 | _fillWithCounter: function(x, y, w, h, clerk){ 265 | for (var xx = x; xx < x + w; xx++){ 266 | for (var yy = y; yy < y + h; yy++){ 267 | if (xx === x || xx === x + w - 1 || yy === y || yy === y + h - 1){ 268 | this.level.map[xx][yy] = Tiles.WALL; 269 | } else { 270 | this.level.map[xx][yy] = Tiles.FLOOR; 271 | } 272 | } 273 | } 274 | // Place door, counter and clerk 275 | var xd = x + 2 + Random.n(0, w - 4); 276 | var yd = y + 2 + Random.n(0, h - 4); 277 | switch (Random.n(0,3)){ 278 | case 0: 279 | yd = y; 280 | break; 281 | case 1: 282 | yd = y+h-1; 283 | break; 284 | case 2: 285 | xd = x; 286 | break; 287 | case 3: 288 | xd = x+w-1; 289 | break; 290 | } 291 | this.level.map[xd][yd] = Tiles.FLOOR; 292 | var cx = xd; 293 | var cy = yd; 294 | var itemAreaBounds = { 295 | x1: x+1, 296 | y1: y+1, 297 | x2: x+w-2, 298 | y2: y+h-2, 299 | }; 300 | if (xd === x){ 301 | cx = x + w - 2; 302 | for (var yy = y+1; yy < y + h -1; yy++){ 303 | this.level.map[x + w - 3][yy] = Tiles.V_COUNTER; 304 | } 305 | itemAreaBounds.x2 = x + w - 4; 306 | } else if (xd === x + w - 1){ 307 | cx = x + 1; 308 | for (var yy = y+1; yy < y + h -1; yy++){ 309 | this.level.map[x + 2][yy] = Tiles.V_COUNTER; 310 | } 311 | itemAreaBounds.x1 = x + 3; 312 | } else if (yd === y){ 313 | cy = y + h - 2; 314 | for (var xx = x + 1; xx < x + w - 1; xx++){ 315 | this.level.map[xx][y+h-3] = Tiles.H_COUNTER; 316 | } 317 | itemAreaBounds.y2 = y + h - 4; 318 | } else { 319 | cy = y + 1; 320 | for (var xx = x + 1; xx < x + w - 1; xx++){ 321 | this.level.map[xx][y+2] = Tiles.H_COUNTER; 322 | } 323 | itemAreaBounds.y1 = y + 3; 324 | } 325 | this.level.addBeing(new Being(this.level.game, this.level, clerk), cx, cy); 326 | return itemAreaBounds; 327 | }, 328 | fillHospital: function(x, y, w, h){ 329 | this._fillWithCounter(x, y, w, h, NPCRaces.NURSE); 330 | }, 331 | fillMart: function(x, y, w, h, items){ 332 | var itemAreaBounds = this._fillWithCounter(x, y, w, h, NPCRaces.STORE_CLERK) 333 | // Let's make this Nethack style lol! 334 | for (var xx = itemAreaBounds.x1; xx <= itemAreaBounds.x2; xx++){ 335 | for (var yy = itemAreaBounds.y1; yy <= itemAreaBounds.y2; yy++){ 336 | if (Random.chance(80)){ 337 | this.level.addItem(new Item(Random.fromWeighted(items).item, true), xx, yy); 338 | } 339 | this.level.storePlaces.push({x: xx, y: yy}); 340 | } 341 | } 342 | }, 343 | fillGym: function(x, y, w, h, feature){ 344 | for (var xx = x; xx < x + w; xx++){ 345 | for (var yy = y; yy < y + h; yy++){ 346 | this.level.map[xx][yy] = Tiles.WALL; 347 | } 348 | } 349 | // Place door 350 | var xd = x + 2 + Random.n(0, w - 4); 351 | var yd = y + 2 + Random.n(0, h - 4); 352 | switch (Random.n(0,3)){ 353 | case 0: 354 | yd = y; 355 | break; 356 | case 1: 357 | yd = y+h-1; 358 | break; 359 | case 2: 360 | xd = x; 361 | break; 362 | case 3: 363 | xd = x+w-1; 364 | break; 365 | } 366 | this.level.map[xd][yd] = Tiles.GYM_ENTRANCE; 367 | this.level.addExit(xd, yd, feature.toId); 368 | this.level.gymInfo = feature; 369 | }, 370 | buildGym: function(level, specs){ 371 | for (var x = 0; x < specs.width; x++){ 372 | level.map[x] = []; 373 | for (var y = 0; y < specs.height; y++){ 374 | level.map[x][y] = Tiles.FLOOR; 375 | } 376 | } 377 | var trainer = new Being(level.game, level, specs.trainer.race); 378 | level.addBeing(trainer, Math.floor(specs.width/2), 3); 379 | trainer.intent = 'TRAINER'; 380 | trainer.isTrainer = true; 381 | trainer.monsters = specs.trainer.monsters.map(function(monsterSpec){ 382 | var being = new Being(level.game, level, monsterSpec.race, monsterSpec.level); 383 | being.isTame = true; 384 | being.owner = trainer; 385 | return being; 386 | }, this); 387 | trainer.prize = specs.badge; 388 | trainer.prizeMoney = specs.gymNumber * 1500; 389 | }, 390 | 391 | } -------------------------------------------------------------------------------- /src/js/Player.js: -------------------------------------------------------------------------------- 1 | var Tiles = require('./Tiles.enum'); 2 | var Being = require('./Being.class'); 3 | 4 | module.exports = { 5 | badgesCount: 0, 6 | MAX_SIGHT_RANGE: 10, 7 | x: 20, 8 | y: 20, 9 | tile: new ut.Tile('@', 255, 255, 255), 10 | visible: [], 11 | memory: {}, 12 | items: [], 13 | monsterSlots: [], 14 | observedMonsters: [], 15 | addMonster: function(being, slotNumber){ 16 | if (slotNumber && this.monsterSlots[slotNumber]){ 17 | this.game.display.message("Cannot put monster in that slot"); 18 | return; 19 | } 20 | if (slotNumber === undefined){ 21 | slotNumber = this.getAvailableSlotNumber(); 22 | } 23 | if (slotNumber === undefined){ 24 | this.game.display.message("No slots available."); 25 | return; 26 | } 27 | being.isFriendly = true; 28 | this.monsterSlots[slotNumber] = { 29 | being: being, 30 | onPocket: true 31 | } 32 | being.isTame = true; 33 | being.slotNumber = slotNumber; 34 | }, 35 | getAvailableSlotNumber: function(){ 36 | for (var i = 0; i < 6; i++){ 37 | if (!this.game.player.monsterSlots[i]){ 38 | return i; 39 | } 40 | } 41 | return false; 42 | }, 43 | init: function(game){ 44 | this.game = game; 45 | for (var j = -this.MAX_SIGHT_RANGE; j <= this.MAX_SIGHT_RANGE; j++){ 46 | this.visible[j] = []; 47 | } 48 | }, 49 | tryMove: function(dir){ 50 | if (!this.game.world.level.canWalkTo(this.x+dir.x, this.y+dir.y)){ 51 | // Check for NPCs 52 | var npc = this.game.world.level.getBeing(this.x+dir.x, this.y+dir.y); 53 | if (npc && npc.race.interact){ 54 | this.game.input.inputEnabled = true; 55 | npc.race.interact(this); 56 | return; 57 | } 58 | // Check for counters 59 | var tile = this.game.world.level.getCell(this.x+dir.x, this.y+dir.y); 60 | if (tile && tile.isCounter){ 61 | // Check for NPC further 62 | var npc = this.game.world.level.getBeing(this.x+dir.x*2, this.y+dir.y*2); 63 | if (npc && npc.race.interact){ 64 | this.game.input.inputEnabled = true; 65 | npc.race.interact(this); 66 | return; 67 | } 68 | } 69 | this.game.input.inputEnabled = true; 70 | return; 71 | } 72 | this.x += dir.x; 73 | this.y += dir.y; 74 | this.land(); 75 | }, 76 | die: function(){ 77 | this.game.display.message("You die!"); 78 | this.game.input.mode = 'NONE'; 79 | }, 80 | land: function(){ 81 | if (this.game.world.level.map[this.x][this.y] === Tiles.GYM_ENTRANCE && !this.game.world.level.gymComplete){ 82 | this.game.display.message("Are you ready to challenge "+this.game.world.level.gymInfo.name+"? [Y/N]"); 83 | this.game.input.mode = "PROMPT"; 84 | this.game.input.promptFunction = this.confirmChallengeGym.bind(this); 85 | this.endTurn(); 86 | } else if (!this.gymTown && this.game.world.level.exits[this.x] && this.game.world.level.exits[this.x][this.y]){ 87 | this.game.world.loadLevel(this.game.world.level.exits[this.x][this.y]); 88 | this.endTurn(); 89 | } else { 90 | this.endTurn(); 91 | } 92 | }, 93 | confirmChallengeGym: function(confirm){ 94 | if (confirm){ 95 | this.gymTown = this.game.world.level; 96 | this.game.world.loadLevel(this.game.world.level.gymInfo.toId); 97 | this.endTurn(); 98 | } else { 99 | this.game.display.message("Some other time."); 100 | } 101 | this.game.input.mode = "MOVEMENT"; 102 | }, 103 | endTurn: function(){ 104 | this.game.display.textBox.checkFaint(); 105 | this.updateFOV(); 106 | this.game.display.refresh(); 107 | for (var i = 0; i < this.monsterSlots.length; i++){ 108 | var slot = this.monsterSlots[i]; 109 | if (slot && slot.onPocket){ 110 | slot.being.endTurn(); 111 | } 112 | } 113 | this.game.world.level.beingsTurn(); 114 | }, 115 | remember: function(x, y){ 116 | var memory = this.memory[this.game.world.level.id]; 117 | if (!memory){ 118 | memory = []; 119 | this.memory[this.game.world.level.id] = memory; 120 | } 121 | if (!memory[x]){ 122 | memory[x] = []; 123 | } 124 | memory[x][y] = true; 125 | }, 126 | remembers: function(x, y){ 127 | var memory = this.memory[this.game.world.level.id]; 128 | if (!memory){ 129 | return false; 130 | } 131 | if (!memory[x]){ 132 | return false; 133 | } 134 | return memory[x][y] === true; 135 | }, 136 | canSee: function(dx, dy){ 137 | try { 138 | return this.visible[dx][dy] === true; 139 | } catch(err) { 140 | // Catch OOB 141 | return false; 142 | } 143 | }, 144 | getSightRange: function(){ 145 | return 15; 146 | }, 147 | updateFOV: function(){ 148 | /* 149 | * This function uses simple raycasting, 150 | * use something better for longer ranges 151 | * or increased performance 152 | */ 153 | for (var j = -this.MAX_SIGHT_RANGE; j <= this.MAX_SIGHT_RANGE; j++) 154 | for (var i = -this.MAX_SIGHT_RANGE; i <= this.MAX_SIGHT_RANGE; i++) 155 | this.visible[i][j] = false; 156 | this.observedMonsters.length = 0; 157 | var step = Math.PI * 2.0 / 1080; 158 | for (var a = 0; a < Math.PI * 2; a += step) 159 | this.shootRay(a); 160 | }, 161 | shootRay: function (a) { 162 | var rayStrength = 2; 163 | var step = 0.3333; 164 | var maxdist = this.getSightRange() < this.MAX_SIGHT_RANGE ? this.getSightRange() : this.MAX_SIGHT_RANGE; 165 | maxdist /= step; 166 | rayStrength /= step; 167 | var dx = Math.cos(a) * step; 168 | var dy = -Math.sin(a) * step; 169 | var xx = this.x, yy = this.y; 170 | for (var i = 0; i < maxdist; ++i) { 171 | var testx = Math.round(xx); 172 | var testy = Math.round(yy); 173 | this.visible[testx-this.x][testy-this.y] = true; 174 | this.remember(testx, testy); 175 | var monster = this.game.world.level.getBeing(testx, testy); 176 | if (monster && !monster.isFriendly){ 177 | if (this.observedMonsters.indexOf(monster) === -1){ 178 | this.observedMonsters.push(monster); 179 | } 180 | } 181 | try { 182 | if (this.game.world.level.map[testx][testy].opaque){ 183 | return; 184 | } else if (this.game.world.level.map[testx][testy].semiopaque){ 185 | rayStrength --; 186 | if (rayStrength < 0){ 187 | return; 188 | } 189 | } 190 | } catch(err) { 191 | // Catch OOB 192 | return; 193 | } 194 | xx += dx; yy += dy; 195 | } 196 | }, 197 | canPick: function(){ 198 | return this.items.length < 24; 199 | }, 200 | addItem: function(item){ 201 | if (this.items.length === 24){ 202 | return; 203 | } 204 | this.items.push(item); 205 | this.items.sort(this.itemSorter); 206 | }, 207 | removeItem: function(item){ 208 | this.items.splice(this.items.indexOf(item), 1); 209 | this.items.sort(this.itemSorter); 210 | }, 211 | itemSorter: function(a, b){ 212 | if (a.def.type.name === b.def.type.name){ 213 | return a.def.name > b.def.name ? 1 : -1; 214 | } else { 215 | return a.def.type.name > b.def.type.name ? 1 : -1; 216 | } 217 | }, 218 | tryPickup: function(){ 219 | var item = this.game.world.level.getItem(this.x, this.y); 220 | if (item){ 221 | if (item.forSale){ 222 | this.game.display.message("This "+item.def.name+" costs "+item.def.cost+". Do you want to buy it? [Y/N]"); 223 | this.game.input.mode = "PROMPT"; 224 | this.buyingItem = item; 225 | this.game.input.promptFunction = this.confirmBuy.bind(this); 226 | } else if (item.def.type.pickupFunction){ 227 | item.def.type.pickupFunction(this.game, item); 228 | } else if (!this.canPick()){ 229 | this.game.display.message("You can't pickup the "+item.def.name); 230 | } else { 231 | this.game.display.message("You pickup the "+item.def.name); 232 | this.game.world.level.removeItem(this.x, this.y); 233 | this.addItem(item); 234 | } 235 | } 236 | }, 237 | confirmBuy: function(confirm){ 238 | if (confirm){ 239 | if (!this.canPick()) 240 | this.game.display.message("You can't pickup the "+this.buyingItem.def.name); 241 | else if (this.money >= this.buyingItem.def.cost){ 242 | this.money -= this.buyingItem.def.cost; 243 | this.game.display.message("Thank you!"); 244 | this.game.world.level.removeItem(this.x, this.y); 245 | this.addItem(this.buyingItem); 246 | } else { 247 | this.game.display.message("Sorry, you can't afford it."); 248 | } 249 | } else { 250 | this.game.display.message("Alright"); 251 | } 252 | this.game.input.mode = "MOVEMENT"; 253 | this.game.player.endTurn(); 254 | }, 255 | tryDrop: function(item){ 256 | var underItem = this.game.world.level.items[this.x] && this.game.world.level.items[this.x][this.y]; 257 | if (underItem){ 258 | this.game.display.message("Cannot drop the "+item.def.name+" here."); 259 | } else { 260 | this.game.world.level.addItem(item, this.x, this.y); 261 | this.removeItem(item); 262 | this.game.display.message("You drop the "+item.def.name+"."); 263 | } 264 | }, 265 | tryUse: function(item, dx, dy){ 266 | item.def.type.useFunction(this.game, item, dx, dy); 267 | }, 268 | releaseMonster: function(dir){ 269 | if (this.gymTown){ 270 | if (this.releasedMonster){ 271 | this.game.display.message("You are on a one on one duel."); 272 | return; 273 | } else { 274 | this.releasedMonster = true; 275 | } 276 | } 277 | var slot = this.monsterSlots[this.game.input.selectedMonsterSlot]; 278 | if (this.game.world.level.canWalkTo(this.x + dir.x, this.y + dir.y) && 279 | this.game.world.level.canWalkTo(this.x + dir.x * 2, this.y + dir.y * 2)){ 280 | slot.onPocket = false; 281 | slot.being.level = this.game.world.level; 282 | this.game.world.level.addBeing(slot.being, this.x + dir.x * 2, this.y + dir.y * 2); 283 | this.game.display.message("Go "+slot.being.race.name+"!"); 284 | this.endTurn(); 285 | } else { 286 | this.game.display.message("No space!"); 287 | } 288 | }, 289 | pullBack: function(){ 290 | if (this.gymTown){ 291 | this.releasedMonster = false; 292 | } 293 | var slot = this.monsterSlots[this.game.input.selectedMonsterSlot]; 294 | slot.onPocket = true; 295 | this.game.world.level.removeBeing(slot.being); 296 | this.game.display.message(slot.being.race.name+"... come back."); 297 | this.endTurn(); 298 | }, 299 | pullBackAll: function(){ 300 | for (var i = 0; i < this.monsterSlots.length; i++){ 301 | var slot = this.monsterSlots[i]; 302 | if (slot && !slot.onPocket){ 303 | slot.onPocket = true; 304 | this.game.world.level.removeBeing(slot.being); 305 | } 306 | } 307 | }, 308 | healAll: function(){ 309 | for (var i = 0; i < this.monsterSlots.length; i++){ 310 | var slot = this.monsterSlots[i]; 311 | if (slot && slot.being){ 312 | slot.being.heal(); 313 | } 314 | } 315 | this.hp.replenish(); 316 | }, 317 | getMonster: function(race){ 318 | if (this.getAvailableSlotNumber() === false){ 319 | this.game.display.message("You don't have any monster slots available"); 320 | return; 321 | } 322 | if (this.pickedStarter){ 323 | this.game.display.message("You already picked your starter monsters."); 324 | return; 325 | } 326 | 327 | this.game.display.message("Do you want to pick "+race.name+"? [Y/N]"); 328 | this.game.input.mode = "PROMPT"; 329 | this.game.input.promptFunction = function(confirm){ 330 | if (confirm){ 331 | var monster = new Being(this.game, false, race, 5); 332 | this.addMonster(monster); 333 | this.game.display.message(race.name+" joins!"); 334 | this.game.world.level.removeItem(this.x, this.y); 335 | this.game.input.selectAvailableMonsterSlot(); 336 | this.pickedStarter = true; 337 | } else { 338 | this.game.display.message(" N."); 339 | } 340 | this.endTurn(); 341 | this.game.input.mode = "MOVEMENT"; 342 | }.bind(this); 343 | }, 344 | giveUpGymBattle: function(){ 345 | if (!this.gymTown){ 346 | return; 347 | } 348 | this.game.display.message("Do you want to give up this gym battle? [Y/N] "); 349 | this.game.input.mode = "PROMPT"; 350 | this.game.input.promptFunction = function(confirm){ 351 | if (confirm){ 352 | this.game.display.message("Better luck next time!"); 353 | this.gymTown = false; 354 | } else { 355 | this.game.display.message("Keep fighting!"); 356 | } 357 | this.endTurn(); 358 | this.game.input.mode = "MOVEMENT"; 359 | }.bind(this); 360 | } 361 | } -------------------------------------------------------------------------------- /static-lib/unicodetiles/ut.WebGLRenderer.js: -------------------------------------------------------------------------------- 1 | /*global ut */ 2 | 3 | /// Class: WebGLRenderer 4 | /// Renders the with WebGL. 5 | /// Given decent GPU drivers and browser support, this is the fastest renderer. 6 | /// 7 | /// *Note:* This is an internal class used by 8 | ut.WebGLRenderer = function(view) { 9 | "use strict"; 10 | this.view = view; 11 | this.canvas = document.createElement("canvas"); 12 | // Try to fetch the context 13 | if (!this.canvas.getContext) throw("Canvas not supported"); 14 | this.gl = this.canvas.getContext("experimental-webgl"); 15 | if (!this.gl) throw("WebGL not supported"); 16 | var gl = this.gl; 17 | view.elem.appendChild(this.canvas); 18 | 19 | this.charMap = {}; 20 | this.charArray = []; 21 | this.defaultColors = { r: 1.0, g: 1.0, b: 1.0, br: 0.0, bg: 0.0, bb: 0.0 }; 22 | 23 | this.attribs = { 24 | position: { buffer: null, data: null, itemSize: 2, location: null, hint: gl.STATIC_DRAW }, 25 | texCoord: { buffer: null, data: null, itemSize: 2, location: null, hint: gl.STATIC_DRAW }, 26 | color: { buffer: null, data: null, itemSize: 3, location: null, hint: gl.DYNAMIC_DRAW }, 27 | bgColor: { buffer: null, data: null, itemSize: 3, location: null, hint: gl.DYNAMIC_DRAW }, 28 | charIndex: { buffer: null, data: null, itemSize: 1, location: null, hint: gl.DYNAMIC_DRAW } 29 | }; 30 | 31 | function insertQuad(arr, i, x, y, w, h) { 32 | var x1 = x, y1 = y, x2 = x + w, y2 = y + h; 33 | arr[ i] = x1; arr[++i] = y1; 34 | arr[++i] = x2; arr[++i] = y1; 35 | arr[++i] = x1; arr[++i] = y2; 36 | arr[++i] = x1; arr[++i] = y2; 37 | arr[++i] = x2; arr[++i] = y1; 38 | arr[++i] = x2; arr[++i] = y2; 39 | } 40 | 41 | this.initBuffers = function() { 42 | var a, attrib, attribs = this.attribs; 43 | var w = this.view.w, h = this.view.h; 44 | // Allocate data arrays 45 | for (a in this.attribs) { 46 | attrib = attribs[a]; 47 | attrib.data = new Float32Array(attrib.itemSize * 6 * w * h); 48 | } 49 | // Generate static data 50 | for (var j = 0; j < h; ++j) { 51 | for (var i = 0; i < w; ++i) { 52 | // Position & texCoords 53 | var k = attribs.position.itemSize * 6 * (j * w + i); 54 | insertQuad(attribs.position.data, k, i * this.tw, j * this.th, this.tw, this.th); 55 | insertQuad(attribs.texCoord.data, k, 0.0, 0.0, 1.0, 1.0); 56 | } 57 | } 58 | // Upload 59 | for (a in this.attribs) { 60 | attrib = attribs[a]; 61 | if (attrib.buffer) gl.deleteBuffer(attrib.buffer); 62 | attrib.buffer = gl.createBuffer(); 63 | gl.bindBuffer(gl.ARRAY_BUFFER, attrib.buffer); 64 | gl.bufferData(gl.ARRAY_BUFFER, attrib.data, attrib.hint); 65 | gl.enableVertexAttribArray(attrib.location); 66 | gl.vertexAttribPointer(attrib.location, attrib.itemSize, gl.FLOAT, false, 0, 0); 67 | } 68 | }; 69 | 70 | // Create an offscreen canvas for rendering text to texture 71 | if (!this.offscreen) 72 | this.offscreen = document.createElement("canvas"); 73 | this.offscreen.style.position = "absolute"; 74 | this.offscreen.style.top = "0px"; 75 | this.offscreen.style.left = "0px"; 76 | this.ctx = this.offscreen.getContext("2d"); 77 | if (!this.ctx) throw "Failed to acquire offscreen canvas drawing context"; 78 | // WebGL drawing canvas 79 | this.updateStyle(); 80 | this.canvas.width = (view.squarify ? this.th : this.tw) * view.w; 81 | this.canvas.height = this.th * view.h; 82 | this.offscreen.width = 0; 83 | this.offscreen.height = 0; 84 | // Doing this again since setting canvas w/h resets the state 85 | this.updateStyle(); 86 | 87 | gl.viewport(0, 0, this.canvas.width, this.canvas.height); 88 | 89 | // Setup GLSL 90 | function compileShader(type, source) { 91 | var shader = gl.createShader(type); 92 | gl.shaderSource(shader, source); 93 | gl.compileShader(shader); 94 | var ok = gl.getShaderParameter(shader, gl.COMPILE_STATUS); 95 | if (!ok) { 96 | var msg = "Error compiling shader: " + gl.getShaderInfoLog(shader); 97 | gl.deleteShader(shader); 98 | throw msg; 99 | } 100 | return shader; 101 | } 102 | var vertexShader = compileShader(gl.VERTEX_SHADER, ut.WebGLRenderer.VERTEX_SHADER); 103 | var fragmentShader = compileShader(gl.FRAGMENT_SHADER, ut.WebGLRenderer.FRAGMENT_SHADER); 104 | var program = gl.createProgram(); 105 | gl.attachShader(program, vertexShader); 106 | gl.attachShader(program, fragmentShader); 107 | gl.linkProgram(program); 108 | gl.deleteShader(vertexShader); 109 | gl.deleteShader(fragmentShader); 110 | var ok = gl.getProgramParameter(program, gl.LINK_STATUS); 111 | if (!ok) { 112 | var msg = "Error linking program: " + gl.getProgramInfoLog(program); 113 | gl.deleteProgram(program); 114 | throw msg; 115 | } 116 | gl.useProgram(program); 117 | 118 | // Get attribute locations 119 | this.attribs.position.location = gl.getAttribLocation(program, "position"); 120 | this.attribs.texCoord.location = gl.getAttribLocation(program, "texCoord"); 121 | this.attribs.color.location = gl.getAttribLocation(program, "color"); 122 | this.attribs.bgColor.location = gl.getAttribLocation(program, "bgColor"); 123 | this.attribs.charIndex.location = gl.getAttribLocation(program, "charIndex"); 124 | 125 | // Setup buffers and uniforms 126 | this.initBuffers(); 127 | var resolutionLocation = gl.getUniformLocation(program, "uResolution"); 128 | gl.uniform2f(resolutionLocation, this.canvas.width, this.canvas.height); 129 | this.tileCountsLocation = gl.getUniformLocation(program, "uTileCounts"); 130 | gl.uniform2f(this.tileCountsLocation, this.view.w, this.view.h); 131 | this.paddingLocation = gl.getUniformLocation(program, "uPadding"); 132 | gl.uniform2f(this.paddingLocation, 0.0, 0.0); 133 | 134 | // Setup texture 135 | //view.elem.appendChild(this.offscreen); // Debug offscreen 136 | var texture = gl.createTexture(); 137 | gl.bindTexture(gl.TEXTURE_2D, texture); 138 | this.cacheChars(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); 139 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 140 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 141 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 142 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 143 | gl.activeTexture(gl.TEXTURE0); 144 | 145 | var _this = this; 146 | setTimeout(function() { _this.updateStyle(); _this.buildTexture(); _this.render(); }, 100); 147 | }; 148 | 149 | 150 | ///////////////// 151 | // Build texture 152 | ut.WebGLRenderer.prototype.buildTexture = function() { 153 | "use strict"; 154 | var gl = this.gl; 155 | var w = this.offscreen.width / (this.tw + this.pad), h = this.offscreen.height / (this.th + this.pad); 156 | // Check if need to resize the canvas 157 | var charCount = this.charArray.length; 158 | if (charCount > Math.floor(w) * Math.floor(h)) { 159 | w = Math.ceil(Math.sqrt(charCount)); 160 | h = w + 2; // Allocate some extra space too 161 | this.offscreen.width = w * (this.tw + this.pad); 162 | this.offscreen.height = h * (this.th + this.pad); 163 | this.updateStyle(); 164 | gl.uniform2f(this.tileCountsLocation, w, h); 165 | } 166 | gl.uniform2f(this.paddingLocation, this.pad / this.offscreen.width, this.pad / this.offscreen.height); 167 | 168 | var c = 0, ch; 169 | var halfGap = 0.5 * this.gap; // Squarification 170 | this.ctx.fillStyle = "#000000"; 171 | this.ctx.fillRect(0, 0, this.offscreen.width, this.offscreen.height); 172 | this.ctx.fillStyle = "#ffffff"; 173 | var tw = this.tw + this.pad; 174 | var th = this.th + this.pad; 175 | var y = 0.5 * th; // Half because textBaseline is middle 176 | for (var j = 0; j < h; ++j) { 177 | var x = this.pad * 0.5; 178 | for (var i = 0; i < w; ++i, ++c) { 179 | ch = this.charArray[c]; 180 | if (ch === undefined) break; 181 | this.ctx.fillText(ch, x + halfGap, y); 182 | x += tw; 183 | } 184 | if (!ch) break; 185 | y += th; 186 | } 187 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.offscreen); 188 | }; 189 | 190 | 191 | /////////////// 192 | // Cache chars 193 | ut.WebGLRenderer.prototype.cacheChars = function(chars, build) { 194 | "use strict"; 195 | if (!this.gl) return; // Nothing to do if not using WebGL renderer 196 | var changed = false; 197 | for (var i = 0; i < chars.length; ++i) { 198 | if (!this.charMap[chars[i]]) { 199 | changed = true; 200 | this.charArray.push(chars[i]); 201 | this.charMap[chars[i]] = this.charArray.length-1; 202 | } 203 | } 204 | 205 | if (changed && build !== false) this.buildTexture(); 206 | }; 207 | 208 | 209 | //////////////// 210 | // Update style 211 | ut.WebGLRenderer.prototype.updateStyle = function(s) { 212 | "use strict"; 213 | s = s || window.getComputedStyle(this.view.elem, null); 214 | this.ctx.font = s.fontSize + "/" + s.lineHeight + " " + s.fontFamily; 215 | this.ctx.textBaseline = "middle"; 216 | this.ctx.fillStyle = "#ffffff"; 217 | this.tw = this.ctx.measureText("M").width; 218 | this.th = parseInt(s.fontSize, 10); 219 | this.gap = this.view.squarify ? (this.th - this.tw) : 0; 220 | if (this.view.squarify) this.tw = this.th; 221 | this.pad = Math.ceil(this.th * 0.2) * 2.0; // Must be even number 222 | var color = s.color.match(/\d+/g); 223 | var bgColor = s.backgroundColor.match(/\d+/g); 224 | this.defaultColors.r = parseInt(color[0], 10) / 255; 225 | this.defaultColors.g = parseInt(color[1], 10) / 255; 226 | this.defaultColors.b = parseInt(color[2], 10) / 255; 227 | this.defaultColors.br = parseInt(bgColor[0], 10) / 255; 228 | this.defaultColors.bg = parseInt(bgColor[1], 10) / 255; 229 | this.defaultColors.bb = parseInt(bgColor[2], 10) / 255; 230 | }; 231 | 232 | ut.WebGLRenderer.prototype.clear = function() { /* No op */ }; 233 | 234 | 235 | ////////// 236 | // Render 237 | ut.WebGLRenderer.prototype.render = function() { 238 | "use strict"; 239 | var gl = this.gl; 240 | gl.clear(gl.COLOR_BUFFER_BIT); 241 | var attribs = this.attribs; 242 | var w = this.view.w, h = this.view.h; 243 | // Create new tile data 244 | var tiles = this.view.buffer; 245 | var defaultColor = this.view.defaultColor; 246 | var defaultBgColor = this.view.defaultBackground; 247 | var newChars = false; 248 | for (var j = 0; j < h; ++j) { 249 | for (var i = 0; i < w; ++i) { 250 | var tile = tiles[j][i]; 251 | var ch = this.charMap[tile.ch]; 252 | if (ch === undefined) { // Auto-cache new characters 253 | this.cacheChars(tile.ch, false); 254 | newChars = true; 255 | ch = this.charMap[tile.ch]; 256 | } 257 | var k = attribs.color.itemSize * 6 * (j * w + i); 258 | var kk = attribs.charIndex.itemSize * 6 * (j * w + i); 259 | var r = tile.r === undefined ? this.defaultColors.r : tile.r / 255; 260 | var g = tile.g === undefined ? this.defaultColors.g : tile.g / 255; 261 | var b = tile.b === undefined ? this.defaultColors.b : tile.b / 255; 262 | var br = tile.br === undefined ? this.defaultColors.br : tile.br / 255; 263 | var bg = tile.bg === undefined ? this.defaultColors.bg : tile.bg / 255; 264 | var bb = tile.bb === undefined ? this.defaultColors.bb : tile.bb / 255; 265 | for (var m = 0; m < 6; ++m) { 266 | var n = k + m * attribs.color.itemSize; 267 | attribs.color.data[n+0] = r; 268 | attribs.color.data[n+1] = g; 269 | attribs.color.data[n+2] = b; 270 | attribs.bgColor.data[n+0] = br; 271 | attribs.bgColor.data[n+1] = bg; 272 | attribs.bgColor.data[n+2] = bb; 273 | attribs.charIndex.data[kk+m] = ch; 274 | } 275 | } 276 | } 277 | // Upload 278 | if (newChars) this.buildTexture(); 279 | gl.bindBuffer(gl.ARRAY_BUFFER, attribs.color.buffer); 280 | gl.bufferData(gl.ARRAY_BUFFER, attribs.color.data, attribs.color.hint); 281 | gl.bindBuffer(gl.ARRAY_BUFFER, attribs.bgColor.buffer); 282 | gl.bufferData(gl.ARRAY_BUFFER, attribs.bgColor.data, attribs.bgColor.hint); 283 | gl.bindBuffer(gl.ARRAY_BUFFER, attribs.charIndex.buffer); 284 | gl.bufferData(gl.ARRAY_BUFFER, attribs.charIndex.data, attribs.charIndex.hint); 285 | 286 | var attrib = this.attribs.position; 287 | gl.drawArrays(gl.TRIANGLES, 0, attrib.data.length / attrib.itemSize); 288 | }; 289 | 290 | 291 | ut.WebGLRenderer.VERTEX_SHADER = [ 292 | "attribute vec2 position;", 293 | "attribute vec2 texCoord;", 294 | "attribute vec3 color;", 295 | "attribute vec3 bgColor;", 296 | "attribute float charIndex;", 297 | "uniform vec2 uResolution;", 298 | "uniform vec2 uTileCounts;", 299 | "uniform vec2 uPadding;", 300 | "varying vec2 vTexCoord;", 301 | "varying vec3 vColor;", 302 | "varying vec3 vBgColor;", 303 | 304 | "void main() {", 305 | "vec2 tileCoords = floor(vec2(mod(charIndex, uTileCounts.x), charIndex / uTileCounts.x));", 306 | "vTexCoord = (texCoord + tileCoords) / uTileCounts;", 307 | "vTexCoord += (0.5 - texCoord) * uPadding;", 308 | "vColor = color;", 309 | "vBgColor = bgColor;", 310 | "vec2 pos = position / uResolution * 2.0 - 1.0;", 311 | "gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);", 312 | "}" 313 | ].join('\n'); 314 | 315 | ut.WebGLRenderer.FRAGMENT_SHADER = [ 316 | "precision mediump float;", 317 | "uniform sampler2D uFont;", 318 | "varying vec2 vTexCoord;", 319 | "varying vec3 vColor;", 320 | "varying vec3 vBgColor;", 321 | 322 | "void main() {", 323 | "vec4 color = texture2D(uFont, vTexCoord);", 324 | "color.rgb = mix(vBgColor, vColor, color.rgb);", 325 | "gl_FragColor = color;", 326 | "}" 327 | ].join('\n'); 328 | -------------------------------------------------------------------------------- /src/js/Being.class.js: -------------------------------------------------------------------------------- 1 | var Random = require('./Random'); 2 | var Distance = require('./Distance'); 3 | var Direction = require('./util/Direction'); 4 | var Stat = require('./Stat.class'); 5 | var Item = require('./Item.class'); 6 | var Effects = require('./monster/Effects.enum'); 7 | var Stats = require('./monster/Stats.enum'); 8 | var TrainerAI = require('./ai/Trainer'); 9 | 10 | function Being(game, level, race, xpLevel){ 11 | this.game = game; 12 | this.race = race; 13 | this.level = level; 14 | this.tile = race.tile; 15 | this.x = null; 16 | this.y = null; 17 | this.counters = []; 18 | this.iv = { 19 | hp: 0, 20 | attack: 0, 21 | defense: 0, 22 | spAttack: 0, 23 | spDefense: 0 24 | }; 25 | 26 | this.ev = { 27 | hp: 1, 28 | attack: 1, 29 | defense: 1, 30 | spAttack: 1, 31 | spDefense: 1 32 | } 33 | 34 | this.speed = new Stat(race.speed); 35 | this.intent = 'CHASE'; 36 | this.isFriendly = false; 37 | this.xpLevel = xpLevel; 38 | this.randomId = Random.n(8,10000); 39 | if (xpLevel){ 40 | this.xp = this.calculateXP(xpLevel); 41 | this.nextLevelXP = this.calculateXP(xpLevel+1); 42 | this.calculateStats(); 43 | } 44 | if (race.skills && xpLevel){ 45 | this.skills = race.skills.filter(function(def){return def.level <= xpLevel;}); 46 | this.skills = this.skills.map(function(val){ 47 | return { 48 | skill: val.skill, 49 | pp: new Stat(val.skill.pp) 50 | }; 51 | }); 52 | if (race.parentRace){ 53 | var newSkills = race.parentRace.skills.filter(function(def){return def.level <= xpLevel;}); 54 | newSkills = newSkills.map(function(val){ 55 | return { 56 | skill: val.skill, 57 | pp: new Stat(val.skill.pp) 58 | }; 59 | }); 60 | this.skills = this.skills.concat(newSkills); 61 | } 62 | while (this.skills.length > 4){ 63 | Random.from(this.skills, true); 64 | } 65 | this.basicAttackSkill = this.skills.find(function(skill){return skill.skill.effect === Effects.DAMAGE;}); 66 | } 67 | } 68 | 69 | Being.prototype = { 70 | act: function(){ 71 | if (this.nextSkill){ 72 | if (this.validateSkill(this.nextSkill)){ 73 | this.nextSkill.pp.reduce(1); 74 | this.nextSkill.skill.effect(this, this.nextSkill.skill); 75 | } 76 | this.nextSkill = false; 77 | return; 78 | } 79 | 80 | switch (this.intent){ 81 | case 'RANDOM': 82 | this.actRandom(); 83 | break; 84 | case 'CHASE': 85 | this.actChase(); 86 | break; 87 | case 'STILL': 88 | this.actStill(); 89 | break; 90 | case 'TRAINER': 91 | this.actAsTrainer(); 92 | break; 93 | } 94 | }, 95 | actAsTrainer: function(){ 96 | TrainerAI.act(this); 97 | }, 98 | actRandom: function(){ 99 | var dx = Random.n(-1, 1); 100 | var dy = Random.n(-1, 1); 101 | if (!this.level.canWalkTo(this.x+dx,this.y+dy)){ 102 | return; 103 | } 104 | this.moveTo(dx, dy); 105 | }, 106 | actStill: function(){ 107 | if (this.seen){ 108 | this.actRandom(); 109 | } else { 110 | // Do nothing 111 | } 112 | }, 113 | actChase: function(){ 114 | var nearestEnemy = this.getNearestEnemy(); 115 | if (!nearestEnemy){ 116 | return; 117 | } 118 | var dx = Math.sign(nearestEnemy.x - this.x); 119 | var dy = Math.sign(nearestEnemy.y - this.y); 120 | if (this.game.player.x === this.x+dx && this.game.player.y === this.y+dy){ 121 | this.tacklePlayer(); 122 | return; 123 | } 124 | var being = this.level.getBeing(this.x+dx,this.y+dy); 125 | if (being){ 126 | if (this.isFriendly !== being.isFriendly){ 127 | this.tackle(being); 128 | } 129 | return; 130 | } 131 | if (!this.level.canWalkTo(this.x+dx,this.y+dy)){ 132 | return; 133 | } 134 | this.moveTo(dx, dy); 135 | }, 136 | tackle: function(enemy){ 137 | if (!this.basicAttackSkill){ 138 | return; 139 | } 140 | var selectedSkill = this.basicAttackSkill; 141 | if (Random.chance(50)){ 142 | selectedSkill = Random.from(this.skills); 143 | } 144 | if (selectedSkill.pp.empty()){ 145 | // Try to get another basic attack skill 146 | selectedSkill = this.skills.find( 147 | function(skill){ 148 | return skill.skill.effect === Effects.DAMAGE && !skill.pp.empty(); 149 | } 150 | ); 151 | if (!selectedSkill){ 152 | // Try to use any skill 153 | selectedSkill = this.skills.find( 154 | function(skill){ 155 | return !skill.pp.empty(); 156 | } 157 | ); 158 | } 159 | 160 | if (!selectedSkill){ 161 | // Struggle it is then 162 | this.game.display.message("The "+this.race.name+" can't attack!"); 163 | return; 164 | } 165 | } 166 | if (!this.validateSkill(selectedSkill)){ 167 | return; 168 | } 169 | selectedSkill.pp.reduce(1); 170 | selectedSkill.skill.effect(this, selectedSkill.skill); 171 | }, 172 | tacklePlayer: function(){ 173 | this.game.display.message("The "+this.race.name+" attacks you."); 174 | this.game.player.hp.reduce(1); 175 | if (this.game.player.hp.current <= 0){ 176 | this.game.player.die(); 177 | } 178 | }, 179 | die: function(){ 180 | if (this.slotNumber !== undefined){ 181 | this.game.display.message("Your "+this.race.name+" dies!"); 182 | this.game.player.monsterSlots[this.slotNumber] = false; 183 | if (this.game.input.selectedMonsterSlot === this.slotNumber){ 184 | this.game.input.selectAvailableMonsterSlot(); 185 | } 186 | if (this.game.player.gymTown){ 187 | this.game.player.releasedMonster = false; 188 | } 189 | } else { 190 | this.game.display.message("The "+this.race.name+" dies!"); 191 | if (this.enemiesList && this.enemiesList.length > 0){ 192 | // The monster can die without enemies, for example by suicide 193 | var xp = Math.floor(this.calculateXPGain() / this.enemiesList.length); 194 | this.enemiesList.forEach(function(enemy){ 195 | if (!enemy.dead){ 196 | enemy.addXP(xp); 197 | } 198 | }); 199 | } 200 | } 201 | this.dead = true; 202 | this.game.world.level.removeBeing(this); 203 | if (this.owner){ 204 | this.owner.monsterDied(this); 205 | } 206 | }, 207 | getNearestEnemy: function(){ 208 | var nearestDistance = 999; 209 | var nearestEnemy = false; 210 | for (var i = 0; i < this.level.beingsList.length; i++){ 211 | var being = this.level.beingsList[i]; 212 | if (this.isTame && being.isTrainer){ 213 | continue; 214 | } 215 | if (being.isFriendly != this.isFriendly){ 216 | var distance = Distance.flatDistance(being.x, being.y, this.x, this.y); 217 | if (distance < this.getSightRange() && distance < nearestDistance){ 218 | nearestDistance = distance; 219 | nearestEnemy = being; 220 | } 221 | } 222 | } 223 | if (!this.isFriendly && !this.isTame){ 224 | // Consider the player too 225 | var distance = Distance.flatDistance(this.game.player.x, this.game.player.y, this.x, this.y); 226 | if (distance < this.getSightRange() && distance < nearestDistance){ 227 | nearestDistance = distance; 228 | nearestEnemy = this.game.player; 229 | } 230 | } 231 | return nearestEnemy; 232 | }, 233 | moveTo: function(dx,dy){ 234 | this.level.beings[this.x][this.y] = false; 235 | this.x = this.x + dx; 236 | this.y = this.y + dy; 237 | if (!this.level.beings[this.x]) 238 | this.level.beings[this.x] = []; 239 | this.level.beings[this.x][this.y] = this; 240 | }, 241 | getSightRange: function(){ 242 | return 10; 243 | }, 244 | recoverHP: function(points){ 245 | this.hp.increase(points); 246 | }, 247 | // Trainer functions 248 | pullBackMonster: function(){ 249 | var monster = this.monsterDeployed; 250 | this.game.display.message(this.race.name+" calls back "+monster.race.name+"."); 251 | this.game.world.level.removeBeing(monster); 252 | this.monsterDeployed = false; 253 | }, 254 | useItem: function(item){ 255 | //Used on the current monster 256 | }, 257 | issueOrder: function(order){ 258 | 259 | }, 260 | releaseMonster: function(monster){ 261 | // Select a direction toward player 262 | var dir = Direction.direction(this.x, this.y, this.game.player.x, this.game.player.y); 263 | if (this.game.world.level.canWalkTo(this.x + dir.x, this.y + dir.y) && 264 | this.game.world.level.canWalkTo(this.x + dir.x * 2, this.y + dir.y * 2)){ 265 | monster.level = this.level; 266 | this.game.world.level.addBeing(monster, this.x + dir.x * 2, this.y + dir.y * 2); 267 | this.game.display.message(this.race.name+" sends out "+monster.race.name+"."); 268 | this.monsterDeployed = monster; 269 | } 270 | }, 271 | monsterDied: function(monster){ 272 | this.monsterDeployed = false; 273 | this.monsters.splice(this.monsters.indexOf(monster), 1); 274 | }, 275 | givePrize: function(){ 276 | if (this.prize){ 277 | this.game.display.message("Congratulations! take the "+this.prize.name+"!"); 278 | this.game.player.addItem(new Item(this.prize)); 279 | this.gavePrize = true; 280 | this.game.player.gymTown.gymComplete = true; 281 | this.game.player.gymTown = false; 282 | this.game.player.badgesCount++; 283 | this.game.player.money += this.prizeMoney; 284 | if (this.game.player.badgesCount === 8){ 285 | this.game.display.showScene("VICTORY"); 286 | } 287 | } else if (this.prizeMoney){ 288 | 289 | } 290 | }, 291 | healAll: function(){ 292 | for (var i = 0; i < this.monsters.length; i++){ 293 | this.monsters[i].heal(); 294 | } 295 | }, 296 | validateSkill: function(skill){ 297 | if (skill.pp.empty()){ 298 | this.game.display.message(this.race.name+" has no power points!"); 299 | return false; 300 | } 301 | if (Effects.skipTarget(skill.skill)){ 302 | return true; 303 | } 304 | var nearestEnemy = this.getNearestEnemy(); 305 | if (!nearestEnemy){ 306 | this.game.display.message("No target!"); 307 | return false; 308 | } 309 | var distance = Distance.flatDistance(this.x, this.y, nearestEnemy.x, nearestEnemy.y); 310 | if (!skill.skill.range){ 311 | skill.skill.range = 1; 312 | } 313 | if (distance > skill.skill.range){ 314 | this.game.display.message(nearestEnemy.race.name+" is too far!"); 315 | return false; 316 | } 317 | return true; 318 | }, 319 | useSkill: function(index){ 320 | if (this.skills && this.skills[index]){ 321 | this.nextSkill = this.skills[index]; 322 | this.game.display.message(this.race.name+", use "+this.skills[index].skill.name+"!"); 323 | this.game.player.endTurn(); 324 | } 325 | }, 326 | heal: function(){ 327 | this.hp.replenish(); 328 | this.skills.forEach(function(skill){skill.pp.replenish();}); 329 | }, 330 | calculateXPGain: function(){ 331 | var a = this.isTame ? 1.5 : 1; 332 | var b = this.race.xp; 333 | var e = 1; // Lucky Egg 334 | var f = 1; // Affection 335 | var L = this.xpLevel; 336 | var p = 1; // Exp Points Power 337 | var s = this.enemiesList.length; 338 | var t = 1; // Trading origin 339 | var v = 1; // Non evolve bonus 340 | if (s === 0) 341 | s = 1; 342 | return Math.floor((a*t*b*e*L*p*f*v)/(7*s)); 343 | }, 344 | calculateXP: function(level){ 345 | return Math.pow(level, 3); // Corresponds to the Medium Fast group 346 | /* 347 | // Alternate 348 | if (level === 1){ 349 | return 0; 350 | } 351 | var xp = 100; 352 | for (var i = 2; i <= level; i++){ 353 | xp += (i-1) * 50; 354 | } 355 | return xp; 356 | */ 357 | }, 358 | recordHitBy: function(enemy){ 359 | if (!this.enemiesList){ 360 | this.enemiesMap = {}; 361 | this.enemiesList = []; 362 | } 363 | if (!this.enemiesMap[enemy.randomId]){ 364 | this.enemiesList.push(enemy); 365 | this.enemiesMap[enemy.randomId] = enemy; 366 | } 367 | }, 368 | addXP: function(xp){ 369 | this.game.display.message(this.race.name+" gets "+xp+" XP"); 370 | this.xp += xp; 371 | if (this.xp >= this.nextLevelXP){ 372 | this.levelUp(); 373 | } 374 | }, 375 | levelUp: function(){ 376 | this.game.display.message(this.race.name+" levels up!"); 377 | this.xpLevel++; 378 | this.xp = this.calculateXP(this.xpLevel); 379 | this.nextLevelXP = this.calculateXP(this.xpLevel+1); 380 | // Increase stats 381 | this.calculateStats(); 382 | 383 | // Check evolution and new skills 384 | var newSkill = this.race.skills.find(function(def){ 385 | return def.level === this.xpLevel; 386 | }, this); 387 | 388 | if (newSkill && this.skills.find(function(existingSkill){ return existingSkill.skill === newSkill.skill;})){ 389 | newSkill = false; 390 | } 391 | 392 | if (newSkill){ 393 | if (this.skills.length === 4){ 394 | // Randomly forget one ;) 395 | var oldSkill = Random.from(this.skills, true); 396 | this.game.display.message(this.race.name+" forgets "+oldSkill.skill.name+"."); 397 | } 398 | this.skills.push({ 399 | skill: newSkill.skill, 400 | pp: new Stat(newSkill.skill.pp) 401 | }); 402 | this.game.display.message(this.race.name+" learns "+newSkill.skill.name+"."); 403 | } 404 | if (this.race.evolution && this.race.evolution.minLevel === this.xpLevel){ 405 | var newRace = this.race.evolution.race; 406 | this.game.display.message(this.race.name+" evolves into "+newRace.name+"!"); 407 | this.setRace(newRace); 408 | } 409 | }, 410 | setRace: function(race){ 411 | this.race = race; 412 | this.tile = race.tile; 413 | }, 414 | _calculateStatBase: function(stat){ 415 | var base = this.race[stat]; 416 | var iv = this.iv[stat]; 417 | var ev = this.ev[stat]; 418 | //return Math.floor((Math.floor((base + iv) * 2 + (Math.sqrt(ev) / 4))*this.xpLevel)/100); 419 | if (stat === 'hp'){ 420 | var statValue = (base + iv + 50) * this.xpLevel / 50 + 10; 421 | } else { 422 | statValue = (base + iv) * this.xpLevel / 50 + 5; 423 | } 424 | statValue = Math.floor(statValue); 425 | var extraPoints = Math.floor(((Math.sqrt(ev - 1)) + 1) * this.xpLevel / 400); 426 | extraPoints = Math.floor(extraPoints); 427 | return statValue + extraPoints; 428 | }, 429 | calculateStats: function(){ 430 | var hp = this._calculateStatBase('hp') + this.xpLevel; 431 | var attack = this._calculateStatBase('attack'); 432 | var defense = this._calculateStatBase('defense'); 433 | var spAttack = this._calculateStatBase('spAttack'); 434 | var spDefense = this._calculateStatBase('spDefense'); 435 | 436 | if (this.hp){ 437 | this.hp.max = hp; 438 | } else { 439 | this.hp = new Stat(hp); 440 | } 441 | this.attack = new Stat(attack); 442 | this.defense = new Stat(defense); 443 | this.spAttack = new Stat(spAttack); 444 | this.spDefense = new Stat(spDefense); 445 | }, 446 | takeDamage: function(damage){ 447 | this.hp.reduce(damage); 448 | if (this.hp.current <= 0){ 449 | this.die(); 450 | } 451 | }, 452 | isUnderground: function(){ 453 | return false; //TODO: Implement 454 | }, 455 | changeStat: function(stat, turns, level){ 456 | this.counters.push({type: "CHANGE_STAT", stat: stat, turns: turns, level: level}); 457 | }, 458 | lowerCounters: function(){ 459 | for (var i = 0; i < this.counters.length; i++){ 460 | this.counters[i].turns--; 461 | if (this.counters[i].turns < 0){ 462 | if (this.counters[i].type === "CHANGE_STAT"){ 463 | if (this.counters[i].level < 0) 464 | this.game.display.message("The "+this.race.name+"'s "+Stats[this.counters[i].stat].name+" recovers."); 465 | } 466 | this.counters.splice(i,1); 467 | i--; 468 | } 469 | } 470 | }, 471 | getEffectiveAttack: function(){ 472 | return this.getEffectiveStat('attack', 'ATK'); 473 | }, 474 | getEffectiveDefense: function(){ 475 | return this.getEffectiveStat('defense', 'DEF'); 476 | }, 477 | getEffectiveSpecialAttack: function(){ 478 | return this.getEffectiveStat('spAttack', 'SP_ATK'); 479 | }, 480 | getEffectiveSpecialDefense: function(){ 481 | return this.getEffectiveStat('spDefense', 'SP_DEF'); 482 | }, 483 | getEffectiveSpeed: function(){ 484 | return this.getEffectiveStat('speed', 'SPD'); 485 | }, 486 | getEffectiveAccuracy: function(){ 487 | return this.getEffectiveStat('accuracy', 'ACC'); 488 | }, 489 | getEffectiveEvasion: function(){ 490 | return this.getEffectiveStat('evasion', 'EVA'); 491 | }, 492 | getEffectiveStat: function(statName, statId){ 493 | var variation = 0; 494 | var base = 0; 495 | for (var i = 0; i < this.counters.length; i++){ 496 | if (this.counters[i].type === "CHANGE_STAT" && this.counters[i].stat === statId){ 497 | variation += this.counters[i].level; 498 | } 499 | } 500 | if (statId === 'EVA' || statId === 'ACC'){ 501 | base = 1; 502 | } else { 503 | base = this[statName].current; 504 | } 505 | if (variation < -6) 506 | variation = -6; 507 | if (variation > 6){ 508 | variation = 6; 509 | } 510 | return base * this.STAGE_MULTIPLIERS[variation]; 511 | }, 512 | STAGE_MULTIPLIERS: { 513 | "-6": 25 / 100, 514 | "-5": 28 / 100, 515 | "-4": 33 / 100, 516 | "-3": 40 / 100, 517 | "-2": 50 / 100, 518 | "-1": 66 / 100, 519 | "0": 100 / 100, 520 | "1": 150 / 100, 521 | "2": 200 / 100, 522 | "3": 250 / 100, 523 | "4": 300 / 100, 524 | "5": 350 / 100, 525 | "6": 400 / 100 526 | }, 527 | inflictStatus: function(status, turns){ 528 | this.counters.push({type: "SET_STATUS", statusId: status.id, turns: turns}); 529 | }, 530 | endTurn: function(){ 531 | this.lowerCounters(); 532 | }, 533 | hasStatus: function(status){ 534 | for (var i = 0; i < this.counters.length; i++){ 535 | if (this.counters[i].type === "SET_STATUS" && this.counters[i].statusId === status.id){ 536 | return true; 537 | } 538 | } 539 | } 540 | } 541 | 542 | module.exports = Being; -------------------------------------------------------------------------------- /static-lib/unicodetiles/unicodetiles.js: -------------------------------------------------------------------------------- 1 | /// File: unicodetiles.js 2 | /// This file contains the main tile engine namespace. 3 | /// All coordinates are assumed to be integers - behaviour is undefined 4 | /// if you feed in floats (or anything other) as x and y (or similar) parameters. 5 | 6 | /*jshint browser:true devel:true trailing:true latedef:true undef:true unused:true newcap:true */ 7 | 8 | if (!window.console) { 9 | window.console = { log: function(){}, warn: function(){}, error: function(){} } 10 | } 11 | 12 | /// Namespace: ut 13 | /// Container namespace. 14 | var ut = ut || {}; 15 | 16 | /// Constants: Semi-internal constants for ut namespace 17 | /// VERSION - Version of the library as string. 18 | /// NULLCHAR - Character used when none is specified otherwise. 19 | /// CSSCLASS - The CSS class name used for the tile engine element. 20 | /// NULLTILE - The tile used as placeholder for empty tile. 21 | ut.VERSION = "2.1"; 22 | ut.NULLCHAR = " "; 23 | ut.CSSCLASS = "unicodetiles"; 24 | ut.NULLTILE = {}; // Initialized properly after ut.Tile is defined 25 | 26 | /// Class: Tile 27 | /// Represents a unicode character tile with various attributes. 28 | 29 | /// Constructor: Tile 30 | /// Constructs a new Tile object. 31 | /// 32 | /// Parameters: 33 | /// ch - a character to display for this tile 34 | /// r - (optional) red foregorund color component 0-255 35 | /// g - (optional) green foreground color component 0-255 36 | /// b - (optional) blue foreground color component 0-255 37 | /// br - (optional) red background color component 0-255 38 | /// bg - (optional) green background color component 0-255 39 | /// bb - (optional) blue background color component 0-255 40 | ut.Tile = function(ch, r, g, b, br, bg, bb) { 41 | "use strict"; 42 | this.ch = ch || ut.NULLCHAR; 43 | this.r = r; 44 | this.g = g; 45 | this.b = b; 46 | this.br = br; 47 | this.bg = bg; 48 | this.bb = bb; 49 | }; 50 | 51 | /// Function: getChar 52 | /// Returns the character of this tile. 53 | ut.Tile.prototype.getChar = function() { return this.ch; }; 54 | /// Function: setChar 55 | /// Sets the character of this tile. 56 | ut.Tile.prototype.setChar = function(ch) { this.ch = ch; }; 57 | /// Function: setColor 58 | /// Sets the foreground color of this tile. 59 | ut.Tile.prototype.setColor = function(r, g, b) { this.r = r; this.g = g; this.b = b; }; 60 | /// Function: setGrey 61 | /// Sets the foreground color to the given shade (0-255) of grey. 62 | ut.Tile.prototype.setGrey = function(grey) { this.r = grey; this.g = grey; this.b = grey; }; 63 | /// Function: setBackground 64 | /// Sets the background color of this tile. 65 | ut.Tile.prototype.setBackground = function(r, g, b) { this.br = r; this.bg = g; this.bb = b; }; 66 | /// Function: resetColor 67 | /// Clears the color of this tile / assigns default color. 68 | ut.Tile.prototype.resetColor = function() { this.r = this.g = this.b = undefined; }; 69 | /// Function: resetBackground 70 | /// Clears the background color of this tile. 71 | ut.Tile.prototype.resetBackground = function() { this.br = this.bg = this.bb = undefined; }; 72 | /// Function: getColorHex 73 | /// Returns the hexadecimal representation of the color 74 | ut.Tile.prototype.getColorHex = function() { 75 | if (this.r !== undefined && this.g !== undefined && this.b !== undefined) 76 | return "#" + this.r.toString(16) + this.g.toString(16) + this.b.toString(16); 77 | else return ""; 78 | }; 79 | /// Function: getBackgroundHex 80 | /// Returns the hexadecimal representation of the background color 81 | ut.Tile.prototype.getBackgroundHex = function() { 82 | if (this.br !== undefined && this.bg !== undefined && this.bb !== undefined) 83 | return "#" + this.br.toString(16) + this.bg.toString(16) + this.bb.toString(16); 84 | else return ""; 85 | }; 86 | /// Function: getColorRGB 87 | /// Returns the CSS rgb(r,g,b) representation of the color 88 | ut.Tile.prototype.getColorRGB = function() { 89 | if (this.r !== undefined && this.g !== undefined && this.b !== undefined) 90 | return 'rgb('+this.r+','+this.g+','+this.b+')'; 91 | else return ""; 92 | }; 93 | /// Function: getBackgroundRGB 94 | /// Returns the CSS rgb(r,g,b) representation of the background color 95 | ut.Tile.prototype.getBackgroundRGB = function() { 96 | if (this.br !== undefined && this.bg !== undefined && this.bb !== undefined) 97 | return 'rgb('+this.br+','+this.bg+','+this.bb+')'; 98 | else return ""; 99 | }; 100 | /// Function: getColorJSON 101 | /// Returns the JSON representation of the color, i.e. object { r, g, b } 102 | ut.Tile.prototype.getColorJSON = function() { 103 | if (this.r !== undefined && this.g !== undefined && this.b !== undefined) 104 | return { "r": this.r, "g": this.g, "b": this.b }; 105 | else return {}; 106 | }; 107 | /// Function: getBackgroundJSON 108 | /// Returns the JSON representation of the background color, i.e. object { r, g, b } 109 | ut.Tile.prototype.getBackgroundJSON = function() { 110 | if (this.r !== undefined && this.g !== undefined && this.b !== undefined) 111 | return { "r": this.br, "g": this.bg, "b": this.bb }; 112 | else return {}; 113 | }; 114 | /// Function: copy 115 | /// Makes this tile identical to the one supplied. Custom properties are not copied. 116 | ut.Tile.prototype.copy = function(other) { 117 | this.ch = other.ch; 118 | this.r = other.r; this.g = other.g; this.b = other.b; 119 | this.br = other.br; this.bg = other.bg; this.bb = other.bb; 120 | }; 121 | /// Function: clone 122 | /// Returns a new copy of this tile. Custom properties are not cloned. 123 | ut.Tile.prototype.clone = function() { 124 | return new ut.Tile(this.ch, this.r, this.g, this.b, this.br, this.bg, this.bb); 125 | }; 126 | 127 | ut.NULLTILE = new ut.Tile(); 128 | 129 | 130 | /// Class: Viewport 131 | /// The tile engine viewport / window. Takes care of initializing a proper renderer. 132 | 133 | /// Constructor: Viewport 134 | /// Constructs a new Viewport object. 135 | /// If you wish to display a player character at the center, you should use odd sizes. 136 | /// 137 | /// Parameters: 138 | /// elem - the DOM element which shall be transformed into the tile engine 139 | /// w - (integer) width in tiles 140 | /// h - (integer) height in tiles 141 | /// renderer - (optional) choose rendering engine, see , defaults to "auto". 142 | /// squarify - (optional) set to true to force the tiles square; may break some box drawing 143 | ut.Viewport = function(elem, w, h, renderer, squarify) { 144 | "use strict"; 145 | this.elem = elem; 146 | this.elem.innerHTML = ""; 147 | this.w = w; 148 | this.h = h; 149 | this.renderer = null; // setRenderer() is called later 150 | this.squarify = squarify; 151 | this.cx = Math.floor(w/2); 152 | this.cy = Math.floor(h/2); 153 | 154 | // Add CSS class if not added already 155 | if (elem.className.indexOf(ut.CSSCLASS) === -1) { 156 | if (elem.className.length === 0) elem.className = ut.CSSCLASS; 157 | else elem.className += " " + ut.CSSCLASS; 158 | } 159 | 160 | // Create two 2-dimensional array to hold the viewport tiles 161 | this.buffer = new Array(h); 162 | for (var j = 0; j < h; ++j) { 163 | this.buffer[j] = new Array(w); 164 | for (var i = 0; i < w; ++i) { 165 | this.buffer[j][i] = new ut.Tile(); 166 | } 167 | } 168 | 169 | /// Function: updateStyle 170 | /// If the style of the parent element is modified, this needs to be called. 171 | this.updateStyle = function(updateRenderer) { 172 | var s = window.getComputedStyle(this.elem, null); 173 | this.defaultColor = s.color; 174 | this.defaultBackground = s.backgroundColor; 175 | if (updateRenderer !== false) 176 | this.renderer.updateStyle(s); 177 | }; 178 | 179 | /// Function: setRenderer 180 | /// Switch renderer at runtime. All methods fallback to "dom" if unsuccesful. 181 | /// Possible values: 182 | /// * "webgl" - Use WebGL with an HTML5 element 183 | /// * "canvas" - Use HTML5 element 184 | /// * "dom" - Use regular HTML element manipulation through DOM 185 | /// * "auto" - Use best available, i.e. try the above in order, picking the first that works 186 | this.setRenderer = function(newrenderer) { 187 | this.elem.innerHTML = ""; 188 | if (newrenderer === "auto" || newrenderer === "webgl") { 189 | try { 190 | this.renderer = new ut.WebGLRenderer(this); 191 | } catch (e) { 192 | console.error(e); 193 | newrenderer = "canvas"; 194 | this.elem.innerHTML = ""; 195 | } 196 | } 197 | if (newrenderer === "canvas") { 198 | try { 199 | this.renderer = new ut.CanvasRenderer(this); 200 | } catch (e) { 201 | console.error(e); 202 | newrenderer = "dom"; 203 | this.elem.innerHTML = ""; 204 | } 205 | } 206 | if (newrenderer === "dom") { 207 | this.renderer = new ut.DOMRenderer(this); 208 | } 209 | this.updateStyle(false); 210 | }; 211 | 212 | this.setRenderer(renderer || "auto"); 213 | }; 214 | 215 | /// Function: getRendererString 216 | /// Gets the currently used renderer. 217 | /// 218 | /// Returns: 219 | /// One of "webgl", "canvas", "dom", "". 220 | ut.Viewport.prototype.getRendererString = function() { 221 | if (this.renderer instanceof ut.WebGLRenderer) return "webgl"; 222 | if (this.renderer instanceof ut.CanvasRenderer) return "canvas"; 223 | if (this.renderer instanceof ut.DOMRenderer) return "dom"; 224 | return ""; 225 | }; 226 | 227 | /// Function: put 228 | /// Puts a tile to the given coordinates. 229 | /// Checks bounds and does nothing if invalid coordinates are given. 230 | /// 231 | /// Parameters: 232 | /// tile - the tile to put 233 | /// x - (integer) x coordinate 234 | /// y - (integer) y coordinate 235 | ut.Viewport.prototype.put = function(tile, x, y) { 236 | if (x < 0 || y < 0 || x >= this.w || y >= this.h) return; 237 | this.buffer[y][x] = tile; 238 | }; 239 | 240 | /// Function: unsafePut 241 | /// Puts a tile to the given coordinates. 242 | /// Does *not* check bounds; throws exception if invalid coordinates are given. 243 | /// 244 | /// Parameters: 245 | /// tile - the tile to put 246 | /// x - (integer) x coordinate 247 | /// y - (integer) y coordinate 248 | ut.Viewport.prototype.unsafePut = function(tile, x, y) { 249 | this.buffer[y][x] = tile; 250 | }; 251 | 252 | /// Function: putString 253 | /// Creates a row of tiles with the chars of the given string. 254 | /// Wraps to next line if it can't fit the chars on one line. 255 | /// 256 | /// Parameters: 257 | /// str - (string) the string to put 258 | /// x - (integer) x coordinate (column) 259 | /// y - (integer) y coordinate (row) 260 | /// r - (optional) red foregorund color component 0-255 261 | /// g - (optional) green foreground color component 0-255 262 | /// b - (optional) blue foreground color component 0-255 263 | /// br - (optional) red background color component 0-255 264 | /// bg - (optional) green background color component 0-255 265 | /// bb - (optional) blue background color component 0-255 266 | ut.Viewport.prototype.putString = function(str, x, y, r, g, b, br, bg, bb) { 267 | var len = str.length; 268 | var tile; 269 | if (x < 0 || y < 0) return; 270 | for (var i = 0; i < len; ++i) { 271 | if (x >= this.w) { x = 0; ++y;} 272 | if (y >= this.h) return; 273 | tile = new ut.Tile(str[i], r, g, b, br, bg, bb); 274 | this.unsafePut(tile, x, y); 275 | ++x; 276 | } 277 | }; 278 | 279 | /// Function: get 280 | /// Returns the tile in the given coordinates. 281 | /// Checks bounds and returns empty tile if invalid coordinates are given. 282 | /// 283 | /// Parameters: 284 | /// x - (integer) x coordinate 285 | /// y - (integer) y coordinate 286 | /// 287 | /// Returns: 288 | /// The tile. 289 | ut.Viewport.prototype.get = function(x, y) { 290 | if (x < 0 || y < 0 || x >= this.w || y >= this.h) return ut.NULLTILE; 291 | return this.buffer[y][x]; 292 | }; 293 | 294 | /// Function: clear 295 | /// Clears the viewport buffer by assigning empty tiles. 296 | ut.Viewport.prototype.clear = function() { 297 | for (var j = 0; j < this.h; ++j) { 298 | for (var i = 0; i < this.w; ++i) { 299 | this.buffer[j][i] = ut.NULLTILE; 300 | } 301 | } 302 | this.renderer.clear(); 303 | }; 304 | 305 | /// Function: render 306 | /// Renders the buffer as html to the element specified at construction. 307 | ut.Viewport.prototype.render = function() { 308 | this.renderer.render(); 309 | }; 310 | 311 | 312 | /// Class: Engine 313 | /// The tile engine itself. 314 | 315 | /// Constructor: Engine 316 | /// Constructs a new Engine object. If width or height is given, 317 | /// it will not attempt to fetch tiles outside the boundaries. 318 | /// In that case 0,0 is assumed as the upper-left corner of the world, 319 | /// but if no width/height is given also negative coords are valid. 320 | /// 321 | /// Parameters: 322 | /// vp - the instance to use as the viewport 323 | /// func - the function used for fetching tiles 324 | /// w - (integer) (optional) world width in tiles 325 | /// h - (integer) (optional) world height in tiles 326 | ut.Engine = function(vp, func, w, h) { 327 | "use strict"; 328 | this.viewport = vp; 329 | this.tileFunc = func; 330 | this.w = w; 331 | this.h = h; 332 | this.refreshCache = true; 333 | this.cacheEnabled = false; 334 | this.transitionTimer = null; 335 | this.transitionDuration = 0; 336 | this.transition = null; 337 | this.cachex = 0; 338 | this.cachey = 0; 339 | this.tileCache = new Array(vp.h); 340 | this.tileCache2 = new Array(vp.h); 341 | for (var j = 0; j < vp.h; ++j) { 342 | this.tileCache[j] = new Array(vp.w); 343 | this.tileCache2[j] = new Array(vp.w); 344 | } 345 | }; 346 | 347 | /// Function: setTileFunc 348 | /// Sets the function to be called with coordinates to fetch each tile. 349 | /// Optionally can apply a transition effect. Effects are: 350 | /// "boxin", "boxout", "circlein", "circleout", "random" 351 | /// 352 | /// Parameters: 353 | /// func - function taking parameters (x, y) and returning an ut.Tile 354 | /// effect - (string) (optional) name of effect to use (see above for legal values) 355 | /// duration - (integer) (optional) how many milliseconds the transition effect should last 356 | ut.Engine.prototype.setTileFunc = function(func, effect, duration) { 357 | "use strict"; 358 | if (effect) { 359 | this.transition = undefined; 360 | if (typeof effect === "string") { 361 | if (effect === "boxin") this.transition = function(x, y, w, h, new_t, old_t, factor) { 362 | var halfw = w * 0.5, halfh = h * 0.5; 363 | x -= halfw; y -= halfh; 364 | if (Math.abs(x) < halfw * factor && Math.abs(y) < halfh * factor) return new_t; 365 | else return old_t; 366 | }; 367 | else if (effect === "boxout") this.transition = function(x, y, w, h, new_t, old_t, factor) { 368 | var halfw = w * 0.5, halfh = h * 0.5; 369 | x -= halfw; y -= halfh; 370 | factor = 1.0 - factor; 371 | if (Math.abs(x) < halfw * factor && Math.abs(y) < halfh * factor) return old_t; 372 | else return new_t; 373 | }; 374 | else if (effect === "circlein") this.transition = function(x, y, w, h, new_t, old_t, factor) { 375 | var halfw = w * 0.5, halfh = h * 0.5; 376 | x -= halfw; y -= halfh; 377 | if (x*x + y*y < (halfw*halfw + halfh*halfh) * factor) return new_t; 378 | else return old_t; 379 | }; 380 | else if (effect === "circleout") this.transition = function(x, y, w, h, new_t, old_t, factor) { 381 | var halfw = w * 0.5, halfh = h * 0.5; 382 | x -= halfw; y -= halfh; 383 | factor = 1.0 - factor; 384 | if (x*x + y*y > (halfw*halfw + halfh*halfh) * factor) return new_t; 385 | else return old_t; 386 | }; 387 | else if (effect === "random") this.transition = function(x, y, w, h, new_t, old_t, factor) { 388 | if (Math.random() > factor) return old_t; 389 | else return new_t; 390 | }; 391 | } 392 | if (this.transition) { 393 | this.transitionTimer = (new Date()).getTime(); 394 | this.transitionDuration = duration || 500; 395 | } 396 | } 397 | this.tileFunc = func; 398 | }; 399 | 400 | /// Function: setMaskFunc 401 | /// Sets the function to be called to fetch mask information according to coordinates. 402 | /// If mask function returns false to some coordinates, then that tile is not rendered. 403 | /// 404 | /// Parameters: 405 | /// func - function taking parameters (x, y) and returning a true if the tile is visible 406 | ut.Engine.prototype.setMaskFunc = function(func) { this.maskFunc = func; }; 407 | 408 | /// Function: setShaderFunc 409 | /// Sets the function to be called to post-process / shade each visible tile. 410 | /// Shader function is called even if caching is enabled, see . 411 | /// 412 | /// Parameters: 413 | /// func - function taking parameters (tile, x, y) and returning an ut.Tile 414 | ut.Engine.prototype.setShaderFunc = function(func) { this.shaderFunc = func; }; 415 | 416 | /// Function: setWorldSize 417 | /// Tiles outside of the range x = [0,width[; y = [0,height[ are not fetched. 418 | /// Set to undefined in order to make the world infinite. 419 | /// 420 | /// Parameters: 421 | /// width - (integer) new world width 422 | /// height - (integer) new world height 423 | ut.Engine.prototype.setWorldSize = function(width, height) { this.w = width; this.h = height; }; 424 | 425 | /// Function: setCacheEnabled 426 | /// Enables or disables the usage of tile cache. This means that 427 | /// extra measures are taken to not call the tile function unnecessarily. 428 | /// This means that all animating must be done in a shader function, 429 | /// see . 430 | /// Cache is off by default, but should be enabled if the tile function 431 | /// does more computation than a simple array look-up. 432 | /// 433 | /// Parameters: 434 | /// mode - true to enable, false to disable 435 | ut.Engine.prototype.setCacheEnabled = function(mode) { this.cacheEnabled = mode; this.refreshCache = true; }; 436 | 437 | /// Function: update 438 | /// Updates the viewport according to the given player coordinates. 439 | /// The algorithm goes as follows: 440 | /// * Record the current time 441 | /// * For each viewport tile: 442 | /// * Check if the tile is visible by testing the mask 443 | /// * If not visible, continue to the next tile in the viewport 444 | /// * Otherwise, if cache is enabled try to fetch the tile from there 445 | /// * Otherwise, call the tile function and check for shader function presence 446 | /// * If there is shader function, apply it to the tile, passing the recorded time 447 | /// * Put the tile to viewport 448 | /// 449 | /// Parameters: 450 | /// x - (integer) viewport center x coordinate in the tile world 451 | /// y - (integer) viewport center y coordinate in the tile world 452 | ut.Engine.prototype.update = function(x, y) { 453 | "use strict"; 454 | x = x || 0; 455 | y = y || 0; 456 | // World coords of upper left corner of the viewport 457 | var xx = x - this.viewport.cx; 458 | var yy = y - this.viewport.cy; 459 | var timeNow = (new Date()).getTime(); // For passing to shaderFunc 460 | var transTime; 461 | if (this.transition) transTime = (timeNow - this.transitionTimer) / this.transitionDuration; 462 | if (transTime >= 1.0) this.transition = undefined; 463 | var tile; 464 | // For each tile in viewport... 465 | for (var j = 0; j < this.viewport.h; ++j) { 466 | for (var i = 0; i < this.viewport.w; ++i) { 467 | var ixx = i+xx, jyy = j+yy; 468 | // Check horizontal bounds if requested 469 | if (this.w && (ixx < 0 || ixx >= this.w)) { 470 | tile = ut.NULLTILE; 471 | // Check vertical bounds if requested 472 | } else if (this.h && (jyy < 0 || jyy >= this.w)) { 473 | tile = ut.NULLTILE; 474 | // Check mask 475 | } else if (this.maskFunc && !this.maskFunc(ixx, jyy)) { 476 | tile = ut.NULLTILE; 477 | // Check transition effect 478 | } else if (this.transition && !this.refreshCache) { 479 | tile = this.transition(i, j, this.viewport.w, this.viewport.h, 480 | this.tileFunc(ixx, jyy), this.tileCache[j][i], transTime); 481 | // Check cache 482 | } else if (this.cacheEnabled && !this.refreshCache) { 483 | var lookupx = ixx - this.cachex; 484 | var lookupy = jyy - this.cachey; 485 | if (lookupx >= 0 && lookupx < this.viewport.w && lookupy >= 0 && lookupy < this.viewport.h) { 486 | tile = this.tileCache[lookupy][lookupx]; 487 | if (tile === ut.NULLTILE) tile = this.tileFunc(ixx, jyy); 488 | } else // Cache miss 489 | tile = this.tileFunc(ixx, jyy); 490 | // If all else fails, call tileFunc 491 | } else tile = this.tileFunc(ixx, jyy); 492 | // Save the tile to cache (always due to transition effects) 493 | this.tileCache2[j][i] = tile; 494 | // Apply shader function 495 | if (this.shaderFunc && tile !== ut.NULLTILE) 496 | tile = this.shaderFunc(tile, ixx, jyy, timeNow); 497 | // Put shaded tile to viewport 498 | this.viewport.unsafePut(tile, i, j); 499 | } 500 | } 501 | // Cache stuff is enabled always, because it is also required by transitions 502 | // Save the new cache origin 503 | this.cachex = xx; 504 | this.cachey = yy; 505 | // Swap cache buffers 506 | var tempCache = this.tileCache; 507 | this.tileCache = this.tileCache2; 508 | this.tileCache2 = tempCache; 509 | this.refreshCache = false; 510 | }; 511 | -------------------------------------------------------------------------------- /static-lib/unicodetiles/unicodetiles-lcd.js: -------------------------------------------------------------------------------- 1 | /// File: unicodetiles.js 2 | /// This file contains the main tile engine namespace. 3 | /// All coordinates are assumed to be integers - behaviour is undefined 4 | /// if you feed in floats (or anything other) as x and y (or similar) parameters. 5 | 6 | /*jshint browser:true devel:true trailing:true latedef:true undef:true unused:true newcap:true */ 7 | 8 | if (!window.console) { 9 | window.console = { log: function(){}, warn: function(){}, error: function(){} } 10 | } 11 | 12 | /// Namespace: ut 13 | /// Container namespace. 14 | var ut = ut || {}; 15 | 16 | /// Constants: Semi-internal constants for ut namespace 17 | /// VERSION - Version of the library as string. 18 | /// NULLCHAR - Character used when none is specified otherwise. 19 | /// CSSCLASS - The CSS class name used for the tile engine element. 20 | /// NULLTILE - The tile used as placeholder for empty tile. 21 | ut.VERSION = "2.1"; 22 | ut.NULLCHAR = " "; 23 | ut.CSSCLASS = "unicodetiles"; 24 | ut.NULLTILE = {}; // Initialized properly after ut.Tile is defined 25 | 26 | /// Class: Tile 27 | /// Represents a unicode character tile with various attributes. 28 | 29 | /// Constructor: Tile 30 | /// Constructs a new Tile object. 31 | /// 32 | /// Parameters: 33 | /// ch - a character to display for this tile 34 | /// r - (optional) red foregorund color component 0-255 35 | /// g - (optional) green foreground color component 0-255 36 | /// b - (optional) blue foreground color component 0-255 37 | /// br - (optional) red background color component 0-255 38 | /// bg - (optional) green background color component 0-255 39 | /// bb - (optional) blue background color component 0-255 40 | ut.Tile = function(ch, r, g, b, br, bg, bb) { 41 | "use strict"; 42 | this.ch = ch || ut.NULLCHAR; 43 | this.r = r; 44 | this.g = g; 45 | this.b = b; 46 | this.br = br; 47 | this.bg = bg; 48 | this.bb = bb; 49 | 50 | // Inverse patch 51 | /*this.br = 255; 52 | this.bg = 255; 53 | this.bb = 255;*/ 54 | 55 | this.br = 127; 56 | this.bg = 136; 57 | this.bb = 109; 58 | 59 | if (this.r === 255 && this.g === 255 && this.b === 255){ 60 | this.r = 0; 61 | this.g = 0; 62 | this.b = 0; 63 | }; 64 | 65 | if (this.r) this.r = Math.floor(this.r / 3); 66 | if (this.g) this.g = Math.floor(this.g / 3); 67 | if (this.b) this.b = Math.floor(this.b / 3); 68 | }; 69 | 70 | /// Function: getChar 71 | /// Returns the character of this tile. 72 | ut.Tile.prototype.getChar = function() { return this.ch; }; 73 | /// Function: setChar 74 | /// Sets the character of this tile. 75 | ut.Tile.prototype.setChar = function(ch) { this.ch = ch; }; 76 | /// Function: setColor 77 | /// Sets the foreground color of this tile. 78 | ut.Tile.prototype.setColor = function(r, g, b) { this.r = r; this.g = g; this.b = b; }; 79 | /// Function: setGrey 80 | /// Sets the foreground color to the given shade (0-255) of grey. 81 | ut.Tile.prototype.setGrey = function(grey) { this.r = grey; this.g = grey; this.b = grey; }; 82 | /// Function: setBackground 83 | /// Sets the background color of this tile. 84 | ut.Tile.prototype.setBackground = function(r, g, b) { this.br = r; this.bg = g; this.bb = b; }; 85 | /// Function: resetColor 86 | /// Clears the color of this tile / assigns default color. 87 | ut.Tile.prototype.resetColor = function() { this.r = this.g = this.b = undefined; }; 88 | /// Function: resetBackground 89 | /// Clears the background color of this tile. 90 | ut.Tile.prototype.resetBackground = function() { this.br = this.bg = this.bb = undefined; }; 91 | /// Function: getColorHex 92 | /// Returns the hexadecimal representation of the color 93 | ut.Tile.prototype.getColorHex = function() { 94 | if (this.r !== undefined && this.g !== undefined && this.b !== undefined) 95 | return "#" + this.r.toString(16) + this.g.toString(16) + this.b.toString(16); 96 | else return ""; 97 | }; 98 | /// Function: getBackgroundHex 99 | /// Returns the hexadecimal representation of the background color 100 | ut.Tile.prototype.getBackgroundHex = function() { 101 | if (this.br !== undefined && this.bg !== undefined && this.bb !== undefined) 102 | return "#" + this.br.toString(16) + this.bg.toString(16) + this.bb.toString(16); 103 | else return ""; 104 | }; 105 | /// Function: getColorRGB 106 | /// Returns the CSS rgb(r,g,b) representation of the color 107 | ut.Tile.prototype.getColorRGB = function() { 108 | if (this.r !== undefined && this.g !== undefined && this.b !== undefined) 109 | return 'rgb('+this.r+','+this.g+','+this.b+')'; 110 | else return ""; 111 | }; 112 | /// Function: getBackgroundRGB 113 | /// Returns the CSS rgb(r,g,b) representation of the background color 114 | ut.Tile.prototype.getBackgroundRGB = function() { 115 | if (this.br !== undefined && this.bg !== undefined && this.bb !== undefined) 116 | return 'rgb('+this.br+','+this.bg+','+this.bb+')'; 117 | else return ""; 118 | }; 119 | /// Function: getColorJSON 120 | /// Returns the JSON representation of the color, i.e. object { r, g, b } 121 | ut.Tile.prototype.getColorJSON = function() { 122 | if (this.r !== undefined && this.g !== undefined && this.b !== undefined) 123 | return { "r": this.r, "g": this.g, "b": this.b }; 124 | else return {}; 125 | }; 126 | /// Function: getBackgroundJSON 127 | /// Returns the JSON representation of the background color, i.e. object { r, g, b } 128 | ut.Tile.prototype.getBackgroundJSON = function() { 129 | if (this.r !== undefined && this.g !== undefined && this.b !== undefined) 130 | return { "r": this.br, "g": this.bg, "b": this.bb }; 131 | else return {}; 132 | }; 133 | /// Function: copy 134 | /// Makes this tile identical to the one supplied. Custom properties are not copied. 135 | ut.Tile.prototype.copy = function(other) { 136 | this.ch = other.ch; 137 | this.r = other.r; this.g = other.g; this.b = other.b; 138 | this.br = other.br; this.bg = other.bg; this.bb = other.bb; 139 | }; 140 | /// Function: clone 141 | /// Returns a new copy of this tile. Custom properties are not cloned. 142 | ut.Tile.prototype.clone = function() { 143 | return new ut.Tile(this.ch, this.r, this.g, this.b, this.br, this.bg, this.bb); 144 | }; 145 | 146 | ut.NULLTILE = new ut.Tile(); 147 | 148 | 149 | /// Class: Viewport 150 | /// The tile engine viewport / window. Takes care of initializing a proper renderer. 151 | 152 | /// Constructor: Viewport 153 | /// Constructs a new Viewport object. 154 | /// If you wish to display a player character at the center, you should use odd sizes. 155 | /// 156 | /// Parameters: 157 | /// elem - the DOM element which shall be transformed into the tile engine 158 | /// w - (integer) width in tiles 159 | /// h - (integer) height in tiles 160 | /// renderer - (optional) choose rendering engine, see , defaults to "auto". 161 | /// squarify - (optional) set to true to force the tiles square; may break some box drawing 162 | ut.Viewport = function(elem, w, h, renderer, squarify) { 163 | "use strict"; 164 | this.elem = elem; 165 | this.elem.innerHTML = ""; 166 | this.w = w; 167 | this.h = h; 168 | this.renderer = null; // setRenderer() is called later 169 | this.squarify = squarify; 170 | this.cx = Math.floor(w/2); 171 | this.cy = Math.floor(h/2); 172 | 173 | // Add CSS class if not added already 174 | if (elem.className.indexOf(ut.CSSCLASS) === -1) { 175 | if (elem.className.length === 0) elem.className = ut.CSSCLASS; 176 | else elem.className += " " + ut.CSSCLASS; 177 | } 178 | 179 | // Create two 2-dimensional array to hold the viewport tiles 180 | this.buffer = new Array(h); 181 | for (var j = 0; j < h; ++j) { 182 | this.buffer[j] = new Array(w); 183 | for (var i = 0; i < w; ++i) { 184 | this.buffer[j][i] = new ut.Tile(); 185 | } 186 | } 187 | 188 | /// Function: updateStyle 189 | /// If the style of the parent element is modified, this needs to be called. 190 | this.updateStyle = function(updateRenderer) { 191 | var s = window.getComputedStyle(this.elem, null); 192 | this.defaultColor = s.color; 193 | this.defaultBackground = s.backgroundColor; 194 | if (updateRenderer !== false) 195 | this.renderer.updateStyle(s); 196 | }; 197 | 198 | /// Function: setRenderer 199 | /// Switch renderer at runtime. All methods fallback to "dom" if unsuccesful. 200 | /// Possible values: 201 | /// * "webgl" - Use WebGL with an HTML5 element 202 | /// * "canvas" - Use HTML5 element 203 | /// * "dom" - Use regular HTML element manipulation through DOM 204 | /// * "auto" - Use best available, i.e. try the above in order, picking the first that works 205 | this.setRenderer = function(newrenderer) { 206 | this.elem.innerHTML = ""; 207 | if (newrenderer === "auto" || newrenderer === "webgl") { 208 | try { 209 | this.renderer = new ut.WebGLRenderer(this); 210 | } catch (e) { 211 | console.error(e); 212 | newrenderer = "canvas"; 213 | this.elem.innerHTML = ""; 214 | } 215 | } 216 | if (newrenderer === "canvas") { 217 | try { 218 | this.renderer = new ut.CanvasRenderer(this); 219 | } catch (e) { 220 | console.error(e); 221 | newrenderer = "dom"; 222 | this.elem.innerHTML = ""; 223 | } 224 | } 225 | if (newrenderer === "dom") { 226 | this.renderer = new ut.DOMRenderer(this); 227 | } 228 | this.updateStyle(false); 229 | }; 230 | 231 | this.setRenderer(renderer || "auto"); 232 | }; 233 | 234 | /// Function: getRendererString 235 | /// Gets the currently used renderer. 236 | /// 237 | /// Returns: 238 | /// One of "webgl", "canvas", "dom", "". 239 | ut.Viewport.prototype.getRendererString = function() { 240 | if (this.renderer instanceof ut.WebGLRenderer) return "webgl"; 241 | if (this.renderer instanceof ut.CanvasRenderer) return "canvas"; 242 | if (this.renderer instanceof ut.DOMRenderer) return "dom"; 243 | return ""; 244 | }; 245 | 246 | /// Function: put 247 | /// Puts a tile to the given coordinates. 248 | /// Checks bounds and does nothing if invalid coordinates are given. 249 | /// 250 | /// Parameters: 251 | /// tile - the tile to put 252 | /// x - (integer) x coordinate 253 | /// y - (integer) y coordinate 254 | ut.Viewport.prototype.put = function(tile, x, y) { 255 | if (x < 0 || y < 0 || x >= this.w || y >= this.h) return; 256 | this.buffer[y][x] = tile; 257 | }; 258 | 259 | /// Function: unsafePut 260 | /// Puts a tile to the given coordinates. 261 | /// Does *not* check bounds; throws exception if invalid coordinates are given. 262 | /// 263 | /// Parameters: 264 | /// tile - the tile to put 265 | /// x - (integer) x coordinate 266 | /// y - (integer) y coordinate 267 | ut.Viewport.prototype.unsafePut = function(tile, x, y) { 268 | this.buffer[y][x] = tile; 269 | }; 270 | 271 | /// Function: putString 272 | /// Creates a row of tiles with the chars of the given string. 273 | /// Wraps to next line if it can't fit the chars on one line. 274 | /// 275 | /// Parameters: 276 | /// str - (string) the string to put 277 | /// x - (integer) x coordinate (column) 278 | /// y - (integer) y coordinate (row) 279 | /// r - (optional) red foregorund color component 0-255 280 | /// g - (optional) green foreground color component 0-255 281 | /// b - (optional) blue foreground color component 0-255 282 | /// br - (optional) red background color component 0-255 283 | /// bg - (optional) green background color component 0-255 284 | /// bb - (optional) blue background color component 0-255 285 | ut.Viewport.prototype.putString = function(str, x, y, r, g, b, br, bg, bb) { 286 | var len = str.length; 287 | var tile; 288 | if (x < 0 || y < 0) return; 289 | for (var i = 0; i < len; ++i) { 290 | if (x >= this.w) { x = 0; ++y;} 291 | if (y >= this.h) return; 292 | tile = new ut.Tile(str[i], r, g, b, br, bg, bb); 293 | this.unsafePut(tile, x, y); 294 | ++x; 295 | } 296 | }; 297 | 298 | /// Function: get 299 | /// Returns the tile in the given coordinates. 300 | /// Checks bounds and returns empty tile if invalid coordinates are given. 301 | /// 302 | /// Parameters: 303 | /// x - (integer) x coordinate 304 | /// y - (integer) y coordinate 305 | /// 306 | /// Returns: 307 | /// The tile. 308 | ut.Viewport.prototype.get = function(x, y) { 309 | if (x < 0 || y < 0 || x >= this.w || y >= this.h) return ut.NULLTILE; 310 | return this.buffer[y][x]; 311 | }; 312 | 313 | /// Function: clear 314 | /// Clears the viewport buffer by assigning empty tiles. 315 | ut.Viewport.prototype.clear = function() { 316 | for (var j = 0; j < this.h; ++j) { 317 | for (var i = 0; i < this.w; ++i) { 318 | this.buffer[j][i] = ut.NULLTILE; 319 | } 320 | } 321 | this.renderer.clear(); 322 | }; 323 | 324 | /// Function: render 325 | /// Renders the buffer as html to the element specified at construction. 326 | ut.Viewport.prototype.render = function() { 327 | this.renderer.render(); 328 | }; 329 | 330 | 331 | /// Class: Engine 332 | /// The tile engine itself. 333 | 334 | /// Constructor: Engine 335 | /// Constructs a new Engine object. If width or height is given, 336 | /// it will not attempt to fetch tiles outside the boundaries. 337 | /// In that case 0,0 is assumed as the upper-left corner of the world, 338 | /// but if no width/height is given also negative coords are valid. 339 | /// 340 | /// Parameters: 341 | /// vp - the instance to use as the viewport 342 | /// func - the function used for fetching tiles 343 | /// w - (integer) (optional) world width in tiles 344 | /// h - (integer) (optional) world height in tiles 345 | ut.Engine = function(vp, func, w, h) { 346 | "use strict"; 347 | this.viewport = vp; 348 | this.tileFunc = func; 349 | this.w = w; 350 | this.h = h; 351 | this.refreshCache = true; 352 | this.cacheEnabled = false; 353 | this.transitionTimer = null; 354 | this.transitionDuration = 0; 355 | this.transition = null; 356 | this.cachex = 0; 357 | this.cachey = 0; 358 | this.tileCache = new Array(vp.h); 359 | this.tileCache2 = new Array(vp.h); 360 | for (var j = 0; j < vp.h; ++j) { 361 | this.tileCache[j] = new Array(vp.w); 362 | this.tileCache2[j] = new Array(vp.w); 363 | } 364 | }; 365 | 366 | /// Function: setTileFunc 367 | /// Sets the function to be called with coordinates to fetch each tile. 368 | /// Optionally can apply a transition effect. Effects are: 369 | /// "boxin", "boxout", "circlein", "circleout", "random" 370 | /// 371 | /// Parameters: 372 | /// func - function taking parameters (x, y) and returning an ut.Tile 373 | /// effect - (string) (optional) name of effect to use (see above for legal values) 374 | /// duration - (integer) (optional) how many milliseconds the transition effect should last 375 | ut.Engine.prototype.setTileFunc = function(func, effect, duration) { 376 | "use strict"; 377 | if (effect) { 378 | this.transition = undefined; 379 | if (typeof effect === "string") { 380 | if (effect === "boxin") this.transition = function(x, y, w, h, new_t, old_t, factor) { 381 | var halfw = w * 0.5, halfh = h * 0.5; 382 | x -= halfw; y -= halfh; 383 | if (Math.abs(x) < halfw * factor && Math.abs(y) < halfh * factor) return new_t; 384 | else return old_t; 385 | }; 386 | else if (effect === "boxout") this.transition = function(x, y, w, h, new_t, old_t, factor) { 387 | var halfw = w * 0.5, halfh = h * 0.5; 388 | x -= halfw; y -= halfh; 389 | factor = 1.0 - factor; 390 | if (Math.abs(x) < halfw * factor && Math.abs(y) < halfh * factor) return old_t; 391 | else return new_t; 392 | }; 393 | else if (effect === "circlein") this.transition = function(x, y, w, h, new_t, old_t, factor) { 394 | var halfw = w * 0.5, halfh = h * 0.5; 395 | x -= halfw; y -= halfh; 396 | if (x*x + y*y < (halfw*halfw + halfh*halfh) * factor) return new_t; 397 | else return old_t; 398 | }; 399 | else if (effect === "circleout") this.transition = function(x, y, w, h, new_t, old_t, factor) { 400 | var halfw = w * 0.5, halfh = h * 0.5; 401 | x -= halfw; y -= halfh; 402 | factor = 1.0 - factor; 403 | if (x*x + y*y > (halfw*halfw + halfh*halfh) * factor) return new_t; 404 | else return old_t; 405 | }; 406 | else if (effect === "random") this.transition = function(x, y, w, h, new_t, old_t, factor) { 407 | if (Math.random() > factor) return old_t; 408 | else return new_t; 409 | }; 410 | } 411 | if (this.transition) { 412 | this.transitionTimer = (new Date()).getTime(); 413 | this.transitionDuration = duration || 500; 414 | } 415 | } 416 | this.tileFunc = func; 417 | }; 418 | 419 | /// Function: setMaskFunc 420 | /// Sets the function to be called to fetch mask information according to coordinates. 421 | /// If mask function returns false to some coordinates, then that tile is not rendered. 422 | /// 423 | /// Parameters: 424 | /// func - function taking parameters (x, y) and returning a true if the tile is visible 425 | ut.Engine.prototype.setMaskFunc = function(func) { this.maskFunc = func; }; 426 | 427 | /// Function: setShaderFunc 428 | /// Sets the function to be called to post-process / shade each visible tile. 429 | /// Shader function is called even if caching is enabled, see . 430 | /// 431 | /// Parameters: 432 | /// func - function taking parameters (tile, x, y) and returning an ut.Tile 433 | ut.Engine.prototype.setShaderFunc = function(func) { this.shaderFunc = func; }; 434 | 435 | /// Function: setWorldSize 436 | /// Tiles outside of the range x = [0,width[; y = [0,height[ are not fetched. 437 | /// Set to undefined in order to make the world infinite. 438 | /// 439 | /// Parameters: 440 | /// width - (integer) new world width 441 | /// height - (integer) new world height 442 | ut.Engine.prototype.setWorldSize = function(width, height) { this.w = width; this.h = height; }; 443 | 444 | /// Function: setCacheEnabled 445 | /// Enables or disables the usage of tile cache. This means that 446 | /// extra measures are taken to not call the tile function unnecessarily. 447 | /// This means that all animating must be done in a shader function, 448 | /// see . 449 | /// Cache is off by default, but should be enabled if the tile function 450 | /// does more computation than a simple array look-up. 451 | /// 452 | /// Parameters: 453 | /// mode - true to enable, false to disable 454 | ut.Engine.prototype.setCacheEnabled = function(mode) { this.cacheEnabled = mode; this.refreshCache = true; }; 455 | 456 | /// Function: update 457 | /// Updates the viewport according to the given player coordinates. 458 | /// The algorithm goes as follows: 459 | /// * Record the current time 460 | /// * For each viewport tile: 461 | /// * Check if the tile is visible by testing the mask 462 | /// * If not visible, continue to the next tile in the viewport 463 | /// * Otherwise, if cache is enabled try to fetch the tile from there 464 | /// * Otherwise, call the tile function and check for shader function presence 465 | /// * If there is shader function, apply it to the tile, passing the recorded time 466 | /// * Put the tile to viewport 467 | /// 468 | /// Parameters: 469 | /// x - (integer) viewport center x coordinate in the tile world 470 | /// y - (integer) viewport center y coordinate in the tile world 471 | ut.Engine.prototype.update = function(x, y) { 472 | "use strict"; 473 | x = x || 0; 474 | y = y || 0; 475 | // World coords of upper left corner of the viewport 476 | var xx = x - this.viewport.cx; 477 | var yy = y - this.viewport.cy; 478 | var timeNow = (new Date()).getTime(); // For passing to shaderFunc 479 | var transTime; 480 | if (this.transition) transTime = (timeNow - this.transitionTimer) / this.transitionDuration; 481 | if (transTime >= 1.0) this.transition = undefined; 482 | var tile; 483 | // For each tile in viewport... 484 | for (var j = 0; j < this.viewport.h; ++j) { 485 | for (var i = 0; i < this.viewport.w; ++i) { 486 | var ixx = i+xx, jyy = j+yy; 487 | // Check horizontal bounds if requested 488 | if (this.w && (ixx < 0 || ixx >= this.w)) { 489 | tile = ut.NULLTILE; 490 | // Check vertical bounds if requested 491 | } else if (this.h && (jyy < 0 || jyy >= this.w)) { 492 | tile = ut.NULLTILE; 493 | // Check mask 494 | } else if (this.maskFunc && !this.maskFunc(ixx, jyy)) { 495 | tile = ut.NULLTILE; 496 | // Check transition effect 497 | } else if (this.transition && !this.refreshCache) { 498 | tile = this.transition(i, j, this.viewport.w, this.viewport.h, 499 | this.tileFunc(ixx, jyy), this.tileCache[j][i], transTime); 500 | // Check cache 501 | } else if (this.cacheEnabled && !this.refreshCache) { 502 | var lookupx = ixx - this.cachex; 503 | var lookupy = jyy - this.cachey; 504 | if (lookupx >= 0 && lookupx < this.viewport.w && lookupy >= 0 && lookupy < this.viewport.h) { 505 | tile = this.tileCache[lookupy][lookupx]; 506 | if (tile === ut.NULLTILE) tile = this.tileFunc(ixx, jyy); 507 | } else // Cache miss 508 | tile = this.tileFunc(ixx, jyy); 509 | // If all else fails, call tileFunc 510 | } else tile = this.tileFunc(ixx, jyy); 511 | // Save the tile to cache (always due to transition effects) 512 | this.tileCache2[j][i] = tile; 513 | // Apply shader function 514 | if (this.shaderFunc && tile !== ut.NULLTILE) 515 | tile = this.shaderFunc(tile, ixx, jyy, timeNow); 516 | // Put shaded tile to viewport 517 | this.viewport.unsafePut(tile, i, j); 518 | } 519 | } 520 | // Cache stuff is enabled always, because it is also required by transitions 521 | // Save the new cache origin 522 | this.cachex = xx; 523 | this.cachey = yy; 524 | // Swap cache buffers 525 | var tempCache = this.tileCache; 526 | this.tileCache = this.tileCache2; 527 | this.tileCache2 = tempCache; 528 | this.refreshCache = false; 529 | }; 530 | -------------------------------------------------------------------------------- /src/js/monster/Skills.enum.js: -------------------------------------------------------------------------------- 1 | var Types = require('./Types.enum'); 2 | var Effects = require('./Effects.enum'); 3 | var Stats = require('./Stats.enum'); 4 | var Status = require('./Status.enum'); 5 | 6 | module.exports = { 7 | MIMIC: {name: 'Mimic', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.COPY_MOVE, pp: 10, damagetype: 'STAT', params: {}}, 8 | MIRROR_MOVE: {name: 'Mirror Move', type: Types.FLYING, power: 0, accuracy: 0, effect: Effects.COPY_MOVE, pp: 20, damagetype: 'STAT', params: {}}, 9 | CONVERSION: {name: 'Conversion', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.COPY_TYPE, pp: 30, damagetype: 'STAT', params: {}}, 10 | SUBSTITUTE: {name: 'Substitute', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.CREATE_DECOY, pp: 10, damagetype: 'STAT', params: {}}, 11 | BARRAGE: {name: 'Barrage', type: Types.NORMAL, power: 15, accuracy: 85, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {multihit: 5}}, 12 | BIND: {name: 'Bind', type: Types.NORMAL, power: 15, accuracy: 85, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {multihit: 5, trap: true}}, 13 | BITE: {name: 'Bite', type: Types.NORMAL, power: 60, accuracy: 100, effect: Effects.DAMAGE, pp: 25, damagetype: 'PHYS', params: {flinchChance: 10}}, 14 | BODY_SLAM: {name: 'Body Slam', type: Types.NORMAL, power: 85, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', range: 2, params: {assault: true, paralyzeChance: 30}}, 15 | BONE_CLUB: {name: 'Bone Club', type: Types.GROUND, power: 65, accuracy: 85, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {flinchChance: 10}}, 16 | BONEMERANG: {name: 'Bonemerang', type: Types.GROUND, power: 50, accuracy: 90, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', range: 3, params: {multihit: 2}}, 17 | CLAMP: {name: 'Clamp', type: Types.WATER, power: 35, accuracy: 85, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', params: {multihit: 5, trap: true}}, 18 | COMET_PUNCH: {name: 'Comet Punch', type: Types.NORMAL, power: 18, accuracy: 85, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {multihit: 5}}, 19 | CONSTRICT: {name: 'Constrict', type: Types.NORMAL, power: 10, accuracy: 100, effect: Effects.DAMAGE, pp: 35, damagetype: 'PHYS', params: {lowerChance: 10, lowerStat: Stats.SPD}}, 20 | CRABHAMMER: {name: 'Crabhammer', type: Types.WATER, power: 100, accuracy: 90, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', params: {critBoost: true}}, 21 | CUT: {name: 'Cut', type: Types.NORMAL, power: 50, accuracy: 95, effect: Effects.DAMAGE, pp: 30, damagetype: 'PHYS', params: {}}, 22 | DIZZY_PUNCH: {name: 'Dizzy Punch', type: Types.NORMAL, power: 70, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', params: {}}, 23 | DOUBLE_KICK: {name: 'Double Kick', type: Types.FIGHTING, power: 30, accuracy: 100, effect: Effects.DAMAGE, pp: 30, damagetype: 'PHYS', params: {multihit: 2}}, 24 | DOUBLE_SLAP: {name: 'Double Slap', type: Types.NORMAL, power: 15, accuracy: 85, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', params: {multihit: 5}}, 25 | DOUBLE_EDGE: {name: 'Double-Edge', type: Types.NORMAL, power: 120, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {recoil: 25}}, 26 | DRILL_PECK: {name: 'Drill Peck', type: Types.FLYING, power: 80, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {}}, 27 | EARTHQUAKE: {name: 'Earthquake', type: Types.GROUND, power: 100, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', range: 5, params: {undergroundBonus: 2, splashRange: 4}}, 28 | EGG_BOMB: {name: 'Egg Bomb', type: Types.NORMAL, power: 100, accuracy: 75, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', range: 3, params: {}}, 29 | EXPLOSION: {name: 'Explosion', type: Types.NORMAL, power: 250, accuracy: 100, effect: Effects.DAMAGE, pp: 5, damagetype: 'PHYS', params: {suicide: true, splashRange: 3}}, 30 | FIRE_PUNCH: {name: 'Fire Punch', type: Types.FIRE, power: 75, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {burnChance: 10}}, 31 | FURY_ATTACK: {name: 'Fury Attack', type: Types.NORMAL, power: 15, accuracy: 85, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {multihit: 5}}, 32 | FURY_SWIPES: {name: 'Fury Swipes', type: Types.NORMAL, power: 18, accuracy: 80, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {multihit: 5}}, 33 | HEADBUTT: {name: 'Headbutt', type: Types.NORMAL, power: 70, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {flinchChance: 30}}, 34 | HIGH_JUMP_KICK: {name: 'High Jump Kick', type: Types.FIGHTING, power: 130, accuracy: 90, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', range: 2, params: {damageOnMiss: 1}}, 35 | HORN_ATTACK: {name: 'Horn Attack', type: Types.NORMAL, power: 65, accuracy: 100, effect: Effects.DAMAGE, pp: 25, damagetype: 'PHYS', params: {}}, 36 | HYPER_FANG: {name: 'Hyper Fang', type: Types.NORMAL, power: 80, accuracy: 90, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {flinchChance: 10}}, 37 | ICE_PUNCH: {name: 'Ice Punch', type: Types.ICE, power: 75, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {freezeChance: 10}}, 38 | JUMP_KICK: {name: 'Jump Kick', type: Types.FIGHTING, power: 100, accuracy: 95, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', range: 2, params: {damageOnMiss: 1}}, 39 | KARATE_CHOP: {name: 'Karate Chop', type: Types.FIGHTING, power: 50, accuracy: 100, effect: Effects.DAMAGE, pp: 25, damagetype: 'PHYS', params: {critBoost: true}}, 40 | LEECH_LIFE: {name: 'Leech Life', type: Types.BUG, power: 80, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', params: {absorbHPPercentage: 50}}, 41 | LICK: {name: 'Lick', type: Types.GHOST, power: 30, accuracy: 100, effect: Effects.DAMAGE, pp: 30, damagetype: 'PHYS', params: {paralyzeChance: 30}}, 42 | LOW_KICK: {name: 'Low Kick', type: Types.FIGHTING, power: 50, accuracy: 90, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {flinchChance: 30}}, 43 | MEGA_KICK: {name: 'Mega Kick', type: Types.NORMAL, power: 120, accuracy: 75, effect: Effects.DAMAGE, pp: 5, damagetype: 'PHYS', params: {}}, 44 | MEGA_PUNCH: {name: 'Mega Punch', type: Types.NORMAL, power: 80, accuracy: 85, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {}}, 45 | PAY_DAY: {name: 'Pay Day', type: Types.NORMAL, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {dropCoins: true}}, 46 | PECK: {name: 'Peck', type: Types.FLYING, power: 35, accuracy: 100, effect: Effects.DAMAGE, pp: 35, damagetype: 'PHYS', params: {}}, 47 | PIN_MISSILE: {name: 'Pin Missile', type: Types.BUG, power: 25, accuracy: 95, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', range: 3, params: {multihit: 5}}, 48 | POISON_STING: {name: 'Poison Sting', type: Types.POISON, power: 15, accuracy: 100, effect: Effects.DAMAGE, pp: 35, damagetype: 'PHYS', range: 2, params: {poisonChance: 30}}, 49 | POUND: {name: 'Pound', type: Types.NORMAL, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 35, damagetype: 'PHYS', params: {}}, 50 | QUICK_ATTACK: {name: 'Quick Attack', type: Types.NORMAL, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 30, damagetype: 'PHYS', range: 2, params: {assault: true}}, 51 | RAGE: {name: 'Rage', type: Types.NORMAL, power: 20, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {rageTurns: 50, raiseAttackWhileRaging: true}}, 52 | RAZOR_LEAF: {name: 'Razor Leaf', type: Types.GRASS, power: 55, accuracy: 95, effect: Effects.DAMAGE, pp: 25, damagetype: 'PHYS', range: 3, params: {critBoost: true, splashRange: 2}}, 53 | ROCK_SLIDE: {name: 'Rock Slide', type: Types.ROCK, power: 75, accuracy: 90, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', range: 2, params: {}}, 54 | ROCK_THROW: {name: 'Rock Throw', type: Types.ROCK, power: 50, accuracy: 90, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', range: 3, params: {}}, 55 | ROLLING_KICK: {name: 'Rolling Kick', type: Types.FIGHTING, power: 60, accuracy: 85, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', range: 2, params: {flinchChance: 30}}, 56 | SCRATCH: {name: 'Scratch', type: Types.NORMAL, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 35, damagetype: 'PHYS', params: {}}, 57 | SEISMIC_TOSS: {name: 'Seismic Toss', type: Types.FIGHTING, power: 0, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: { fixedDamage: "Level"}}, 58 | SELF_DESTRUCT: {name: 'Self-Destruct', type: Types.NORMAL, power: 200, accuracy: 100, effect: Effects.DAMAGE, pp: 5, damagetype: 'PHYS', params: {suicide: true}}, 59 | SKULL_BASH: {name: 'Skull Bash', type: Types.NORMAL, power: 130, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', params: { buildUpTurns: 1 }}, 60 | SKY_ATTACK: {name: 'Sky Attack', type: Types.FLYING, power: 140, accuracy: 90, effect: Effects.DAMAGE, pp: 5, damagetype: 'PHYS', range: 3, params: { buildUpTurns: 1 }}, 61 | SLAM: {name: 'Slam', type: Types.NORMAL, power: 80, accuracy: 75, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {}}, 62 | SLASH: {name: 'Slash', type: Types.NORMAL, power: 70, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {critBoost: true}}, 63 | SPIKE_CANNON: {name: 'Spike Cannon', type: Types.NORMAL, power: 20, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', range: 3, params: {multihit: 5}}, 64 | STOMP: {name: 'Stomp', type: Types.NORMAL, power: 65, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {flinchChance: 30}}, 65 | STRENGTH: {name: 'Strength', type: Types.NORMAL, power: 80, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {}}, 66 | STRUGGLE: {name: 'Struggle', type: Types.NORMAL, power: 50, accuracy: 100, effect: Effects.DAMAGE, pp: 0, damagetype: 'PHYS', params: {recoil: 50}}, 67 | SUBMISSION: {name: 'Submission', type: Types.FIGHTING, power: 80, accuracy: 80, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {}}, 68 | SUPER_FANG: {name: 'Super Fang', type: Types.NORMAL, power: 0, accuracy: 90, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', params: {fixedDamagePercentage: 50}}, 69 | TACKLE: {name: 'Tackle', type: Types.NORMAL, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 35, damagetype: 'PHYS', params: {}}, 70 | TAKE_DOWN: {name: 'Take Down', type: Types.NORMAL, power: 90, accuracy: 85, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {recoil: 25}}, 71 | THRASH: {name: 'Thrash', type: Types.NORMAL, power: 120, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', params: {rageTurns: 3}}, 72 | THUNDER_PUNCH: {name: 'Thunder Punch', type: Types.ELECTRIC, power: 75, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {paralyzeChance: 10}}, 73 | TWINEEDLE: {name: 'Twineedle', type: Types.BUG, power: 25, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {multihit: 2, poisonChance: 20}}, 74 | VICE_GRIP: {name: 'Vice Grip', type: Types.NORMAL, power: 55, accuracy: 100, effect: Effects.DAMAGE, pp: 30, damagetype: 'PHYS', params: {}}, 75 | VINE_WHIP: {name: 'Vine Whip', type: Types.GRASS, power: 45, accuracy: 100, effect: Effects.DAMAGE, pp: 25, damagetype: 'PHYS', params: {}}, 76 | WATERFALL: {name: 'Waterfall', type: Types.WATER, power: 80, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {}}, 77 | WING_ATTACK: {name: 'Wing Attack', type: Types.FLYING, power: 60, accuracy: 100, effect: Effects.DAMAGE, pp: 35, damagetype: 'PHYS', params: {}}, 78 | WRAP: {name: 'Wrap', type: Types.NORMAL, power: 15, accuracy: 90, effect: Effects.DAMAGE, pp: 20, damagetype: 'PHYS', params: {multihit: 5, trap: true}}, 79 | ABSORB: {name: 'Absorb', type: Types.GRASS, power: 20, accuracy: 100, effect: Effects.DAMAGE, pp: 25, damagetype: 'SPEC', params: {absorbHPPercentage: 50}}, 80 | ACID: {name: 'Acid', type: Types.POISON, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 30, damagetype: 'SPEC', range: 2, params: {lowerChance: 10, lowerStat: Stats.DEF, splashRange: 2}}, 81 | AURORA_BEAM: {name: 'Aurora Beam', type: Types.ICE, power: 65, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'SPEC', range: 5, params: {lowerChance: 10, lowerStat: Stats.ATK}}, 82 | BLIZZARD: {name: 'Blizzard', type: Types.ICE, power: 110, accuracy: 70, effect: Effects.DAMAGE, pp: 5, damagetype: 'SPEC', range: 5, params: {freezeChance: 10}}, 83 | BUBBLE: {name: 'Bubble', type: Types.WATER, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 30, damagetype: 'SPEC', range: 2, params: {lowerChance: 10, lowerStat: Stats.SPD, splashRange: 2}}, 84 | BUBBLE_BEAM: {name: 'Bubble Beam', type: Types.WATER, power: 65, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'SPEC', range: 4, params: {lowerChance: 10, lowerStat: Stats.SPD}}, 85 | CONFUSION: {name: 'Confusion', type: Types.PSYCHIC, power: 50, accuracy: 100, effect: Effects.DAMAGE, pp: 25, damagetype: 'SPEC', params: {confuseChance: 10}}, 86 | DRAGON_RAGE: {name: 'Dragon Rage', type: Types.DRAGON, power: 0, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'SPEC', range: 3, params: {fixedDamage: 40}}, 87 | DREAM_EATER: {name: 'Dream Eater', type: Types.PSYCHIC, power: 100, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'SPEC', range: 3, params: {requireStatus: "SLEEP", absorbHPPercentage: 50}}, 88 | EMBER: {name: 'Ember', type: Types.FIRE, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 25, damagetype: 'SPEC', range: 2, params: {burnChance: 10}}, 89 | FIRE_BLAST: {name: 'Fire Blast', type: Types.FIRE, power: 110, accuracy: 85, effect: Effects.DAMAGE, pp: 5, damagetype: 'SPEC', range: 4, params: {burnChance: 30}}, 90 | FIRE_SPIN: {name: 'Fire Spin', type: Types.FIRE, power: 35, accuracy: 85, effect: Effects.DAMAGE, pp: 15, damagetype: 'SPEC', range: 3, params: {multihit: 5, trap: true}}, 91 | FLAMETHROWER: {name: 'Flamethrower', type: Types.FIRE, power: 90, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'SPEC', range: 3, params: {burnChance: 10}}, 92 | GUST: {name: 'Gust', type: Types.NORMAL, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 35, damagetype: 'SPEC', range: 2, params: {}}, 93 | HYDRO_PUMP: {name: 'Hydro Pump', type: Types.WATER, power: 110, accuracy: 80, effect: Effects.DAMAGE, pp: 5, damagetype: 'SPEC', range: 5, params: {}}, 94 | HYPER_BEAM: {name: 'Hyper Beam', type: Types.NORMAL, power: 150, accuracy: 90, effect: Effects.DAMAGE, pp: 5, damagetype: 'SPEC', range: 5, params: { recoverTurns: 1 }}, 95 | ICE_BEAM: {name: 'Ice Beam', type: Types.ICE, power: 90, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'SPEC', range: 5, params: {freezeChance: 10}}, 96 | MEGA_DRAIN: {name: 'Mega Drain', type: Types.GRASS, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'SPEC', range: 2, params: {absorbHPPercentage: 50}}, 97 | NIGHT_SHADE: {name: 'Night Shade', type: Types.GHOST, power: 0, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'SPEC', params: {fixedDamage: "Level"}}, 98 | PETAL_DANCE: {name: 'Petal Dance', type: Types.GRASS, power: 120, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'SPEC', params: {multihit: 3, afterHits: "CONFUSED"}}, 99 | PSYBEAM: {name: 'Psybeam', type: Types.PSYCHIC, power: 65, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'SPEC', range: 5, params: {confuseChance: 10}}, 100 | PSYCHIC: {name: 'Psychic', type: Types.PSYCHIC, power: 90, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'SPEC', range: 2, params: {lowerChance: 33, lowerStat: Stats.SP_ATK}}, 101 | PSYWAVE: {name: 'Psywave', type: Types.PSYCHIC, power: 0, accuracy: 80, effect: Effects.DAMAGE, pp: 15, damagetype: 'SPEC', range: 2, params: {specialDamage: "psywave"}}, 102 | RAZOR_WIND: {name: 'Razor Wind', type: Types.NORMAL, power: 80, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'SPEC', range: 2, params: {critBoost: true, buildUpTurns: 1}}, 103 | SLUDGE: {name: 'Sludge', type: Types.POISON, power: 65, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'SPEC', range: 2, params: {poisonChance: 30}}, 104 | SMOG: {name: 'Smog', type: Types.POISON, power: 30, accuracy: 70, effect: Effects.DAMAGE, pp: 20, damagetype: 'SPEC', range: 3, params: {poisonChance: 40, splashRange: 2}}, 105 | SOLAR_BEAM: {name: 'Solar Beam', type: Types.GRASS, power: 120, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'SPEC', range: 5, params: {buildUpTurns: 1}}, 106 | SONIC_BOOM: {name: 'Sonic Boom', type: Types.NORMAL, power: 0, accuracy: 90, effect: Effects.DAMAGE, pp: 20, damagetype: 'SPEC', range: 2, params: {fixedDamage: 20}}, 107 | SURF: {name: 'Surf', type: Types.WATER, power: 90, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'SPEC', params: {splashRange: 3}}, 108 | SWIFT: {name: 'Swift', type: Types.NORMAL, power: 60, accuracy: 100, effect: Effects.DAMAGE, pp: 20, damagetype: 'SPEC', params: {alwaysHit: true}}, 109 | THUNDER: {name: 'Thunder', type: Types.ELECTRIC, power: 110, accuracy: 70, effect: Effects.DAMAGE, pp: 10, damagetype: 'SPEC', range: 5, params: {paralyzeChance: 10}}, 110 | THUNDER_SHOCK: {name: 'Thunder Shock', type: Types.ELECTRIC, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 30, damagetype: 'SPEC', range: 2, params: {paralyzeChance: 10}}, 111 | THUNDERBOLT: {name: 'Thunderbolt', type: Types.ELECTRIC, power: 90, accuracy: 100, effect: Effects.DAMAGE, pp: 15, damagetype: 'SPEC', range: 4, params: {paralyzeChance: 10}}, 112 | TRI_ATTACK: {name: 'Tri Attack', type: Types.NORMAL, power: 80, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'SPEC', params: {}}, 113 | WATER_GUN: {name: 'Water Gun', type: Types.WATER, power: 40, accuracy: 100, effect: Effects.DAMAGE, pp: 25, damagetype: 'SPEC', params: {}}, 114 | DIG: {name: 'Dig', type: Types.GROUND, power: 80, accuracy: 100, effect: Effects.DAMAGE, pp: 10, damagetype: 'PHYS', params: {hideTurns: 1}}, 115 | FLY: {name: 'Fly', type: Types.FLYING, power: 90, accuracy: 95, effect: Effects.DAMAGE, pp: 15, damagetype: 'PHYS', params: {hideTurns: 1}}, 116 | DISABLE: {name: 'Disable', type: Types.NORMAL, power: 0, accuracy: 100, effect: Effects.DISABLE_MOVE, pp: 20, damagetype: 'STAT', params: {}}, 117 | CONFUSE_RAY: {name: 'Confuse Ray', type: Types.GHOST, power: 0, accuracy: 100, effect: Effects.INFLICT_STATUS, pp: 10, damagetype: 'STAT', range: 5, params: {status: "CONFUSION"}}, 118 | GLARE: {name: 'Glare', type: Types.NORMAL, power: 0, accuracy: 100, effect: Effects.INFLICT_STATUS, pp: 30, damagetype: 'STAT', range: 4, params: {status: "PARALYSIS"}}, 119 | HYPNOSIS: {name: 'Hypnosis', type: Types.PSYCHIC, power: 0, accuracy: 60, effect: Effects.INFLICT_STATUS, pp: 20, damagetype: 'STAT', range: 3, params: {status: "SLEEP"}}, 120 | LOVELY_KISS: {name: 'Lovely Kiss', type: Types.NORMAL, power: 0, accuracy: 75, effect: Effects.INFLICT_STATUS, pp: 10, damagetype: 'STAT', range: 1, params: {status: "SLEEP"}}, 121 | POISON_GAS: {name: 'Poison Gas', type: Types.POISON, power: 0, accuracy: 90, effect: Effects.INFLICT_STATUS, pp: 40, damagetype: 'STAT', range: 2, params: {status: "POISON"}}, 122 | POISON_POWDER: {name: 'Poison Powder', type: Types.POISON, power: 0, accuracy: 75, effect: Effects.INFLICT_STATUS, pp: 35, damagetype: 'STAT', range: 1, params: {status: "POISON"}}, 123 | SING: {name: 'Sing', type: Types.NORMAL, power: 0, accuracy: 55, effect: Effects.INFLICT_STATUS, pp: 15, damagetype: 'STAT', range: 5, params: {status: "SLEEP"}}, 124 | SLEEP_POWDER: {name: 'Sleep Powder', type: Types.GRASS, power: 0, accuracy: 75, effect: Effects.INFLICT_STATUS, pp: 15, damagetype: 'STAT', range: 1, params: {status: "SLEEP"}}, 125 | SPORE: {name: 'Spore', type: Types.GRASS, power: 0, accuracy: 100, effect: Effects.INFLICT_STATUS, pp: 15, damagetype: 'STAT', range: 1, params: {status: "SLEEP"}}, 126 | STUN_SPORE: {name: 'Stun Spore', type: Types.GRASS, power: 0, accuracy: 75, effect: Effects.INFLICT_STATUS, pp: 30, damagetype: 'STAT', range: 1, params: {status: "PARALYSIS"}}, 127 | SUPERSONIC: {name: 'Supersonic', type: Types.NORMAL, power: 0, accuracy: 55, effect: Effects.INFLICT_STATUS, pp: 20, damagetype: 'STAT', range: 3, params: {status: "CONFUSION"}}, 128 | THUNDER_WAVE: {name: 'Thunder Wave', type: Types.ELECTRIC, power: 0, accuracy: 90, effect: Effects.INFLICT_STATUS, pp: 20, damagetype: 'STAT', range: 3, params: {status: "PARALYSIS"}}, 129 | TOXIC: {name: 'Toxic', type: Types.POISON, power: 0, accuracy: 90, effect: Effects.INFLICT_STATUS, pp: 10, damagetype: 'STAT', range: 2, params: {status: "BAD_POISON"}}, 130 | LEECH_SEED: {name: 'Leech Seed', type: Types.GRASS, power: 0, accuracy: 90, effect: Effects.INFLICT_STATUS, pp: 10, damagetype: 'STAT', range: 2, params: {status: "SEEDED"}}, 131 | FISSURE: {name: 'Fissure', type: Types.GROUND, power: 0, accuracy: 0, effect: Effects.KO, pp: 5, damagetype: 'PHYS', params: {}}, 132 | GUILLOTINE: {name: 'Guillotine', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.KO, pp: 5, damagetype: 'PHYS', params: {}}, 133 | HORN_DRILL: {name: 'Horn Drill', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.KO, pp: 5, damagetype: 'PHYS', params: {}}, 134 | FLASH: {name: 'Flash', type: Types.NORMAL, power: 0, accuracy: 100, effect: Effects.LOWER_STAT, pp: 20, damagetype: 'STAT', range: 4, params: {stat: "ACC"}}, 135 | GROWL: {name: 'Growl', type: Types.NORMAL, power: 0, accuracy: 100, effect: Effects.LOWER_STAT, pp: 40, damagetype: 'STAT', range: 4, params: {stat: "ATK"}}, 136 | KINESIS: {name: 'Kinesis', type: Types.PSYCHIC, power: 0, accuracy: 80, effect: Effects.LOWER_STAT, pp: 15, damagetype: 'STAT', range: 3, params: {stat: "ACC"}}, 137 | LEER: {name: 'Leer', type: Types.NORMAL, power: 0, accuracy: 100, effect: Effects.LOWER_STAT, pp: 30, damagetype: 'STAT', range: 4, params: {stat: "DEF"}}, 138 | SAND_ATTACK: {name: 'Sand Attack', type: Types.GROUND, power: 0, accuracy: 100, effect: Effects.LOWER_STAT, pp: 15, damagetype: 'STAT', range: 3, params: {stat: "ACC"}}, 139 | SCREECH: {name: 'Screech', type: Types.NORMAL, power: 0, accuracy: 85, effect: Effects.LOWER_STAT, pp: 40, damagetype: 'STAT', range: 4, params: {stat: "DEF", level: 2}}, 140 | SMOKESCREEN: {name: 'Smokescreen', type: Types.NORMAL, power: 0, accuracy: 100, effect: Effects.LOWER_STAT, pp: 20, damagetype: 'STAT', range: 3, params: {stat: "ACC"}}, 141 | STRING_SHOT: {name: 'String Shot', type: Types.BUG, power: 0, accuracy: 95, effect: Effects.LOWER_STAT, pp: 40, damagetype: 'STAT', range: 3, params: {stat: "SPD", level: 2}}, 142 | TAIL_WHIP: {name: 'Tail Whip', type: Types.NORMAL, power: 0, accuracy: 100, effect: Effects.LOWER_STAT, pp: 30, damagetype: 'STAT', range: 4, params: {stat: "DEF"}}, 143 | SPLASH: {name: 'Splash', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.NOTHING, pp: 40, damagetype: 'STAT', params: {}}, 144 | DOUBLE_TEAM: {name: 'Double Team', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 15, damagetype: 'SPEC', params: {stat: "EVA"}}, 145 | ACID_ARMOR: {name: 'Acid Armor', type: Types.POISON, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 20, damagetype: 'STAT', params: {stat: "DEF", level: 2}}, 146 | AGILITY: {name: 'Agility', type: Types.PSYCHIC, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 30, damagetype: 'STAT', params: {stat: "SPD", level: 2}}, 147 | AMNESIA: {name: 'Amnesia', type: Types.PSYCHIC, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 20, damagetype: 'STAT', params: {stat: "SP_DEF", level: 2}}, 148 | BARRIER: {name: 'Barrier', type: Types.PSYCHIC, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 20, damagetype: 'STAT', params: {stat: "DEF", level: 2}}, 149 | DEFENSE_CURL: {name: 'Defense Curl', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 40, damagetype: 'STAT', params: {stat: "DEF"}}, 150 | FOCUS_ENERGY: {name: 'Focus Energy', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.SET_STATUS, pp: 30, damagetype: 'STAT', params: {status: "FOCUSED"}}, 151 | GROWTH: {name: 'Growth', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 40, damagetype: 'STAT', params: {stat: "ATK"}}, 152 | HARDEN: {name: 'Harden', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 30, damagetype: 'STAT', params: {stat: "DEF"}}, 153 | MEDITATE: {name: 'Meditate', type: Types.PSYCHIC, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 40, damagetype: 'STAT', params: {stat: "ATK"}}, 154 | MINIMIZE: {name: 'Minimize', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 10, damagetype: 'STAT', params: {stat: "DEF", level: 2}}, 155 | SHARPEN: {name: 'Sharpen', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 30, damagetype: 'STAT', params: {stat: "ATK"}}, 156 | SWORDS_DANCE: {name: 'Swords Dance', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 20, damagetype: 'STAT', params: {stat: "DEF", level: 2}}, 157 | WITHDRAW: {name: 'Withdraw', type: Types.WATER, power: 0, accuracy: 0, effect: Effects.RAISE_STAT, pp: 40, damagetype: 'STAT', params: {stat: "DEF"}}, 158 | METRONOME: {name: 'Metronome', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RANDOM_MOVE, pp: 10, damagetype: 'STAT', params: {}}, 159 | RECOVER: {name: 'Recover', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RECOVER_HP, pp: 10, damagetype: 'STAT', params: {proportion: 50}}, 160 | SOFT_BOILED: {name: 'Soft-Boiled', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.RECOVER_HP, pp: 10, damagetype: 'STAT', params: {proportion: 50}}, 161 | HAZE: {name: 'Haze', type: Types.ICE, power: 0, accuracy: 0, effect: Effects.RESET_STATUS, pp: 30, damagetype: 'STAT', params: {}}, 162 | REST: {name: 'Rest', type: Types.PSYCHIC, power: 0, accuracy: 0, effect: Effects.SET_FLAG, pp: 10, damagetype: 'STAT', params: {flag: "RESTING", turns: 2}}, 163 | BIDE: {name: 'Bide', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.SET_FLAG, pp: 10, damagetype: 'PHYS', params: {flag: "BIDE"}}, 164 | COUNTER: {name: 'Counter', type: Types.FIGHTING, power: 0, accuracy: 100, effect: Effects.SET_FLAG, pp: 20, damagetype: 'PHYS', params: {flag: "BIDE"}}, 165 | LIGHT_SCREEN: {name: 'Light Screen', type: Types.PSYCHIC, power: 0, accuracy: 0, effect: Effects.SET_FLAG, pp: 30, damagetype: 'STAT', params: {flag: "PROTECTED_SPATK", turns: 5}}, 166 | MIST: {name: 'Mist', type: Types.ICE, power: 0, accuracy: 0, effect: Effects.SET_FLAG, pp: 30, damagetype: 'STAT', params: {flag: "STATUS_NULL", turns: 2}}, 167 | REFLECT: {name: 'Reflect', type: Types.PSYCHIC, power: 0, accuracy: 0, effect: Effects.SET_FLAG, pp: 20, damagetype: 'STAT', params: {flag: "PROTECTED_ATK", turns: 5}}, 168 | TRANSFORM: {name: 'Transform', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.TRANSFORM, pp: 10, damagetype: 'STAT', params: {}}, 169 | TELEPORT: {name: 'Teleport', type: Types.PSYCHIC, power: 0, accuracy: 0, effect: Effects.WARP_AWAY, pp: 20, damagetype: 'STAT', params: {}}, 170 | ROAR: {name: 'Roar', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.WHIRLWIND, pp: 20, damagetype: 'STAT', params: {}}, 171 | WHIRLWIND: {name: 'Whirlwind', type: Types.NORMAL, power: 0, accuracy: 0, effect: Effects.WHIRLWIND, pp: 20, damagetype: 'STAT', params: {}} 172 | } --------------------------------------------------------------------------------