in development
A Pen by Lovro Selic on CodePen.
in development
A Pen by Lovro Selic on CodePen.
| <div id="preload" class="hidden"></div> | |
| <div class="win" id="setup"> | |
| <div id="load"> | |
| </div> | |
| <!--<a href="/Games/" title="Return to games' index"><img alt="Anxys" class="fr logo" src="/Images/Eyes.png" width="20" height="20"></a>--> | |
| <div id="SC"></div> | |
| <h1 id="title"></h1> | |
| <p>text</p> | |
| Hero's name: <input type = "text" id = "HeroName" value = "HERO" maxlength="10"/> | |
| <p id="buttons"> | |
| <input type='button' id='toggleHelp' value='Show/Hide Instructions'> | |
| <input type='button' id='toggleAbout' value='About'> | |
| </p> | |
| <div id="help" class="section"> | |
| <fieldset> | |
| <legend> | |
| Instructions: | |
| </legend> | |
| <p><strong>KEYS:</strong></p> | |
| <p>Use cursor keys to move.</p> | |
| <p>CTRL ... cast magic.</p> | |
| <p>H ... use healing potion</p> | |
| <p>M ... use mana potion</p> | |
| <p>A, D ... move scroll selection cursor</p> | |
| <p>TAB ... level up</p> | |
| <p>ENTER ... cast selected scroll</p> | |
| <p><strong>SCROLLS:</strong></p> | |
| <div> | |
| <image src="https://www.c00lsch00l.eu/Games/AA/Light.png" alt="Light" class="fl pic" title="Light"> | |
| <p style="position: relative; top: 24px">Magic lamp</p> | |
| <p class="cb"></p> | |
| </div> | |
| <div> | |
| <image src="https://www.c00lsch00l.eu/Games/AA/DrainMana.png" alt="DrainMana" class="fl pic" title="DrainMana"> | |
| <p style="position: relative; top: 24px">Drain Mana: drains mana from all creatures in the area. Also yours!</p> | |
| <p class="cb"></p> | |
| </div> | |
| <div> | |
| <image src="https://www.c00lsch00l.eu/Games/AA/Map.png" alt="Map" class="fl pic" title="Map"> | |
| <p style="position: relative; top: 24px">Map: reveals the map of the exit area</p> | |
| <p class="cb"></p> | |
| </div> | |
| <div> | |
| <image src="https://www.c00lsch00l.eu/Games/AA/BoostWeapon.png" alt="BoostWeapon" class="fl pic" title="BoostWeapon"> | |
| <p style="position: relative; top: 24px">Increase the damage of your sword for the duration of the fight.</p> | |
| <p class="cb"></p> | |
| </div> | |
| <div> | |
| <image src="https://www.c00lsch00l.eu/Games/AA/BoostArmor.png" alt="BoostArmor" class="fl pic" title="BoostArmor"> | |
| <p style="position: relative; top: 24px">Increase your armor for the duration of the fight.</p> | |
| <p class="cb"></p> | |
| </div> | |
| <div> | |
| <image src="https://www.c00lsch00l.eu/Games/AA/DestroyArmor.png" alt="DestroyArmor" class="fl pic" title="DestroyArmor"> | |
| <p style="position: relative; top: 24px">Decrease your opponent's armor for the duration of the fight.</p> | |
| <p class="cb"></p> | |
| </div> | |
| <div> | |
| <image src="https://www.c00lsch00l.eu/Games/AA/DestroyWeapon.png" alt="DestroyWeapon" class="fl pic" title="DestroyWeapon"> | |
| <p style="position: relative; top: 24px">Decrease the damage of your opponent's sword for the duration of the fight.</p> | |
| <p class="cb"></p> | |
| </div> | |
| <div> | |
| <image src="https://www.c00lsch00l.eu/Games/AA/Invisibility.png" alt="Invisibility" class="fl pic" title="Invisibility"> | |
| <p style="position: relative; top: 24px">Invisibility: I will let you figure this one by yourself..</p> | |
| <p class="cb"></p> | |
| </div> | |
| </fieldset> | |
| </div> | |
| <div id="about" class="section"> | |
| <fieldset> | |
| <legend> | |
| About: | |
| </legend> | |
| <image src="https://www.c00lsch00l.eu/Images/SoF.png" alt="Sword of Fargoal" class="fl pic" title="Sword of Fargoal"> | |
| <p> 'Deep Down Into the Darkness' was inspired by C64 classic <a href="https://www.c64-wiki.com/wiki/Sword_of_Fargoal" target="_blank">Sword of Fargoal</a> from 1983, which was itself influenced by 1980 Unix game <a href="https://en.wikipedia.org/wiki/Rogue_(video_game)" | |
| target="_blank">Rogue</a>.</p> | |
| </fieldset> | |
| </div> | |
| <p class="version cb" id="version"></p> | |
| </div> | |
| <div id="game" class="winTrans"></div> | |
| <div id="bottom" class="cb" style="margin-top: 720px"></div> | |
| <div id="temp" class="hidden"></div> | |
| <div id="temp2" class="hidden"></div> |
| //console.clear(); | |
| ///////////////////////////////////////////////// | |
| /* | |
| to do: | |
| MONSTER:Don't melee attack if mana available | |
| Statistics on death | |
| TEST. BALANCE | |
| more scrolls, | |
| new scroll ideas: | |
| - | |
| monster strategies | |
| known bugs: | |
| -can heal dead hero on fight form | |
| */ | |
| //////////////////////////////////////////////////// | |
| var DEBUG = { | |
| frameCount: 0, | |
| fog: false, | |
| coord: false, | |
| level: false, | |
| viewMonsters: true, | |
| addScrolls: function() { | |
| let scrolls = [ | |
| //{ type: "Light", use: "explore" }, | |
| //{ type: "Invisibility", use: "explore" }, | |
| //{ type: "Map", use: "explore" }, | |
| //{ type: "DrainMana", use: "explore" }, | |
| //{ type: "Cripple", use: "explore" }, | |
| //{ type: "BoostWeapon", use: "fight" }, | |
| //{ type: "BoostArmor", use: "fight" }, | |
| //{ type: "DestroyArmor", use: "fight" }, | |
| //{ type: "DestroyWeapon", use: "fight" } | |
| ]; | |
| for (let q = 0, QL = scrolls.length; q < QL; q++) { | |
| let selected = scrolls[q]; | |
| let scroll = new Scroll(new Grid(0, 0), selected.type, selected.use); | |
| HERO.scrolls.add(scroll); | |
| //HERO.scrolls.add(scroll); | |
| //HERO.scrolls.add(scroll); | |
| } | |
| //add potion2 | |
| //let temp = new Potion("health", null); | |
| //temp.exe(); | |
| } | |
| }; | |
| var INI = { | |
| LAST_LEVEL: 2, | |
| GBpLVL: 10, | |
| CpLVL: 10, | |
| GBpDE: 2, | |
| CpCRD: 5, | |
| HPpLVL: 10, | |
| MPpCRD: 9, | |
| WpLVL: 2, | |
| CHpLVL: 6, | |
| LMPpLVL: 3, | |
| SCRpLVL: 6, | |
| Health_INC: 0.4, | |
| EXP_KEY: 50, | |
| MINI_PIX: 4, | |
| TEMPLE_TIMEOUT: 10000, | |
| EXP: 400, | |
| MAGIC_EXP: 50, | |
| EXP_FACTOR: 1.2, | |
| MAGIC_EXP_FACTOR: 1.4, | |
| PTS_LVL: 3, | |
| LVL_HEALTH: 5, | |
| LVL_MANA: 7, | |
| MAGIC_FAIL: 1.5, | |
| MAGIC_POWER_COST: 2, | |
| ORB_MAX_RANGE: 10, | |
| ENEMY_COMMON: 3, | |
| ENEMY_START: 1, | |
| ENEMY_KEY_ADD: 2, | |
| ENEMY_END_ADD: 2, | |
| ENEMY_TEMPLE: 1, | |
| ENEMY_CORRIDOR: 25, | |
| TRIGGER_WAKE: 11, | |
| TRIGGER_VISION: 9, | |
| SHOOT_TIMEOUT: 3000, | |
| INVISIBILITY_TIME: 20, | |
| MANA_DRAIN_RANGE: 10, | |
| MAP_RADIUS: 5, | |
| INITIATIVE_BONUS: 5, | |
| ATTACk_OFFSET: -1, | |
| AGILITY_TURN: 20, | |
| FIGHT_PANEL_WIDTH: 200, | |
| FLEE_AGILITY_DELTA: 5, | |
| POINTS_ON_START: 4, | |
| NEMESIS_RESPAWN: 900, | |
| //NEMESIS_RESPAWN: 30, | |
| STALK_DISTANCE: 3 | |
| }; | |
| var PRG = { | |
| VERSION: "0.27.13.dev", | |
| CSS: "color: #80f709", | |
| NAME: "Deep Down Into the Darkness", | |
| YEAR: 2019, | |
| INIT: function() { | |
| console.log("%c****************************", PRG.CSS); | |
| console.log( | |
| `%c${PRG.NAME} ${PRG.VERSION} by Lovro Selic, (c) C00lSch00l ${ | |
| PRG.YEAR | |
| } on ˘${navigator.userAgent}`, | |
| PRG.CSS | |
| ); | |
| $("#title").html(PRG.NAME); | |
| $("#version").html( | |
| PRG.NAME + | |
| " V" + | |
| PRG.VERSION + | |
| " <span style='font-size:14px'>©</span> C00lSch00l 2019" | |
| ); | |
| $("input#toggleAbout").val("About " + PRG.NAME); | |
| $("#about fieldset legend").append(" " + PRG.NAME + " "); | |
| ENGINE.readyCall = GAME.setup; | |
| ENGINE.init(); | |
| }, | |
| setup: function() { | |
| $("#toggleHelp").click(function() { | |
| $("#help").toggle(400); | |
| }); | |
| $("#toggleAbout").click(function() { | |
| $("#about").toggle(400); | |
| }); | |
| }, | |
| start: function() { | |
| console.log(`%c${PRG.NAME} started.`, PRG.CSS); | |
| $("#startGame").addClass("hidden"); | |
| var disableKeys = ["enter", "space", "tab", "back"]; | |
| for (let key in disableKeys) ENGINE.disableKey(key); | |
| //add boxes | |
| if (!GAME.restarted) { | |
| console.log("%cAdding boxes, setting ENGINE ....", PRG.CSS); | |
| ENGINE.gameWIDTH = 768; | |
| ENGINE.sideWIDTH = 1024 - ENGINE.gameWIDTH; | |
| ENGINE.gameHEIGHT = 768; | |
| ENGINE.titleHEIGHT = 80; | |
| ENGINE.titleWIDTH = 1024; | |
| ENGINE.bottomHEIGHT = 40; | |
| ENGINE.bottomWIDTH = 1024; | |
| ENGINE.checkProximity = false; | |
| ENGINE.checkIntersection = false; | |
| ENGINE.setCollisionsafe(49); | |
| $("#bottom").css( | |
| "margin-top", | |
| ENGINE.gameHEIGHT + ENGINE.titleHEIGHT + ENGINE.bottomHEIGHT | |
| ); | |
| $(ENGINE.gameWindowId).width(ENGINE.gameWIDTH + ENGINE.sideWIDTH + 4); | |
| ENGINE.addBOX( | |
| "TITLE", | |
| ENGINE.titleWIDTH, | |
| ENGINE.titleHEIGHT, | |
| ["title"], | |
| null | |
| ); | |
| ENGINE.addBOX( | |
| "ROOM", | |
| ENGINE.gameWIDTH, | |
| ENGINE.gameHEIGHT, | |
| [ | |
| "background", | |
| "coordview", | |
| "grave", | |
| "animation", | |
| "actors", | |
| "orbs", | |
| "explosion", | |
| "fogview", | |
| "text" | |
| ], | |
| "side" | |
| ); | |
| ENGINE.addBOX( | |
| "SIDE", | |
| ENGINE.sideWIDTH, | |
| ENGINE.gameHEIGHT, | |
| ["sideback", "status", "map", "time"], | |
| "fside" | |
| ); | |
| ENGINE.addBOX( | |
| "DOWN", | |
| ENGINE.bottomWIDTH, | |
| ENGINE.bottomHEIGHT, | |
| ["bottom"], | |
| null | |
| ); | |
| ENGINE.addBOX( | |
| "LEVEL", | |
| ENGINE.gameWIDTH, | |
| ENGINE.gameHEIGHT, | |
| ["floor", "wall", "config", "fog", "coord"], | |
| null | |
| ); | |
| if (!DEBUG.level) $("#LEVEL").addClass("hidden"); | |
| } | |
| GAME.start(); | |
| } | |
| }; | |
| class Gold { | |
| constructor(value, grid) { | |
| this.name = "Gold"; | |
| this.value = value; | |
| this.grid = grid; | |
| this.static = false; | |
| if (value === 100) { | |
| this.sprite = SPRITE.Gold; | |
| } else this.sprite = SPRITE.Coin; | |
| } | |
| exe() { | |
| HERO.gold += this.value; | |
| TEXTPOOL.pool.push( | |
| new TextSprite(this.value, GRID.gridToCoord(this.grid), "#DAA520") | |
| ); | |
| TITLE.change(); | |
| } | |
| } | |
| class Potion { | |
| constructor(type, grid) { | |
| this.name = "Potion"; | |
| this.type = type; | |
| this.grid = grid; | |
| this.static = false; | |
| switch (type) { | |
| case "health": | |
| this.sprite = SPRITE.RedPotion; | |
| this.exe = () => { | |
| HERO.redPotion++; | |
| TITLE.change(); | |
| }; | |
| break; | |
| case "magic": | |
| this.sprite = SPRITE.BluePotion; | |
| this.exe = () => { | |
| HERO.bluePotion++; | |
| TITLE.change(); | |
| }; | |
| break; | |
| } | |
| } | |
| } | |
| class Chest { | |
| constructor(grid) { | |
| this.name = "Chest"; | |
| this.grid = grid; | |
| this.sprite = SPRITE.Chest; | |
| this.static = true; | |
| let option = RND(1, 5); | |
| switch (option) { | |
| case 1: | |
| case 2: | |
| this.contains = new Gold(100, this.grid); | |
| break; | |
| case 3: | |
| case 4: | |
| let type = ["health", "magic"]; | |
| this.contains = new Potion(type.chooseRandom(), this.grid); | |
| break; | |
| case 5: | |
| let boosts = ["health", "mana", "weapon", "armor", "magic"]; | |
| this.contains = new Boost(boosts.chooseRandom(), this.grid); | |
| break; | |
| } | |
| } | |
| exe() { | |
| MAP[GAME.level].DUNGEON.chests.push(this.contains); | |
| } | |
| } | |
| class Boost { | |
| constructor(type, grid) { | |
| this.name = "Boost"; | |
| this.type = type; | |
| this.grid = grid; | |
| this.static = false; | |
| switch (type) { | |
| case "health": | |
| this.sprite = SPRITE.Heart; | |
| this.exe = () => { | |
| HERO.maxHealth += INI.LVL_HEALTH; | |
| HERO.health = HERO.maxHealth; | |
| TITLE.change(); | |
| }; | |
| break; | |
| case "mana": | |
| this.sprite = SPRITE.Mana; | |
| this.exe = () => { | |
| HERO.maxMana += INI.LVL_MANA; | |
| HERO.mana = HERO.maxMana; | |
| TITLE.change(); | |
| }; | |
| break; | |
| case "weapon": | |
| this.sprite = SPRITE.Sword; | |
| this.exe = () => { | |
| HERO.weapon++; | |
| TITLE.change(); | |
| }; | |
| break; | |
| case "armor": | |
| this.sprite = SPRITE.Shield; | |
| this.exe = () => { | |
| HERO.armor++; | |
| TITLE.change(); | |
| }; | |
| break; | |
| case "magic": | |
| this.sprite = SPRITE.Magic; | |
| this.exe = () => { | |
| HERO.magic++; | |
| //HERO.magic += 2; | |
| TITLE.change(); | |
| }; | |
| break; | |
| case "agility": | |
| this.sprite = SPRITE.Agility; | |
| this.exe = () => { | |
| HERO.agility++; | |
| TITLE.change(); | |
| }; | |
| break; | |
| } | |
| } | |
| } | |
| class Lamp { | |
| constructor(grid) { | |
| this.grid = grid; | |
| this.sprite = SPRITE.Lamp; | |
| this.value = 99; | |
| this.start = null; | |
| this.now = null; | |
| this.delta = null; | |
| } | |
| exe() { | |
| if (!HERO.lamp) { | |
| HERO.visibility = 2; | |
| HERO.lamp = this; | |
| HERO.inventory.add(SPRITE.Lamp); | |
| TITLE.change(); | |
| this.switchOn(); | |
| } else { | |
| HERO.lamp.value += this.value; | |
| } | |
| } | |
| update() { | |
| this.now = performance.now(); | |
| this.delta = Math.round((this.now - this.start) / 1000); | |
| if (this.delta >= this.value) this.switchOff(); | |
| } | |
| switchOn() { | |
| this.start = performance.now(); | |
| } | |
| switchOff() { | |
| HERO.visibility = 1; | |
| HERO.lamp = false; | |
| HERO.inventory.delete(SPRITE.Lamp); | |
| TITLE.change(); | |
| } | |
| } | |
| class Scroll { | |
| constructor(grid, type, use) { | |
| this.name = "Scroll"; | |
| this.type = type; | |
| this.id = this.name + this.type; | |
| this.sprite = SPRITE["SCR_" + type] || SPRITE.Scroll; | |
| this.grid = grid; | |
| this.static = false; | |
| this.use = use; | |
| } | |
| exe() { | |
| HERO.scrolls.add(this); | |
| } | |
| action() { | |
| let POOL; | |
| switch (this.type) { | |
| case "Cripple": | |
| console.log("action cripple"); | |
| POOL = MAP[GAME.level].DUNGEON.ENEMY; | |
| for (let q = 0, PL = POOL.length; q < PL; q++) { | |
| let enemy = POOL[q]; | |
| let distance = HERO.MoveState.endGrid.distanceDiagonal( | |
| enemy.MoveState.endGrid | |
| ); | |
| if (distance <= INI.MANA_DRAIN_RANGE) { | |
| enemy.speed = 1; | |
| enemy.agility = drain(enemy.agility); | |
| } | |
| } | |
| break; | |
| case "Map": | |
| let grid; | |
| if (MAP[GAME.level].DUNGEON.mapAnchors.length) { | |
| grid = MAP[GAME.level].DUNGEON.mapAnchors.shift(); | |
| } else grid = MAP[GAME.level].DUNGEON.getAnyGrid(); | |
| MINIMAP.unveil(grid); | |
| TITLE.change(); | |
| break; | |
| case "Light": | |
| let lamp = new Lamp(new Grid(0, 0)); | |
| lamp.exe(); | |
| break; | |
| case "Invisibility": | |
| HERO.invisible(); | |
| break; | |
| case "DrainMana": | |
| HERO.mana = 0; | |
| TITLE.change(); | |
| POOL = MAP[GAME.level].DUNGEON.ENEMY; | |
| for (let q = 0, PL = POOL.length; q < PL; q++) { | |
| let enemy = POOL[q]; | |
| let distance = HERO.MoveState.endGrid.distanceDiagonal( | |
| enemy.MoveState.endGrid | |
| ); | |
| if (distance <= INI.MANA_DRAIN_RANGE) { | |
| enemy.mana = 0; | |
| } | |
| } | |
| break; | |
| case "BoostWeapon": | |
| $("#hero_sword").css({ color: "blue" }); | |
| HERO.weapon = inflate(HERO.weapon); | |
| CONSOLE.print( | |
| `<span class="blue">${ | |
| HERO.name | |
| }</span> applied magical sharpening oil to the sword.` | |
| ); | |
| break; | |
| case "BoostArmor": | |
| $("#hero_shield").css({ color: "blue" }); | |
| HERO.armor = inflate(HERO.armor); | |
| CONSOLE.print( | |
| `<span class="blue">${ | |
| HERO.name | |
| }</span> applied magic protection oil to the armor.` | |
| ); | |
| break; | |
| case "DestroyArmor": | |
| $("#enemy_armor").css({ color: "red" }); | |
| GAME.TURN.enemy.armor = drain(GAME.TURN.enemy.armor); | |
| CONSOLE.print( | |
| `<span class="blue">${ | |
| HERO.name | |
| }</span> magically lowered <span class="red">${ | |
| GAME.TURN.enemy.type.title | |
| }'s</span> defense.` | |
| ); | |
| break; | |
| case "DestroyWeapon": | |
| $("#enemy_weapon").css({ color: "red" }); | |
| GAME.TURN.enemy.weapon = drain(GAME.TURN.enemy.weapon); | |
| CONSOLE.print( | |
| `<span class="blue">${ | |
| HERO.name | |
| }</span> magically drained <span class="red">${ | |
| GAME.TURN.enemy.type.title | |
| }'s</span> weapons.` | |
| ); | |
| break; | |
| default: | |
| console.log("Scroll action ERROR!"); | |
| break; | |
| } | |
| function drain(number) { | |
| let N = RND(Math.floor(0.333 * number), Math.ceil(0.666 * number)); | |
| if (N === number) N = number - 1; | |
| return N; | |
| } | |
| function inflate(number) { | |
| let N = RND(Math.floor(1.2 * number), Math.ceil(1.5 * number)); | |
| if (N === number) N = number + 1; | |
| return N; | |
| } | |
| } | |
| } | |
| var HERO = { | |
| construct: function() { | |
| //HERO.name = "HERO"; | |
| HERO.name = HERO.getName(); | |
| HERO.gold = 0; | |
| //HERO.gold = 999; //DEBUG | |
| HERO.redPotion = 0; | |
| HERO.bluePotion = 0; | |
| HERO.maxHealth = 8; | |
| HERO.maxHealth = 58; //debug | |
| HERO.health = HERO.maxHealth; | |
| HERO.maxMana = 13; | |
| HERO.maxMana = 999; //debug | |
| HERO.mana = HERO.maxMana; | |
| HERO.armor = 1; | |
| HERO.armor = 6; //debug | |
| HERO.weapon = 1; | |
| HERO.weapon = 6; //debug | |
| HERO.magic = 2; | |
| HERO.magic = 20; //debug | |
| HERO.agility = 1; | |
| //HERO.agility = 25; //debug | |
| HERO.magicResistance = 0; | |
| HERO.inventory = new Set(); | |
| HERO.scrolls = new Inventory(); | |
| HERO.silverKey = false; | |
| HERO.goldKey = false; | |
| HERO.depth = 1; | |
| //HERO.level = 1; | |
| HERO.level = 0; | |
| HERO.experience = 0; | |
| //HERO.experience = 399; //debug | |
| HERO.expBuffer = 0; | |
| //HERO.expBuffer = 399; //debug | |
| HERO.magicExpBuffer = 0; | |
| //HERO.magicExpBuffer = 48; //debug | |
| HERO.speed = 6; | |
| HERO.visibility = 1; | |
| HERO.dark = false; | |
| HERO.dead = false; | |
| HERO.cloak = false; | |
| HERO.lamp = false; | |
| HERO.points = 0; | |
| HERO.canEnterTemple = true; | |
| HERO.canLevelUp = false; | |
| HERO.spriteClass = "Knight"; | |
| HERO.asset = ASSET[HERO.spriteClass]; | |
| HERO.actor = new ACTOR(HERO.spriteClass, 0, 0, "front", HERO.asset); | |
| HERO.inFight = false; | |
| HERO.maxDepth = 1; | |
| }, | |
| getName: function() { | |
| console.log("getting Hero name"); | |
| let name = $("#HeroName").val(); | |
| return name.toLowerCase().capitalize(); | |
| }, | |
| invisible: function() { | |
| HERO.dark = true; | |
| HERO.cloak = new SimpleTimer(INI.INVISIBILITY_TIME, HERO.visible); | |
| HERO.spriteClass = "KnightInvisible"; | |
| HERO.setSpriteClass(HERO.spriteClass); | |
| }, | |
| visible: function() { | |
| HERO.dark = false; | |
| HERO.cloak = false; | |
| HERO.spriteClass = "Knight"; | |
| HERO.setSpriteClass(HERO.spriteClass); | |
| }, | |
| setSpriteClass: function(spriteClass) { | |
| HERO.asset = ASSET[spriteClass]; | |
| HERO.actor.class = spriteClass; | |
| HERO.actor.asset = HERO.asset; | |
| HERO.actor.resetIndexes(); | |
| HERO.actor.animateMove(HERO.actor.orientation); | |
| }, | |
| init: function() { | |
| GRID.gridToSprite(MAP[GAME.level].DUNGEON[GAME.location], HERO.actor); | |
| HERO.MoveState = new MoveState( | |
| MAP[GAME.level].DUNGEON[GAME.location], | |
| DOWN | |
| ); | |
| ENGINE.VIEWPORT.check(HERO.actor); | |
| ENGINE.VIEWPORT.alignTo(HERO.actor); | |
| }, | |
| draw: function() { | |
| if (HERO.dead) return; | |
| ENGINE.spriteDraw( | |
| "actors", | |
| HERO.actor.vx, | |
| HERO.actor.vy, | |
| HERO.actor.sprite() | |
| ); | |
| ENGINE.layersToClear.add("actors"); | |
| }, | |
| move: function() { | |
| if (HERO.dead) return; | |
| if (HERO.MoveState.moving) { | |
| GRID.translateMove(HERO, true, HeroOnFinish); | |
| } | |
| function HeroOnFinish() { | |
| TITLE.change(); | |
| HERO.updView(); | |
| } | |
| }, | |
| changeDirection: function(dir) { | |
| if (HERO.MoveState.moving) return; | |
| let x = HERO.MoveState.endGrid.x + dir.x; | |
| let y = HERO.MoveState.endGrid.y + dir.y; | |
| if (!GRID.isBlock(x, y)) { | |
| HERO.MoveState.next(dir); | |
| } | |
| }, | |
| interactLists: function() { | |
| let LIST = ["boosts", "chests", "gold", "potions", "lamps", "scrolls"]; | |
| for (let outer = 0, LN = LIST.length; outer < LN; outer++) { | |
| let LL = MAP[GAME.level].DUNGEON[LIST[outer]].length; | |
| for (let list_index = 0; list_index < LL; list_index++) { | |
| let element = MAP[GAME.level].DUNGEON[LIST[outer]][list_index]; | |
| let hit = GRID.collision(HERO, element.grid); | |
| if (hit) { | |
| //console.log(hit, element); | |
| if (element.static) HERO.MoveState.reverse(); | |
| element.exe(); | |
| ENGINE.VIEWPORT.changed = true; | |
| MAP[GAME.level].DUNGEON[LIST[outer]].splice(list_index, 1); | |
| GAME.PAINT.config(); | |
| return; | |
| } | |
| } | |
| } | |
| }, | |
| interactStatic: function() { | |
| let LIST = [ | |
| "door", | |
| "gate", | |
| "entrance", | |
| "exit", | |
| "goldKey", | |
| "silverKey", | |
| "temple" | |
| ]; | |
| let element; | |
| for (let q = 0, LN = LIST.length; q < LN; q++) { | |
| element = check(LIST[q]); | |
| if (element !== null) break; | |
| } | |
| if (element === null) return; | |
| switch (element) { | |
| case "door": | |
| if (HERO.silverKey) { | |
| HERO.silverKey = false; | |
| HERO.incExp(getKeyExp()); | |
| SpritePOOL.pool.push( | |
| new PartSprite( | |
| GRID.gridToCoord(MAP[GAME.level].DUNGEON.door), | |
| SPRITE.Door, | |
| SPRITE.Door.height, | |
| 1 | |
| ) | |
| ); | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON.door) | |
| ] = 1; | |
| MAP[GAME.level].DUNGEON.door = null; | |
| MAP[GAME.level].DUNGEON.setObstacles( | |
| MAP[GAME.level].DUNGEON.door, | |
| MAP[GAME.level].DUNGEON.gate | |
| ); | |
| HERO.inventory.delete(SPRITE.silverKey); | |
| GAME.PAINT.config(); | |
| TITLE.change(); | |
| } else { | |
| HERO.MoveState.reverse(); | |
| } | |
| break; | |
| case "gate": | |
| if (HERO.goldKey) { | |
| HERO.goldKey = false; | |
| HERO.incExp(getKeyExp()); | |
| SpritePOOL.pool.push( | |
| new PartSprite( | |
| GRID.gridToCoord(MAP[GAME.level].DUNGEON.gate), | |
| SPRITE.Gate, | |
| SPRITE.Gate.height, | |
| 1 | |
| ) | |
| ); | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON.gate) | |
| ] = 1; | |
| MAP[GAME.level].DUNGEON.gate = null; | |
| MAP[GAME.level].DUNGEON.setObstacles( | |
| MAP[GAME.level].DUNGEON.door, | |
| MAP[GAME.level].DUNGEON.gate | |
| ); | |
| HERO.inventory.delete(SPRITE.goldKey); | |
| GAME.PAINT.config(); | |
| TITLE.change(); | |
| } else { | |
| HERO.MoveState.reverse(); | |
| } | |
| break; | |
| case "entrance": | |
| if (GAME.level > 1) { | |
| console.log("Going up!"); | |
| if (ENGINE.GAME.keymap[ENGINE.KEY.map.tab]) { | |
| HERO.usingStairs(-1); | |
| } | |
| } | |
| break; | |
| case "exit": | |
| console.log("Going down"); | |
| if (ENGINE.GAME.keymap[ENGINE.KEY.map.tab]) { | |
| HERO.usingStairs(1); | |
| } | |
| break; | |
| case "goldKey": | |
| case "silverKey": | |
| HERO[element] = true; | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON[element]) | |
| ] = 1; | |
| MAP[GAME.level].DUNGEON[element] = null; | |
| GAME.PAINT.config(); | |
| HERO.incExp(getKeyExp()); | |
| HERO.inventory.add(SPRITE[element]); | |
| TITLE.change(); | |
| break; | |
| case "temple": | |
| if (HERO.canEnterTemple) GAME.visitTemple(); | |
| console.log("Temple"); | |
| break; | |
| } | |
| function check(instance) { | |
| let hit = GRID.collision(HERO, MAP[GAME.level].DUNGEON[instance]); | |
| if (hit) { | |
| return instance; | |
| } else return null; | |
| } | |
| function getKeyExp() { | |
| let exp = INI.EXP_KEY; | |
| for (let i = 1; i <= GAME.level - 1; i++) { | |
| exp *= INI.EXP_FACTOR; | |
| } | |
| console.log("GATE, KEY exp awarded", exp); | |
| return round10(exp); | |
| } | |
| }, | |
| useManaPotion: function() { | |
| if (HERO.mana === HERO.maxMana) return; | |
| if (HERO.bluePotion > 0) { | |
| HERO.bluePotion--; | |
| HERO.incMana(); | |
| } | |
| }, | |
| useHealingPotion: function() { | |
| if (HERO.health === HERO.maxHealth) return 0; | |
| if (HERO.redPotion > 0) { | |
| HERO.redPotion--; | |
| return HERO.heal(); | |
| } | |
| }, | |
| heal: function() { | |
| let addHealth = Math.round(INI.Health_INC * HERO.maxHealth); | |
| let realHealing = Math.min(addHealth, HERO.maxHealth - HERO.health); | |
| HERO.health += addHealth; | |
| let above = GRID.gridToCoord(HERO.MoveState.homeGrid.add(UP)).add( | |
| new Vector(0, 24) | |
| ); | |
| TEXTPOOL.pool.push( | |
| new TextSprite(("+" + addHealth).toString(), above, "#DD0000", 50) | |
| ); | |
| HERO.health = Math.min(HERO.health, HERO.maxHealth); | |
| TITLE.change(); | |
| return realHealing; | |
| }, | |
| incMana: function() { | |
| let above = GRID.gridToCoord(HERO.MoveState.homeGrid.add(UP)).add( | |
| new Vector(0, 24) | |
| ); | |
| let addMana = Math.round(INI.Health_INC * HERO.maxMana); | |
| HERO.mana += addMana; | |
| TEXTPOOL.pool.push( | |
| new TextSprite(("+" + addMana).toString(), above, "#0000EE", 50) | |
| ); | |
| HERO.mana = Math.min(HERO.mana, HERO.maxMana); | |
| TITLE.change(); | |
| }, | |
| decMana: function(dec) { | |
| HERO.mana -= dec; | |
| if (HERO.mana <= 0) HERO.mana = 0; | |
| TITLE.change(); | |
| }, | |
| updView: function() { | |
| ENGINE.VIEWPORT.changed = true; | |
| TITLE.change(); | |
| let x = HERO.MoveState.endGrid.x - 1; | |
| let y = HERO.MoveState.endGrid.y - 1; | |
| let left = x * ENGINE.INI.GRIDPIX; | |
| let top = y * ENGINE.INI.GRIDPIX; | |
| ENGINE.cutManyGrids(LAYER.fog, new Point(left, top), 3); | |
| for (let q = x; q < x + 3; q++) { | |
| for (let w = y; w < y + 3; w++) { | |
| let temp = new Grid(q, w); | |
| MINIMAP.maps[GAME.level].map[GRID.gridToIndex(temp)] &= 127; | |
| MINIMAP.maps[GAME.level].map[GRID.gridToIndex(temp)] |= 64; //set fog info | |
| } | |
| } | |
| if (HERO.visibility !== 2) return; | |
| for (let q = 0; q < ENGINE.directions.length; q++) { | |
| let test = HERO.MoveState.endGrid.add(ENGINE.directions[q]); | |
| if (!GRID.gridIsBlock(test)) { | |
| test = test.add(ENGINE.directions[q]); | |
| ENGINE.cutGrid(LAYER.fog, GRID.gridToCoord(test)); | |
| MINIMAP.maps[GAME.level].map[GRID.gridToIndex(test)] &= 127; | |
| MINIMAP.maps[GAME.level].map[GRID.gridToIndex(test)] |= 64; //set fog info | |
| } | |
| } | |
| var vectors = [new Vector(1, 1), new Vector(1, 0), new Vector(0, 1)]; | |
| for (let q = 0; q < ENGINE.corners.length; q++) { | |
| let test = HERO.MoveState.endGrid.add(ENGINE.corners[q]); | |
| if (!GRID.gridIsBlock(test)) { | |
| for (let w = 0; w < vectors.length; w++) { | |
| let tVector = ENGINE.corners[q].mul(vectors[w]); | |
| let lightGrid = HERO.MoveState.endGrid.add(tVector); | |
| ENGINE.cutGrid(LAYER.fog, GRID.gridToCoord(lightGrid)); | |
| MINIMAP.maps[GAME.level].map[GRID.gridToIndex(lightGrid)] &= 127; | |
| MINIMAP.maps[GAME.level].map[GRID.gridToIndex(lightGrid)] |= 64; //set fog info | |
| } | |
| } | |
| } | |
| }, | |
| manage: function() { | |
| HERO.move(); | |
| if (HERO.lamp) HERO.lamp.update(); | |
| if (HERO.cloak) HERO.cloak.update(); | |
| HERO.interactLists(); | |
| HERO.interactStatic(); | |
| HERO.collisionEnemy(); | |
| }, | |
| incExp: function(exp) { | |
| HERO.experience += exp; | |
| HERO.expBuffer += exp; | |
| if (HERO.expBuffer >= GAME.EXP) { | |
| HERO.expBuffer -= GAME.EXP; | |
| HERO.canLevelUp = true; | |
| GAME.EXP *= INI.EXP_FACTOR; | |
| GAME.EXP = Math.round(GAME.EXP); | |
| console.log("NEW EXP limit", GAME.EXP); | |
| } | |
| }, | |
| castMagic: function() { | |
| const dir = HERO.MoveState.dir; | |
| const cost = HERO.magic + INI.MAGIC_POWER_COST; | |
| if (cost > HERO.mana) { | |
| //sound failed magic | |
| return; | |
| } | |
| if (success() && dir !== null) { | |
| const power = setPower(); | |
| HERO.mana -= cost; | |
| TITLE.change(); | |
| //sound casting magic | |
| ORBS.pool.push( | |
| new Orb(HERO.MoveState.homeGrid, dir, power, HERO.magic, "friendly") | |
| ); | |
| } else { | |
| HERO.decMana(1); | |
| //sound failed magic | |
| EXPLOSIONS.pool.push( | |
| new AnimationSPRITE(HERO.actor.x, HERO.actor.y, "Fizzle_", 10) | |
| ); | |
| } | |
| function success() { | |
| let prop = Math.round(HERO.magic / (HERO.magic + INI.MAGIC_FAIL) * 100); | |
| return probable(prop); | |
| } | |
| function setPower() { | |
| let bottom = Math.round(HERO.magic * 0.8); | |
| if (bottom === 0) bottom = 1; | |
| const power = RND(bottom, Math.round(HERO.magic * 1.2)); | |
| return power; | |
| } | |
| }, | |
| collisionEnemy: function() { | |
| let POOL = MAP[GAME.level].DUNGEON.ENEMY; | |
| for (let PL = POOL.length, q = PL - 1; q >= 0; q--) { | |
| let enemy = POOL[q]; | |
| if (!enemy.visible) continue; | |
| //let hitGrid = GRID.spriteToSpriteCollision(HERO, enemy); | |
| let hitShape = ENGINE.collision(HERO.actor, enemy.actor); | |
| //research hitGrid || hitShape, hitGrid && hitShape | |
| if (hitShape) { | |
| GAME.fight(enemy, true, q); | |
| break; | |
| } | |
| } | |
| }, | |
| turn: function(enemy) { | |
| if (!GAME.TURN.fight_active) return; | |
| let damage = GAME.TURN.damage(HERO, enemy); | |
| if (damage > 0) { | |
| CONSOLE.print( | |
| `<span class="blue">${ | |
| HERO.name | |
| }</span> hits and makes <span class="orange">${damage}</span> damage.` | |
| ); | |
| enemy.health -= damage; | |
| if (enemy.health <= 0) { | |
| enemy.die(); | |
| CONSOLE.print( | |
| `<span class="red">${enemy.type.title}</span> was killed.` | |
| ); | |
| CONSOLE.print( | |
| `<span class="blue">${HERO.name} gets ${enemy.type.exp} XP.</span>` | |
| ); | |
| GAME.TURN.fight_active = false; | |
| MAP[GAME.level].DUNGEON.ENEMY.splice(GAME.TURN.enemyIndex, 1); | |
| GAME.CLICK.endFight(); | |
| LOG[enemy.type.title].kills++; | |
| } | |
| enemy.health = Math.max(0, enemy.health); | |
| GAME.fightRefresh(enemy); | |
| } else { | |
| CONSOLE.print(`<span class="blue">${HERO.name}</span> misses.`); | |
| } | |
| }, | |
| death: function() { | |
| console.log(`%cHERO died`, PRG.CSS); | |
| HERO.dead = true; | |
| HERO.dark = true; | |
| ENGINE.spriteDraw("grave", HERO.actor.vx, HERO.actor.vy, SPRITE.Grave); | |
| ENGINE.TEXT.setFS(60); | |
| ENGINE.TEXT.centeredText("THE END", 300); | |
| //Game still runs!! | |
| GAME.over(); | |
| }, | |
| usingStairs: function(delta) { | |
| GAME.prepareLevel = GAME.level + delta; | |
| switch (delta) { | |
| case 1: | |
| GAME.location = "entrance"; | |
| HERO.maxDepth = Math.max(HERO.maxDepth, GAME.prepareLevel); | |
| break; | |
| case -1: | |
| GAME.location = "exit"; | |
| break; | |
| } | |
| console.log( | |
| delta, | |
| "HERO using the stairs to level", | |
| GAME.prepareLevel, | |
| GAME.location | |
| ); | |
| console.log("GAME waitin for all processes to complete"); | |
| ENGINE.GAME.ANIMATION.STACK.push(GAME.coolDown); | |
| ENGINE.GAME.ANIMATION.stop(); | |
| } | |
| }; | |
| class Orb { | |
| constructor(grid, dir, power, range, type) { | |
| this.grid = Grid.toClass(grid); | |
| this.type = type; | |
| this.power = power; | |
| this.speed = 16; | |
| this.range = Math.min(range, INI.ORB_MAX_RANGE); | |
| let id; | |
| if (type === "friendly") { | |
| id = "MagicOrb"; | |
| } else id = "RedMagic"; | |
| this.actor = new ACTOR(id, 0, 0, "linear", ASSET[id]); | |
| GRID.gridToSprite(this.grid, this.actor); | |
| this.alignToViewport(); | |
| this.MoveState = new MoveState(this.grid, dir); | |
| this.MoveState.next(dir); | |
| } | |
| alignToViewport() { | |
| ENGINE.VIEWPORT.alignTo(this.actor); | |
| } | |
| draw() { | |
| ENGINE.spriteDraw( | |
| "orbs", | |
| this.actor.vx, | |
| this.actor.vy, | |
| this.actor.sprite() | |
| ); | |
| } | |
| fizzle() { | |
| EXPLOSIONS.pool.push( | |
| new AnimationSPRITE(this.actor.x, this.actor.y, "Fizzle_", 10) | |
| ); | |
| } | |
| } | |
| var ORBS = { | |
| pool: [], | |
| draw: function() { | |
| let OPL = ORBS.pool.length; | |
| if (OPL === 0) return; | |
| ENGINE.layersToClear.add("orbs"); | |
| for (let q = OPL - 1; q >= 0; q--) { | |
| ORBS.pool[q].draw(); | |
| } | |
| }, | |
| manage: function() { | |
| ORBS.move(); | |
| ORBS.collide(); | |
| }, | |
| move: function() { | |
| let OPL = ORBS.pool.length; | |
| if (OPL === 0) return; | |
| for (let q = OPL - 1; q >= 0; q--) { | |
| let orb = ORBS.pool[q]; | |
| if (orb.range === 0) { | |
| orb.fizzle(); | |
| ORBS.pool.splice(q, 1); | |
| continue; | |
| } | |
| if (orb.MoveState.moving) { | |
| GRID.translateMove(orb); | |
| } else { | |
| orb.MoveState.next(orb.MoveState.dir); | |
| orb.range--; | |
| } | |
| } | |
| }, | |
| collide: function() { | |
| //to background | |
| let OPL = ORBS.pool.length; | |
| if (OPL === 0) return; | |
| for (let q = OPL - 1; q >= 0; q--) { | |
| let orb = ORBS.pool[q]; | |
| if (GRID.gridIsBlock(orb.MoveState.homeGrid)) { | |
| EXPLOSIONS.pool.push( | |
| new AnimationSPRITE(orb.actor.x, orb.actor.y, "ShipExp", 8) | |
| ); | |
| ORBS.pool.splice(q, 1); | |
| continue; //next orb | |
| } | |
| } | |
| //to enemy | |
| OPL = ORBS.pool.length; | |
| if (OPL === 0) return; | |
| for (let q = OPL - 1; q >= 0; q--) { | |
| let orb = ORBS.pool[q]; | |
| if (orb.type === "friendly") { | |
| let ENML = MAP[GAME.level].DUNGEON.ENEMY.length; | |
| for (let w = ENML - 1; w >= 0; w--) { | |
| let enemy = MAP[GAME.level].DUNGEON.ENEMY[w]; | |
| //let hit = GRID.spriteToSpriteCollision(orb, enemy); | |
| let hit = ENGINE.collision(orb.actor, enemy.actor); | |
| if (hit) { | |
| // | |
| HERO.magicExpBuffer++; | |
| //console.log("HERO.magicExpBuffer", HERO.magicExpBuffer); | |
| if (HERO.magicExpBuffer >= GAME.MAGIC_EXP) { | |
| HERO.magicExpBuffer -= GAME.MAGIC_EXP; | |
| GAME.MAGIC_EXP *= INI.MAGIC_EXP_FACTOR; | |
| GAME.MAGIC_EXP = round10(GAME.MAGIC_EXP); | |
| HERO.magic++; | |
| console.log( | |
| "magic increased due to succesful use, new EXP limit", | |
| GAME.MAGIC_EXP | |
| ); | |
| TITLE.change(); | |
| } | |
| // | |
| let damage = orb.power - enemy.type.magicResistance; | |
| enemy.health -= damage; | |
| damage = Math.max(damage, 0); | |
| let above = GRID.gridToCoord(enemy.MoveState.homeGrid.add(UP)).add( | |
| new Vector(0, 24) | |
| ); | |
| if (damage === 0) { | |
| TEXTPOOL.pool.push( | |
| new TextSprite("Resisted".toString(), above, "#AAA", 50) | |
| ); | |
| EXPLOSIONS.pool.push( | |
| new AnimationSPRITE(orb.actor.x, orb.actor.y, "Fizzle_", 10) | |
| ); | |
| } else if (enemy.health > 0) { | |
| TEXTPOOL.pool.push( | |
| new TextSprite(("-" + damage).toString(), above, "#FF0000", 50) | |
| ); | |
| EXPLOSIONS.pool.push( | |
| new AnimationSPRITE(orb.actor.x, orb.actor.y, "AlienExp", 6) | |
| ); | |
| } else { | |
| EXPLOSIONS.pool.push( | |
| new AnimationSPRITE(orb.actor.x, orb.actor.y, "ShipExp", 8) | |
| ); | |
| enemy.die(); | |
| MAP[GAME.level].DUNGEON.ENEMY.splice(w, 1); | |
| } | |
| ORBS.pool.splice(q, 1); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| //to HERO | |
| OPL = ORBS.pool.length; | |
| if (OPL === 0) return; | |
| for (let q = OPL - 1; q >= 0; q--) { | |
| let orb = ORBS.pool[q]; | |
| if (orb.type !== "friendly") { | |
| //let hit = GRID.spriteToSpriteCollision(orb, HERO); | |
| let hit = ENGINE.collision(orb.actor, HERO.actor); | |
| if (hit) { | |
| let resistance = Math.floor(HERO.magic / 3); | |
| let damage = orb.power - resistance; | |
| HERO.health -= damage; | |
| HERO.health = Math.max(HERO.health, 0); | |
| TITLE.change(); | |
| damage = Math.max(damage, 0); | |
| let above = GRID.gridToCoord(HERO.MoveState.homeGrid.add(UP)).add( | |
| new Vector(0, 22) | |
| ); | |
| if (damage === 0) { | |
| TEXTPOOL.pool.push( | |
| new TextSprite("Resisted".toString(), above, "#AAA", 50) | |
| ); | |
| EXPLOSIONS.pool.push( | |
| new AnimationSPRITE(orb.actor.x, orb.actor.y, "Fizzle_", 10) | |
| ); | |
| } else if (HERO.health > 0) { | |
| TEXTPOOL.pool.push( | |
| new TextSprite(("-" + damage).toString(), above, "#FF0022", 50) | |
| ); | |
| EXPLOSIONS.pool.push( | |
| new AnimationSPRITE(orb.actor.x, orb.actor.y, "AlienExp", 6) | |
| ); | |
| } else { | |
| EXPLOSIONS.pool.push( | |
| new AnimationSPRITE(orb.actor.x, orb.actor.y, "ShipExp", 8) | |
| ); | |
| console.log("HERO died from Orb collision"); | |
| HERO.death(); | |
| } | |
| ORBS.pool.splice(q, 1); | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| var ENEMY = { | |
| draw: function() { | |
| let POOL = MAP[GAME.level].DUNGEON.ENEMY; | |
| for (let q = 0, PL = POOL.length; q < PL; q++) { | |
| let enemy = POOL[q]; | |
| ENGINE.VIEWPORT.alignTo(enemy.actor); //pazi, v translateMove se ponovi!! samo za DEV, lahko isključiš ko riše samo visible | |
| if (enemy.visible || DEBUG.viewMonsters) { | |
| ENGINE.spriteDraw( | |
| "actors", | |
| enemy.actor.vx, | |
| enemy.actor.vy, | |
| enemy.actor.sprite() | |
| ); | |
| } | |
| ENGINE.layersToClear.add("actors"); | |
| } | |
| }, | |
| manage: function() { | |
| //DEBUG.frameCount++; | |
| //console.log("**************** REVIEW cycle", DEBUG.frameCount); | |
| //if (DEBUG.frameCount > 12) GAME.abort(); | |
| let POOL = MAP[GAME.level].DUNGEON.ENEMY; | |
| //let t1 = performance.now(); | |
| for (let PL = POOL.length, q = PL - 1; q >= 0; q--) { | |
| let enemy = POOL[q]; | |
| let distance = HERO.MoveState.endGrid.distanceDiagonal( | |
| enemy.MoveState.endGrid | |
| ); | |
| if (enemy.MoveState.moving) { | |
| if (checkFreedom(q)) GRID.translateMove(enemy); | |
| if (distance <= INI.TRIGGER_VISION) enemy.visible = enemy.isVisible(); | |
| continue; | |
| } | |
| let node; | |
| let path = []; | |
| if (enemy.awake) { | |
| //AWAKE | |
| //should go to sleep? | |
| if (distance > INI.TRIGGER_WAKE && enemy.strategy !== "hunt") { | |
| //console.log(enemy, "goes to sleep"); | |
| enemy.sleep(); | |
| continue; | |
| } | |
| //go to sleep if path too long | |
| node = GRID.findDungeonPath( | |
| enemy.MoveState.endGrid, | |
| HERO.MoveState.endGrid, | |
| MAP[GAME.level].DUNGEON.obstacles, | |
| GAME.DISTANCE_WAKE | |
| ); | |
| //hunters should be switched to wander in order to sleep! | |
| if (node && node.dist !== 0 && node.priority > INI.TRIGGER_WAKE && enemy.strategy !== "hunt") { | |
| enemy.sleep(); | |
| continue; | |
| } | |
| //sleep, awake evaluation end | |
| //check vision, line of sight | |
| //if in visible range, check if truly visible | |
| if (distance <= INI.TRIGGER_VISION) enemy.visible = enemy.isVisible(); | |
| //end checking vision | |
| //not moving | |
| //SHOOT | |
| if (!HERO.dark && enemy.magic > 0) { | |
| //has ability to shoot | |
| const cost = enemy.magic + INI.MAGIC_POWER_COST; | |
| if (cost <= enemy.mana && distance <= enemy.magic) { | |
| //has enough mana and range | |
| if (distance <= INI.STALK_DISTANCE && enemy.visible && !enemy.canShoot){ | |
| //stalk while on cool down, or else shoot | |
| enemy.strategy = "stalk"; | |
| } else enemy.strategy = "hunt"; | |
| if (enemy.canShoot) { | |
| let direction = enemy.MoveState.endGrid.absDirection( | |
| HERO.MoveState.endGrid | |
| ); | |
| if (direction.isOrto() || direction.isDiagonal()) { | |
| //has proper direction | |
| if ( | |
| GRID.vision(enemy.MoveState.endGrid, HERO.MoveState.endGrid) | |
| ) { | |
| enemy.mana -= cost; | |
| ORBS.pool.push( | |
| new Orb( | |
| enemy.MoveState.homeGrid, | |
| direction, | |
| setPower(enemy), | |
| enemy.magic, | |
| "deadly" | |
| ) | |
| ); | |
| enemy.casted(); | |
| continue; | |
| } | |
| } | |
| } | |
| } else { | |
| //not enough mana to cast | |
| //change strategy --> hunt | |
| enemy.strategy = "hunt"; | |
| } | |
| } | |
| //SHOOT END | |
| //has stack? | |
| if (enemy.dirStack.length > 0) { | |
| // has stack, but let's check first if it should hunt | |
| if (!HERO.dark && distance <= enemy.type.triggers.hunt) { | |
| //switch to hunt and clear stack | |
| enemy.strategy = "hunt"; | |
| enemy.dirStack.clear(); | |
| continue; | |
| } else { | |
| //yes, move from stack | |
| enemy.makeMove(); | |
| continue; | |
| } | |
| } else { | |
| //no, stack is empty | |
| //set direction stack from strategy | |
| switch (enemy.strategy) { | |
| case "wander": | |
| //check first for status change | |
| if (!HERO.dark && distance <= enemy.type.triggers.hunt) { | |
| enemy.strategy = "hunt"; | |
| break; | |
| } | |
| //finished checking for status | |
| path = GRID.AI.wanderer.hunt(enemy.MoveState).return; | |
| break; | |
| case "hunt": | |
| //check first for status change | |
| if (HERO.dark || distance >= enemy.type.triggers.wander) { | |
| enemy.strategy = "wander"; | |
| break; | |
| } | |
| //finished checking for status | |
| node = GRID.findDungeonPath( | |
| enemy.MoveState.endGrid, | |
| HERO.MoveState.endGrid, | |
| MAP[GAME.level].DUNGEON.obstacles | |
| ); | |
| //debug | |
| if (enemy.type.name === "Wizard"){ | |
| console.log(q, enemy, "hunter-->", node); | |
| } | |
| //debug end | |
| if (node === null) { | |
| GAME.fight(enemy, false, q); | |
| continue; | |
| } else { | |
| path = node.stack; | |
| path.length = 1; | |
| } | |
| //end finding path | |
| break; | |
| case "stalk": | |
| //console.log("Stalking!"); | |
| path = GRID.AI.keepTheDistance.hunt(enemy.MoveState, HERO.MoveState.endGrid, INI.STALK_DISTANCE).return | |
| //console.log("Stalking!", path, path.length, path[0]); | |
| break; | |
| case "goto": | |
| //console.log("goto"); | |
| break; | |
| case "guard": | |
| break; | |
| case "seek": | |
| break; | |
| case "flee": | |
| break; | |
| default: | |
| console.log("monster strategy ERROR"); | |
| break; | |
| } | |
| //end of switch, if not continue(d) break points to here: | |
| if (path && path.length > 0) { | |
| enemy.dirStack = path; | |
| enemy.makeMove(); | |
| continue; | |
| } else { | |
| continue; | |
| } | |
| } | |
| //stack end | |
| } else { | |
| //HYBERNATING | |
| //should wake? | |
| if (distance <= INI.TRIGGER_WAKE) { | |
| //console.log("check if it should wake", enemy); | |
| node = GRID.findDungeonPath( | |
| enemy.MoveState.endGrid, | |
| HERO.MoveState.endGrid, | |
| MAP[GAME.level].DUNGEON.obstacles, | |
| GAME.DISTANCE_WAKE | |
| ); | |
| if (node.status === "NoSolution") continue; | |
| if ( | |
| node === null || | |
| (node.dist === 0 && node.priority <= INI.TRIGGER_WAKE) | |
| ) | |
| enemy.wake(); | |
| } | |
| continue; | |
| } | |
| } | |
| //GAME.abort() | |
| //let t2 = performance.now() - t1; | |
| //console.log("spent", t2, "ms"); | |
| function checkFreedom(q) { | |
| for (let W = 0; W < q; W++) { | |
| if (MAP[GAME.level].DUNGEON.ENEMY[W].awake) { | |
| if ( | |
| GRID.same( | |
| MAP[GAME.level].DUNGEON.ENEMY[q].MoveState.endGrid, | |
| MAP[GAME.level].DUNGEON.ENEMY[W].MoveState.endGrid | |
| ) | |
| ) | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| function setPower(enemy) { | |
| let bottom = Math.round(enemy.type.magic * 0.8); | |
| if (bottom === 0) bottom = 1; | |
| const power = RND(bottom, Math.round(enemy.type.magic * 1.2)); | |
| return power; | |
| } | |
| } | |
| }; | |
| var GAME = { | |
| CSS: "color: #0F0", | |
| abort: function() { | |
| ENGINE.GAME.stopAnimation = true; | |
| console.error("..... aborting GAME, DEBUG info:"); | |
| console.log("scrolls", HERO.scrolls); | |
| }, | |
| start: function() { | |
| GAME.DISTANCE_WAKE = Math.floor(2 * Math.sqrt((INI.TRIGGER_WAKE ** 2) / 2)); | |
| console.log("GAME.DISTANCE_WAKE", GAME.DISTANCE_WAKE); | |
| ENGINE.GAME.start(); //INIT game loop | |
| ENGINE.KEY.on(); // keymapping active | |
| CreateDungeon.init(); | |
| GAME.prepareForRestart(); //everything required for safe restart | |
| GAME.level = 1; | |
| GAME.prepareLevel = null; | |
| GAME.location = "entrance"; | |
| GAME.score = 0; | |
| ENGINE.INI.ANIMATION_INTERVAL = 16; | |
| HERO.construct(); | |
| ENGINE.GAME.ANIMATION.waitThen(GAME.levelStart, 2); | |
| GAME.EXP = INI.EXP; | |
| GAME.MAGIC_EXP = INI.MAGIC_EXP; | |
| GAME.time = new Timer(); | |
| }, | |
| prepareForRestart: function() { | |
| console.log("preparing game for start or safe restart ..."); | |
| //everything required for safe restart | |
| ENGINE.GAME.ANIMATION.stop(); | |
| //clear layers | |
| ENGINE.clearLayer("text"); | |
| ENGINE.clearLayer("animation"); | |
| ENGINE.clearLayer("grave"); | |
| ENGINE.clearLayer("actors"); | |
| }, | |
| coolDown: function() { | |
| ENGINE.GAME.ANIMATION.stop(); | |
| console.log("%c ...all processes completed", PRG.CSS); | |
| GAME.nextLevel(); | |
| }, | |
| spawnNemesis: function() { | |
| console.log("GAME.spawnNemesis"); | |
| CreateDungeon.spawnNemesis(GAME.level); | |
| //just one for debug purpose! uncomment line below!! | |
| GAME.levelTime = new SimpleTimer(INI.NEMESIS_RESPAWN, GAME.spawnNemesis); | |
| //GAME.levelTime = new SimpleTimer(9999, GAME.spawnNemesis); | |
| }, | |
| levelExecute: function() { | |
| console.log(`%cLevel ${GAME.level} executes ...`, GAME.CSS); | |
| ENGINE.VIEWPORT.reset(); | |
| HERO.init(); | |
| EXPLOSIONS.pool.clear(); | |
| GAME.firstFrameDraw(GAME.level); | |
| GAME.levelTime = new SimpleTimer(INI.NEMESIS_RESPAWN, GAME.spawnNemesis); | |
| if (HERO.level === 0) { | |
| GAME.levelUp(INI.POINTS_ON_START); | |
| } else GAME.levelContinue(); | |
| }, | |
| levelContinue: function() { | |
| console.log("LEVEL", GAME.level, "continues ..."); | |
| ENGINE.GAME.ANIMATION.STACK.push(GAME.run); | |
| ENGINE.GAME.ANIMATION.queue(); | |
| }, | |
| levelStart: function() { | |
| console.log(`%cStarting level ${GAME.level}`, GAME.CSS); | |
| GAME.initLevel(GAME.level); | |
| GAME.levelExecute(); | |
| }, | |
| nextLevel: function() { | |
| GAME.level = GAME.prepareLevel; | |
| console.log("creating next level: ", GAME.level); | |
| if (GAME.level > INI.LAST_LEVEL) { | |
| console.log("Game have been won or last level has been played."); | |
| //add end game stuff | |
| //TITLE.gameWon(); | |
| //ENGINE.GAME.ANIMATION.stop(); | |
| //GAME.endAnimationStart(); | |
| //GAME.end(); | |
| } else { | |
| console.log("Starting next level:", GAME.level); | |
| ENGINE.GAME.ANIMATION.waitThen(GAME.levelStart, 2); | |
| } | |
| }, | |
| levelEnd: function() { | |
| console.log("level", GAME.level, "ended."); | |
| GAME.levelCompleted = true; | |
| ENGINE.GAME.ANIMATION.STACK.push( | |
| ENGINE.KEY.waitFor.bind(null, GAME.nextLevel) | |
| ); | |
| TITLE.endLevel(); | |
| ENGINE.GAME.ANIMATION.stop(); | |
| }, | |
| initLevel: function(level) { | |
| if (!MAP[level].dungeonExist) { | |
| CreateDungeon.create(level); | |
| MAP[level].pw = MAP[level].width * ENGINE.INI.GRIDPIX; | |
| MAP[level].ph = MAP[level].height * ENGINE.INI.GRIDPIX; | |
| } else MAP[level].returning = true; | |
| //MAP[level].pw = MAP[level].width * ENGINE.INI.GRIDPIX; | |
| //MAP[level].ph = MAP[level].height * ENGINE.INI.GRIDPIX; | |
| ENGINE.VIEWPORT.setMax({ x: MAP[level].pw, y: MAP[level].ph }); | |
| MINIMAP.create(level); | |
| //DEBUG | |
| //let grid = MAP[GAME.level].DUNGEON.entrance.add(UP) | |
| //MAP[level].DUNGEON.scrolls.push(new Scroll(grid, "Light", "explore")); | |
| //grid = MAP[GAME.level].DUNGEON.entrance.add(DOWN) | |
| //MAP[level].DUNGEON.scrolls.push(new Scroll(grid, "Light", "explore")); | |
| DEBUG.addScrolls(); | |
| // | |
| }, | |
| updateVieport: function() { | |
| if (!ENGINE.VIEWPORT.changed) return; | |
| // do required repaints | |
| ENGINE.VIEWPORT.change("floor", "background"); | |
| ENGINE.VIEWPORT.change("config", "background"); | |
| ENGINE.clearLayer("fogview"); | |
| ENGINE.VIEWPORT.change("fog", "fogview"); | |
| if (DEBUG.coord) { | |
| ENGINE.clearLayer("coordview"); | |
| ENGINE.VIEWPORT.change("coord", "coordview"); | |
| } | |
| // | |
| ENGINE.VIEWPORT.changed = false; | |
| }, | |
| frameDraw: function() { | |
| ENGINE.clearLayerStack(); | |
| GAME.updateVieport(); | |
| TEXTPOOL.draw("animation"); | |
| SpritePOOL.draw("animation"); | |
| EXPLOSIONS.draw(); | |
| HERO.draw(); | |
| ORBS.draw(); | |
| TITLE.time(); | |
| TITLE.status(); | |
| ENEMY.draw(); | |
| }, | |
| firstFrameDraw: function(level) { | |
| ENGINE.resizeBOX("LEVEL", MAP[level].pw, MAP[level].ph); | |
| GRID.repaint( | |
| MAP[level].grid, | |
| TEXTURE[MAP[level].floor], | |
| TEXTURE[MAP[level].background] | |
| ); | |
| ENGINE.flattenLayers("wall", "floor"); | |
| GAME.PAINT.config(); | |
| if (DEBUG.fog) { | |
| ENGINE.fill(LAYER.fog, TEXTURE.Fog); | |
| GAME.adjustFogToMap(); | |
| } | |
| if (DEBUG.coord) GAME.PAINT.coord(); | |
| ENGINE.VIEWPORT.changed = true; | |
| HERO.updView(); | |
| GAME.updateVieport(); | |
| TITLE.main(); | |
| TITLE.time(); | |
| TITLE.status(); | |
| ENGINE.clearLayer("actors"); | |
| ENGINE.clearLayer("explosion"); | |
| }, | |
| adjustFogToMap: function() { | |
| console.log("Adjusting fog from MINIMAP"); | |
| for (let x = 0; x < MAP[GAME.level].width; x++) { | |
| for (let y = 0; y < MAP[GAME.level].height; y++) { | |
| let grid = new Grid(x, y); | |
| if (MINIMAP.maps[GAME.level].map[GRID.gridToIndex(grid)] & 64) { | |
| ENGINE.cutGrid(LAYER.fog, GRID.gridToCoord(grid)); | |
| } | |
| } | |
| } | |
| }, | |
| run: function() { | |
| //let t1 = performance.now(); | |
| GAME.frameCount++; | |
| //GAME.run() template | |
| if (ENGINE.GAME.stopAnimation) return; | |
| //do all game loop stuff here | |
| GAME.respond(); | |
| ORBS.manage(); | |
| ENEMY.manage(); | |
| HERO.manage(); | |
| GAME.levelTime.update(); | |
| // | |
| GAME.frameDraw(); | |
| //let t2 = performance.now() - t1; | |
| //console.log("Frame", t2); | |
| //if (GAME.frameCount > 100) GAME.abort(); | |
| }, | |
| respond: function() { | |
| //GAME.respond() template | |
| if (HERO.dead) return; | |
| var map = ENGINE.GAME.keymap; | |
| //fall throught section | |
| if (map[ENGINE.KEY.map.F9]) { | |
| //GAME.abort(); | |
| //teleport to temple | |
| /* | |
| console.log("teleporting to temple room"); | |
| GRID.teleportToGrid(HERO, MAP[GAME.level].DUNGEON.temple, true); | |
| HERO.updView(); | |
| */ | |
| //cheats | |
| /* | |
| console.log("CHEATING"); | |
| HERO.armor += 10; | |
| HERO.weapon += 10; | |
| HERO.agility +=10; | |
| HERO.heal(); | |
| HERO.incMana(); | |
| TITLE.change(); | |
| */ | |
| //teleport to exit and purge enemies | |
| console.log("teleport to exit"); | |
| //MAP[GAME.level].DUNGEON.ENEMY.clear(); | |
| GRID.teleportToGrid(HERO, MAP[GAME.level].DUNGEON.exit.add(UP), true); | |
| HERO.updView(); | |
| } | |
| if (map[ENGINE.KEY.map.F8]) { | |
| console.log("instaDeath"); | |
| HERO.death(); | |
| } | |
| if (map[ENGINE.KEY.map.tab]) { | |
| if (!HERO.canLevelUp) return; | |
| GAME.levelUp(INI.PTS_LVL); | |
| ENGINE.GAME.keymap[ENGINE.KEY.map.tab] = false; //NO repeat | |
| } | |
| if (map[ENGINE.KEY.map.A]) { | |
| TITLE.stack.scrollIndex--; | |
| TITLE.stack.scrollIndex = Math.max(0, TITLE.stack.scrollIndex); | |
| TITLE.change(); | |
| ENGINE.GAME.keymap[ENGINE.KEY.map.A] = false; | |
| } | |
| if (map[ENGINE.KEY.map.D]) { | |
| TITLE.stack.scrollIndex++; | |
| TITLE.stack.scrollIndex = Math.min( | |
| HERO.scrolls.size() - 1, | |
| TITLE.stack.scrollIndex | |
| ); | |
| TITLE.change(); | |
| ENGINE.GAME.keymap[ENGINE.KEY.map.D] = false; | |
| } | |
| if (map[ENGINE.KEY.map.H]) { | |
| HERO.useHealingPotion(); | |
| ENGINE.GAME.keymap[ENGINE.KEY.map.H] = false; //NO repeat | |
| } | |
| if (map[ENGINE.KEY.map.M]) { | |
| HERO.useManaPotion(); | |
| ENGINE.GAME.keymap[ENGINE.KEY.map.M] = false; //NO repeat | |
| } | |
| if (map[ENGINE.KEY.map.ctrl]) { | |
| HERO.castMagic(); | |
| ENGINE.GAME.keymap[ENGINE.KEY.map.ctrl] = false; //NO repeat | |
| } | |
| if (map[ENGINE.KEY.map.space]) { | |
| console.log("SPACE"); | |
| ENGINE.GAME.keymap[ENGINE.KEY.map.space] = false; //NO repeat | |
| } | |
| if (map[ENGINE.KEY.map.enter]) { | |
| if (HERO.scrolls.size() === 0) return; | |
| let scroll = HERO.scrolls.remove(TITLE.stack.scrollIndex); | |
| scroll.action(); | |
| TITLE.change(); | |
| ENGINE.GAME.keymap[ENGINE.KEY.map.enter] = false; //NO repeat | |
| } | |
| //single key section | |
| if (map[ENGINE.KEY.map.left]) { | |
| HERO.changeDirection(LEFT); | |
| return; | |
| } | |
| if (map[ENGINE.KEY.map.right]) { | |
| HERO.changeDirection(RIGHT); | |
| return; | |
| } | |
| if (map[ENGINE.KEY.map.up]) { | |
| HERO.changeDirection(UP); | |
| return; | |
| } | |
| if (map[ENGINE.KEY.map.down]) { | |
| HERO.changeDirection(DOWN); | |
| return; | |
| } | |
| return; | |
| }, | |
| setup: function() { | |
| console.log("%cGAME SETUP started", PRG.CSS); | |
| }, | |
| end: function() { | |
| console.log("GAME ENDED"); | |
| GAME.checkScore(); | |
| }, | |
| PAINT: { | |
| coord: function(layer = "coord") { | |
| ENGINE.clearLayer(layer); | |
| for (let x = 0; x < MAP[GAME.level].width; x++) { | |
| for (let y = 0; y < MAP[GAME.level].height; y++) { | |
| if (!GRID.isBlock(x, y)) { | |
| let point = GRID.gridToCoord(new Grid(x, y)); | |
| let text = `${x},${y}`; | |
| GRID.paintText(point, text, layer); | |
| } | |
| } | |
| } | |
| }, | |
| config: function() { | |
| ENGINE.VIEWPORT.changed = true; | |
| ENGINE.clearLayer("config"); | |
| let layer = "config"; | |
| ENGINE.spriteToGrid( | |
| layer, | |
| MAP[GAME.level].DUNGEON.entrance, | |
| SPRITE.Entrance | |
| ); | |
| ENGINE.spriteToGrid(layer, MAP[GAME.level].DUNGEON.exit, SPRITE.Exit); | |
| ENGINE.spriteToGrid(layer, MAP[GAME.level].DUNGEON.temple, SPRITE.temple); | |
| if (MAP[GAME.level].DUNGEON.gate) | |
| ENGINE.spriteToGrid(layer, MAP[GAME.level].DUNGEON.gate, SPRITE.Gate); | |
| if (MAP[GAME.level].DUNGEON.door) | |
| ENGINE.spriteToGrid(layer, MAP[GAME.level].DUNGEON.door, SPRITE.Door); | |
| if (MAP[GAME.level].DUNGEON.goldKey) | |
| ENGINE.spriteToGrid( | |
| layer, | |
| MAP[GAME.level].DUNGEON.goldKey, | |
| SPRITE.goldKey | |
| ); | |
| if (MAP[GAME.level].DUNGEON.silverKey) | |
| ENGINE.spriteToGrid( | |
| layer, | |
| MAP[GAME.level].DUNGEON.silverKey, | |
| SPRITE.silverKey | |
| ); | |
| GAME.PAINT.gold(layer); | |
| GAME.PAINT.lamp(layer); | |
| GAME.PAINT.potion(layer); | |
| GAME.PAINT.boost(layer); | |
| GAME.PAINT.chest(layer); | |
| GAME.PAINT.scroll(layer); | |
| }, | |
| gold: function(layer) { | |
| for (let q = 0, GL = MAP[GAME.level].DUNGEON.gold.length; q < GL; q++) { | |
| let gold = MAP[GAME.level].DUNGEON.gold[q]; | |
| ENGINE.spriteToGrid(layer, gold.grid, gold.sprite); | |
| } | |
| }, | |
| lamp: function(layer) { | |
| for (let q = 0, GL = MAP[GAME.level].DUNGEON.lamps.length; q < GL; q++) { | |
| let lamp = MAP[GAME.level].DUNGEON.lamps[q]; | |
| ENGINE.spriteToGrid(layer, lamp.grid, lamp.sprite); | |
| } | |
| }, | |
| potion: function(layer) { | |
| for ( | |
| let q = 0, GL = MAP[GAME.level].DUNGEON.potions.length; | |
| q < GL; | |
| q++ | |
| ) { | |
| let potion = MAP[GAME.level].DUNGEON.potions[q]; | |
| ENGINE.spriteToGrid(layer, potion.grid, potion.sprite); | |
| } | |
| }, | |
| boost: function(layer) { | |
| for (let q = 0, GL = MAP[GAME.level].DUNGEON.boosts.length; q < GL; q++) { | |
| let boost = MAP[GAME.level].DUNGEON.boosts[q]; | |
| ENGINE.spriteToGrid(layer, boost.grid, boost.sprite); | |
| } | |
| }, | |
| chest: function(layer) { | |
| for (let q = 0, GL = MAP[GAME.level].DUNGEON.chests.length; q < GL; q++) { | |
| let chest = MAP[GAME.level].DUNGEON.chests[q]; | |
| ENGINE.spriteToGrid(layer, chest.grid, chest.sprite); | |
| } | |
| }, | |
| scroll: function(layer) { | |
| for ( | |
| let q = 0, GL = MAP[GAME.level].DUNGEON.scrolls.length; | |
| q < GL; | |
| q++ | |
| ) { | |
| let scroll = MAP[GAME.level].DUNGEON.scrolls[q]; | |
| ENGINE.spriteToGrid(layer, scroll.grid, scroll.sprite); | |
| } | |
| } | |
| }, | |
| CLICK: { | |
| sacrificeGold: function() { | |
| HERO.gold -= 1000; | |
| HERO.points += 1; | |
| GAME.templeRefresh(); | |
| }, | |
| manageTemple: function() { | |
| GAME.CLICK.decPoint(this); | |
| GAME.templeRefresh(); | |
| }, | |
| manageCharacter: function() { | |
| GAME.CLICK.decPoint(this); | |
| GAME.heroRefresh(); | |
| }, | |
| decPoint: function(that) { | |
| HERO.points -= 1; | |
| let id = that.id; | |
| id = id.substr(id.lastIndexOf("_") + 1); | |
| if (id === "maxHealth") { | |
| HERO[id] += INI.LVL_HEALTH; | |
| } else if (id === "maxMana") { | |
| HERO[id] += INI.LVL_MANA; | |
| } else HERO[id]++; | |
| }, | |
| endFight: function() { | |
| $("#form_continue").prop("disabled", false); | |
| $("#form_make_turn").prop("disabled", true); | |
| $("#form_flee").prop("disabled", true); | |
| }, | |
| usePotion: function() { | |
| let healed = HERO.useHealingPotion(); | |
| //console.log("using red potion for", healed, "HP"); | |
| GAME.fightRefresh(); | |
| if (healed === 0) { | |
| CONSOLE.print( | |
| `<span class="blue">${HERO.name}</span> has full health already.` | |
| ); | |
| } else { | |
| CONSOLE.print(` | |
| <span class="blue">${HERO.name}</span> heals ${healed} points. | |
| `); | |
| } | |
| }, | |
| useScroll: function() { | |
| const regex = /_([a-zA-Z]+)/; | |
| let type = regex.exec(this.id)[1]; | |
| let temp = new Scroll(null, type, null); | |
| temp.action(); | |
| HERO.scrolls.remove(HERO.scrolls.find("type", type)); | |
| GAME.fightRefresh(); | |
| } | |
| }, | |
| levelUp: function(points) { | |
| console.log("%cCharacter screen", PRG.CSS); | |
| ENGINE.GAME.ANIMATION.stop(); | |
| HERO.canLevelUp = false; | |
| let x = ENGINE.gameWIDTH / 4; | |
| let y = ENGINE.gameHEIGHT / 4; | |
| let w = ENGINE.gameWIDTH / 2 + 16; | |
| let h = ENGINE.gameHEIGHT / 2 - 50; | |
| HERO.level++; | |
| HERO.points = points; | |
| HERO.maxHealth += INI.LVL_HEALTH; | |
| HERO.maxMana += INI.LVL_MANA; | |
| const temp = character(); | |
| function character() { | |
| const temp = new Form(HERO.name, x, y, w, h, FORM_WEDGE.HERO); | |
| FORM_WEDGE.hero(); | |
| GAME.heroRefresh(); | |
| return temp; | |
| } | |
| }, | |
| visitTemple: function() { | |
| console.log("%cEntered temple", PRG.CSS); | |
| ENGINE.GAME.ANIMATION.stop(); | |
| let x = ENGINE.gameWIDTH / 4; | |
| let y = ENGINE.gameHEIGHT / 4; | |
| let w = ENGINE.gameWIDTH / 2 + 16; | |
| let h = ENGINE.gameHEIGHT / 2 + 32; | |
| const temp = temple(); | |
| function temple() { | |
| const temp = new Form("The Temple", x, y, w, h, FORM_WEDGE.TEMPLE); | |
| FORM_WEDGE.temple(); | |
| GAME.templeRefresh(); | |
| return temp; | |
| } | |
| }, | |
| heroRefresh: function() { | |
| if (HERO.points > 0) { | |
| $("#form_done").prop("disabled", true); | |
| $(".skill").prop("disabled", false); | |
| } else { | |
| $("#form_done").prop("disabled", false); | |
| $(".skill").prop("disabled", true); | |
| } | |
| $("#hero_points").html(HERO.points); | |
| $("#hero_sword").html(HERO.weapon.toString().padStart(2, "0")); | |
| $("#hero_shield").html(HERO.armor.toString().padStart(2, "0")); | |
| $("#hero_agility").html(HERO.agility.toString().padStart(2, "0")); | |
| $("#hero_magic").html(HERO.magic.toString().padStart(2, "0")); | |
| HERO.health = HERO.maxHealth; | |
| HERO.mana = HERO.maxMana; | |
| TITLE.change(); | |
| TITLE.status(); | |
| }, | |
| templeRefresh: function() { | |
| $("#hero_gold").html(HERO.gold); | |
| if (HERO.gold < 1000) { | |
| $("#form_sacrifice_gold").prop("disabled", true); | |
| } else $("#form_sacrifice_gold").prop("disabled", false); | |
| if (HERO.points === 0) { | |
| $(".skill").prop("disabled", true); | |
| } else $(".skill").prop("disabled", false); | |
| $("#hero_points").html(HERO.points); | |
| $("#hero_vitality").html(HERO.maxHealth.toString().padStart(2, "0")); | |
| $("#hero_mana").html(HERO.maxMana.toString().padStart(2, "0")); | |
| $("#hero_sword").html(HERO.weapon.toString().padStart(2, "0")); | |
| $("#hero_shield").html(HERO.armor.toString().padStart(2, "0")); | |
| $("#hero_agility").html(HERO.agility.toString().padStart(2, "0")); | |
| $("#hero_magic").html(HERO.magic.toString().padStart(2, "0")); | |
| HERO.health = HERO.maxHealth; | |
| HERO.mana = HERO.maxMana; | |
| TITLE.change(); | |
| TITLE.status(); | |
| }, | |
| fightRefresh: function() { | |
| let enemy = GAME.TURN.enemy; | |
| $("#hero_sword").html(HERO.weapon.toString().padStart(2, "0")); | |
| $("#hero_shield").html(HERO.armor.toString().padStart(2, "0")); | |
| $("#hero_agility").html(HERO.agility.toString().padStart(2, "0")); | |
| if (LOG[enemy.type.title].kills >= 1) { | |
| $("#enemy_agility").html(enemy.agility.toString().padStart(2, "0")); | |
| } else $("#enemy_agility").html("??"); | |
| if (LOG[enemy.type.title].kills >= 2) { | |
| $("#enemy_armor").html(enemy.armor.toString().padStart(2, "0")); | |
| } else $("#enemy_armor").html("??"); | |
| if (LOG[enemy.type.title].kills >= 3) { | |
| $("#enemy_weapon").html(enemy.weapon.toString().padStart(2, "0")); | |
| } else $("#enemy_weapon").html("??"); | |
| ENGINE.clearLayer("hero_health"); | |
| ENGINE.clearLayer("enemy_health"); | |
| ENGINE.statusBar( | |
| LAYER.hero_health, | |
| 0, | |
| 0, | |
| INI.FIGHT_PANEL_WIDTH / 2 - 10, | |
| 20, | |
| HERO.health, | |
| HERO.maxHealth, | |
| "red" | |
| ); | |
| ENGINE.statusBar( | |
| LAYER.enemy_health, | |
| 0, | |
| 0, | |
| INI.FIGHT_PANEL_WIDTH / 2 - 10, | |
| 20, | |
| enemy.health, | |
| enemy.maxHealth, | |
| "orange" | |
| ); | |
| $("#count_redPotion").html(HERO.redPotion.toString().padStart(2, "0")); | |
| if (HERO.redPotion > 0) { | |
| $("#redPotion").prop("disabled", false); | |
| } else { | |
| $("#redPotion").prop("disabled", true); | |
| //delete if none | |
| $("#redPotion").remove(); | |
| $("#count_redPotion").remove(); | |
| } | |
| scrollPanel(); | |
| TITLE.forceChange(); | |
| return; | |
| function scrollPanel() { | |
| let scrolls = [ | |
| "BoostWeapon", | |
| "BoostArmor", | |
| "DestroyWeapon", | |
| "DestroyArmor" | |
| ]; | |
| scrolls.forEach(scroll => { | |
| let count = HERO.scrolls.getCount("type", scroll); | |
| $(`#count_${scroll}`).html(count.toString().padStart(2, "0")); | |
| if (count === 0) { | |
| $(`#SCR_${scroll}`).prop("disabled", true); | |
| //delete those not present | |
| $(`#SCR_${scroll}`).remove(); | |
| $(`#count_${scroll}`).remove(); | |
| } else $(`#SCR_${scroll}`).prop("disabled", false); | |
| }); | |
| } | |
| }, | |
| leaveTemple: function() { | |
| console.log("leaving temple ..."); | |
| HERO.canEnterTemple = false; | |
| $("#FORM").remove(); | |
| setTimeout(() => { | |
| console.log("HERO.canEnterTemple"); | |
| HERO.canEnterTemple = true; | |
| }, INI.TEMPLE_TIMEOUT); | |
| GAME.levelContinue(); | |
| }, | |
| endFight: function() { | |
| console.log("fight ends ..."); | |
| $("#FORM").remove(); | |
| HERO.inFight = false; | |
| GAME.TURN.fight_active = false; | |
| HERO.weapon = GAME.TURN.HERO_weapon; | |
| HERO.armor = GAME.TURN.HERO_armor; | |
| GAME.levelContinue(); | |
| }, | |
| fleeFight: function() { | |
| let enemy = GAME.TURN.enemy; | |
| CONSOLE.print(`<span class="blue">${HERO.name}</span> tries to flee ...`); | |
| let delta = HERO.agility - enemy.agility; | |
| const top = 2 * INI.FLEE_AGILITY_DELTA; | |
| let chance; | |
| if (delta > top) { | |
| chance = top; | |
| } else { | |
| chance = RND(-top + delta, top); | |
| } | |
| let selectedDir = safeSpot(); //bugfix for dead ends | |
| if ( | |
| chance > 0 && | |
| GAME.TURN.agility_accumulator > INI.AGILITY_TURN * -1 && | |
| selectedDir !== null | |
| ) { | |
| CONSOLE.print(` ... and succeeds.`); | |
| //let selectedDir = safeSpot(); | |
| HERO.MoveState.dir = selectedDir; | |
| GRID.blockMove(HERO, true); | |
| HERO.updView(); | |
| GAME.CLICK.endFight(); | |
| } else { | |
| CONSOLE.print(` ... and fails ...`); | |
| GAME.TURN.agility_accumulator += chance; | |
| enemy.turn(); | |
| GAME.fightRefresh(); //bugfix | |
| } | |
| function safeSpot() { | |
| let obstacles = MAP[GAME.level].DUNGEON.obstacles.clone(); | |
| obstacles.push(enemy.MoveState.homeGrid); | |
| let dirs = GRID.getDirections(HERO.MoveState.startGrid, obstacles); | |
| if (dirs.length === 0) return null; | |
| let selectedDir; | |
| let enemyDirIndex = enemy.MoveState.dir.isInAt(dirs); | |
| if (enemyDirIndex >= 0) { | |
| selectedDir = dirs[enemyDirIndex]; | |
| } else { | |
| selectedDir = dirs.chooseRandom(); | |
| } | |
| return selectedDir; | |
| } | |
| }, | |
| charDone: function() { | |
| console.log("levelUp done"); | |
| $("#FORM").remove(); | |
| GAME.levelContinue(); | |
| }, | |
| debug: function() {}, | |
| fight: function(enemy, initiative, index) { | |
| if (HERO.inFight) return; | |
| if (HERO.dead) return; | |
| if (!HERO.MoveState.moving) initiative = false; | |
| if (initiative) { | |
| initiative = 1; | |
| } else initiative = 0; | |
| HERO.inFight = true; | |
| ENGINE.GAME.ANIMATION.stop(); | |
| let x = ENGINE.gameWIDTH / 4; | |
| let y = ENGINE.gameHEIGHT / 4 - 60; | |
| let w = ENGINE.gameWIDTH / 2 + 16; | |
| let h = ENGINE.gameHEIGHT / 2 + 32 + 168; | |
| INI.FIGHT_PANEL_WIDTH = w; | |
| GAME.TURN.enemy = enemy; | |
| kills(); | |
| fight(enemy); | |
| CONSOLE.set("Console"); | |
| GAME.TURN.agility_accumulator = 0; | |
| GAME.TURN.counter = 1; | |
| GAME.TURN.HERO_weapon = HERO.weapon; | |
| GAME.TURN.HERO_armor = HERO.armor; | |
| //GAME.TURN.enemy = enemy; | |
| GAME.TURN.fight_active = true; | |
| GAME.TURN.enemyIndex = index; | |
| let delta = HERO.agility - enemy.agility; | |
| if (delta + initiative * INI.INITIATIVE_BONUS >= 0) { | |
| //hero starts | |
| CONSOLE.print( | |
| `<span class="blue">${HERO.name}</span> attacks <span class="red">${ | |
| enemy.type.title | |
| }</span>.` | |
| ); | |
| } else { | |
| //enemy attacks | |
| CONSOLE.print( | |
| `<span class="red">${ | |
| enemy.type.title | |
| }</span> attacks <span class="blue">${HERO.name}</span>.` | |
| ); | |
| enemy.turn(enemy); | |
| } | |
| //GAME.fightRefresh(enemy); | |
| GAME.fightRefresh(); | |
| return; | |
| function fight(enemy) { | |
| const temp = new Form("Fight!", x, y, w, h, FORM_WEDGE.FIGHT); | |
| FORM_WEDGE.fight(enemy); | |
| GAME.fightRefresh(); | |
| return temp; | |
| } | |
| function kills() { | |
| if (LOG[enemy.type.title] === undefined) { | |
| LOG[enemy.type.title] = new Log(); | |
| } | |
| return LOG[enemy.type.title].kills; | |
| } | |
| }, | |
| turn: function() { | |
| if (!GAME.TURN.fight_active) return; | |
| GAME.TURN.setQuickResolve(); | |
| let enemy = GAME.TURN.enemy; | |
| GAME.TURN.counter++; | |
| let delta = HERO.agility - enemy.type.agility; | |
| GAME.TURN.agility_accumulator += delta; | |
| //console.log("GAME.TURN.agility_accumulator", GAME.TURN.agility_accumulator); | |
| if (GAME.TURN.agility_accumulator > INI.AGILITY_TURN * -1) { | |
| //hero turn | |
| HERO.turn(enemy); | |
| } else { | |
| //hero skips turn | |
| GAME.TURN.agility_accumulator += INI.AGILITY_TURN; | |
| CONSOLE.print( | |
| `<span class="blue">${HERO.name}</span> fell behind and missed turn.` | |
| ); | |
| } | |
| if (!GAME.TURN.fight_active) return; | |
| if (GAME.TURN.agility_accumulator < INI.AGILITY_TURN) { | |
| enemy.turn(); | |
| } else { | |
| GAME.TURN.agility_accumulator -= INI.AGILITY_TURN; | |
| CONSOLE.print( | |
| `<span class="red">${ | |
| enemy.type.title | |
| }</span> fell behind and missed turn.` | |
| ); | |
| } | |
| GAME.fightRefresh(enemy); | |
| TITLE.forceChange(); | |
| if (GAME.TURN.fight_active && GAME.turn.quickResolve) { | |
| return GAME.turn(); | |
| } else return; | |
| }, | |
| TURN: { | |
| counter: 0, | |
| agility_accumulator: 0, | |
| HERO_weapon: null, | |
| HERO_armor: null, | |
| fight_active: null, | |
| enemy: null, | |
| quickResolve: false, | |
| setQuickResolve: function() { | |
| if ($("#form_quick_resolve").prop("checked")) { | |
| GAME.turn.quickResolve = true; | |
| } else GAME.turn.quickResolve = false; | |
| }, | |
| enemyIndex: null, | |
| damage: function(attacker, defender) { | |
| if (attacker.weapon === 0) return 0; | |
| //let delta = Math.max(attacker.weapon - defender.armor, 1); | |
| //let damage = RND(INI.ATTACk_OFFSET, delta); | |
| let delta = attacker.weapon - defender.armor; | |
| let damage = RND( | |
| Math.min(INI.ATTACk_OFFSET, Math.floor(delta / 2)), | |
| Math.max(delta, 1) | |
| ); | |
| return damage; | |
| } | |
| }, | |
| over: function() { | |
| //$("#Restart").remove(); | |
| $("#buttons").prepend( | |
| `<input type='button' id='Restart' value='Restart game'>` | |
| ); | |
| $("#Restart").on("click", GAME.restartGame); | |
| }, | |
| restartGame: function() { | |
| //$("#Restart").addClass("hidden"); | |
| $("#Restart").remove(); | |
| console.log("GAME RESTARTED"); | |
| GAME.discardMaps(); | |
| GAME.start(); | |
| }, | |
| discardMaps: function() { | |
| console.log("discarding old maps"); | |
| MINIMAP.maps = {}; | |
| MAP.clear(); | |
| LOG = {}; | |
| } | |
| }; | |
| var MINIMAP = { | |
| /* | |
| 1 - wall, path (0), | |
| 2 - door/gate | |
| 4 - key | |
| 8 - entrance, exit | |
| 16 - temple | |
| 32 - | |
| 64 - fog info | |
| 128 - fog | |
| */ | |
| key: "#00F", | |
| door: "#F00", | |
| wall: "#8B4513", | |
| path: "#000", | |
| hero: "#0F0", | |
| temple: "yellow", | |
| exit: "#FFF", | |
| maps: {}, | |
| create: function(level) { | |
| console.log("MINIMAP creation - level:", level); | |
| if (MINIMAP.maps[GAME.level] === undefined) { | |
| MINIMAP.maps[GAME.level] = {}; | |
| MINIMAP.maps[GAME.level].buffer = new ArrayBuffer( | |
| MAP[GAME.level].width * MAP[GAME.level].height | |
| ); | |
| MINIMAP.maps[GAME.level].map = new Uint8Array( | |
| MINIMAP.maps[GAME.level].buffer | |
| ); | |
| let index; | |
| for (let y = 0; y < MAP[GAME.level].height; y++) { | |
| for (let x = 0; x < MAP[GAME.level].width; x++) { | |
| index = x + y * MAP[GAME.level].width; | |
| if (GRID.isBlock(x, y)) { | |
| MINIMAP.maps[GAME.level].map[index] = 0b10000000; | |
| } else MINIMAP.maps[GAME.level].map[index] = 0b10000001; | |
| } | |
| } | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON.door) | |
| ] |= 2; | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON.gate) | |
| ] |= 2; | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON.silverKey) | |
| ] |= 4; | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON.goldKey) | |
| ] |= 4; | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON.entrance) | |
| ] |= 8; | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON.exit) | |
| ] |= 8; | |
| MINIMAP.maps[GAME.level].map[ | |
| GRID.gridToIndex(MAP[GAME.level].DUNGEON.temple) | |
| ] |= 16; | |
| //reserve 64 for fog storage | |
| } else console.log(`MINIMAP to ${GAME.level} already exists`); | |
| //console.log("MINIMAP in MINIMAP", MINIMAP); | |
| }, | |
| draw: function(x, y, CTX) { | |
| MINIMAP.maps[GAME.level].map.forEach((value, index) => { | |
| if (value < 128) { | |
| if (value & 16) { | |
| CTX.fillStyle = MINIMAP.temple; | |
| } else if (value & 8) { | |
| CTX.fillStyle = MINIMAP.exit; | |
| } else if (value & 4) { | |
| CTX.fillStyle = MINIMAP.key; | |
| } else if (value & 2) { | |
| CTX.fillStyle = MINIMAP.door; | |
| } else if (value & 1) { | |
| CTX.fillStyle = MINIMAP.path; | |
| } else { | |
| CTX.fillStyle = MINIMAP.wall; | |
| } | |
| let grid = GRID.indexToGrid(index); | |
| CTX.pixelAt( | |
| x + grid.x * INI.MINI_PIX, | |
| y + grid.y * INI.MINI_PIX, | |
| INI.MINI_PIX | |
| ); | |
| } | |
| CTX.fillStyle = MINIMAP.hero; | |
| CTX.pixelAt( | |
| x + HERO.MoveState.homeGrid.x * INI.MINI_PIX, | |
| y + HERO.MoveState.homeGrid.y * INI.MINI_PIX, | |
| INI.MINI_PIX | |
| ); | |
| }); | |
| }, | |
| unveil: function(grid, r = INI.MAP_RADIUS) { | |
| let startX = Math.max(grid.x - r, 0); | |
| let startY = Math.max(grid.y - r, 0); | |
| let maxX = Math.min(MAP[GAME.level].DUNGEON.maxX + 1, startX + 2 * r); | |
| let maxY = Math.min(MAP[GAME.level].DUNGEON.maxY + 1, startY + 2 * r); | |
| for (let y = startY; y <= maxY; y++) { | |
| for (let x = startX; x <= maxX; x++) { | |
| let index = x + y * MAP[GAME.level].width; | |
| MINIMAP.maps[GAME.level].map[index] &= 0b01111111; | |
| } | |
| } | |
| } | |
| }; | |
| var TITLE = { | |
| stack: { | |
| scrollIndex: 0, | |
| scrollInRow: 3, | |
| scrollDelta: 72 | |
| }, | |
| change: function() { | |
| TITLE.STATUS.changed = true; | |
| }, | |
| STATUS: { | |
| changed: true, | |
| layer: "status" | |
| }, | |
| main: function() { | |
| TITLE.title(); | |
| TITLE.bottom(); | |
| TITLE.side(); | |
| }, | |
| title: function() { | |
| var CTX = LAYER.title; | |
| TITLE.background(); | |
| var fs = 42; | |
| CTX.font = fs + "px Arcade"; | |
| CTX.textAlign = "center"; | |
| var txt = CTX.measureText(PRG.NAME); | |
| var x = ENGINE.titleWIDTH / 2; | |
| var y = fs + 10; | |
| var gx = x - txt.width / 2; | |
| var gy = y - fs; | |
| var grad = CTX.createLinearGradient(gx, gy + 10, gx, gy + fs); | |
| grad.addColorStop("0", "#0F0"); | |
| grad.addColorStop("0.1", "#0E0"); | |
| grad.addColorStop("0.2", "#0D0"); | |
| grad.addColorStop("0.3", "#0C0"); | |
| grad.addColorStop("0.4", "#0B0"); | |
| grad.addColorStop("0.5", "#0A0"); | |
| grad.addColorStop("0.6", "#090"); | |
| grad.addColorStop("0.7", "#080"); | |
| grad.addColorStop("0.8", "#070"); | |
| grad.addColorStop("0.9", "#060"); | |
| grad.addColorStop("1", "#050"); | |
| GAME.grad = grad; | |
| CTX.fillStyle = grad; | |
| CTX.shadowColor = "#040"; | |
| CTX.shadowOffsetX = 2; | |
| CTX.shadowOffsetY = 2; | |
| CTX.shadowBlur = 3; | |
| CTX.fillText(PRG.NAME, x, y); | |
| }, | |
| background: function() { | |
| var CTX = LAYER.title; | |
| CTX.fillStyle = "#000"; | |
| CTX.roundRect( | |
| 0, | |
| 0, | |
| ENGINE.titleWIDTH, | |
| ENGINE.titleHEIGHT, | |
| { | |
| upperLeft: 20, | |
| upperRight: 20, | |
| lowerLeft: 0, | |
| lowerRight: 0 | |
| }, | |
| true, | |
| true | |
| ); | |
| }, | |
| bottom: function() { | |
| var CTX = LAYER.bottom; | |
| CTX.fillStyle = "#000"; | |
| CTX.roundRect( | |
| 0, | |
| 0, | |
| ENGINE.bottomWIDTH, | |
| ENGINE.bottomHEIGHT, | |
| { | |
| upperLeft: 0, | |
| upperRight: 0, | |
| lowerLeft: 20, | |
| lowerRight: 20 | |
| }, | |
| true, | |
| true | |
| ); | |
| CTX.textAlign = "center"; | |
| var x = ENGINE.bottomWIDTH / 2; | |
| var y = ENGINE.bottomHEIGHT / 2; | |
| CTX.font = "10px Consolas"; | |
| CTX.fillStyle = GAME.grad; | |
| CTX.shadowOffsetX = 2; | |
| CTX.shadowOffsetY = 2; | |
| CTX.shadowBlur = 5; | |
| CTX.shadowColor = "#040"; | |
| CTX.fillText("Version " + PRG.VERSION + " by Lovro Selič", x, y); | |
| }, | |
| side() { | |
| ENGINE.clearLayer("sideback"); | |
| ENGINE.fillLayer("sideback", "#000"); | |
| }, | |
| forceChange: function() { | |
| TITLE.change(); | |
| TITLE.status(); | |
| }, | |
| status: function() { | |
| if (!TITLE.STATUS.changed) return; | |
| TITLE.STATUS.changed = false; | |
| ENGINE.clearLayer("status"); | |
| TITLE.hero(); | |
| TITLE.time(); | |
| TITLE.health(); | |
| TITLE.gold(); | |
| TITLE.inv(); | |
| TITLE.skills(); | |
| TITLE.scrolls(); | |
| TITLE.map(); | |
| }, | |
| scrolls: function() { | |
| TITLE.stack.scrollIndex = Math.min( | |
| TITLE.stack.scrollIndex, | |
| HERO.scrolls.size() - 1 | |
| ); | |
| let scrollSpread = ENGINE.spreadAroundCenter( | |
| TITLE.stack.scrollInRow, | |
| ENGINE.sideWIDTH / 2 - 16, | |
| TITLE.stack.scrollDelta | |
| ); | |
| var CTX = LAYER[TITLE.STATUS.layer]; | |
| CTX.save(); | |
| ENGINE.resetShadow(CTX); | |
| let x = 16; | |
| let fs = 16; | |
| let y = TITLE.stack.y + 4; | |
| let LN = HERO.scrolls.size(); | |
| let start = Math.max( | |
| 0, | |
| TITLE.stack.scrollIndex - TITLE.stack.scrollInRow + 1 | |
| ); | |
| start = Math.min(start, LN - TITLE.stack.scrollInRow); | |
| if (start < 0) start = 0; | |
| let max = start + Math.min(TITLE.stack.scrollInRow, LN); | |
| for (let q = start; q < max; q++) { | |
| let scroll = HERO.scrolls.list[q]; | |
| if (scroll.object.use === "explore") { | |
| CTX.globalAlpha = 1; | |
| CTX.strokeStyle = "#0F0"; | |
| } else { | |
| CTX.globalAlpha = 0.3; | |
| CTX.strokeStyle = "#F00"; | |
| } | |
| x = scrollSpread.shift(); | |
| ENGINE.draw(TITLE.STATUS.layer, x, y, scroll.object.sprite); | |
| CTX.font = "10px Consolas"; | |
| CTX.fillStyle = "#FFF"; | |
| CTX.fillText( | |
| scroll.count.toString().padStart(2, "0"), | |
| x + 32, | |
| y + 18 + 4 | |
| ); | |
| if (q === TITLE.stack.scrollIndex) { | |
| CTX.globalAlpha = 0.5; | |
| CTX.lineWidth = "1"; | |
| CTX.beginPath(); | |
| CTX.rect(x - 14, y - 3, 60, 44); | |
| CTX.closePath(); | |
| CTX.stroke(); | |
| } | |
| } | |
| CTX.globalAlpha = 1; | |
| y += 2 * fs + 12; | |
| x = (ENGINE.sideWIDTH - SPRITE.LineTop.width) / 2; | |
| CTX.restore(); | |
| ENGINE.draw(TITLE.STATUS.layer, x, y, SPRITE.LineBottom); | |
| y += SPRITE.LineBottom.height + 4; | |
| TITLE.stack.y = y; | |
| }, | |
| map: function() { | |
| var CTX = LAYER.map; | |
| ENGINE.clearLayer("map"); | |
| let y = TITLE.stack.y + 14; | |
| let x = 28; | |
| CTX.strokeStyle = "#FFF"; | |
| CTX.beginPath(); | |
| CTX.rect(x - 1, y - 1, 202, 202); | |
| CTX.closePath(); | |
| CTX.stroke(); | |
| CTX.fillStyle = "#444"; | |
| CTX.fillRect(x, y, 200, 200); | |
| MINIMAP.draw(x, y, CTX); | |
| }, | |
| statusBar: function(x, y, value, max, color) { | |
| var CTX = LAYER[TITLE.STATUS.layer]; | |
| let h = 16; | |
| let w = 160; | |
| ENGINE.statusBar(CTX, x, y, w, h, value, max, color); | |
| }, | |
| healthBar: function(x, y) { | |
| TITLE.statusBar(x, y, HERO.health, HERO.maxHealth, "#F00"); | |
| }, | |
| manaBar: function(x, y) { | |
| TITLE.statusBar(x, y, HERO.mana, HERO.maxMana, "#00F"); | |
| }, | |
| inv: function() { | |
| var CTX = LAYER[TITLE.STATUS.layer]; | |
| let fs = 16; | |
| let y = TITLE.stack.y + fs + 4; | |
| let x; | |
| let NUM = 2; | |
| let delta = 80; | |
| let xS = ENGINE.spreadAroundCenter(NUM, ENGINE.sideWIDTH / 2, delta); | |
| x = xS.shift(); | |
| ENGINE.spriteDraw(TITLE.STATUS.layer, x, y, SPRITE.RedPotion); | |
| x += SPRITE.RedPotion.width / 2 + 5; | |
| CTX.fillText(HERO.redPotion, x, y + 6); | |
| x = xS.shift(); | |
| ENGINE.spriteDraw(TITLE.STATUS.layer, x, y, SPRITE.BluePotion); | |
| x += SPRITE.RedPotion.width / 2 + 5; | |
| CTX.fillText(HERO.bluePotion, x, y + 6); | |
| y += fs + 4; | |
| x = (ENGINE.sideWIDTH - SPRITE.LineTop.width) / 2; | |
| ENGINE.draw(TITLE.STATUS.layer, x, y, SPRITE.LineBottom); | |
| y += SPRITE.LineBottom.height; | |
| TITLE.stack.y = y; | |
| }, | |
| gold: function() { | |
| var CTX = LAYER[TITLE.STATUS.layer]; | |
| CTX.save(); | |
| CTX.fillStyle = "#CFB53B"; | |
| CTX.shadowColor = "#DAA520"; | |
| let fs = 16; | |
| let y = TITLE.stack.y + 1.5 * fs; | |
| let x = 16; | |
| CTX.fillText("Gold:", x, y); | |
| CTX.fillText(HERO.gold.toString().padStart(6, "0"), TITLE.stack.tab, y); | |
| CTX.restore(); | |
| y += fs; | |
| x = (ENGINE.sideWIDTH - SPRITE.LineTop.width) / 2; | |
| ENGINE.draw(TITLE.STATUS.layer, x, y, SPRITE.LineTop); | |
| y += SPRITE.LineBottom.height; | |
| TITLE.stack.y = y; | |
| }, | |
| skills: function() { | |
| var CTX = LAYER[TITLE.STATUS.layer]; | |
| let fs = 16; | |
| let y = TITLE.stack.y + 1.5 * fs; | |
| let x = 16; | |
| let pad1 = 80; | |
| let pad2 = 32; | |
| CTX.fillText("Sword:", x, y); | |
| x += pad1; | |
| CTX.fillText(HERO.weapon.toString().padStart(2, "0"), x, y); | |
| x += pad2; | |
| CTX.fillText("Shield:", x, y); | |
| x += pad1; | |
| CTX.fillText(HERO.armor.toString().padStart(2, "0"), x, y); | |
| y += 1.5 * fs; | |
| x = 16; | |
| CTX.fillText("Agility:", x, y); | |
| x += pad1; | |
| CTX.fillText(HERO.agility.toString().padStart(2, "0"), x, y); | |
| x += pad2; | |
| CTX.fillText("Magic:", x, y); | |
| x += pad1; | |
| CTX.fillText(HERO.magic.toString().padStart(2, "0"), x, y); | |
| y += fs - 2; | |
| x = (ENGINE.sideWIDTH - SPRITE.LineTop.width) / 2; | |
| ENGINE.draw(TITLE.STATUS.layer, x, y, SPRITE.LineBottom); | |
| y += SPRITE.LineBottom.height + 2; | |
| TITLE.stack.y = y; | |
| }, | |
| health: function() { | |
| var CTX = LAYER[TITLE.STATUS.layer]; | |
| let fs = 16; | |
| let y = TITLE.stack.y + 1.5 * fs; | |
| let x = 16; | |
| CTX.font = fs + "px Times"; | |
| CTX.fillStyle = "#AAA"; | |
| CTX.shadowColor = "#666"; | |
| CTX.shadowOffsetX = 1; | |
| CTX.shadowOffsetY = 1; | |
| CTX.shadowBlur = 1; | |
| if (HERO.canLevelUp) { | |
| CTX.fillStyle = "#0F0"; | |
| CTX.shadowColor = "#0D0"; | |
| } | |
| CTX.fillText("Level:", x, y); | |
| CTX.fillText(HERO.level.toString().padStart(2, "0"), 100, y); | |
| y += 1.5 * fs; | |
| CTX.fillText("Experience:", x, y); | |
| CTX.fillText(HERO.experience.toString().padStart(6, "0"), 100, y); | |
| if (HERO.canLevelUp) { | |
| CTX.fillStyle = "#00F"; | |
| CTX.shadowColor = "#00D"; | |
| CTX.font = "10px Arial"; | |
| CTX.fillText("Press TAB for Level Up!", x + 116, y - 26); | |
| } | |
| CTX.font = fs + "px Times"; | |
| CTX.fillStyle = "#AAA"; | |
| CTX.shadowColor = "#666"; | |
| y += 1.5 * fs; | |
| x = (ENGINE.sideWIDTH - SPRITE.LineTop.width) / 2; | |
| ENGINE.draw(TITLE.STATUS.layer, x, y, SPRITE.LineBottom); | |
| y += SPRITE.LineBottom.height + 1.5 * fs; | |
| x = 16; | |
| CTX.fillText("Health:", x, y); | |
| var bx, by; | |
| inc(); | |
| TITLE.healthBar(bx, by); | |
| x = 16; | |
| y += 1.5 * fs; | |
| CTX.fillText("Mana:", x, y); | |
| inc(); | |
| TITLE.manaBar(bx, by); | |
| y += 1.5 * fs; | |
| x = (ENGINE.sideWIDTH - SPRITE.LineTop.width) / 2; | |
| ENGINE.draw(TITLE.STATUS.layer, x, y, SPRITE.LineBottom); | |
| y += SPRITE.LineBottom.height; | |
| TITLE.stack.y = y; | |
| TITLE.stack.tab = bx; | |
| function inc() { | |
| const pad = 3; | |
| bx = x + 64; | |
| by = y - fs + pad; | |
| } | |
| }, | |
| time: function() { | |
| var CTX = LAYER["time"]; | |
| ENGINE.clearLayer("time"); | |
| let fs = 14; | |
| CTX.font = fs + "px Times"; | |
| CTX.fillStyle = "#0D0"; | |
| CTX.shadowColor = "#0F0"; | |
| CTX.shadowOffsetX = 0; | |
| CTX.shadowOffsetY = 0; | |
| CTX.shadowBlur = 0; | |
| CTX.fillText(GAME.time.timeString(), 180, 28); | |
| }, | |
| hero: function() { | |
| let x = 48; | |
| let y = 20; | |
| let fs = 14; | |
| ENGINE.spriteDraw(TITLE.STATUS.layer, x, y, SPRITE.Knight_front_0); | |
| var CTX = LAYER[TITLE.STATUS.layer]; | |
| x = 102; | |
| CTX.font = fs + "px Times"; | |
| CTX.fillStyle = "#0A0"; | |
| CTX.shadowColor = "#0F0"; | |
| CTX.shadowOffsetX = 0; | |
| CTX.shadowOffsetY = 0; | |
| CTX.shadowBlur = 0; | |
| CTX.fillText(HERO.name, x, y); | |
| y += fs + 4; | |
| CTX.fillText(`Depth: ${GAME.level.toString().padStart(2, "0")}`, x, y); | |
| //reset shadow | |
| CTX.fillStyle = "#AAA"; | |
| CTX.shadowColor = "#666"; | |
| // | |
| y = 26 + 32; | |
| x = (ENGINE.sideWIDTH - SPRITE.LineTop.width) / 2; | |
| ENGINE.draw(TITLE.STATUS.layer, x, y, SPRITE.LineTop); | |
| let delta = 48; | |
| let NUM = HERO.inventory.size; | |
| let xS = ENGINE.spreadAroundCenter(NUM, ENGINE.sideWIDTH / 2, delta); | |
| y += 32; | |
| fs = 16; | |
| HERO.inventory.forEach(sprite => { | |
| ENGINE.spriteDraw(TITLE.STATUS.layer, xS.shift(), y, sprite); | |
| }); | |
| y += fs; | |
| ENGINE.draw(TITLE.STATUS.layer, x, y, SPRITE.LineBottom); | |
| y += SPRITE.LineBottom.height; | |
| TITLE.stack.y = y; | |
| } | |
| }; | |
| class Monster { | |
| constructor(type, grid, go = false) { | |
| this.enemy = true; | |
| this.type = type; | |
| this.grid = Grid.toClass(grid); | |
| this.awake = false; | |
| this.actor = new ACTOR( | |
| this.type.name, | |
| 0, | |
| 0, | |
| "front", | |
| ASSET[this.type.name] | |
| ); | |
| GRID.gridToSprite(this.grid, this.actor); | |
| this.alignToViewport(); | |
| this.MoveState = new MoveState(this.grid, DOWN); | |
| this.dirStack = []; | |
| this.strategy = this.type.strategy; | |
| this.speed = this.type.speed; | |
| this.visible = false; | |
| this.health = this.type.health; | |
| this.weapon = this.type.weapon; | |
| this.armor = this.type.armor; | |
| this.maxHealth = this.type.health; | |
| this.mana = this.type.mana; | |
| this.magic = this.type.magic; | |
| this.agility = this.type.agility; | |
| //this.casted(2000); | |
| this.casted(0); | |
| if (go) this.wake(); | |
| /* | |
| strategy: | |
| wander, hunt | |
| goto, guard (door, key) | |
| seek | |
| flee, wander | |
| */ | |
| } | |
| alignToViewport() { | |
| ENGINE.VIEWPORT.alignTo(this.actor); | |
| } | |
| makeMove() { | |
| this.MoveState.dir = this.dirStack.shift(); | |
| this.MoveState.next(this.MoveState.dir); | |
| } | |
| wake() { | |
| //console.log("waking", this); | |
| this.awake = true; | |
| } | |
| sleep() { | |
| this.awake = false; | |
| this.hide(); | |
| } | |
| show() { | |
| this.visible = true; | |
| } | |
| hide() { | |
| this.visible = false; | |
| } | |
| isVisible() { | |
| let targetGrid = this.MoveState.closerGrid(HERO.MoveState); | |
| return GRID.vision(Grid.toClass(HERO.MoveState.homeGrid), targetGrid); | |
| } | |
| casted(add = 0) { | |
| this.canShoot = false; | |
| this.shootCoolDown(add); | |
| } | |
| shootCoolDown(add) { | |
| setTimeout(() => (this.canShoot = true), INI.SHOOT_TIMEOUT + add); | |
| } | |
| drop(grid) { | |
| let drop; | |
| if (this.type.inventory) { | |
| switch (this.type.inventory.type) { | |
| case "potion": | |
| drop = new Potion(this.type.inventory.value.chooseRandom(), grid); | |
| MAP[GAME.level].DUNGEON.potions.push(drop); | |
| break; | |
| case "boost": | |
| drop = new Boost(this.type.inventory.value.chooseRandom(), grid); | |
| MAP[GAME.level].DUNGEON.boosts.push(drop); | |
| break; | |
| default: | |
| console.log("Monster dropping", this.type.inventory, "ERROR"); | |
| break; | |
| } | |
| } else { | |
| let value = RND(Math.floor(this.type.gold / 2), this.type.gold); | |
| drop = new Gold(value, grid); | |
| MAP[GAME.level].DUNGEON.gold.push(drop); | |
| } | |
| ENGINE.VIEWPORT.changed = true; | |
| GAME.PAINT.config(); | |
| } | |
| turn() { | |
| if (!GAME.TURN.fight_active) return; | |
| let damage = GAME.TURN.damage(this, HERO); | |
| if (damage > 0) { | |
| CONSOLE.print( | |
| `<span class="red">${ | |
| this.type.title | |
| }</span> hits and makes <span class="red">${damage}</span> damage.` | |
| ); | |
| HERO.health -= damage; | |
| HERO.health = Math.max(HERO.health, 0); | |
| if (HERO.health <= 0) { | |
| console.log("HERO dies in fight"); | |
| GAME.TURN.fight_active = false; | |
| CONSOLE.print(`<span class="blue">${HERO.name} was killed.`); | |
| GAME.CLICK.endFight(); | |
| HERO.death(); | |
| } | |
| } else { | |
| CONSOLE.print(`<span class="red">${this.type.title}</span> misses.`); | |
| } | |
| } | |
| die() { | |
| HERO.incExp(this.type.exp); | |
| //HERO.experience += this.type.exp; | |
| TITLE.change(); | |
| this.drop(this.MoveState.homeGrid); | |
| TEXTPOOL.pool.push( | |
| new TextSprite( | |
| (this.type.exp + "XP").toString(), | |
| GRID.gridToCoord(this.MoveState.homeGrid), | |
| "#00FF00", | |
| 100 | |
| ) | |
| ); | |
| } | |
| } | |
| var LOG = {}; | |
| class Log { | |
| constructor() { | |
| this.kills = 0; | |
| } | |
| } | |
| ///////////////// | |
| $(function() { | |
| PRG.INIT(); | |
| PRG.setup(); | |
| ENGINE.LOAD.preload(); | |
| SCORE.init("SC", "DDID", 10, 2500); | |
| SCORE.loadHS(); | |
| SCORE.hiScore(); | |
| BACKUP_MAP = $.extend(true, {}, MAP); | |
| }); |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> | |
| <script src="https://codepen.io/laughingskull/pen/MOJejy"></script> | |
| <script src="https://codepen.io/laughingskull/pen/ooZWOB"></script> | |
| <script src="https://codepen.io/laughingskull/pen/MOmNNE"></script> | |
| <script src="https://codepen.io/laughingskull/pen/qwbKMK"></script> | |
| <script src="https://codepen.io/laughingskull/pen/KEpzbV"></script> | |
| <script src="https://codepen.io/laughingskull/pen/vMGjqe"></script> |
| @font-face { | |
| font-family: "Emulogic"; | |
| src: url("https://www.c00lsch00l.eu/Fonts/emulogic.ttf"); | |
| } | |
| @font-face { | |
| font-family: "Arcade"; | |
| src: url("https://www.c00lsch00l.eu/Fonts/Arcade Classic.ttf"); | |
| } | |
| :root { | |
| background-color: orange; | |
| background: -webkit-linear-gradient(90deg, orange, #ffc04d); | |
| background: -o-linear-gradient(90deg, orange, #ffc04d); | |
| background: -moz-linear-gradient(90deg, orange, #ffc04d); | |
| background: -ms-linear-gradient(90deg, orange, #ffc04d); | |
| background: linear-gradient(90deg, orange, #ffc04d); | |
| font-family: "Source Sans Pro", sans-serif; | |
| } | |
| h1, | |
| h2 { | |
| text-align: left; | |
| font-size: 25px; | |
| text-shadow: 1px 1px #666; | |
| font-family: "Arcade"; | |
| } | |
| p { | |
| color: #000; | |
| font-size: 18px; | |
| /*font-family: "Emulogic";*/ | |
| } | |
| hr { | |
| border: 0; | |
| height: 1px; | |
| background-image: -webkit-linear-gradient( | |
| left, | |
| rgba(0, 0, 0, 0), | |
| rgba(0, 0, 0, 0.75), | |
| rgba(0, 0, 0, 0) | |
| ); | |
| background-image: -moz-linear-gradient( | |
| left, | |
| rgba(0, 0, 0, 0), | |
| rgba(0, 0, 0, 0.75), | |
| rgba(0, 0, 0, 0) | |
| ); | |
| background-image: -ms-linear-gradient( | |
| left, | |
| rgba(0, 0, 0, 0), | |
| rgba(0, 0, 0, 0.75), | |
| rgba(0, 0, 0, 0) | |
| ); | |
| background-image: -o-linear-gradient( | |
| left, | |
| rgba(0, 0, 0, 0), | |
| rgba(0, 0, 0, 0.75), | |
| rgba(0, 0, 0, 0) | |
| ); | |
| } | |
| .center { | |
| text-align: center; | |
| } | |
| .big { | |
| font-size: 22px; | |
| } | |
| .fr { | |
| float: right; | |
| } | |
| .fl { | |
| float: left; | |
| } | |
| .cb { | |
| clear: both; | |
| } | |
| .cr { | |
| clear: right; | |
| } | |
| .cl { | |
| clear: left; | |
| } | |
| .win { | |
| width: 960px; | |
| margin: auto; | |
| margin-top: 20px; | |
| padding: 20px 32px 32px; | |
| background-color: #ffd78e; | |
| background: -webkit-linear-gradient( | |
| to right, | |
| #ffd78e, | |
| #ffd382, | |
| #ffe5b4, | |
| #ffd78e, | |
| #ffd78e | |
| ); | |
| background: -o-linear-gradient( | |
| to right, | |
| #ffd78e, | |
| #ffd382, | |
| #ffe5b4, | |
| #ffd78e, | |
| #ffd78e | |
| ); | |
| background: -moz-linear-gradient( | |
| to right, | |
| #ffd78e, | |
| #ffd382, | |
| #ffe5b4, | |
| #ffd78e, | |
| #ffd78e | |
| ); | |
| background: -ms-linear-gradient( | |
| to right, | |
| #ffd78e, | |
| #ffd382, | |
| #ffe5b4, | |
| #ffd78e, | |
| #ffd78e | |
| ); | |
| background: linear-gradient( | |
| to right, | |
| #ffd78e, | |
| #ffd382, | |
| #ffe5b4, | |
| #ffd78e, | |
| #ffd78e | |
| ); | |
| border-radius: 13px; | |
| box-shadow: 5px 5px 20px 7px #e69400; | |
| } | |
| /**NEW**/ | |
| .setup_container { | |
| padding: 10px; | |
| display: inline-block; | |
| float: left; | |
| margin: 12px; | |
| } | |
| input[type="button"] { | |
| cursor: pointer; | |
| } | |
| input[type="text"] { | |
| text-align: center; | |
| } | |
| .version { | |
| font-family: Consolas; | |
| font-size: 10px; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| .winTrans { | |
| width: 960px; | |
| margin: auto; | |
| margin-top: 20px; | |
| padding: 20px 32px 32px; | |
| } | |
| input[type="button"] { | |
| -webkit-border-radius: 0px 5px 5px 5px; | |
| border-radius: 0px 5px 5px 5px; | |
| } | |
| #startGame, #Restart { | |
| font-weight: bold; | |
| } | |
| #setup2 input[type="text"] { | |
| font-size: 12px; | |
| font-family: Consolas; | |
| } | |
| .section { | |
| display: none; | |
| font-size: 12px; | |
| font-family: Consolas; | |
| } | |
| .section fieldset { | |
| border-radius: 7px; | |
| width: 600px; | |
| } | |
| .section fieldset p { | |
| font-size: 12px; | |
| } | |
| .layer { | |
| position: absolute; | |
| left: 0; | |
| } | |
| #hiscore { | |
| border: 1px dotted #888; | |
| border-radius: 13px; | |
| padding: 20px; | |
| box-shadow: 2px 3px 3px 3px #333; | |
| font-family: "Courier New", Courier, monospace; | |
| font-size: 16px; | |
| white-space: pre; | |
| width: 230px; | |
| margin-top: 8px; | |
| } | |
| #SC { | |
| float: right; | |
| margin-left: 8px; | |
| } | |
| .gw { | |
| float: left; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .gh { | |
| float: none; | |
| clear: both; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .bordered { | |
| border: 1px solid black; | |
| border-radius: 7px; | |
| width: 240px; | |
| height: 100px; | |
| } | |
| img.pic { | |
| margin: 10px; | |
| border: 1px solid #000; | |
| padding: 2px; | |
| max-width: 100%; | |
| height: auto; | |
| } | |
| /*FORM*/ | |
| .form { | |
| padding-left: 8px; | |
| padding-right: 8px; | |
| } | |
| .form input[type="button"] { | |
| margin: 4px; | |
| -webkit-border-radius: 5px 5px 5px 5px; | |
| border-radius: 5px; | |
| background-color: #999; | |
| border: 1px solid #666; | |
| color: #111; | |
| box-shadow: 3px 3px 3px 0px #222; | |
| cursor: pointer; | |
| } | |
| .form input[type="button"]:active { | |
| background-color: #111; | |
| border: 1px solid #666; | |
| color: #999; | |
| cursor: pointer; | |
| } | |
| .form input[type="button"]:disabled { | |
| background-color: #333; | |
| border: 1px solid #222; | |
| box-shadow: 3px 3px 3px 0px #111; | |
| color: #222; | |
| cursor: not-allowed; | |
| } | |
| #FORM { | |
| position: absolute; | |
| border: 1px solid #645454; | |
| border-radius: 7px; | |
| background: #000; | |
| z-index: 999; | |
| } | |
| #FORM h1 { | |
| font-family: "Garamond"; | |
| text-align: center; | |
| font-size: 16px; | |
| color: #999; | |
| text-shadow: 1px 1px #666; | |
| margin: 0px; | |
| margin-top: 4px; | |
| padding: 0px; | |
| } | |
| #FORM hr { | |
| border: 0; | |
| border-top: 1px solid #222; | |
| } | |
| #FORM img { | |
| position: relative; | |
| top: 4px; | |
| } | |
| .form span { | |
| font-family: "Consolas"; | |
| color: #777; | |
| padding: 16px; | |
| } | |
| .fightWindow { | |
| padding: 0px; | |
| float: left; | |
| width: 100; | |
| } | |
| .fightWindow p { | |
| color: #0d0; | |
| font-size: 14px; | |
| text-align: center; | |
| padding: 0px; | |
| margin: 0px; | |
| } | |
| .fightWindow p.attr { | |
| font-family: "Garamond"; | |
| color: #999; | |
| text-shadow: 1px 1px #666; | |
| font-size: 16px; | |
| text-align: left; | |
| padding: 0px; | |
| padding-left: 12px; | |
| margin: 0px; | |
| } | |
| div .healthbar { | |
| padding: 5px; | |
| } | |
| span.scroll_counter { | |
| padding: 0px; | |
| padding-right: 0px; | |
| /*position: relative; | |
| top: -10px;*/ | |
| padding-right: 20px; | |
| font-size: 14px; | |
| } | |
| .form input[type="image"] { | |
| } | |
| .form input[type="image"]:disabled { | |
| cursor: not-allowed; | |
| opacity: 0.4 | |
| } | |
| div#Console {} | |
| div#Console p { | |
| color: #0F0; | |
| font-family: Consolas; | |
| font-size: 11px; | |
| padding-left: 8px; | |
| margin: 0px; | |
| } | |
| .blue { | |
| color: #00F !important; | |
| padding: 0px !important; | |
| } | |
| .red { | |
| color: #F00 !important; | |
| padding: 0px !important; | |
| } | |
| .orange { | |
| color: #FFA500 !important; | |
| padding: 0px !important; | |
| } | |
| #scrollPanel { | |
| clear: both; | |
| overflow-x: scroll; | |
| white-space: nowrap; | |
| scroll-behavior: smooth; | |
| scrollbar-width: thin; | |
| scrollbar-color: #333 #000; | |
| justify-content: safe center; | |
| display: flex; | |
| align-items: center; | |
| height: 50px; | |
| } | |
| #scrollPanel input[type="image"]{ | |
| padding-right: 0px; | |
| flex-shrink: 0; | |
| } |