From 618b8696b0e8678c459ae6caebb4f98e850deb66 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 14 Aug 2013 08:39:43 -0400 Subject: [PATCH 01/14] Coverted a few files to CoffeeScript --- client/js/area.coffee | 15 +++++++++++++++ client/js/item.coffee | 24 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 client/js/area.coffee create mode 100644 client/js/item.coffee diff --git a/client/js/area.coffee b/client/js/area.coffee new file mode 100644 index 000000000..39f42cdba --- /dev/null +++ b/client/js/area.coffee @@ -0,0 +1,15 @@ +define -> + + class Area + constructor: (@x, @y, @width, @height) -> + + contains: (entity) -> + if entity? + entity.gridX >= this.x and + entity.grixY => this.x and + entity.gridX < this.x + this.width and + entity.gridY < this.y + this.height + else + false + + Area diff --git a/client/js/item.coffee b/client/js/item.coffee new file mode 100644 index 000000000..8f27827cc --- /dev/null +++ b/client/js/item.coffee @@ -0,0 +1,24 @@ +define(['entity'], -> (Entity) + class Item extends Entity + constructor: (id, kind, type) -> + super id, kind + + this.itemKind = Types.getKindAsString kind + this.type = type + this.wasDropped = false + + hasShadow: -> true + + onLoot: (player) -> + if this.type == "weapon" + player.switchWeapon this.itemKind + else if this.type == "armor" + player.armorloot_callback this.itemKind + + getSpriteName: -> + "item-" + this.itemKind + + getLootMessage: -> + this.lootMessage + + Item From ea4952ea38ba4bc487e4a09fa54fed5f2f475802 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 14 Aug 2013 09:14:49 -0400 Subject: [PATCH 02/14] Fixed typo in area.coffee --- client/js/area.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/js/area.coffee b/client/js/area.coffee index 39f42cdba..727be79c7 100644 --- a/client/js/area.coffee +++ b/client/js/area.coffee @@ -1,12 +1,11 @@ define -> - class Area constructor: (@x, @y, @width, @height) -> contains: (entity) -> if entity? entity.gridX >= this.x and - entity.grixY => this.x and + entity.gridY >= this.x and entity.gridX < this.x + this.width and entity.gridY < this.y + this.height else From d63e0508453e67a7d704efdbf888f408c56cabc2 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 14 Aug 2013 09:19:13 -0400 Subject: [PATCH 03/14] Fixed up item.coffee --- client/js/item.coffee | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/client/js/item.coffee b/client/js/item.coffee index 8f27827cc..6b985df81 100644 --- a/client/js/item.coffee +++ b/client/js/item.coffee @@ -1,24 +1,23 @@ -define(['entity'], -> (Entity) - class Item extends Entity - constructor: (id, kind, type) -> - super id, kind +define ['entity'], -> (Entity) +class Item extends Entity + constructor: (id, kind, @type) -> + super id, kind - this.itemKind = Types.getKindAsString kind - this.type = type - this.wasDropped = false + this.itemKind = Types.getKindAsString kind + this.wasDropped = false - hasShadow: -> true + hasShadow: -> true - onLoot: (player) -> - if this.type == "weapon" - player.switchWeapon this.itemKind - else if this.type == "armor" - player.armorloot_callback this.itemKind + onLoot: (player) -> + if this.type == "weapon" + player.switchWeapon this.itemKind + else if this.type == "armor" + player.armorloot_callback this.itemKind - getSpriteName: -> - "item-" + this.itemKind + getSpriteName: -> + "item-" + this.itemKind - getLootMessage: -> - this.lootMessage + getLootMessage: -> + this.lootMessage - Item +Item From fe21ffa7855bbb9970f1c25f1bafd4b1f04708e8 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 14 Aug 2013 09:42:03 -0400 Subject: [PATCH 04/14] Added animation.coffee --- client/js/animation.coffee | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 client/js/animation.coffee diff --git a/client/js/animation.coffee b/client/js/animation.coffee new file mode 100644 index 000000000..23ffde878 --- /dev/null +++ b/client/js/animation.coffee @@ -0,0 +1,44 @@ +define -> + class Animation + constructor: (@name, @length, @row, @width, @height) -> + @reset() + + tick: -> + i = @currentFrame.index + + if i < @length - 1 then i += 1 else i = 0 + + if @count > 0 and i is 0 + @count -= 1 + if @count is 0 + @currentFrame.index = 0 + @endcount_callback() + return + + @currentFrame.x = @width * i + @currentFrame.y = @height * @row + @currentFrame.index = i + + setSpeed: (@speed) -> + + setCount: (@count, @endcount_callback) -> + + isTimeToAnimate: (time) -> + (time - @lastTime) > @speed + + update: (time) -> + if @lastTime is 0 and @name[..3] is "atk" + @lastTime = time + + if @isTimeToAnimate time + @lastTime = time + @tick() + true + else + false + + reset: -> + @lastTime = 0 + @currentFrame = { index: 0, x: 0, y: @row * @height } + + Animation From f1b359804f65b49cae559f3697a8069f36652be7 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 14 Aug 2013 09:43:51 -0400 Subject: [PATCH 05/14] Changed use of this to @ --- client/js/area.coffee | 8 ++++---- client/js/item.coffee | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/js/area.coffee b/client/js/area.coffee index 727be79c7..ba8f43d0a 100644 --- a/client/js/area.coffee +++ b/client/js/area.coffee @@ -4,10 +4,10 @@ define -> contains: (entity) -> if entity? - entity.gridX >= this.x and - entity.gridY >= this.x and - entity.gridX < this.x + this.width and - entity.gridY < this.y + this.height + entity.gridX >= @x and + entity.gridY >= @x and + entity.gridX < @x + @width and + entity.gridY < @y + @height else false diff --git a/client/js/item.coffee b/client/js/item.coffee index 6b985df81..82e81ab7e 100644 --- a/client/js/item.coffee +++ b/client/js/item.coffee @@ -3,21 +3,21 @@ class Item extends Entity constructor: (id, kind, @type) -> super id, kind - this.itemKind = Types.getKindAsString kind - this.wasDropped = false + @itemKind = Types.getKindAsString kind + @wasDropped = false hasShadow: -> true onLoot: (player) -> - if this.type == "weapon" - player.switchWeapon this.itemKind - else if this.type == "armor" - player.armorloot_callback this.itemKind + if @type == "weapon" + player.switchWeapon @itemKind + else if @type == "armor" + player.armorloot_callback @itemKind getSpriteName: -> - "item-" + this.itemKind + "item-" + @itemKind getLootMessage: -> - this.lootMessage + @lootMessage Item From 74ffd54afadd9873dfb3674c5d06f801e1dc0356 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 14 Aug 2013 09:54:06 -0400 Subject: [PATCH 06/14] Added tile.coffee --- client/js/tile.coffee | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 client/js/tile.coffee diff --git a/client/js/tile.coffee b/client/js/tile.coffee new file mode 100644 index 000000000..e158b24e2 --- /dev/null +++ b/client/js/tile.coffee @@ -0,0 +1,23 @@ +define -> + class Tile + + class AnimatedTile extends Tile + constructor: (@id, @length, @speed, @index) -> + @startId = id + @lastTime = 0 + + tick: -> + if (@id - @startId) < @length - 1 + @id += 1 + else + @id = @startId + + animate: (time) -> + if (time - @lastTime) > @speed + @tick() + @lastTime = time + true + else + false + + AnimatedTile From d941804285cefb964aa204c236e4c38894ff1265 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Thu, 15 Aug 2013 17:34:50 -0400 Subject: [PATCH 07/14] Added Cakefile --- Cakefile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Cakefile diff --git a/Cakefile b/Cakefile new file mode 100644 index 000000000..ae66c6358 --- /dev/null +++ b/Cakefile @@ -0,0 +1,15 @@ +{exec} = require 'child_process' +COFFEE = 'coffee --compile' +COFFE_MAP = '#{COFFEE} --map' + +task 'build', 'Build client and server code', -> + invoke 'build:client' + invoke 'build:server' + +task 'build:client', 'Build client code only', -> + exec "#{COFFEE} client/js/", (err, stdout, stderr) -> + throw err if err + +task 'build:server', 'Build server code only', -> + exec "#{COFFEE} server/js/", (err, stdout, stderr) -> + throw err if err From cf63c43519f2ee0fc793a5d8c9a89e157614ed5e Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 4 Sep 2013 15:47:43 -0400 Subject: [PATCH 08/14] Added bubble.coffee --- client/js/bubble.coffee | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 client/js/bubble.coffee diff --git a/client/js/bubble.coffee b/client/js/bubble.coffee new file mode 100644 index 000000000..39ecb6d09 --- /dev/null +++ b/client/js/bubble.coffee @@ -0,0 +1,55 @@ +define ['jquery', 'timer'] ($, timer) -> + + class Bubble + constructor: (@id, @element, time) -> + @timer = new Timer(5000, time) + + isOver: (time) -> + if @timer.isOver time + true + false + + destroy: -> + $(@element).remove() + + reset: (time) -> + @timer.lastTime = time + + class BubbleManager + constructor: (@container) -> + @bubbles = {} + + getBubbleById: (id) -> + if id in @bubbles + @bubbles[id] + null + + create: (id, message, time) -> + if @bubbles[id] + @bubbles[id].reset(time) + $("#"+id+" p").html(message) + else + el = $("

"+message+"

") #attr('id', id) + $(el).appendTo @container + @bubbles[id] = new Bubble(id, el, time) + + update: (time) -> + for bubble in @bubbles[0..@bubbles.length] # Don't mutate the list being iterated over + if bubble.isOver(time) + bubble.destroy() + delete @bubbles[bubble.id] + + clean: -> + bubble.destroy(); delete @bubbles[bubble.id] for bubble in @bubbles + @bubbles = {} + + destroyBubble: (id) -> + bubble = @getBubbleById(id) + if bubble + bubble.destroy() + delete @bubbles[id] + + forEachBubble: (callback) -> + calback(bubble) for bubble in @bubbles + + BubbleManager From 0cac48b1299a90534f43db6469efa57275914987 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 8 Sep 2013 10:45:10 -0400 Subject: [PATCH 09/14] Fixed indentation in item.coffee --- client/js/item.coffee | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/client/js/item.coffee b/client/js/item.coffee index 82e81ab7e..7a8ebbb99 100644 --- a/client/js/item.coffee +++ b/client/js/item.coffee @@ -1,23 +1,23 @@ define ['entity'], -> (Entity) -class Item extends Entity - constructor: (id, kind, @type) -> - super id, kind + class Item extends Entity + constructor: (id, kind, @type) -> + super id, kind - @itemKind = Types.getKindAsString kind - @wasDropped = false + @itemKind = Types.getKindAsString kind + @wasDropped = false - hasShadow: -> true + hasShadow: -> true - onLoot: (player) -> - if @type == "weapon" - player.switchWeapon @itemKind - else if @type == "armor" - player.armorloot_callback @itemKind + onLoot: (player) -> + if @type == "weapon" + player.switchWeapon @itemKind + else if @type == "armor" + player.armorloot_callback @itemKind - getSpriteName: -> - "item-" + @itemKind + getSpriteName: -> + "item-" + @itemKind - getLootMessage: -> - @lootMessage + getLootMessage: -> + @lootMessage -Item + Item From e37824962dca8f2c058e3fc45dbc4c0d147ce332 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 8 Sep 2013 12:22:14 -0400 Subject: [PATCH 10/14] Fixed function definition in item.coffee --- client/js/item.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/item.coffee b/client/js/item.coffee index 7a8ebbb99..833e948e3 100644 --- a/client/js/item.coffee +++ b/client/js/item.coffee @@ -1,4 +1,4 @@ -define ['entity'], -> (Entity) +define ['entity'], (Entity) -> class Item extends Entity constructor: (id, kind, @type) -> super id, kind From d1ac3a4da9000a2ebc09a57f9a059e9572656ab5 Mon Sep 17 00:00:00 2001 From: Justin Clift Date: Sun, 8 Sep 2013 18:55:09 +0100 Subject: [PATCH 11/14] Add coffee-script to the npm package dependencies --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1778891de..c00e9982e 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ , "private": false , "dependencies": { "underscore": ">=1.4" + , "coffee-script": ">=0" , "log": ">=0" , "bison": ">=0" , "websocket": ">=0" From b1b633336b8ce53c7c4eafc721d315d8feed761f Mon Sep 17 00:00:00 2001 From: Justin Clift Date: Sun, 8 Sep 2013 18:55:48 +0100 Subject: [PATCH 12/14] Converted using js2coffee Some file couldn't be converted, failing with the error 'SyntaxError: Illegal token': * pathfinder.js * infomanager.js * gameclient.js * game.js * entity.js * camera.js --- client/js/app.coffee | 506 +++++++++++++++++++++++++++ client/js/audio.coffee | 190 +++++++++++ client/js/build.coffee | 25 ++ client/js/character.coffee | 446 ++++++++++++++++++++++++ client/js/chest.coffee | 19 ++ client/js/config.coffee | 21 ++ client/js/detect.coffee | 28 ++ client/js/entityfactory.coffee | 156 +++++++++ client/js/entrypoint.coffee | 24 ++ client/js/exceptions.coffee | 6 + client/js/guild.coffee | 22 ++ client/js/home.coffee | 3 + client/js/items.coffee | 68 ++++ client/js/main.coffee | 426 +++++++++++++++++++++++ client/js/map.coffee | 253 ++++++++++++++ client/js/mapworker.coffee | 55 +++ client/js/mob.coffee | 8 + client/js/mobs.coffee | 117 +++++++ client/js/npc.coffee | 81 +++++ client/js/npcs.coffee | 55 +++ client/js/player.coffee | 223 ++++++++++++ client/js/renderer.coffee | 601 +++++++++++++++++++++++++++++++++ client/js/sprite.coffee | 131 +++++++ client/js/sprites.coffee | 8 + client/js/storage.coffee | 148 ++++++++ client/js/text.coffee | 136 ++++++++ client/js/timer.coffee | 15 + client/js/transition.coffee | 43 +++ client/js/updater.coffee | 189 +++++++++++ client/js/util.coffee | 25 ++ client/js/warrior.coffee | 6 + 31 files changed, 4034 insertions(+) create mode 100644 client/js/app.coffee create mode 100644 client/js/audio.coffee create mode 100644 client/js/build.coffee create mode 100644 client/js/character.coffee create mode 100644 client/js/chest.coffee create mode 100644 client/js/config.coffee create mode 100644 client/js/detect.coffee create mode 100644 client/js/entityfactory.coffee create mode 100644 client/js/entrypoint.coffee create mode 100644 client/js/exceptions.coffee create mode 100644 client/js/guild.coffee create mode 100644 client/js/home.coffee create mode 100644 client/js/items.coffee create mode 100644 client/js/main.coffee create mode 100644 client/js/map.coffee create mode 100644 client/js/mapworker.coffee create mode 100644 client/js/mob.coffee create mode 100644 client/js/mobs.coffee create mode 100644 client/js/npc.coffee create mode 100644 client/js/npcs.coffee create mode 100644 client/js/player.coffee create mode 100644 client/js/renderer.coffee create mode 100644 client/js/sprite.coffee create mode 100644 client/js/sprites.coffee create mode 100644 client/js/storage.coffee create mode 100644 client/js/text.coffee create mode 100644 client/js/timer.coffee create mode 100644 client/js/transition.coffee create mode 100644 client/js/updater.coffee create mode 100644 client/js/util.coffee create mode 100644 client/js/warrior.coffee diff --git a/client/js/app.coffee b/client/js/app.coffee new file mode 100644 index 000000000..84b523a28 --- /dev/null +++ b/client/js/app.coffee @@ -0,0 +1,506 @@ +define ["jquery", "storage"], ($, Storage) -> + App = Class.extend( + init: -> + @currentPage = 1 + @blinkInterval = null + @isParchmentReady = true + @ready = false + @storage = new Storage() + @watchNameInputInterval = setInterval(@toggleButton.bind(this), 100) + @initFormFields() + if localStorage and localStorage.data + @frontPage = "loadcharacter" + else + @frontPage = "createcharacter" + + setGame: (game) -> + @game = game + @isMobile = @game.renderer.mobile + @isTablet = @game.renderer.tablet + @isDesktop = not (@isMobile or @isTablet) + @supportsWorkers = !!window.Worker + @ready = true + + initFormFields: -> + + # Play button + @$playButton = $(".play") + @$playDiv = $(".play div") + + + # Login form fields + @$loginnameinput = $("#loginnameinput") + @$loginpwinput = $("#loginpwinput") + @loginFormFields = [@$loginnameinput, @$loginpwinput] + + # Create new character form fields + @$nameinput = $("#nameinput") + @$pwinput = $("#pwinput") + @$pwinput2 = $("#pwinput2") + @$email = $("#emailinput") + @createNewCharacterFormFields = [@$nameinput, @$pwinput, @$pwinput2, @$email] + + # Functions to return the proper username / password fields to use, depending on which form + # (login or create new character) is currently active. + @getUsernameField = -> + (if @createNewCharacterFormActive() then @$nameinput else @$loginnameinput) + + @getPasswordField = -> + (if @createNewCharacterFormActive() then @$pwinput else @$loginpwinput) + + center: -> + window.scrollTo 0, 1 + + canStartGame: -> + if @isDesktop + @game and @game.map and @game.map.isLoaded + else + @game + + tryStartingGame: -> + self = this + $play = @$playButton + username = @getUsernameField().attr("value") + userpw = @getPasswordField().attr("value") + email = "" + userpw2 = undefined + if @createNewCharacterFormActive() + email = @$email.attr("value") + userpw2 = @$pwinput2.attr("value") + return unless @validateFormFields(username, userpw, userpw2, email) + if not @ready or not @canStartGame() + + # on desktop and tablets, add a spinner to the play button + $play.addClass "loading" unless @isMobile + @$playDiv.unbind "click" + watchCanStart = setInterval(-> + log.debug "waiting..." + if self.canStartGame() + setTimeout (-> + $play.removeClass "loading" unless self.isMobile + ), 1500 + clearInterval watchCanStart + self.startGame username, userpw, email + , 100) + else + @$playDiv.unbind "click" + @startGame username, userpw, email + + startGame: (username, userpw, email) -> + self = this + @hideIntro -> + + # On mobile and tablet we load the map after the player has clicked + # on the PLAY button instead of loading it in a web worker. + self.game.loadMap() unless self.isDesktop + self.start username, userpw, email + + + start: (username, userpw, email) -> + self = this + firstTimePlaying = not self.storage.hasAlreadyPlayed() + if username and not @game.started + optionsSet = false + config = @config + + #>>includeStart("devHost", pragmas.devHost); + if config.local + log.debug "Starting game with local dev config." + @game.setServerOptions config.local.host, config.local.port, username, userpw, email + else + log.debug "Starting game with default dev config." + @game.setServerOptions config.dev.host, config.dev.port, username, userpw, email + optionsSet = true + + #>>includeEnd("devHost"); + + #>>includeStart("prodHost", pragmas.prodHost); + unless optionsSet + log.debug "Starting game with build config." + @game.setServerOptions config.build.host, config.build.port, username, userpw, email + + #>>includeEnd("prodHost"); + @center() + @game.run -> + $("body").addClass "started" + self.toggleInstructions() if firstTimePlaying + + + loginFormActive: -> + $("#parchment").hasClass "loadcharacter" + + createNewCharacterFormActive: -> + $("#parchment").hasClass "createcharacter" + + + ### + Handles the Enter key in the Login / Create New Character forms. (Assumes one of these forms is + currently active.) + ### + handleEnter: -> + fields = (if @loginFormActive() then @loginFormFields else @createNewCharacterFormFields) + isFieldEmpty = (field) -> + $.trim(field.val()) is 0 + + isEmpty = -> + isFieldEmpty $(this) + + allFieldsFilledOut = (fields) -> + $.map(fields, isFieldEmpty).every (v) -> + not v + + + if allFieldsFilledOut(fields) + + # If all fields have been filled out, then the Enter key should start the game. + @tryStartingGame() + else + + # Otherwise, pressing Enter should switch focus to the first missing field + firstMissingField = $.grep(fields, isFieldEmpty)[0] + firstMissingField.focus() if firstMissingField isnt `undefined` + + + ### + Performs some basic validation on the login / create new character forms (required fields are filled + out, passwords match, email looks valid). Assumes either the login or the create new character form + is currently active. + ### + validateFormFields: (username, userpw, userpw2, email) -> + return false if not username or not userpw + if @createNewCharacterFormActive() # In Create New Character form (rather than login form) + if userpw isnt userpw2 + alert "The passwords you entered do not match. Please make sure you typed the password correctly." + @$pwinput.select() + return false + + # Email field is not required, but if it's filled out, then it should look like a valid email. + if email and not @validateEmail(email) + alert "The email you entered appears to be invalid. Please enter a valid email (or leave the email blank)." + @$email.select() + return false + true + + validateEmail: (email) -> + + # Regex borrowed from http://stackoverflow.com/a/46181/393005 + re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + re.test email + + setMouseCoordinates: (event) -> + gamePos = $("#container").offset() + scale = @game.renderer.getScaleFactor() + width = @game.renderer.getWidth() + height = @game.renderer.getHeight() + mouse = @game.mouse + mouse.x = event.pageX - gamePos.left - ((if @isMobile then 0 else 5 * scale)) + mouse.y = event.pageY - gamePos.top - ((if @isMobile then 0 else 7 * scale)) + if mouse.x <= 0 + mouse.x = 0 + else mouse.x = width - 1 if mouse.x >= width + if mouse.y <= 0 + mouse.y = 0 + else mouse.y = height - 1 if mouse.y >= height + + + #Init the hud that makes it show what creature you are mousing over and attacking + initTargetHud: -> + self = this + scale = self.game.renderer.getScaleFactor() + healthMaxWidth = $("#inspector .health").width() - (12 * scale) + timeout = undefined + @game.player.onSetTarget (target, name, mouseover) -> + el = "#inspector" + sprite = target.sprite + x = ((sprite.animationData.idle_down.length - 1) * sprite.width) + y = ((sprite.animationData.idle_down.row) * sprite.height) + $(el + " .name").text name + + #Show how much Health creature has left. Currently does not work. The reason health doesn't currently go down has to do with the lines below down to initExpBar... + if target.healthPoints + $(el + " .health").css "width", Math.round(target.healthPoints / target.maxHp * 100) + "%" + else + $(el + " .health").css "width", "0%" + level = Types.getMobLevel(Types.getKindFromString(name)) + if level isnt `undefined` + $(el + " .level").text "Level " + level + else + $("#inspector .level").text "" + $(el).fadeIn "fast" + + self.game.onUpdateTarget (target) -> + $("#inspector .health").css "width", Math.round(target.healthPoints / target.maxHp * 100) + "%" + + self.game.player.onRemoveTarget (targetId) -> + $("#inspector").fadeOut "fast" + $("#inspector .level").text "" + self.game.player.inspecting = null + + + initExpBar: -> + maxHeight = $("#expbar").height() + @game.onPlayerExpChange (expInThisLevel, expForLevelUp) -> + barHeight = Math.round((maxHeight / expForLevelUp) * ((if expInThisLevel > 0 then expInThisLevel else 0))) + $("#expbar").css "height", barHeight + "px" + + + initHealthBar: -> + scale = @game.renderer.getScaleFactor() + healthMaxWidth = $("#healthbar").width() - (12 * scale) + @game.onPlayerHealthChange (hp, maxHp) -> + barWidth = Math.round((healthMaxWidth / maxHp) * ((if hp > 0 then hp else 0))) + $("#hitpoints").css "width", barWidth + "px" + + @game.onPlayerHurt @blinkHealthBar.bind(this) + + blinkHealthBar: -> + $hitpoints = $("#hitpoints") + $hitpoints.addClass "white" + setTimeout (-> + $hitpoints.removeClass "white" + ), 500 + + toggleButton: -> + name = $("#parchment input").val() + $play = $("#createcharacter .play") + if name and name.length > 0 + $play.removeClass "disabled" + $("#character").removeClass "disabled" + else + $play.addClass "disabled" + $("#character").addClass "disabled" + + hideIntro: (hidden_callback) -> + clearInterval @watchNameInputInterval + $("body").removeClass "intro" + setTimeout (-> + $("body").addClass "game" + hidden_callback() + ), 1000 + + showChat: -> + if @game.started + $("#chatbox").addClass "active" + $("#chatinput").focus() + $("#chatbutton").addClass "active" + + hideChat: -> + if @game.started + $("#chatbox").removeClass "active" + $("#chatinput").blur() + $("#chatbutton").removeClass "active" + + toggleInstructions: -> + if $("#achievements").hasClass("active") + @toggleAchievements() + $("#achievementsbutton").removeClass "active" + $("#instructions").toggleClass "active" + + toggleAchievements: -> + if $("#instructions").hasClass("active") + @toggleInstructions() + $("#helpbutton").removeClass "active" + @resetPage() + $("#achievements").toggleClass "active" + + resetPage: -> + self = this + $achievements = $("#achievements") + if $achievements.hasClass("active") + $achievements.bind TRANSITIONEND, -> + $achievements.removeClass("page" + self.currentPage).addClass "page1" + self.currentPage = 1 + $achievements.unbind TRANSITIONEND + + + initEquipmentIcons: -> + scale = @game.renderer.getScaleFactor() + getIconPath = (spriteName) -> + "img/" + scale + "/item-" + spriteName + ".png" + + weapon = @game.player.getWeaponName() + armor = @game.player.getSpriteName() + weaponPath = getIconPath(weapon) + armorPath = getIconPath(armor) + $("#weapon").css "background-image", "url(\"" + weaponPath + "\")" + $("#armor").css "background-image", "url(\"" + armorPath + "\")" if armor isnt "firefox" + + hideWindows: -> + if $("#achievements").hasClass("active") + @toggleAchievements() + $("#achievementsbutton").removeClass "active" + if $("#instructions").hasClass("active") + @toggleInstructions() + $("#helpbutton").removeClass "active" + @closeInGameScroll "credits" if $("body").hasClass("credits") + @closeInGameScroll "legal" if $("body").hasClass("legal") + @closeInGameScroll "about" if $("body").hasClass("about") + + showAchievementNotification: (id, name) -> + $notif = $("#achievement-notification") + $name = $notif.find(".name") + $button = $("#achievementsbutton") + $notif.removeClass().addClass "active achievement" + id + $name.text name + if @game.storage.getAchievementCount() is 1 + @blinkInterval = setInterval(-> + $button.toggleClass "blink" + , 500) + setTimeout (-> + $notif.removeClass "active" + $button.removeClass "blink" + ), 5000 + + displayUnlockedAchievement: (id) -> + $achievement = $("#achievements li.achievement" + id) + achievement = @game.getAchievementById(id) + @setAchievementData $achievement, achievement.name, achievement.desc if achievement and achievement.hidden + $achievement.addClass "unlocked" + + unlockAchievement: (id, name) -> + @showAchievementNotification id, name + @displayUnlockedAchievement id + nb = parseInt($("#unlocked-achievements").text()) + $("#unlocked-achievements").text nb + 1 + + initAchievementList: (achievements) -> + self = this + $lists = $("#lists") + $page = $("#page-tmpl") + $achievement = $("#achievement-tmpl") + page = 0 + count = 0 + $p = null + _.each achievements, (achievement) -> + count++ + $a = $achievement.clone() + $a.removeAttr "id" + $a.addClass "achievement" + count + self.setAchievementData $a, achievement.name, achievement.desc unless achievement.hidden + $a.find(".twitter").attr "href", "http://twitter.com/share?url=http%3A%2F%2Fbrowserquest.mozilla.org&text=I%20unlocked%20the%20%27" + achievement.name + "%27%20achievement%20on%20Mozilla%27s%20%23BrowserQuest%21&related=glecollinet:Creators%20of%20BrowserQuest%2Cwhatthefranck" + $a.show() + $a.find("a").click -> + url = $(this).attr("href") + self.openPopup "twitter", url + false + + if (count - 1) % 4 is 0 + page++ + $p = $page.clone() + $p.attr "id", "page" + page + $p.show() + $lists.append $p + $p.append $a + + $("#total-achievements").text $("#achievements").find("li").length + + initUnlockedAchievements: (ids) -> + self = this + _.each ids, (id) -> + self.displayUnlockedAchievement id + + $("#unlocked-achievements").text ids.length + + setAchievementData: ($el, name, desc) -> + $el.find(".achievement-name").html name + $el.find(".achievement-description").html desc + + toggleScrollContent: (content) -> + currentState = $("#parchment").attr("class") + if @game.started + $("#parchment").removeClass().addClass content + $("body").removeClass("credits legal about").toggleClass content + $("body").toggleClass "death" unless @game.player + $("#helpbutton").removeClass "active" if content isnt "about" + else + if currentState isnt "animate" + if currentState is content + @animateParchment currentState, @frontPage + else + @animateParchment currentState, content + + closeInGameScroll: (content) -> + $("body").removeClass content + $("#parchment").removeClass content + $("body").addClass "death" unless @game.player + $("#helpbutton").removeClass "active" if content is "about" + + togglePopulationInfo: -> + $("#population").toggleClass "visible" + + openPopup: (type, url) -> + h = $(window).height() + w = $(window).width() + popupHeight = undefined + popupWidth = undefined + top = undefined + left = undefined + switch type + when "twitter" + popupHeight = 450 + popupWidth = 550 + when "facebook" + popupHeight = 400 + popupWidth = 580 + top = (h / 2) - (popupHeight / 2) + left = (w / 2) - (popupWidth / 2) + newwindow = window.open(url, "name", "height=" + popupHeight + ",width=" + popupWidth + ",top=" + top + ",left=" + left) + newwindow.focus() if window.focus + + animateParchment: (origin, destination) -> + self = this + $parchment = $("#parchment") + duration = 1 + if @isMobile + $parchment.removeClass(origin).addClass destination + else + if @isParchmentReady + duration = 0 if @isTablet + @isParchmentReady = not @isParchmentReady + $parchment.toggleClass "animate" + $parchment.removeClass origin + setTimeout (-> + $("#parchment").toggleClass "animate" + $parchment.addClass destination + ), duration * 1000 + setTimeout (-> + self.isParchmentReady = not self.isParchmentReady + ), duration * 1000 + + animateMessages: -> + $messages = $("#notifications div") + $messages.addClass "top" + + resetMessagesPosition: -> + message = $("#message2").text() + $("#notifications div").removeClass "top" + $("#message2").text "" + $("#message1").text message + + showMessage: (message) -> + $wrapper = $("#notifications div") + $message = $("#notifications #message2") + @animateMessages() + $message.text message + @resetMessageTimer() if @messageTimer + @messageTimer = setTimeout(-> + $wrapper.addClass "top" + , 5000) + + resetMessageTimer: -> + clearTimeout @messageTimer + + resizeUi: -> + if @game + if @game.started + @game.resize() + @initHealthBar() + @initTargetHud() + @initExpBar() + @game.updateBars() + else + newScale = @game.renderer.getScaleFactor() + @game.renderer.rescale newScale + ) + App + diff --git a/client/js/audio.coffee b/client/js/audio.coffee new file mode 100644 index 000000000..39751f575 --- /dev/null +++ b/client/js/audio.coffee @@ -0,0 +1,190 @@ +define ["area"], (Area) -> + AudioManager = Class.extend( + init: (game) -> + self = this + @enabled = true + @extension = (if Detect.canPlayMP3() then "mp3" else "ogg") + @sounds = {} + @game = game + @currentMusic = null + @areas = [] + @musicNames = ["village", "beach", "forest", "cave", "desert", "lavaland", "boss"] + @soundNames = ["loot", "hit1", "hit2", "hurt", "heal", "chat", "revive", "death", "firefox", "achievement", "kill1", "kill2", "noloot", "teleport", "chest", "npc", "npc-end"] + loadSoundFiles = -> + counter = _.size(self.soundNames) + log.info "Loading sound files..." + _.each self.soundNames, (name) -> + self.loadSound name, -> + counter -= 1 + # Disable music on Safari - See bug 738008 + loadMusicFiles() unless Detect.isSafari() if counter is 0 + + + + loadMusicFiles = -> + unless self.game.renderer.mobile # disable music on mobile devices + log.info "Loading music files..." + + # Load the village music first, as players always start here + self.loadMusic self.musicNames.shift(), -> + + # Then, load all the other music files + _.each self.musicNames, (name) -> + self.loadMusic name + + + + unless Detect.isSafari() and Detect.isWindows() + loadSoundFiles() + else + @enabled = false # Disable audio on Safari Windows + + toggle: -> + if @enabled + @enabled = false + @resetMusic @currentMusic if @currentMusic + else + @enabled = true + @currentMusic = null if @currentMusic + @updateMusic() + + load: (basePath, name, loaded_callback, channels) -> + path = basePath + name + "." + @extension + sound = document.createElement("audio") + self = this + sound.addEventListener "canplaythrough", ((e) -> + @removeEventListener "canplaythrough", arguments_.callee, false + log.debug path + " is ready to play." + loaded_callback() if loaded_callback + ), false + sound.addEventListener "error", ((e) -> + log.error "Error: " + path + " could not be loaded." + self.sounds[name] = null + ), false + sound.preload = "auto" + sound.autobuffer = true + sound.src = path + sound.load() + @sounds[name] = [sound] + _.times channels - 1, -> + self.sounds[name].push sound.cloneNode(true) + + + loadSound: (name, handleLoaded) -> + @load "audio/sounds/", name, handleLoaded, 4 + + loadMusic: (name, handleLoaded) -> + @load "audio/music/", name, handleLoaded, 1 + music = @sounds[name][0] + music.loop = true + music.addEventListener "ended", (-> + music.play() + ), false + + getSound: (name) -> + return null unless @sounds[name] + sound = _.detect(@sounds[name], (sound) -> + sound.ended or sound.paused + ) + if sound and sound.ended + sound.currentTime = 0 + else + sound = @sounds[name][0] + sound + + playSound: (name) -> + sound = @enabled and @getSound(name) + sound.play() if sound + + addArea: (x, y, width, height, musicName) -> + area = new Area(x, y, width, height) + area.musicName = musicName + @areas.push area + + getSurroundingMusic: (entity) -> + music = null + area = _.detect(@areas, (area) -> + area.contains entity + ) + if area + music = + sound: @getSound(area.musicName) + name: area.musicName + music + + updateMusic: -> + if @enabled + music = @getSurroundingMusic(@game.player) + if music + unless @isCurrentMusic(music) + @fadeOutCurrentMusic() if @currentMusic + @playMusic music + else + @fadeOutCurrentMusic() + + isCurrentMusic: (music) -> + @currentMusic and (music.name is @currentMusic.name) + + playMusic: (music) -> + if @enabled and music and music.sound + if music.sound.fadingOut + @fadeInMusic music + else + music.sound.volume = 1 + music.sound.play() + @currentMusic = music + + resetMusic: (music) -> + if music and music.sound and music.sound.readyState > 0 + music.sound.pause() + music.sound.currentTime = 0 + + fadeOutMusic: (music, ended_callback) -> + self = this + if music and not music.sound.fadingOut + @clearFadeIn music + music.sound.fadingOut = setInterval(-> + step = 0.02 + volume = music.sound.volume - step + if self.enabled and volume >= step + music.sound.volume = volume + else + music.sound.volume = 0 + self.clearFadeOut music + ended_callback music + , 50) + + fadeInMusic: (music) -> + self = this + if music and not music.sound.fadingIn + @clearFadeOut music + music.sound.fadingIn = setInterval(-> + step = 0.01 + volume = music.sound.volume + step + if self.enabled and volume < 1 - step + music.sound.volume = volume + else + music.sound.volume = 1 + self.clearFadeIn music + , 30) + + clearFadeOut: (music) -> + if music.sound.fadingOut + clearInterval music.sound.fadingOut + music.sound.fadingOut = null + + clearFadeIn: (music) -> + if music.sound.fadingIn + clearInterval music.sound.fadingIn + music.sound.fadingIn = null + + fadeOutCurrentMusic: -> + self = this + if @currentMusic + @fadeOutMusic @currentMusic, (music) -> + self.resetMusic music + + @currentMusic = null + ) + AudioManager + diff --git a/client/js/build.coffee b/client/js/build.coffee new file mode 100644 index 000000000..a2977e842 --- /dev/null +++ b/client/js/build.coffee @@ -0,0 +1,25 @@ +appDir: "../" +baseUrl: "js/" +dir: "../../client-build" +optimize: "uglify" +optimizeCss: "standard.keepLines" +paths: + jquery: "lib/require-jquery" + +modules: [ + + #Optimize the require-jquery.js file by applying any minification + #that is desired via the optimize: setting above. + name: "jquery" +, + name: "game" + exclude: ["jquery"] +, + name: "home" + + # Exclude the jquery module since it is included already in require-jquery.js + exclude: ["jquery", "game"] +] +pragmas: + devHost: false + prodHost: true diff --git a/client/js/character.coffee b/client/js/character.coffee new file mode 100644 index 000000000..83df85d64 --- /dev/null +++ b/client/js/character.coffee @@ -0,0 +1,446 @@ +define ["entity", "transition", "timer"], (Entity, Transition, Timer) -> + Character = Entity.extend( + init: (id, kind) -> + self = this + @_super id, kind + + # Position and orientation + @nextGridX = -1 + @nextGridY = -1 + @orientation = Types.Orientations.DOWN + + # Speeds + @atkSpeed = 50 + @moveSpeed = 120 + @walkSpeed = 100 + @idleSpeed = 450 + @setAttackRate 800 + + # Pathing + @movement = new Transition() + @path = null + @newDestination = null + @adjacentTiles = {} + + # Combat + @target = null + @unconfirmedTarget = null + @attackers = {} + + # Health + @hitPoints = 0 + @maxHitPoints = 0 + + # Modes + @isDead = false + @attackingMode = false + @followingMode = false + @inspecting = null + + clean: -> + @forEachAttacker (attacker) -> + attacker.disengage() + attacker.idle() + + + setMaxHitPoints: (hp) -> + @maxHitPoints = hp + @hitPoints = hp + + setDefaultAnimation: -> + @idle() + + hasWeapon: -> + false + + hasShadow: -> + true + + animate: (animation, speed, count, onEndCount) -> + oriented = ["atk", "walk", "idle"] + o = @orientation + unless @currentAnimation and @currentAnimation.name is "death" # don't change animation if the character is dying + @flipSpriteX = false + @flipSpriteY = false + if _.indexOf(oriented, animation) >= 0 + animation += "_" + ((if o is Types.Orientations.LEFT then "right" else Types.getOrientationAsString(o))) + @flipSpriteX = (if (@orientation is Types.Orientations.LEFT) then true else false) + @setAnimation animation, speed, count, onEndCount + + turnTo: (orientation) -> + @orientation = orientation + @idle() + + setOrientation: (orientation) -> + @orientation = orientation if orientation + + idle: (orientation) -> + @setOrientation orientation + @animate "idle", @idleSpeed + + hit: (orientation) -> + @setOrientation orientation + @animate "atk", @atkSpeed, 1 + + walk: (orientation) -> + @setOrientation orientation + @animate "walk", @walkSpeed + + moveTo_: (x, y, callback) -> + @destination = + gridX: x + gridY: y + + @adjacentTiles = {} + if @isMoving() + @continueTo x, y + else + path = @requestPathfindingTo(x, y) + @followPath path + + requestPathfindingTo: (x, y) -> + if @request_path_callback + @request_path_callback x, y + else + log.error @id + " couldn't request pathfinding to " + x + ", " + y + [] + + onRequestPath: (callback) -> + @request_path_callback = callback + + onStartPathing: (callback) -> + @start_pathing_callback = callback + + onStopPathing: (callback) -> + @stop_pathing_callback = callback + + followPath: (path) -> + if path.length > 1 # Length of 1 means the player has clicked on himself + @path = path + @step = 0 + # following a character + path.pop() if @followingMode + @start_pathing_callback path if @start_pathing_callback + @nextStep() + + continueTo: (x, y) -> + @newDestination = + x: x + y: y + + updateMovement: -> + p = @path + i = @step + @walk Types.Orientations.LEFT if p[i][0] < p[i - 1][0] + @walk Types.Orientations.RIGHT if p[i][0] > p[i - 1][0] + @walk Types.Orientations.UP if p[i][1] < p[i - 1][1] + @walk Types.Orientations.DOWN if p[i][1] > p[i - 1][1] + + updatePositionOnGrid: -> + @setGridPosition @path[@step][0], @path[@step][1] + + nextStep: -> + stop = false + x = undefined + y = undefined + path = undefined + if @isMoving() + @before_step_callback() if @before_step_callback + @updatePositionOnGrid() + @checkAggro() + if @interrupted # if Character.stop() has been called + stop = true + @interrupted = false + else + if @hasNextStep() + @nextGridX = @path[@step + 1][0] + @nextGridY = @path[@step + 1][1] + @step_callback() if @step_callback + if @hasChangedItsPath() + x = @newDestination.x + y = @newDestination.y + path = @requestPathfindingTo(x, y) + @newDestination = null + if path.length < 2 + stop = true + else + @followPath path + else if @hasNextStep() + @step += 1 + @updateMovement() + else + stop = true + if stop # Path is complete or has been interrupted + @path = null + @idle() + @stop_pathing_callback @gridX, @gridY if @stop_pathing_callback + + onBeforeStep: (callback) -> + @before_step_callback = callback + + onStep: (callback) -> + @step_callback = callback + + isMoving: -> + (@path isnt null) + + hasNextStep: -> + @path.length - 1 > @step + + hasChangedItsPath: -> + (@newDestination isnt null) + + isNear: (character, distance) -> + dx = undefined + dy = undefined + near = false + dx = Math.abs(@gridX - character.gridX) + dy = Math.abs(@gridY - character.gridY) + near = true if dx <= distance and dy <= distance + near + + onAggro: (callback) -> + @aggro_callback = callback + + onCheckAggro: (callback) -> + @checkaggro_callback = callback + + checkAggro: -> + @checkaggro_callback() if @checkaggro_callback + + aggro: (character) -> + @aggro_callback character if @aggro_callback + + onDeath: (callback) -> + @death_callback = callback + + + ### + Changes the character's orientation so that it is facing its target. + ### + lookAtTarget: -> + @turnTo @getOrientationTo(@target) if @target + + + ### + ### + go: (x, y) -> + if @isAttacking() + @disengage() + else if @followingMode + @followingMode = false + @target = null + @moveTo_ x, y + + + ### + Makes the character follow another one. + ### + follow: (entity) -> + if entity + @followingMode = true + @moveTo_ entity.gridX, entity.gridY + + + ### + Stops a moving character. + ### + stop: -> + @interrupted = true if @isMoving() + + + ### + Makes the character attack another character. Same as Character.follow but with an auto-attacking behavior. + @see Character.follow + ### + engage: (character) -> + @attackingMode = true + @setTarget character + @follow character + + disengage: -> + @attackingMode = false + @followingMode = false + @removeTarget() + + + ### + Returns true if the character is currently attacking. + ### + isAttacking: -> + @attackingMode + + + ### + Gets the right orientation to face a target character from the current position. + Note: + In order to work properly, this method should be used in the following + situation : + S + S T S + S + (where S is self, T is target character) + + @param {Character} character The character to face. + @returns {String} The orientation. + ### + getOrientationTo: (character) -> + if @gridX < character.gridX + Types.Orientations.RIGHT + else if @gridX > character.gridX + Types.Orientations.LEFT + else if @gridY > character.gridY + Types.Orientations.UP + else + Types.Orientations.DOWN + + + ### + Returns true if this character is currently attacked by a given character. + @param {Character} character The attacking character. + @returns {Boolean} Whether this is an attacker of this character. + ### + isAttackedBy: (character) -> + character.id of @attackers + + + ### + Registers a character as a current attacker of this one. + @param {Character} character The attacking character. + ### + addAttacker: (character) -> + unless @isAttackedBy(character) + @attackers[character.id] = character + else + log.error @id + " is already attacked by " + character.id + + + ### + Unregisters a character as a current attacker of this one. + @param {Character} character The attacking character. + ### + removeAttacker: (character) -> + if @isAttackedBy(character) + delete @attackers[character.id] + else + log.error @id + " is not attacked by " + character.id + + + ### + Loops through all the characters currently attacking this one. + @param {Function} callback Function which must accept one character argument. + ### + forEachAttacker: (callback) -> + _.each @attackers, (attacker) -> + callback attacker + + + + ### + Sets this character's attack target. It can only have one target at any time. + @param {Character} character The target character. + ### + setTarget: (character) -> + if @target isnt character # If it's not already set as the target + @removeTarget() if @hasTarget() # Cleanly remove the previous one + @unconfirmedTarget = null + @target = character + if @settarget_callback + targetName = Types.getKindAsString(character.kind) + @settarget_callback character, targetName + else + log.debug character.id + " is already the target of " + @id + + onSetTarget: (callback) -> + @settarget_callback = callback + + showTarget: (character) -> + if @inspecting isnt character + @inspecting = character + if @settarget_callback + targetName = Types.getKindAsString(character.kind) + @settarget_callback character, targetName, true + + + ### + Removes the current attack target. + ### + removeTarget: -> + self = this + if @target + @target.removeAttacker this if @target instanceof Character + @removetarget_callback @target.id if @removetarget_callback + @target = null + + onRemoveTarget: (callback) -> + @removetarget_callback = callback + + + ### + Returns true if this character has a current attack target. + @returns {Boolean} Whether this character has a target. + ### + hasTarget: -> + (@target isnt null) + + + ### + Marks this character as waiting to attack a target. + By sending an "attack" message, the server will later confirm (or not) + that this character is allowed to acquire this target. + + @param {Character} character The target character + ### + waitToAttack: (character) -> + @unconfirmedTarget = character + + + ### + Returns true if this character is currently waiting to attack the target character. + @param {Character} character The target character. + @returns {Boolean} Whether this character is waiting to attack. + ### + isWaitingToAttack: (character) -> + @unconfirmedTarget is character + + + ### + ### + canAttack: (time) -> + return true if @canReachTarget() and @attackCooldown.isOver(time) + false + + canReachTarget: -> + return true if @hasTarget() and @isAdjacentNonDiagonal(@target) + false + + + ### + ### + die: -> + @removeTarget() + @isDead = true + @death_callback() if @death_callback + + onHasMoved: (callback) -> + @hasmoved_callback = callback + + hasMoved: -> + @setDirty() + @hasmoved_callback this if @hasmoved_callback + + hurt: -> + self = this + @stopHurting() + @sprite = @hurtSprite + @hurting = setTimeout(@stopHurting.bind(this), 75) + + stopHurting: -> + @sprite = @normalSprite + clearTimeout @hurting + + setAttackRate: (rate) -> + @attackCooldown = new Timer(rate) + ) + Character + diff --git a/client/js/chest.coffee b/client/js/chest.coffee new file mode 100644 index 000000000..c90addcae --- /dev/null +++ b/client/js/chest.coffee @@ -0,0 +1,19 @@ +define ["entity"], (Entity) -> + Chest = Entity.extend( + init: (id, kind) -> + @_super id, Types.Entities.CHEST + + getSpriteName: -> + "chest" + + isMoving: -> + false + + open: -> + @open_callback() if @open_callback + + onOpen: (callback) -> + @open_callback = callback + ) + Chest + diff --git a/client/js/config.coffee b/client/js/config.coffee new file mode 100644 index 000000000..4eee1d3cc --- /dev/null +++ b/client/js/config.coffee @@ -0,0 +1,21 @@ +define ["text!../config/config_build.json"], (build) -> + config = + dev: + host: "localhost" + port: 8000 + dispatcher: false + + build: JSON.parse(build) + + + #>>excludeStart("prodHost", pragmas.prodHost); + require ["text!../config/config_local.json"], (local) -> + try + config.local = JSON.parse(local) + + + # Exception triggered when config_local.json does not exist. Nothing to do here. + + #>>excludeEnd("prodHost"); + config + diff --git a/client/js/detect.coffee b/client/js/detect.coffee new file mode 100644 index 000000000..f32817c8c --- /dev/null +++ b/client/js/detect.coffee @@ -0,0 +1,28 @@ +Detect = {} +Detect.supportsWebSocket = -> + window.WebSocket or window.MozWebSocket + +Detect.userAgentContains = (string) -> + navigator.userAgent.indexOf(string) isnt -1 + +Detect.isTablet = (screenWidth) -> + return true if (Detect.userAgentContains("Android") and Detect.userAgentContains("Firefox")) or Detect.userAgentContains("Mobile") if screenWidth > 640 + false + +Detect.isWindows = -> + Detect.userAgentContains "Windows" + +Detect.isChromeOnWindows = -> + Detect.userAgentContains("Chrome") and Detect.userAgentContains("Windows") + +Detect.canPlayMP3 = -> + Modernizr.audio.mp3 + +Detect.isSafari = -> + Detect.userAgentContains("Safari") and not Detect.userAgentContains("Chrome") + +Detect.isOpera = -> + Detect.userAgentContains "Opera" + +Detect.isFirefoxAndroid = -> + Detect.userAgentContains("Android") and Detect.userAgentContains("Firefox") diff --git a/client/js/entityfactory.coffee b/client/js/entityfactory.coffee new file mode 100644 index 000000000..b30119409 --- /dev/null +++ b/client/js/entityfactory.coffee @@ -0,0 +1,156 @@ +define ["mobs", "items", "npcs", "warrior", "chest"], (Mobs, Items, NPCs, Warrior, Chest) -> + EntityFactory = {} + EntityFactory.createEntity = (kind, id, name) -> + unless kind + log.error "kind is undefined", true + return + throw Error(kind + " is not a valid Entity type") unless _.isFunction(EntityFactory.builders[kind]) + EntityFactory.builders[kind] id, name + + + #===== mobs ====== + EntityFactory.builders = [] + EntityFactory.builders[Types.Entities.WARRIOR] = (id, name) -> + new Warrior(id, name) + + EntityFactory.builders[Types.Entities.RAT] = (id) -> + new Mobs.Rat(id) + + EntityFactory.builders[Types.Entities.SKELETON] = (id) -> + new Mobs.Skeleton(id) + + EntityFactory.builders[Types.Entities.SKELETON2] = (id) -> + new Mobs.Skeleton2(id) + + EntityFactory.builders[Types.Entities.SPECTRE] = (id) -> + new Mobs.Spectre(id) + + EntityFactory.builders[Types.Entities.DEATHKNIGHT] = (id) -> + new Mobs.Deathknight(id) + + EntityFactory.builders[Types.Entities.GOBLIN] = (id) -> + new Mobs.Goblin(id) + + EntityFactory.builders[Types.Entities.OGRE] = (id) -> + new Mobs.Ogre(id) + + EntityFactory.builders[Types.Entities.CRAB] = (id) -> + new Mobs.Crab(id) + + EntityFactory.builders[Types.Entities.SNAKE] = (id) -> + new Mobs.Snake(id) + + EntityFactory.builders[Types.Entities.EYE] = (id) -> + new Mobs.Eye(id) + + EntityFactory.builders[Types.Entities.BAT] = (id) -> + new Mobs.Bat(id) + + EntityFactory.builders[Types.Entities.WIZARD] = (id) -> + new Mobs.Wizard(id) + + EntityFactory.builders[Types.Entities.BOSS] = (id) -> + new Mobs.Boss(id) + + + #===== items ====== + EntityFactory.builders[Types.Entities.SWORD2] = (id) -> + new Items.Sword2(id) + + EntityFactory.builders[Types.Entities.AXE] = (id) -> + new Items.Axe(id) + + EntityFactory.builders[Types.Entities.REDSWORD] = (id) -> + new Items.RedSword(id) + + EntityFactory.builders[Types.Entities.BLUESWORD] = (id) -> + new Items.BlueSword(id) + + EntityFactory.builders[Types.Entities.GOLDENSWORD] = (id) -> + new Items.GoldenSword(id) + + EntityFactory.builders[Types.Entities.MORNINGSTAR] = (id) -> + new Items.MorningStar(id) + + EntityFactory.builders[Types.Entities.MAILARMOR] = (id) -> + new Items.MailArmor(id) + + EntityFactory.builders[Types.Entities.LEATHERARMOR] = (id) -> + new Items.LeatherArmor(id) + + EntityFactory.builders[Types.Entities.PLATEARMOR] = (id) -> + new Items.PlateArmor(id) + + EntityFactory.builders[Types.Entities.REDARMOR] = (id) -> + new Items.RedArmor(id) + + EntityFactory.builders[Types.Entities.GOLDENARMOR] = (id) -> + new Items.GoldenArmor(id) + + EntityFactory.builders[Types.Entities.FLASK] = (id) -> + new Items.Flask(id) + + EntityFactory.builders[Types.Entities.FIREPOTION] = (id) -> + new Items.FirePotion(id) + + EntityFactory.builders[Types.Entities.BURGER] = (id) -> + new Items.Burger(id) + + EntityFactory.builders[Types.Entities.CAKE] = (id) -> + new Items.Cake(id) + + EntityFactory.builders[Types.Entities.CHEST] = (id) -> + new Chest(id) + + + #====== NPCs ====== + EntityFactory.builders[Types.Entities.GUARD] = (id) -> + new NPCs.Guard(id) + + EntityFactory.builders[Types.Entities.KING] = (id) -> + new NPCs.King(id) + + EntityFactory.builders[Types.Entities.VILLAGEGIRL] = (id) -> + new NPCs.VillageGirl(id) + + EntityFactory.builders[Types.Entities.VILLAGER] = (id) -> + new NPCs.Villager(id) + + EntityFactory.builders[Types.Entities.CODER] = (id) -> + new NPCs.Coder(id) + + EntityFactory.builders[Types.Entities.AGENT] = (id) -> + new NPCs.Agent(id) + + EntityFactory.builders[Types.Entities.RICK] = (id) -> + new NPCs.Rick(id) + + EntityFactory.builders[Types.Entities.SCIENTIST] = (id) -> + new NPCs.Scientist(id) + + EntityFactory.builders[Types.Entities.NYAN] = (id) -> + new NPCs.Nyan(id) + + EntityFactory.builders[Types.Entities.PRIEST] = (id) -> + new NPCs.Priest(id) + + EntityFactory.builders[Types.Entities.SORCERER] = (id) -> + new NPCs.Sorcerer(id) + + EntityFactory.builders[Types.Entities.OCTOCAT] = (id) -> + new NPCs.Octocat(id) + + EntityFactory.builders[Types.Entities.BEACHNPC] = (id) -> + new NPCs.BeachNpc(id) + + EntityFactory.builders[Types.Entities.FORESTNPC] = (id) -> + new NPCs.ForestNpc(id) + + EntityFactory.builders[Types.Entities.DESERTNPC] = (id) -> + new NPCs.DesertNpc(id) + + EntityFactory.builders[Types.Entities.LAVANPC] = (id) -> + new NPCs.LavaNpc(id) + + EntityFactory + diff --git a/client/js/entrypoint.coffee b/client/js/entrypoint.coffee new file mode 100644 index 000000000..2472d08b4 --- /dev/null +++ b/client/js/entrypoint.coffee @@ -0,0 +1,24 @@ +define ["lib/sha1", "util"], -> + EntryPoint = Class.extend( + init: -> + + #"hashedID" ← use tools/sha1_encode.html to generate: function(){} ← action + @hashes = "Obda3tBpL9VXsXsSsv5xB4QKNo4=": (aGame) -> + aGame.player.switchArmor aGame.sprites["firefox"] + aGame.showNotification "You enter the game as a fox, but not invincible…" + + execute: (game) -> + res = false + ID = getUrlVars()["entrance"] + unless ID is `undefined` + shaObj = new jsSHA(ID, "TEXT") + hash = shaObj.getHash("SHA-1", "B64") + if @hashes[hash] is `undefined` + game.showNotification "Nice try little scoundrel… bad code, though" + else + @hashes[hash] game + res = true + res + ) + EntryPoint + diff --git a/client/js/exceptions.coffee b/client/js/exceptions.coffee new file mode 100644 index 000000000..9b6ec5f1f --- /dev/null +++ b/client/js/exceptions.coffee @@ -0,0 +1,6 @@ +define -> + Exceptions = LootException: Class.extend(init: (message) -> + @message = message + ) + Exceptions + diff --git a/client/js/guild.coffee b/client/js/guild.coffee new file mode 100644 index 000000000..1a881c1c3 --- /dev/null +++ b/client/js/guild.coffee @@ -0,0 +1,22 @@ +define -> + Guild = Class.extend(init: (id, name) -> + @members = [] #name + @id = id + @name = name + ) + #, Maybe useful later… see #updateguild tag + # + # addMembers: function(membersList) { + # //maybe we could have tested the form of the array… + # this.members = _.union(this.members, membersList); + # }, + # + # removeMembers: function(membersList) { + # this.members = _.difference(this.members, membersList); + # }, + # + # listMembers: function(iterator) { + # return _.filter(this.members, iterator); + # } + Guild + diff --git a/client/js/home.coffee b/client/js/home.coffee new file mode 100644 index 000000000..bdaef2225 --- /dev/null +++ b/client/js/home.coffee @@ -0,0 +1,3 @@ +define ["lib/class", "lib/underscore.min", "lib/stacktrace", "util"], -> + require ["main"] + diff --git a/client/js/items.coffee b/client/js/items.coffee new file mode 100644 index 000000000..140ac9aa5 --- /dev/null +++ b/client/js/items.coffee @@ -0,0 +1,68 @@ +define ["item"], (Item) -> + Items = + Sword2: Item.extend(init: (id) -> + @_super id, Types.Entities.SWORD2, "weapon" + @lootMessage = "You pick up a steel sword" + ) + Axe: Item.extend(init: (id) -> + @_super id, Types.Entities.AXE, "weapon" + @lootMessage = "You pick up an axe" + ) + RedSword: Item.extend(init: (id) -> + @_super id, Types.Entities.REDSWORD, "weapon" + @lootMessage = "You pick up a blazing sword" + ) + BlueSword: Item.extend(init: (id) -> + @_super id, Types.Entities.BLUESWORD, "weapon" + @lootMessage = "You pick up a magic sword" + ) + GoldenSword: Item.extend(init: (id) -> + @_super id, Types.Entities.GOLDENSWORD, "weapon" + @lootMessage = "You pick up the ultimate sword" + ) + MorningStar: Item.extend(init: (id) -> + @_super id, Types.Entities.MORNINGSTAR, "weapon" + @lootMessage = "You pick up a morning star" + ) + LeatherArmor: Item.extend(init: (id) -> + @_super id, Types.Entities.LEATHERARMOR, "armor" + @lootMessage = "You equip a leather armor" + ) + MailArmor: Item.extend(init: (id) -> + @_super id, Types.Entities.MAILARMOR, "armor" + @lootMessage = "You equip a mail armor" + ) + PlateArmor: Item.extend(init: (id) -> + @_super id, Types.Entities.PLATEARMOR, "armor" + @lootMessage = "You equip a plate armor" + ) + RedArmor: Item.extend(init: (id) -> + @_super id, Types.Entities.REDARMOR, "armor" + @lootMessage = "You equip a ruby armor" + ) + GoldenArmor: Item.extend(init: (id) -> + @_super id, Types.Entities.GOLDENARMOR, "armor" + @lootMessage = "You equip a golden armor" + ) + Flask: Item.extend(init: (id) -> + @_super id, Types.Entities.FLASK, "object" + @lootMessage = "You drink a health potion" + ) + Cake: Item.extend(init: (id) -> + @_super id, Types.Entities.CAKE, "object" + @lootMessage = "You eat a cake" + ) + Burger: Item.extend(init: (id) -> + @_super id, Types.Entities.BURGER, "object" + @lootMessage = "You can haz rat burger" + ) + FirePotion: Item.extend( + init: (id) -> + @_super id, Types.Entities.FIREPOTION, "object" + @lootMessage = "You feel the power of Firefox!" + + onLoot: (player) -> + player.startInvincibility() + ) + + Items diff --git a/client/js/main.coffee b/client/js/main.coffee new file mode 100644 index 000000000..7b4d57831 --- /dev/null +++ b/client/js/main.coffee @@ -0,0 +1,426 @@ +define ["jquery", "app", "entrypoint"], ($, App, EntryPoint) -> + app = undefined + game = undefined + initApp = -> + $(document).ready -> + app = new App() + app.center() + + # Workaround for graphical glitches on text + $("body").addClass "windows" if Detect.isWindows() + + # Fix for no pointer events + $("body").addClass "opera" if Detect.isOpera() + + # Remove chat placeholder + $("#chatinput").removeAttr "placeholder" if Detect.isFirefoxAndroid() + $("body").click (event) -> + app.toggleScrollContent "credits" if $("#parchment").hasClass("credits") + app.toggleScrollContent "legal" if $("#parchment").hasClass("legal") + app.toggleScrollContent "about" if $("#parchment").hasClass("about") + + $(".barbutton").click -> + $(this).toggleClass "active" + + $("#chatbutton").click -> + if $("#chatbutton").hasClass("active") + app.showChat() + else + app.hideChat() + + $("#helpbutton").click -> + if $("body").hasClass("about") + app.closeInGameScroll "about" + $("#helpbutton").removeClass "active" + else + app.toggleScrollContent "about" + + $("#achievementsbutton").click -> + app.toggleAchievements() + clearInterval app.blinkInterval if app.blinkInterval + $(this).removeClass "blink" + + $("#instructions").click -> + app.hideWindows() + + $("#playercount").click -> + app.togglePopulationInfo() + + $("#population").click -> + app.togglePopulationInfo() + + $(".clickable").click (event) -> + event.stopPropagation() + + $("#toggle-credits").click -> + app.toggleScrollContent "credits" + + $("#toggle-legal").click -> + app.toggleScrollContent "legal" + if game.renderer.mobile + if $("#parchment").hasClass("legal") + $(this).text "close" + else + $(this).text "Privacy" + + $("#create-new span").click -> + app.animateParchment "loadcharacter", "confirmation" + + $("#continue span").click -> + app.storage.clear() + app.animateParchment "confirmation", "createcharacter" + $("body").removeClass "returning" + + $("#cancel span").click -> + app.animateParchment "confirmation", "loadcharacter" + + $(".ribbon").click -> + app.toggleScrollContent "about" + + $("#nameinput").bind "keyup", -> + app.toggleButton() + + $("#pwinput").bind "keyup", -> + app.toggleButton() + + $("#pwinput2").bind "keyup", -> + app.toggleButton() + + $("#emailinput").bind "keyup", -> + app.toggleButton() + + $("#previous").click -> + $achievements = $("#achievements") + if app.currentPage is 1 + false + else + app.currentPage -= 1 + $achievements.removeClass().addClass "active page" + app.currentPage + + $("#next").click -> + $achievements = $("#achievements") + $lists = $("#lists") + nbPages = $lists.children("ul").length + if app.currentPage is nbPages + false + else + app.currentPage += 1 + $achievements.removeClass().addClass "active page" + app.currentPage + + $("#notifications div").bind TRANSITIONEND, app.resetMessagesPosition.bind(app) + $(".close").click -> + app.hideWindows() + + $(".twitter").click -> + url = $(this).attr("href") + app.openPopup "twitter", url + false + + $(".facebook").click -> + url = $(this).attr("href") + app.openPopup "facebook", url + false + + data = app.storage.data + if data.hasAlreadyPlayed + if data.player.name and data.player.name isnt "" + $("#playername").html data.player.name + $("#playerimage").attr "src", data.player.image + $("#playbutton span").click (event) -> + app.tryStartingGame() + + document.addEventListener "touchstart", (-> + ), false + $("#resize-check").bind "transitionend", app.resizeUi.bind(app) + $("#resize-check").bind "webkitTransitionEnd", app.resizeUi.bind(app) + $("#resize-check").bind "oTransitionEnd", app.resizeUi.bind(app) + log.info "App initialized." + initGame() + + + initGame = -> + require ["game"], (Game) -> + canvas = document.getElementById("entities") + background = document.getElementById("background") + foreground = document.getElementById("foreground") + input = document.getElementById("chatinput") + game = new Game(app) + game.setup "#bubbles", canvas, background, foreground, input + game.setStorage app.storage + app.setGame game + game.loadMap() if app.isDesktop and app.supportsWorkers + game.onGameStart -> + app.initEquipmentIcons() + entry = new EntryPoint() + entry.execute game + + game.onDisconnect (message) -> + $("#death").find("p").html message + "Please reload the page." + $("#respawn").hide() + + game.onPlayerDeath -> + $("body").removeClass "credits" if $("body").hasClass("credits") + $("body").addClass "death" + + game.onPlayerEquipmentChange -> + app.initEquipmentIcons() + + game.onPlayerInvincible -> + $("#hitpoints").toggleClass "invincible" + + game.onNbPlayersChange (worldPlayers, totalPlayers) -> + setWorldPlayersString = (string) -> + $("#instance-population").find("span:nth-child(2)").text string + $("#playercount").find("span:nth-child(2)").text string + + setTotalPlayersString = (string) -> + $("#world-population").find("span:nth-child(2)").text string + + $("#playercount").find("span.count").text worldPlayers + $("#instance-population").find("span").text worldPlayers + if worldPlayers is 1 + setWorldPlayersString "player" + else + setWorldPlayersString "players" + $("#world-population").find("string").text Types.getLevel + if totalPlayers is 1 + setTotalPlayersString "player" + else + setTotalPlayersString "players" + + game.onGuildPopulationChange (guildName, guildPopulation) -> + setGuildPlayersString = (string) -> + $("#guild-population").find("span:nth-child(2)").text string + + $("#guild-population").addClass "visible" + $("#guild-population").find("span").text guildPopulation + $("#guild-name").text guildName + if guildPopulation is 1 + setGuildPlayersString "player" + else + setGuildPlayersString "players" + + game.onAchievementUnlock (id, name, description) -> + app.unlockAchievement id, name + + game.onNotification (message) -> + app.showMessage message + + app.initHealthBar() + app.initTargetHud() + app.initExpBar() + $("#nameinput").attr "value", "" + $("#pwinput").attr "value", "" + $("#pwinput2").attr "value", "" + $("#emailinput").attr "value", "" + $("#chatbox").attr "value", "" + if game.renderer.mobile or game.renderer.tablet + $("#foreground").bind "touchstart", (event) -> + app.center() + app.setMouseCoordinates event.originalEvent.touches[0] + game.click() + app.hideWindows() + + else + $("#foreground").click (event) -> + app.center() + app.setMouseCoordinates event + if game and not app.dropDialogPopuped + game.pvpFlag = event.shiftKey + game.click() + app.hideWindows() + + $("body").unbind "click" + $("body").click (event) -> + hasClosedParchment = false + if $("#parchment").hasClass("credits") + if game.started + app.closeInGameScroll "credits" + hasClosedParchment = true + else + app.toggleScrollContent "credits" + if $("#parchment").hasClass("legal") + if game.started + app.closeInGameScroll "legal" + hasClosedParchment = true + else + app.toggleScrollContent "legal" + if $("#parchment").hasClass("about") + if game.started + app.closeInGameScroll "about" + hasClosedParchment = true + else + app.toggleScrollContent "about" + game.click() if game.started and not game.renderer.mobile and game.player and not hasClosedParchment + + $("#respawn").click (event) -> + game.audioManager.playSound "revive" + game.restart() + $("body").removeClass "death" + + $(document).mousemove (event) -> + app.setMouseCoordinates event + if game.started + game.pvpFlag = event.shiftKey + game.movecursor() + + $(document).keyup (e) -> + key = e.which + if game.started and not $("#chatbox").hasClass("active") + switch key + when Types.Keys.LEFT, Types.Keys.A + , Types.Keys.KEYPAD_4 + game.player.moveLeft = false + game.player.disableKeyboardNpcTalk = false + when Types.Keys.RIGHT, Types.Keys.D + , Types.Keys.KEYPAD_6 + game.player.moveRight = false + game.player.disableKeyboardNpcTalk = false + when Types.Keys.UP, Types.Keys.W + , Types.Keys.KEYPAD_8 + game.player.moveUp = false + game.player.disableKeyboardNpcTalk = false + when Types.Keys.DOWN, Types.Keys.S + , Types.Keys.KEYPAD_2 + game.player.moveDown = false + game.player.disableKeyboardNpcTalk = false + else + + $(document).keydown (e) -> + key = e.which + $chat = $("#chatinput") + if key is Types.Keys.ENTER + if $("#chatbox").hasClass("active") + app.hideChat() + else + app.showChat() + else game.pvpFlag = true if key is 16 + if game.started and not $("#chatbox").hasClass("active") + pos = + x: game.player.gridX + y: game.player.gridY + + switch key + when Types.Keys.LEFT, Types.Keys.A + , Types.Keys.KEYPAD_4 + game.player.moveLeft = true + when Types.Keys.RIGHT, Types.Keys.D + , Types.Keys.KEYPAD_6 + game.player.moveRight = true + when Types.Keys.UP, Types.Keys.W + , Types.Keys.KEYPAD_8 + game.player.moveUp = true + when Types.Keys.DOWN, Types.Keys.S + , Types.Keys.KEYPAD_2 + game.player.moveDown = true + when Types.Keys.SPACE + game.makePlayerAttackNext() + when Types.Keys.I + $("#achievementsbutton").click() + when Types.Keys.H + $("#helpbutton").click() + when Types.Keys.M + $("#mutebutton").click() + when Types.Keys.P + $("#playercount").click() + else + + $(document).keyup (e) -> + key = e.which + game.pvpFlag = false if key is 16 + + $("#chatinput").keydown (e) -> + key = e.which + $chat = $("#chatinput") + placeholder = $(this).attr("placeholder") + + # if (!(e.shiftKey && e.keyCode === 16) && e.keyCode !== 9) { + # if ($(this).val() === placeholder) { + # $(this).val(''); + # $(this).removeAttr('placeholder'); + # $(this).removeClass('placeholder'); + # } + # } + if key is 13 + if $chat.attr("value") isnt "" + game.say $chat.attr("value") if game.player + $chat.attr "value", "" + app.hideChat() + $("#foreground").focus() + return false + else + app.hideChat() + return false + if key is 27 + app.hideChat() + false + + $("#chatinput").focus (e) -> + placeholder = $(this).attr("placeholder") + $(this).val placeholder unless Detect.isFirefoxAndroid() + @setSelectionRange 0, 0 if $(this).val() is placeholder + + $("#nameinput").focusin -> + $("#name-tooltip").addClass "visible" + + $("#nameinput").focusout -> + $("#name-tooltip").removeClass "visible" + + $("#nameinput").keypress (event) -> + $("#name-tooltip").removeClass "visible" + + $("#mutebutton").click -> + game.audioManager.toggle() + + $(document).bind "keydown", (e) -> + key = e.which + $chat = $("#chatinput") + if key is 13 # Enter + if game.ready + $chat.focus() + return false + else + if app.loginFormActive() or app.createNewCharacterFormActive() + $("input").blur() # exit keyboard on mobile + app.handleEnter() + return false # prevent form submit + if $("#chatinput:focus").size() is 0 and $("#nameinput:focus").size() is 0 + if key is 27 # ESC + app.hideWindows() + _.each game.player.attackers, (attacker) -> + attacker.stop() + + false + + + # The following may be uncommented for debugging purposes. + # + # if(key === 32 && game.ready) { // Space + # game.togglePathingGrid(); + # return false; + # } + # if(key === 70 && game.ready) { // F + # game.toggleDebugInfo(); + # return false; + # } + $("#healthbar").click (e) -> + hb = $("#healthbar") + hp = $("#hitpoints") + hpg = $("#hpguide") + hbp = hb.position() + hpp = hp.position() + if (e.offsetX >= hpp.left) and (e.offsetX < hb.width()) + if hpg.css("display") is "none" + hpg.css "display", "block" + setInterval (-> + game.eat game.healShortCut if ((game.player.hitPoints / game.player.maxHitPoints) <= game.hpGuide) and (game.healShortCut >= 0) and Types.isHealingItem(game.player.inventory[game.healShortCut]) and (game.player.inventoryCount[game.healShortCut] > 0) + ), 100 + hpg.css "left", e.offsetX + "px" + game.hpGuide = (e.offsetX - hpp.left) / (hb.width() - hpp.left) + false + + $("body").addClass "tablet" if game.renderer.tablet + + + initApp() + diff --git a/client/js/map.coffee b/client/js/map.coffee new file mode 100644 index 000000000..4e54064ee --- /dev/null +++ b/client/js/map.coffee @@ -0,0 +1,253 @@ +define ["jquery", "area"], ($, Area) -> + Map = Class.extend( + init: (loadMultiTilesheets, game) -> + @game = game + @data = [] + @isLoaded = false + @tilesetsLoaded = false + @mapLoaded = false + @loadMultiTilesheets = loadMultiTilesheets + useWorker = not (@game.renderer.mobile or @game.renderer.tablet) + @_loadMap useWorker + @_initTilesets() + + _checkReady: -> + if @tilesetsLoaded and @mapLoaded + @isLoaded = true + @ready_func() if @ready_func + + _loadMap: (useWorker) -> + self = this + filepath = "maps/world_client.json" + if useWorker + log.info "Loading map with web worker." + worker = new Worker("js/mapworker.js") + worker.postMessage 1 + worker.onmessage = (event) -> + map = event.data + self._initMap map + self.grid = map.grid + self.plateauGrid = map.plateauGrid + self.mapLoaded = true + self._checkReady() + else + log.info "Loading map via Ajax." + $.get filepath, ((data) -> + self._initMap data + self._generateCollisionGrid() + self._generatePlateauGrid() + self.mapLoaded = true + self._checkReady() + ), "json" + + _initTilesets: -> + tileset1 = undefined + tileset2 = undefined + tileset3 = undefined + unless @loadMultiTilesheets + @tilesetCount = 1 + tileset1 = @_loadTileset("img/1/tilesheet.png") + else + if @game.renderer.mobile or @game.renderer.tablet + @tilesetCount = 1 + tileset2 = @_loadTileset("img/2/tilesheet.png") + else + @tilesetCount = 2 + tileset2 = @_loadTileset("img/2/tilesheet.png") + tileset3 = @_loadTileset("img/3/tilesheet.png") + @tilesets = [tileset1, tileset2, tileset3] + + _initMap: (map) -> + @width = map.width + @height = map.height + @tilesize = map.tilesize + @data = map.data + @blocking = map.blocking or [] + @plateau = map.plateau or [] + @musicAreas = map.musicAreas or [] + @collisions = map.collisions + @high = map.high + @animated = map.animated + @doors = @_getDoors(map) + @checkpoints = @_getCheckpoints(map) + + _getDoors: (map) -> + doors = {} + self = this + _.each map.doors, (door) -> + o = undefined + switch door.to + when "u" + o = Types.Orientations.UP + when "d" + o = Types.Orientations.DOWN + when "l" + o = Types.Orientations.LEFT + when "r" + o = Types.Orientations.RIGHT + else + o = Types.Orientations.DOWN + doors[self.GridPositionToTileIndex(door.x, door.y)] = + x: door.tx + y: door.ty + orientation: o + cameraX: door.tcx + cameraY: door.tcy + portal: door.p is 1 + + doors + + _loadTileset: (filepath) -> + self = this + tileset = new Image() + tileset.crossOrigin = "Anonymous" + tileset.src = filepath + log.info "Loading tileset: " + filepath + tileset.onload = -> + throw Error("Tileset size should be a multiple of " + self.tilesize) if tileset.width % self.tilesize > 0 + log.info "Map tileset loaded." + self.tilesetCount -= 1 + if self.tilesetCount is 0 + log.debug "All map tilesets loaded." + self.tilesetsLoaded = true + self._checkReady() + + tileset + + ready: (f) -> + @ready_func = f + + tileIndexToGridPosition: (tileNum) -> + x = 0 + y = 0 + getX = (num, w) -> + return 0 if num is 0 + (if (num % w is 0) then w - 1 else (num % w) - 1) + + tileNum -= 1 + x = getX(tileNum + 1, @width) + y = Math.floor(tileNum / @width) + x: x + y: y + + GridPositionToTileIndex: (x, y) -> + (y * @width) + x + 1 + + isColliding: (x, y) -> + return false if @isOutOfBounds(x, y) or not @grid + @grid[y][x] is 1 + + isPlateau: (x, y) -> + return false if @isOutOfBounds(x, y) or not @plateauGrid + @plateauGrid[y][x] is 1 + + _generateCollisionGrid: -> + tileIndex = 0 + self = this + @grid = [] + j = undefined + i = 0 + + while i < @height + @grid[i] = [] + j = 0 + while j < @width + @grid[i][j] = 0 + j++ + i++ + _.each @collisions, (tileIndex) -> + pos = self.tileIndexToGridPosition(tileIndex + 1) + self.grid[pos.y][pos.x] = 1 + + _.each @blocking, (tileIndex) -> + pos = self.tileIndexToGridPosition(tileIndex + 1) + self.grid[pos.y][pos.x] = 1 if self.grid[pos.y] isnt `undefined` + + log.debug "Collision grid generated." + + _generatePlateauGrid: -> + tileIndex = 0 + @plateauGrid = [] + j = undefined + i = 0 + + while i < @height + @plateauGrid[i] = [] + j = 0 + while j < @width + if _.include(@plateau, tileIndex) + @plateauGrid[i][j] = 1 + else + @plateauGrid[i][j] = 0 + tileIndex += 1 + j++ + i++ + log.info "Plateau grid generated." + + + ### + Returns true if the given position is located within the dimensions of the map. + + @returns {Boolean} Whether the position is out of bounds. + ### + isOutOfBounds: (x, y) -> + isInt(x) and isInt(y) and (x < 0 or x >= @width or y < 0 or y >= @height) + + + ### + Returns true if the given tile id is "high", i.e. above all entities. + Used by the renderer to know which tiles to draw after all the entities + have been drawn. + + @param {Number} id The tile id in the tileset + @see Renderer.drawHighTiles + ### + isHighTile: (id) -> + _.indexOf(@high, id + 1) >= 0 + + + ### + Returns true if the tile is animated. Used by the renderer. + @param {Number} id The tile id in the tileset + ### + isAnimatedTile: (id) -> + id + 1 of @animated + + + ### + ### + getTileAnimationLength: (id) -> + @animated[id + 1].l + + + ### + ### + getTileAnimationDelay: (id) -> + animProperties = @animated[id + 1] + if animProperties.d + animProperties.d + else + 100 + + isDoor: (x, y) -> + @doors[@GridPositionToTileIndex(x, y)] isnt `undefined` + + getDoorDestination: (x, y) -> + @doors[@GridPositionToTileIndex(x, y)] + + _getCheckpoints: (map) -> + checkpoints = [] + _.each map.checkpoints, (cp) -> + area = new Area(cp.x, cp.y, cp.w, cp.h) + area.id = cp.id + checkpoints.push area + + checkpoints + + getCurrentCheckpoint: (entity) -> + _.detect @checkpoints, (checkpoint) -> + checkpoint.contains entity + + ) + Map + diff --git a/client/js/mapworker.coffee b/client/js/mapworker.coffee new file mode 100644 index 000000000..a1c8c7cad --- /dev/null +++ b/client/js/mapworker.coffee @@ -0,0 +1,55 @@ +generateCollisionGrid = -> + tileIndex = 0 + mapData.grid = [] + j = undefined + i = 0 + + while i < mapData.height + mapData.grid[i] = [] + j = 0 + while j < mapData.width + mapData.grid[i][j] = 0 + j++ + i++ + _.each mapData.collisions, (tileIndex) -> + pos = tileIndexToGridPosition(tileIndex + 1) + mapData.grid[pos.y][pos.x] = 1 + + _.each mapData.blocking, (tileIndex) -> + pos = tileIndexToGridPosition(tileIndex + 1) + mapData.grid[pos.y][pos.x] = 1 if mapData.grid[pos.y] isnt `undefined` + +generatePlateauGrid = -> + tileIndex = 0 + mapData.plateauGrid = [] + j = undefined + i = 0 + + while i < mapData.height + mapData.plateauGrid[i] = [] + j = 0 + while j < mapData.width + if _.include(mapData.plateau, tileIndex) + mapData.plateauGrid[i][j] = 1 + else + mapData.plateauGrid[i][j] = 0 + tileIndex += 1 + j++ + i++ +tileIndexToGridPosition = (tileNum) -> + x = 0 + y = 0 + getX = (num, w) -> + return 0 if num is 0 + (if (num % w is 0) then w - 1 else (num % w) - 1) + + tileNum -= 1 + x = getX(tileNum + 1, mapData.width) + y = Math.floor(tileNum / mapData.width) + x: x + y: y +importScripts "../maps/world_client.js", "lib/underscore.min.js" +onmessage = (event) -> + generateCollisionGrid() + generatePlateauGrid() + postMessage mapData diff --git a/client/js/mob.coffee b/client/js/mob.coffee new file mode 100644 index 000000000..d0369fc4e --- /dev/null +++ b/client/js/mob.coffee @@ -0,0 +1,8 @@ +define ["character"], (Character) -> + Mob = Character.extend(init: (id, kind) -> + @_super id, kind + @aggroRange = 1 + @isAggressive = true + ) + Mob + diff --git a/client/js/mobs.coffee b/client/js/mobs.coffee new file mode 100644 index 000000000..e755563ef --- /dev/null +++ b/client/js/mobs.coffee @@ -0,0 +1,117 @@ +define ["mob", "timer"], (Mob, Timer) -> + Mobs = + Rat: Mob.extend(init: (id) -> + @_super id, Types.Entities.RAT + @moveSpeed = 350 + @idleSpeed = 700 + @shadowOffsetY = -2 + @isAggressive = false + ) + Skeleton: Mob.extend(init: (id) -> + @_super id, Types.Entities.SKELETON + @moveSpeed = 350 + @atkSpeed = 100 + @idleSpeed = 800 + @shadowOffsetY = 1 + @setAttackRate 1300 + ) + Skeleton2: Mob.extend(init: (id) -> + @_super id, Types.Entities.SKELETON2 + @moveSpeed = 200 + @atkSpeed = 100 + @idleSpeed = 800 + @walkSpeed = 200 + @shadowOffsetY = 1 + @setAttackRate 1300 + ) + Spectre: Mob.extend(init: (id) -> + @_super id, Types.Entities.SPECTRE + @moveSpeed = 150 + @atkSpeed = 50 + @idleSpeed = 200 + @walkSpeed = 200 + @shadowOffsetY = 1 + @setAttackRate 900 + ) + Deathknight: Mob.extend( + init: (id) -> + @_super id, Types.Entities.DEATHKNIGHT + @atkSpeed = 50 + @moveSpeed = 220 + @walkSpeed = 100 + @idleSpeed = 450 + @setAttackRate 800 + @aggroRange = 3 + + idle: (orientation) -> + unless @hasTarget() + @_super Types.Orientations.DOWN + else + @_super orientation + ) + Goblin: Mob.extend(init: (id) -> + @_super id, Types.Entities.GOBLIN + @moveSpeed = 150 + @atkSpeed = 60 + @idleSpeed = 600 + @setAttackRate 700 + ) + Ogre: Mob.extend(init: (id) -> + @_super id, Types.Entities.OGRE + @moveSpeed = 300 + @atkSpeed = 100 + @idleSpeed = 600 + ) + Crab: Mob.extend(init: (id) -> + @_super id, Types.Entities.CRAB + @moveSpeed = 200 + @atkSpeed = 40 + @idleSpeed = 500 + ) + Snake: Mob.extend(init: (id) -> + @_super id, Types.Entities.SNAKE + @moveSpeed = 200 + @atkSpeed = 40 + @idleSpeed = 250 + @walkSpeed = 100 + @shadowOffsetY = -4 + ) + Eye: Mob.extend(init: (id) -> + @_super id, Types.Entities.EYE + @moveSpeed = 200 + @atkSpeed = 40 + @idleSpeed = 50 + ) + Bat: Mob.extend(init: (id) -> + @_super id, Types.Entities.BAT + @moveSpeed = 120 + @atkSpeed = 90 + @idleSpeed = 90 + @walkSpeed = 85 + @isAggressive = false + ) + Wizard: Mob.extend(init: (id) -> + @_super id, Types.Entities.WIZARD + @moveSpeed = 200 + @atkSpeed = 100 + @idleSpeed = 150 + ) + Boss: Mob.extend( + init: (id) -> + @_super id, Types.Entities.BOSS + @moveSpeed = 300 + @atkSpeed = 50 + @idleSpeed = 400 + @atkRate = 2000 + @attackCooldown = new Timer(@atkRate) + @aggroRange = 3 + + idle: (orientation) -> + unless @hasTarget() + @_super Types.Orientations.DOWN + else + @_super orientation + ) + + Mobs + diff --git a/client/js/npc.coffee b/client/js/npc.coffee new file mode 100644 index 000000000..7c112318d --- /dev/null +++ b/client/js/npc.coffee @@ -0,0 +1,81 @@ +define ["character"], (Character) -> + NpcTalk = + guard: ["Hello there", "We don't need to see your identification", "You are not the player we're looking for", "Move along, move along..."] + king: ["Hi, I'm the King", "I run this place", "Like a boss", "I talk to people", "Like a boss", "I wear a crown", "Like a boss", "I do nothing all day", "Like a boss", "Now leave me alone", "Like a boss"] + villagegirl: ["Hi there, adventurer!", "How do you like this game?", "It's all happening in a single web page! Isn't it crazy?", "It's all made possible thanks to WebSockets.", "I don't know much about it, after all I'm just a program.", "Why don't you read this blog post and learn all about it?"] + villager: ["Howdy stranger. Do you like poetry?", "Roses are red, violets are blue...", "I like hunting rats, and so do you...", "The rats are dead, now what to do?", "To be honest, I have no clue.", "Maybe the forest, could interest you...", "or instead, cook a rat stew."] + agent: ["Do not try to bend the sword", "That's impossible", "Instead, only try to realize the truth...", "There is no sword."] + rick: ["We're no strangers to love", "You know the rules and so do I", "A full commitment's what I'm thinking of", "You wouldn't get this from any other guy", "I just wanna tell you how I'm feeling", "Gotta make you understand", "Never gonna give you up", "Never gonna let you down", "Never gonna run around and desert you", "Never gonna make you cry", "Never gonna say goodbye", "Never gonna tell a lie and hurt you"] + scientist: [ + #default + text: ["Greetings.", "I am the inventor of these two potions.", "The red one will replenish your health points...", "The orange one will turn you into a firefox and make you invincible...", "But it only lasts for a short while.", "So make good use of it!", "Now if you'll excuse me, I need to get back to my experiments..."] + , + condition: (game) -> + game.player.invincible + + text: ["Did you not listen to what I said?!!", "the famous fire-potion only lasts a few seconds", "You shouldn't be wasting them talking to me…"] + , + condition: (game) -> + (game.player.getSpriteName() is "firefox") and not (game.player.invincible) + + text: ["Ha ha ha, *name*", "All that glitters is not gold…", "-sigh-", "Did you really think you could abuse me with your disguise?", "I conceived that f…, that potion.", "Better not use your outfit as a deterrent,", "The goons you'll meet will attack you whatever you look like."] + ] + nyan: ["nyan nyan nyan nyan nyan", "nyan nyan nyan nyan nyan nyan nyan", "nyan nyan nyan nyan nyan nyan", "nyan nyan nyan nyan nyan nyan nyan nyan"] + beachnpc: ["lorem ipsum dolor sit amet", "consectetur adipisicing elit, sed do eiusmod tempor"] + forestnpc: ["lorem ipsum dolor sit amet", "consectetur adipisicing elit, sed do eiusmod tempor"] + desertnpc: ["lorem ipsum dolor sit amet", "consectetur adipisicing elit, sed do eiusmod tempor"] + lavanpc: ["lorem ipsum dolor sit amet", "consectetur adipisicing elit, sed do eiusmod tempor"] + priest: ["Oh, hello, young man.", "Wisdom is everything, so I'll share a few guidelines with you.", "You are free to go wherever you like in this world", "but beware of the many foes that await you.", "You can find many weapons and armors by killing enemies.", "The tougher the enemy, the higher the potential rewards.", "You can also unlock achievements by exploring and hunting.", "Click on the small cup icon to see a list of all the achievements.", "Please stay a while and enjoy the many surprises of BrowserQuest", "Farewell, young friend."] + sorcerer: ["Ah... I had foreseen you would come to see me.", "Well? How do you like my new staff?", "Pretty cool, eh?", "Where did I get it, you ask?", "I understand. It's easy to get envious.", "I actually crafted it myself, using my mad wizard skills.", "But let me tell you one thing...", "There are lots of items in this game.", "Some more powerful than others.", "In order to find them, exploration is key.", "Good luck."] + octocat: ["Welcome to BrowserQuest!", "Want to see the source code?", "Check out the repository on GitHub"] + coder: ["Hi! Do you know that you can also play BrowserQuest on your tablet or mobile?", "That's the beauty of HTML5!", "Give it a try..."] + beachnpc: ["Don't mind me, I'm just here on vacation.", "I have to say...", "These giant crabs are somewhat annoying.", "Could you please get rid of them for me?"] + desertnpc: ["One does not simply walk into these mountains...", "An ancient undead lord is said to dwell here.", "Nobody knows exactly what he looks like...", "...for none has lived to tell the tale.", "It's not too late to turn around and go home, kid."] + othernpc: ["lorem ipsum", "lorem ipsum"] + + Npc = Character.extend( + init: (id, kind) -> + @_super id, kind, 1 + @itemKind = Types.getKindAsString(@kind) + if typeof NpcTalk[@itemKind][0] is "string" + @discourse = -1 + @talkCount = NpcTalk[@itemKind].length + else + @discourse = 0 + @talkCount = NpcTalk[@itemKind][@discourse]["text"].length + @talkIndex = 0 + + selectTalk: (game) -> + change = false + unless @discourse is -1 + found = false + i = 1 + + while not found and i < NpcTalk[@itemKind].length + if NpcTalk[@itemKind][i]["condition"](game) + unless @discourse is i + change = true + @discourse = i + @talkCount = NpcTalk[@itemKind][@discourse]["text"].length + found = true + i++ + unless found + unless @discourse is 0 + change = true + @discourse = 0 + @talkCount = NpcTalk[@itemKind][@discourse]["text"].length + change + + talk: (game) -> + msg = "" + @talkIndex = 0 if @selectTalk(game) or (@talkIndex > @talkCount) + if @talkIndex < @talkCount + if @discourse is -1 + msg = NpcTalk[@itemKind][@talkIndex] + else + msg = NpcTalk[@itemKind][@discourse]["text"][@talkIndex] + @talkIndex += 1 + msg.replace "*name*", game.player.name + ) + Npc + diff --git a/client/js/npcs.coffee b/client/js/npcs.coffee new file mode 100644 index 000000000..db825cdeb --- /dev/null +++ b/client/js/npcs.coffee @@ -0,0 +1,55 @@ +define ["npc"], (Npc) -> + NPCs = + Guard: Npc.extend(init: (id) -> + @_super id, Types.Entities.GUARD, 1 + ) + King: Npc.extend(init: (id) -> + @_super id, Types.Entities.KING, 1 + ) + Agent: Npc.extend(init: (id) -> + @_super id, Types.Entities.AGENT, 1 + ) + Rick: Npc.extend(init: (id) -> + @_super id, Types.Entities.RICK, 1 + ) + VillageGirl: Npc.extend(init: (id) -> + @_super id, Types.Entities.VILLAGEGIRL, 1 + ) + Villager: Npc.extend(init: (id) -> + @_super id, Types.Entities.VILLAGER, 1 + ) + Coder: Npc.extend(init: (id) -> + @_super id, Types.Entities.CODER, 1 + ) + Scientist: Npc.extend(init: (id) -> + @_super id, Types.Entities.SCIENTIST, 1 + ) + Nyan: Npc.extend(init: (id) -> + @_super id, Types.Entities.NYAN, 1 + @idleSpeed = 50 + ) + Sorcerer: Npc.extend(init: (id) -> + @_super id, Types.Entities.SORCERER, 1 + @idleSpeed = 150 + ) + Priest: Npc.extend(init: (id) -> + @_super id, Types.Entities.PRIEST, 1 + ) + BeachNpc: Npc.extend(init: (id) -> + @_super id, Types.Entities.BEACHNPC, 1 + ) + ForestNpc: Npc.extend(init: (id) -> + @_super id, Types.Entities.FORESTNPC, 1 + ) + DesertNpc: Npc.extend(init: (id) -> + @_super id, Types.Entities.DESERTNPC, 1 + ) + LavaNpc: Npc.extend(init: (id) -> + @_super id, Types.Entities.LAVANPC, 1 + ) + Octocat: Npc.extend(init: (id) -> + @_super id, Types.Entities.OCTOCAT, 1 + ) + + NPCs + diff --git a/client/js/player.coffee b/client/js/player.coffee new file mode 100644 index 000000000..c153ea7cc --- /dev/null +++ b/client/js/player.coffee @@ -0,0 +1,223 @@ +define ["character", "exceptions"], (Character, Exceptions) -> + Player = Character.extend( + MAX_LEVEL: 10 + init: (id, name, pw, kind, guild) -> + @_super id, kind + @name = name + @pw = pw + @setGuild guild if typeof guild isnt "undefined" + + # Renderer + @nameOffsetY = -10 + + # sprites + @spriteName = "clotharmor" + @armorName = "clotharmor" + @weaponName = "sword1" + + # modes + @isLootMoving = false + @isSwitchingWeapon = true + + # PVP Flag + @pvpFlag = true + + getGuild: -> + @guild + + setGuild: (guild) -> + @guild = guild + $("#guild-population").addClass "visible" + $("#guild-name").html guild.name + + unsetGuild: -> + delete @guild + + $("#guild-population").removeClass "visible" + + hasGuild: -> + typeof @guild isnt "undefined" + + addInvite: (inviteGuildId) -> + @invite = + time: new Date().valueOf() + guildId: inviteGuildId + + deleteInvite: -> + delete @invite + + checkInvite: -> + if @invite and ((new Date().valueOf() - @invite.time) < 595000) + @invite.guildId + else + if @invite + @deleteInvite() + -1 + else + false + + loot: (item) -> + if item + rank = undefined + currentRank = undefined + msg = undefined + currentArmorName = undefined + if @currentArmorSprite + currentArmorName = @currentArmorSprite.name + else + currentArmorName = @spriteName + if item.type is "armor" + rank = Types.getArmorRank(item.kind) + currentRank = Types.getArmorRank(Types.getKindFromString(currentArmorName)) + msg = "You are wearing a better armor" + else if item.type is "weapon" + rank = Types.getWeaponRank(item.kind) + currentRank = Types.getWeaponRank(Types.getKindFromString(@weaponName)) + msg = "You are wielding a better weapon" + if rank and currentRank + if rank is currentRank + throw new Exceptions.LootException("You already have this " + item.type) + else throw new Exceptions.LootException(msg) if rank <= currentRank + log.info "Player " + @id + " has looted " + item.id + @stopInvincibility() if Types.isArmor(item.kind) and @invincible + item.onLoot this + + + ### + Returns true if the character is currently walking towards an item in order to loot it. + ### + isMovingToLoot: -> + @isLootMoving + + getSpriteName: -> + @spriteName + + setSpriteName: (name) -> + @spriteName = name + + getArmorName: -> + sprite = @getArmorSprite() + sprite.id + + getArmorSprite: -> + if @invincible + @currentArmorSprite + else + @sprite + + setArmorName: (name) -> + @armorName = name + + getWeaponName: -> + @weaponName + + setWeaponName: (name) -> + @weaponName = name + + hasWeapon: -> + @weaponName isnt null + + equipFromInventory: (type, inventoryNumber, sprites) -> + itemString = Types.getKindAsString(@inventory[inventoryNumber]) + if itemString + itemSprite = sprites[itemString] + if itemSprite + if type is "armor" + @inventory[inventoryNumber] = Types.getKindFromString(@getArmorName()) + @setSpriteName itemString + @setSprite itemSprite + @setArmorName itemString + else if type is "avatar" + @inventory[inventoryNumber] = null + @setSpriteName itemString + @setSprite itemSprite + + switchArmor: (armorName, sprite) -> + @setSpriteName armorName + @setSprite sprite + @setArmorName armorName + @switch_callback() if @switch_callback + + switchWeapon: (newWeaponName) -> + count = 14 + value = false + self = this + toggle = -> + value = not value + value + + if newWeaponName isnt @getWeaponName() + clearInterval blanking if @isSwitchingWeapon + @switchingWeapon = true + blanking = setInterval(-> + if toggle() + self.setWeaponName newWeaponName + else + self.setWeaponName null + count -= 1 + if count is 1 + clearInterval blanking + self.switchingWeapon = false + self.switch_callback() if self.switch_callback + , 90) + + switchArmor: (newArmorSprite) -> + count = 14 + value = false + self = this + toggle = -> + value = not value + value + + if newArmorSprite and newArmorSprite.id isnt @getSpriteName() + clearInterval blanking if @isSwitchingArmor + @isSwitchingArmor = true + self.setSprite newArmorSprite + self.setSpriteName newArmorSprite.id + blanking = setInterval(-> + self.setVisible toggle() + count -= 1 + if count is 1 + clearInterval blanking + self.isSwitchingArmor = false + self.switch_callback() if self.switch_callback + , 90) + + onArmorLoot: (callback) -> + @armorloot_callback = callback + + onSwitchItem: (callback) -> + @switch_callback = callback + + onInvincible: (callback) -> + @invincible_callback = callback + + startInvincibility: -> + self = this + unless @invincible + @currentArmorSprite = @getSprite() + @invincible = true + @invincible_callback() + else + + # If the player already has invincibility, just reset its duration. + clearTimeout @invincibleTimeout if @invincibleTimeout + @invincibleTimeout = setTimeout(-> + self.stopInvincibility() + self.idle() + , 15000) + + stopInvincibility: -> + @invincible_callback() + @invincible = false + if @currentArmorSprite + @setSprite @currentArmorSprite + @setSpriteName @currentArmorSprite.id + @currentArmorSprite = null + clearTimeout @invincibleTimeout if @invincibleTimeout + + flagPVP: (pvpFlag) -> + @pvpFlag = pvpFlag + ) + Player + diff --git a/client/js/renderer.coffee b/client/js/renderer.coffee new file mode 100644 index 000000000..e08da1a5b --- /dev/null +++ b/client/js/renderer.coffee @@ -0,0 +1,601 @@ +{ + "type": "block", + "src": "{", + "value": "{", + "lineno": 491, + "children": [], + "varDecls": [], + "labels": { + "table": {}, + "size": 0 + }, + "functions": [], + "nonfunctions": [], + "transformed": true +} +define ["camera", "item", "character", "player", "timer"], (Camera, Item, Character, Player, Timer) -> + Renderer = Class.extend( + init: (game, canvas, background, foreground) -> + @game = game + @context = (if (canvas and canvas.getContext) then canvas.getContext("2d") else null) + @background = (if (background and background.getContext) then background.getContext("2d") else null) + @foreground = (if (foreground and foreground.getContext) then foreground.getContext("2d") else null) + @canvas = canvas + @backcanvas = background + @forecanvas = foreground + @initFPS() + @tilesize = 16 + @upscaledRendering = @context.mozImageSmoothingEnabled isnt `undefined` + @supportsSilhouettes = @upscaledRendering + @rescale @getScaleFactor() + @lastTime = new Date() + @frameCount = 0 + @maxFPS = @FPS + @realFPS = 0 + + #Turn on or off Debuginfo (FPS Counter) + @isDebugInfoVisible = false + @animatedTileCount = 0 + @highTileCount = 0 + @tablet = Detect.isTablet(window.innerWidth) + @fixFlickeringTimer = new Timer(100) + + getWidth: -> + @canvas.width + + getHeight: -> + @canvas.height + + setTileset: (tileset) -> + @tileset = tileset + + getScaleFactor: -> + w = window.innerWidth + h = window.innerHeight + scale = undefined + @mobile = false + if w <= 1000 + scale = 2 + @mobile = true + else if w <= 1500 or h <= 870 + scale = 2 + else + scale = 3 + scale + + rescale: (factor) -> + @scale = @getScaleFactor() + @createCamera() + @context.mozImageSmoothingEnabled = false + @background.mozImageSmoothingEnabled = false + @foreground.mozImageSmoothingEnabled = false + @initFont() + @initFPS() + @setTileset @game.map.tilesets[@scale - 1] if not @upscaledRendering and @game.map and @game.map.tilesets + @game.setSpriteScale @scale if @game.renderer + + createCamera: -> + @camera = new Camera(this) + @camera.rescale() + @canvas.width = @camera.gridW * @tilesize * @scale + @canvas.height = @camera.gridH * @tilesize * @scale + log.debug "#entities set to " + @canvas.width + " x " + @canvas.height + @backcanvas.width = @canvas.width + @backcanvas.height = @canvas.height + log.debug "#background set to " + @backcanvas.width + " x " + @backcanvas.height + @forecanvas.width = @canvas.width + @forecanvas.height = @canvas.height + log.debug "#foreground set to " + @forecanvas.width + " x " + @forecanvas.height + + initFPS: -> + @FPS = (if @mobile then 50 else 50) + + initFont: -> + fontsize = undefined + switch @scale + when 1 + fontsize = 10 + when 2 + fontsize = (if Detect.isWindows() then 10 else 13) + when 3 + fontsize = 20 + @setFontSize fontsize + + setFontSize: (size) -> + font = size + "px GraphicPixel" + @context.font = font + @background.font = font + + drawText: (text, x, y, centered, color, strokeColor) -> + ctx = @context + strokeSize = undefined + switch @scale + when 1 + strokeSize = 3 + when 2 + strokeSize = 3 + when 3 + strokeSize = 5 + if text and x and y + ctx.save() + ctx.textAlign = "center" if centered + ctx.strokeStyle = strokeColor or "#373737" + ctx.lineWidth = strokeSize + ctx.strokeText text, x, y + ctx.fillStyle = color or "white" + ctx.fillText text, x, y + ctx.restore() + + drawCellRect: (x, y, color) -> + @context.save() + @context.lineWidth = 2 * @scale + @context.strokeStyle = color + @context.translate x + 2, y + 2 + @context.strokeRect 0, 0, (@tilesize * @scale) - 4, (@tilesize * @scale) - 4 + @context.restore() + + drawRectStroke: (x, y, width, height, color) -> + @context.fillStyle = color + @context.fillRect x, y, (@tilesize * @scale) * width, (@tilesize * @scale) * height + @context.fill() + @context.lineWidth = 5 + @context.strokeStyle = "black" + @context.strokeRect x, y, (@tilesize * @scale) * width, (@tilesize * @scale) * height + + drawRect: (x, y, width, height, color) -> + @context.fillStyle = color + @context.fillRect x, y, (@tilesize * @scale) * width, (@tilesize * @scale) * height + + drawCellHighlight: (x, y, color) -> + s = @scale + ts = @tilesize + tx = x * ts * s + ty = y * ts * s + @drawCellRect tx, ty, color + + drawTargetCell: -> + mouse = @game.getMouseGridPosition() + @drawCellHighlight mouse.x, mouse.y, @game.targetColor if @game.targetCellVisible and not (mouse.x is @game.selectedX and mouse.y is @game.selectedY) + + drawAttackTargetCell: -> + mouse = @game.getMouseGridPosition() + entity = @game.getEntityAt(mouse.x, mouse.y) + s = @scale + @drawCellRect entity.x * s, entity.y * s, "rgba(255, 0, 0, 0.5)" if entity + + drawOccupiedCells: -> + positions = @game.entityGrid + if positions + i = 0 + + while i < positions.length + j = 0 + + while j < positions[i].length + @drawCellHighlight i, j, "rgba(50, 50, 255, 0.5)" unless _.isNull(positions[i][j]) + j += 1 + i += 1 + + drawPathingCells: -> + grid = @game.pathingGrid + if grid and @game.debugPathing + y = 0 + + while y < grid.length + x = 0 + + while x < grid[y].length + @drawCellHighlight x, y, "rgba(50, 50, 255, 0.5)" if grid[y][x] is 1 and @game.camera.isVisiblePosition(x, y) + x += 1 + y += 1 + + drawSelectedCell: -> + sprite = @game.cursors["target"] + anim = @game.targetAnimation + os = (if @upscaledRendering then 1 else @scale) + ds = (if @upscaledRendering then @scale else 1) + if @game.selectedCellVisible + if @mobile or @tablet + if @game.drawTarget + x = @game.selectedX + y = @game.selectedY + @drawCellHighlight @game.selectedX, @game.selectedY, "rgb(51, 255, 0)" + @lastTargetPos = + x: x + y: y + + @game.drawTarget = false + else + if sprite and anim + frame = anim.currentFrame + s = @scale + x = frame.x * os + y = frame.y * os + w = sprite.width * os + h = sprite.height * os + ts = 16 + dx = @game.selectedX * ts * s + dy = @game.selectedY * ts * s + dw = w * ds + dh = h * ds + @context.save() + @context.translate dx, dy + @context.drawImage sprite.image, x, y, w, h, 0, 0, dw, dh + @context.restore() + + clearScaledRect: (ctx, x, y, w, h) -> + s = @scale + ctx.clearRect x * s, y * s, w * s, h * s + + drawCursor: -> + mx = @game.mouse.x + my = @game.mouse.y + s = @scale + os = (if @upscaledRendering then 1 else @scale) + @context.save() + @context.drawImage @game.currentCursor.image, 0, 0, 14 * os, 14 * os, mx, my, 14 * s, 14 * s if @game.currentCursor and @game.currentCursor.isLoaded + @context.restore() + + drawScaledImage: (ctx, image, x, y, w, h, dx, dy) -> + s = (if @upscaledRendering then 1 else @scale) + _.each arguments_, (arg) -> + if _.isUndefined(arg) or _.isNaN(arg) or _.isNull(arg) or arg < 0 + log.error "x:" + x + " y:" + y + " w:" + w + " h:" + h + " dx:" + dx + " dy:" + dy, true + throw Error("A problem occured when trying to draw on the canvas") + + ctx.drawImage image, x * s, y * s, w * s, h * s, dx * @scale, dy * @scale, w * @scale, h * @scale + + drawTile: (ctx, tileid, tileset, setW, gridW, cellid) -> + s = (if @upscaledRendering then 1 else @scale) + # -1 when tile is empty in Tiled. Don't attempt to draw it. + @drawScaledImage ctx, tileset, getX(tileid + 1, (setW / s)) * @tilesize, Math.floor(tileid / (setW / s)) * @tilesize, @tilesize, @tilesize, getX(cellid + 1, gridW) * @tilesize, Math.floor(cellid / gridW) * @tilesize if tileid isnt -1 + + clearTile: (ctx, gridW, cellid) -> + s = @scale + ts = @tilesize + x = getX(cellid + 1, gridW) * ts * s + y = Math.floor(cellid / gridW) * ts * s + w = ts * s + h = w + ctx.clearRect x, y, h, w + + drawEntity: (entity) -> + sprite = entity.sprite + shadow = @game.shadows["small"] + anim = entity.currentAnimation + os = (if @upscaledRendering then 1 else @scale) + ds = (if @upscaledRendering then @scale else 1) + if anim and sprite + frame = anim.currentFrame + s = @scale + x = frame.x * os + y = frame.y * os + w = sprite.width * os + h = sprite.height * os + ox = sprite.offsetX * s + oy = sprite.offsetY * s + dx = entity.x * s + dy = entity.y * s + dw = w * ds + dh = h * ds + if entity.isFading + @context.save() + @context.globalAlpha = entity.fadingAlpha + @drawEntityName entity if not @mobile and not @tablet + @context.save() + if entity.flipSpriteX + @context.translate dx + @tilesize * s, dy + @context.scale -1, 1 + else if entity.flipSpriteY + @context.translate dx, dy + dh + @context.scale 1, -1 + else + @context.translate dx, dy + if entity.isVisible() + @context.drawImage shadow.image, 0, 0, shadow.width * os, shadow.height * os, 0, entity.shadowOffsetY * ds, shadow.width * os * ds, shadow.height * os * ds if entity.hasShadow() + @context.drawImage sprite.image, x, y, w, h, ox, oy, dw, dh + if entity instanceof Item and entity.kind isnt Types.Entities.CAKE + sparks = @game.sprites["sparks"] + anim = @game.sparksAnimation + frame = anim.currentFrame + sx = sparks.width * frame.index * os + sy = sparks.height * anim.row * os + sw = sparks.width * os + sh = sparks.width * os + @context.drawImage sparks.image, sx, sy, sw, sh, sparks.offsetX * s, sparks.offsetY * s, sw * ds, sh * ds + if entity instanceof Character and not entity.isDead and entity.hasWeapon() + weapon = @game.sprites[entity.getWeaponName()] + if weapon + weaponAnimData = weapon.animationData[anim.name] + index = (if frame.index < weaponAnimData.length then frame.index else frame.index % weaponAnimData.length) + wx = weapon.width * index * os + wy = weapon.height * anim.row * os + ww = weapon.width * os + wh = weapon.height * os + @context.drawImage weapon.image, wx, wy, ww, wh, weapon.offsetX * s, weapon.offsetY * s, ww * ds, wh * ds + @context.restore() + @context.restore() if entity.isFading + + drawEntities: (dirtyOnly) -> + self = this + @game.forEachVisibleEntityByDepth (entity) -> + if entity.isLoaded + if dirtyOnly + if entity.isDirty + self.drawEntity entity + entity.isDirty = false + entity.oldDirtyRect = entity.dirtyRect + entity.dirtyRect = null + else + self.drawEntity entity + + + drawDirtyEntities: -> + @drawEntities true + + clearDirtyRect: (r) -> + @context.clearRect r.x, r.y, r.w, r.h + + clearDirtyRects: -> + self = this + count = 0 + @game.forEachVisibleEntityByDepth (entity) -> + if entity.isDirty and entity.oldDirtyRect + self.clearDirtyRect entity.oldDirtyRect + count += 1 + + @game.forEachAnimatedTile (tile) -> + if tile.isDirty + self.clearDirtyRect tile.dirtyRect + count += 1 + + if @game.clearTarget and @lastTargetPos + last = @lastTargetPos + rect = @getTargetBoundingRect(last.x, last.y) + @clearDirtyRect rect + @game.clearTarget = false + count += 1 + count > 0 + + + #log.debug("count:"+count); + getEntityBoundingRect: (entity) -> + rect = {} + s = @scale + spr = undefined + if entity instanceof Player and entity.hasWeapon() + weapon = @game.sprites[entity.getWeaponName()] + spr = weapon + else + spr = entity.sprite + if spr + rect.x = (entity.x + spr.offsetX - @camera.x) * s + rect.y = (entity.y + spr.offsetY - @camera.y) * s + rect.w = spr.width * s + rect.h = spr.height * s + rect.left = rect.x + rect.right = rect.x + rect.w + rect.top = rect.y + rect.bottom = rect.y + rect.h + rect + + getTileBoundingRect: (tile) -> + rect = {} + gridW = @game.map.width + s = @scale + ts = @tilesize + cellid = tile.index + rect.x = ((getX(cellid + 1, gridW) * ts) - @camera.x) * s + rect.y = ((Math.floor(cellid / gridW) * ts) - @camera.y) * s + rect.w = ts * s + rect.h = ts * s + rect.left = rect.x + rect.right = rect.x + rect.w + rect.top = rect.y + rect.bottom = rect.y + rect.h + rect + + getTargetBoundingRect: (x, y) -> + rect = {} + s = @scale + ts = @tilesize + tx = x or @game.selectedX + ty = y or @game.selectedY + rect.x = ((tx * ts) - @camera.x) * s + rect.y = ((ty * ts) - @camera.y) * s + rect.w = ts * s + rect.h = ts * s + rect.left = rect.x + rect.right = rect.x + rect.w + rect.top = rect.y + rect.bottom = rect.y + rect.h + rect + + isIntersecting: (rect1, rect2) -> + not ((rect2.left > rect1.right) or (rect2.right < rect1.left) or (rect2.top > rect1.bottom) or (rect2.bottom < rect1.top)) + + drawEntityName: (entity) -> + @context.save() + if entity.name and entity instanceof Player + color = (if (entity.id is @game.playerId) then "#fcda5c" else "white") + name = (if (entity.level) then "lv." + entity.level + " " + entity.name else entity.name) + @drawText entity.name, (entity.x + 8) * @scale, (entity.y + entity.nameOffsetY) * @scale, true, color + @context.restore() + + drawTerrain: -> + self = this + m = @game.map + tilesetwidth = @tileset.width / m.tilesize + @game.forEachVisibleTile ((id, index) -> + # Don't draw unnecessary tiles + self.drawTile self.background, id, self.tileset, tilesetwidth, m.width, index if not m.isHighTile(id) and not m.isAnimatedTile(id) + ), 1 + + drawAnimatedTiles: (dirtyOnly) -> + self = this + m = @game.map + tilesetwidth = @tileset.width / m.tilesize + @animatedTileCount = 0 + @game.forEachAnimatedTile (tile) -> + if dirtyOnly + if tile.isDirty + self.drawTile self.context, tile.id, self.tileset, tilesetwidth, m.width, tile.index + tile.isDirty = false + else + self.drawTile self.context, tile.id, self.tileset, tilesetwidth, m.width, tile.index + self.animatedTileCount += 1 + + + drawDirtyAnimatedTiles: -> + @drawAnimatedTiles true + + drawHighTiles: (ctx) -> + self = this + m = @game.map + tilesetwidth = @tileset.width / m.tilesize + @highTileCount = 0 + @game.forEachVisibleTile ((id, index) -> + if m.isHighTile(id) + self.drawTile ctx, id, self.tileset, tilesetwidth, m.width, index + self.highTileCount += 1 + ), 1 + + drawBackground: (ctx, color) -> + ctx.fillStyle = color + ctx.fillRect 0, 0, @canvas.width, @canvas.height + + drawFPS: -> + nowTime = new Date() + diffTime = nowTime.getTime() - @lastTime.getTime() + if diffTime >= 1000 + @realFPS = @frameCount + @frameCount = 0 + @lastTime = nowTime + @frameCount++ + + #this.drawText("FPS: " + this.realFPS + " / " + this.maxFPS, 30, 30, false); + @drawText "FPS: " + @realFPS, 30, 30, false + + drawDebugInfo: -> + @drawFPS() if @isDebugInfoVisible + + + #this.drawText("A: " + this.animatedTileCount, 100, 30, false); + #this.drawText("H: " + this.highTileCount, 140, 30, false); + drawCombatInfo: -> + self = this + switch @scale + when 2 + @setFontSize 20 + when 3 + @setFontSize 30 + @game.infoManager.forEachInfo (info) -> + self.context.save() + self.context.globalAlpha = info.opacity + self.drawText info.value, (info.x + 8) * self.scale, Math.floor(info.y * self.scale), true, info.fillColor, info.strokeColor + self.context.restore() + + @initFont() + + setCameraView: (ctx) -> + ctx.translate -@camera.x * @scale, -@camera.y * @scale + + clearScreen: (ctx) -> + ctx.clearRect 0, 0, @canvas.width, @canvas.height + + getPlayerImage: -> + canvas = document.createElement("canvas") + ctx = canvas.getContext("2d") + os = (if @upscaledRendering then 1 else @scale) + player = @game.player + sprite = player.getArmorSprite() + spriteAnim = sprite.animationData["idle_down"] + + # character + row = spriteAnim.row + w = sprite.width * os + h = sprite.height * os + y = row * h + + # weapon + weapon = @game.sprites[@game.player.getWeaponName()] + ww = weapon.width * os + wh = weapon.height * os + wy = wh * row + offsetX = (weapon.offsetX - sprite.offsetX) * os + offsetY = (weapon.offsetY - sprite.offsetY) * os + + # shadow + shadow = @game.shadows["small"] + sw = shadow.width * os + sh = shadow.height * os + ox = -sprite.offsetX * os + oy = -sprite.offsetY * os + canvas.width = w + canvas.height = h + ctx.clearRect 0, 0, w, h + ctx.drawImage shadow.image, 0, 0, sw, sh, ox, oy, sw, sh + ctx.drawImage sprite.image, 0, y, w, h, 0, 0, w, h + ctx.drawImage weapon.image, 0, wy, ww, wh, offsetX, offsetY, ww, wh + canvas.toDataURL "image/png" + + renderStaticCanvases: -> + @background.save() + @setCameraView @background + @drawTerrain() + @background.restore() + if @mobile or @tablet + @clearScreen @foreground + @foreground.save() + @setCameraView @foreground + @drawHighTiles @foreground + @foreground.restore() + + renderFrame: -> + if @mobile or @tablet + @renderFrameMobile() + else + @renderFrameDesktop() + + renderFrameDesktop: -> + @clearScreen @context + @context.save() + @setCameraView @context + @drawAnimatedTiles() + if @game.started and @game.cursorVisible + @drawSelectedCell() + @drawTargetCell() + + #this.drawOccupiedCells(); + @drawPathingCells() + @drawEntities() + @drawCombatInfo() + @drawHighTiles @context + @context.restore() + + # Overlay UI elements + @drawCursor() if @game.cursorVisible + @drawDebugInfo() + + renderFrameMobile: -> + @clearDirtyRects() + @preventFlickeringBug() + @context.save() + @setCameraView @context + @drawDirtyAnimatedTiles() + @drawSelectedCell() + @drawDirtyEntities() + @context.restore() + + preventFlickeringBug: -> + if @fixFlickeringTimer.isOver(@game.currentTime) + @background.fillRect 0, 0, 0, 0 + @context.fillRect 0, 0, 0, 0 + @foreground.fillRect 0, 0, 0, 0 + ) + getX = (id, w) -> + return 0 if id is 0 + (if (id % w is 0) then w - 1 else (id % w) - 1) + + Renderer + diff --git a/client/js/sprite.coffee b/client/js/sprite.coffee new file mode 100644 index 000000000..d51a7a807 --- /dev/null +++ b/client/js/sprite.coffee @@ -0,0 +1,131 @@ +define ["jquery", "animation", "sprites"], ($, Animation, sprites) -> + Sprite = Class.extend( + init: (name, scale) -> + @name = name + @scale = scale + @isLoaded = false + @offsetX = 0 + @offsetY = 0 + @loadJSON sprites[name] + + loadJSON: (data) -> + @id = data.id + @filepath = "img/" + @scale + "/" + @id + ".png" + @animationData = data.animations + @width = data.width + @height = data.height + @offsetX = (if (data.offset_x isnt `undefined`) then data.offset_x else -16) + @offsetY = (if (data.offset_y isnt `undefined`) then data.offset_y else -16) + @load() + + load: -> + self = this + @image = new Image() + @image.crossOrigin = "Anonymous" + @image.src = @filepath + @image.onload = -> + self.isLoaded = true + self.onload_func() if self.onload_func + + createAnimations: -> + animations = {} + for name of @animationData + a = @animationData[name] + animations[name] = new Animation(name, a.length, a.row, @width, @height) + animations + + createHurtSprite: -> + canvas = document.createElement("canvas") + ctx = canvas.getContext("2d") + width = @image.width + height = @image.height + spriteData = undefined + data = undefined + canvas.width = width + canvas.height = height + ctx.drawImage @image, 0, 0, width, height + try + spriteData = ctx.getImageData(0, 0, width, height) + data = spriteData.data + i = 0 + + while i < data.length + data[i] = 255 + data[i + 1] = data[i + 2] = 75 + i += 4 + spriteData.data = data + ctx.putImageData spriteData, 0, 0 + @whiteSprite = + image: canvas + isLoaded: true + offsetX: @offsetX + offsetY: @offsetY + width: @width + height: @height + catch e + log.error "Error getting image data for sprite : " + @name + + getHurtSprite: -> + @whiteSprite + + createSilhouette: -> + canvas = document.createElement("canvas") + ctx = canvas.getContext("2d") + width = @image.width + height = @image.height + spriteData = undefined + finalData = undefined + data = undefined + canvas.width = width + canvas.height = height + try + ctx.drawImage @image, 0, 0, width, height + data = ctx.getImageData(0, 0, width, height).data + finalData = ctx.getImageData(0, 0, width, height) + fdata = finalData.data + getIndex = (x, y) -> + ((width * (y - 1)) + x - 1) * 4 + + getPosition = (i) -> + x = undefined + y = undefined + i = (i / 4) + 1 + x = i % width + y = ((i - x) / width) + 1 + x: x + y: y + + hasAdjacentPixel = (i) -> + pos = getPosition(i) + return true if pos.x < width and not isBlankPixel(getIndex(pos.x + 1, pos.y)) + return true if pos.x > 1 and not isBlankPixel(getIndex(pos.x - 1, pos.y)) + return true if pos.y < height and not isBlankPixel(getIndex(pos.x, pos.y + 1)) + return true if pos.y > 1 and not isBlankPixel(getIndex(pos.x, pos.y - 1)) + false + + isBlankPixel = (i) -> + return true if i < 0 or i >= data.length + data[i] is 0 and data[i + 1] is 0 and data[i + 2] is 0 and data[i + 3] is 0 + + i = 0 + + while i < data.length + if isBlankPixel(i) and hasAdjacentPixel(i) + fdata[i] = fdata[i + 1] = 255 + fdata[i + 2] = 150 + fdata[i + 3] = 150 + i += 4 + finalData.data = fdata + ctx.putImageData finalData, 0, 0 + @silhouetteSprite = + image: canvas + isLoaded: true + offsetX: @offsetX + offsetY: @offsetY + width: @width + height: @height + catch e + @silhouetteSprite = this + ) + Sprite + diff --git a/client/js/sprites.coffee b/client/js/sprites.coffee new file mode 100644 index 000000000..c20ba628f --- /dev/null +++ b/client/js/sprites.coffee @@ -0,0 +1,8 @@ +define ["text!../sprites/agent.json", "text!../sprites/arrow.json", "text!../sprites/axe.json", "text!../sprites/bat.json", "text!../sprites/beachnpc.json", "text!../sprites/bluesword.json", "text!../sprites/boss.json", "text!../sprites/chest.json", "text!../sprites/clotharmor.json", "text!../sprites/coder.json", "text!../sprites/crab.json", "text!../sprites/death.json", "text!../sprites/deathknight.json", "text!../sprites/desertnpc.json", "text!../sprites/eye.json", "text!../sprites/firefox.json", "text!../sprites/forestnpc.json", "text!../sprites/goblin.json", "text!../sprites/goldenarmor.json", "text!../sprites/goldensword.json", "text!../sprites/guard.json", "text!../sprites/hand.json", "text!../sprites/impact.json", "text!../sprites/item-axe.json", "text!../sprites/item-bluesword.json", "text!../sprites/item-burger.json", "text!../sprites/item-cake.json", "text!../sprites/item-firepotion.json", "text!../sprites/item-flask.json", "text!../sprites/item-goldenarmor.json", "text!../sprites/item-goldensword.json", "text!../sprites/item-leatherarmor.json", "text!../sprites/item-mailarmor.json", "text!../sprites/item-morningstar.json", "text!../sprites/item-platearmor.json", "text!../sprites/item-redarmor.json", "text!../sprites/item-redsword.json", "text!../sprites/item-sword1.json", "text!../sprites/item-sword2.json", "text!../sprites/king.json", "text!../sprites/lavanpc.json", "text!../sprites/leatherarmor.json", "text!../sprites/loot.json", "text!../sprites/mailarmor.json", "text!../sprites/morningstar.json", "text!../sprites/nyan.json", "text!../sprites/octocat.json", "text!../sprites/ogre.json", "text!../sprites/platearmor.json", "text!../sprites/priest.json", "text!../sprites/rat.json", "text!../sprites/redarmor.json", "text!../sprites/redsword.json", "text!../sprites/rick.json", "text!../sprites/scientist.json", "text!../sprites/shadow16.json", "text!../sprites/skeleton.json", "text!../sprites/skeleton2.json", "text!../sprites/snake.json", "text!../sprites/sorcerer.json", "text!../sprites/sparks.json", "text!../sprites/spectre.json", "text!../sprites/sword.json", "text!../sprites/sword1.json", "text!../sprites/sword2.json", "text!../sprites/talk.json", "text!../sprites/target.json", "text!../sprites/villagegirl.json", "text!../sprites/villager.json", "text!../sprites/wizard.json"], -> + sprites = {} + _.each arguments_, (spriteJson) -> + sprite = JSON.parse(spriteJson) + sprites[sprite.id] = sprite + + sprites + diff --git a/client/js/storage.coffee b/client/js/storage.coffee new file mode 100644 index 000000000..3eca65758 --- /dev/null +++ b/client/js/storage.coffee @@ -0,0 +1,148 @@ +define -> + Storage = Class.extend( + init: -> + if @hasLocalStorage() and localStorage.data + @data = JSON.parse(localStorage.data) + else + @resetData() + + resetData: -> + @data = + hasAlreadyPlayed: false + player: + name: "" + weapon: "" + armor: "" + guild: "" + image: "" + + achievements: + unlocked: [] + ratCount: 0 + skeletonCount: 0 + totalKills: 0 + totalDmg: 0 + totalRevives: 0 + + hasLocalStorage: -> + Modernizr.localstorage + + save: -> + localStorage.data = JSON.stringify(@data) if @hasLocalStorage() + + clear: -> + if @hasLocalStorage() + localStorage.data = "" + @resetData() + + + # Player + hasAlreadyPlayed: -> + @data.hasAlreadyPlayed + + initPlayer: (name) -> + @data.hasAlreadyPlayed = true + @setPlayerName name + + setPlayerName: (name) -> + @data.player.name = name + @save() + + setPlayerImage: (img) -> + @data.player.image = img + @save() + + setPlayerArmor: (armor) -> + @data.player.armor = armor + @save() + + setPlayerWeapon: (weapon) -> + @data.player.weapon = weapon + @save() + + setPlayerGuild: (guild) -> + if typeof guild isnt "undefined" + @data.player.guild = + id: guild.id + name: guild.name + members: JSON.stringify(guild.members) + + @save() + else + delete @data.player.guild + + @save() + + savePlayer: (img, armor, weapon, guild) -> + @setPlayerImage img + @setPlayerArmor armor + @setPlayerWeapon weapon + @setPlayerGuild guild + + + # Achievements + hasUnlockedAchievement: (id) -> + _.include @data.achievements.unlocked, id + + unlockAchievement: (id) -> + unless @hasUnlockedAchievement(id) + @data.achievements.unlocked.push id + @save() + return true + false + + getAchievementCount: -> + _.size @data.achievements.unlocked + + + # Angry rats + getRatCount: -> + @data.achievements.ratCount + + incrementRatCount: -> + if @data.achievements.ratCount < 10 + @data.achievements.ratCount++ + @save() + + + # Skull Collector + getSkeletonCount: -> + @data.achievements.skeletonCount + + incrementSkeletonCount: -> + if @data.achievements.skeletonCount < 10 + @data.achievements.skeletonCount++ + @save() + + + # Meatshield + getTotalDamageTaken: -> + @data.achievements.totalDmg + + addDamage: (damage) -> + if @data.achievements.totalDmg < 5000 + @data.achievements.totalDmg += damage + @save() + + + # Hunter + getTotalKills: -> + @data.achievements.totalKills + + incrementTotalKills: -> + if @data.achievements.totalKills < 50 + @data.achievements.totalKills++ + @save() + + + # Still Alive + getTotalRevives: -> + @data.achievements.totalRevives + + incrementRevives: -> + if @data.achievements.totalRevives < 5 + @data.achievements.totalRevives++ + @save() + ) + Storage + diff --git a/client/js/text.coffee b/client/js/text.coffee new file mode 100644 index 000000000..819882b2c --- /dev/null +++ b/client/js/text.coffee @@ -0,0 +1,136 @@ +# +# RequireJS text 0.26.0 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved. +# Available via the MIT or new BSD license. +# see: http://github.com/jrburke/requirejs for details +# +(-> + j = ["Msxml2.XMLHTTP", "Microsoft.XMLHTTP", "Msxml2.XMLHTTP.4.0"] + l = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/i + m = /]*>\s*([\s\S]+)\s*<\/body>/i + n = typeof location isnt "undefined" and location.href + i = [] + define -> + e = undefined + h = undefined + k = undefined + (if typeof window isnt "undefined" and window.navigator and window.document then h = (a, b) -> + c = e.createXhr() + c.open "GET", a, not 0 + c.onreadystatechange = -> + c.readyState is 4 and b(c.responseText) + + c.send null + else (if typeof process isnt "undefined" and process.versions and process.versions.node then (k = require.nodeRequire("fs") + h = (a, b) -> + b k.readFileSync(a, "utf8") + + ) else typeof Packages isnt "undefined" and (h = (a, b) -> + c = new java.io.File(a) + g = java.lang.System.getProperty("line.separator") + c = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(c), "utf-8")) + d = undefined + f = undefined + e = "" + try + d = new java.lang.StringBuffer + (f = c.readLine()) and f.length() and f.charAt(0) is 65279 and (f = f.substring(1)) + d.append(f) + while (f = c.readLine()) isnt null + d.append(g) + d.append(f) + e = String(d.toString()) + finally + c.close() + b e + ))) + e = + version: "0.26.0" + strip: (a) -> + if a + a = a.replace(l, "") + b = a.match(m) + b and (a = b[1]) + else + a = "" + a + + jsEscape: (a) -> + a.replace(/(['\\])/g, "\\$1").replace(/[\f]/g, "\\f").replace(/[\b]/g, "\\b").replace(/[\n]/g, "\\n").replace(/[\t]/g, "\\t").replace /[\r]/g, "\\r" + + createXhr: -> + a = undefined + b = undefined + c = undefined + if typeof XMLHttpRequest isnt "undefined" + return new XMLHttpRequest + else + b = 0 + while b < 3 + c = j[b] + try + a = new ActiveXObject(c) + if a + j = [c] + break + b++ + throw Error("createXhr(): XMLHttpRequest not available") unless a + a + + get: h + parseName: (a) -> + b = not 1 + c = a.indexOf(".") + e = a.substring(0, c) + a = a.substring(c + 1, a.length) + c = a.indexOf("!") + c isnt -1 and (b = a.substring(c + 1, a.length) + b = b is "strip" + a = a.substring(0, c) + ) + moduleName: e + ext: a + strip: b + + xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/ + canUseXhr: (a, b, c, g) -> + d = e.xdRegExp.exec(a) + f = undefined + return not 0 unless d + a = d[2] + d = d[3] + d = d.split(":") + f = d[1] + d = d[0] + (not a or a is b) and (not d or d is c) and (not f and not d or f is g) + + finishLoad: (a, b, c, g, d) -> + c = (if b then e.strip(c) else c) + d.isBuild and d.inlineText and (i[a] = c) + g c + + load: (a, b, c, g) -> + d = e.parseName(a) + f = d.moduleName + "." + d.ext + h = b.toUrl(f) + (if not n or e.canUseXhr(h) then e.get(h, (b) -> + e.finishLoad a, d.strip, b, c, g + ) else b([f], (a) -> + e.finishLoad d.moduleName + "." + d.ext, d.strip, a, c, g + )) + + write: (a, b, c) -> + if b of i + g = e.jsEscape(i[b]) + c "define('" + a + "!" + b + "', function () { return '" + g + "';});\n" + + writeFile: (a, b, c, g, d) -> + b = e.parseName(b) + f = b.moduleName + "." + b.ext + h = c.toUrl(b.moduleName + "." + b.ext) + ".js" + e.load f, c, (-> + e.write a, f, ((a) -> + g h, a + ), d + ), d + +)() diff --git a/client/js/timer.coffee b/client/js/timer.coffee new file mode 100644 index 000000000..fda336eb5 --- /dev/null +++ b/client/js/timer.coffee @@ -0,0 +1,15 @@ +define -> + Timer = Class.extend( + init: (duration, startTime) -> + @lastTime = startTime or 0 + @duration = duration + + isOver: (time) -> + over = false + if (time - @lastTime) > @duration + over = true + @lastTime = time + over + ) + Timer + diff --git a/client/js/transition.coffee b/client/js/transition.coffee new file mode 100644 index 000000000..996005d67 --- /dev/null +++ b/client/js/transition.coffee @@ -0,0 +1,43 @@ +define -> + Transition = Class.extend( + init: -> + @startValue = 0 + @endValue = 0 + @duration = 0 + @inProgress = false + + start: (currentTime, updateFunction, stopFunction, startValue, endValue, duration) -> + @startTime = currentTime + @updateFunction = updateFunction + @stopFunction = stopFunction + @startValue = startValue + @endValue = endValue + @duration = duration + @inProgress = true + @count = 0 + + step: (currentTime) -> + if @inProgress + if @count > 0 + @count -= 1 + log.debug currentTime + ": jumped frame" + else + elapsed = currentTime - @startTime + elapsed = @duration if elapsed > @duration + diff = @endValue - @startValue + i = @startValue + ((diff / @duration) * elapsed) + i = Math.round(i) + if elapsed is @duration or i is @endValue + @stop() + @stopFunction() if @stopFunction + else @updateFunction i if @updateFunction + + restart: (currentTime, startValue, endValue) -> + @start currentTime, @updateFunction, @stopFunction, startValue, endValue, @duration + @step currentTime + + stop: -> + @inProgress = false + ) + Transition + diff --git a/client/js/updater.coffee b/client/js/updater.coffee new file mode 100644 index 000000000..192b41ab3 --- /dev/null +++ b/client/js/updater.coffee @@ -0,0 +1,189 @@ +define ["character", "timer"], (Character, Timer) -> + Updater = Class.extend( + init: (game) -> + @game = game + @playerAggroTimer = new Timer(1000) + + update: -> + @updateZoning() + @updateCharacters() + @updatePlayerAggro() + @updateTransitions() + @updateAnimations() + @updateAnimatedTiles() + @updateChatBubbles() + @updateInfos() + @updateKeyboardMovement() + + updateCharacters: -> + self = this + @game.forEachEntity (entity) -> + isCharacter = entity instanceof Character + if entity.isLoaded + if isCharacter + self.updateCharacter entity + self.game.onCharacterUpdate entity + self.updateEntityFading entity + + + updatePlayerAggro: -> + t = @game.currentTime + player = @game.player + + # Check player aggro every 1s when not moving nor attacking + player.checkAggro() if player and not player.isMoving() and not player.isAttacking() and @playerAggroTimer.isOver(t) + + updateEntityFading: (entity) -> + if entity and entity.isFading + duration = 1000 + t = @game.currentTime + dt = t - entity.startFadingTime + if dt > duration + @isFading = false + entity.fadingAlpha = 1 + else + entity.fadingAlpha = dt / duration + + updateTransitions: -> + self = this + m = null + z = @game.currentZoning + @game.forEachEntity (entity) -> + m = entity.movement + m.step self.game.currentTime if m.inProgress if m + + z.step @game.currentTime if z.inProgress if z + + updateZoning: -> + g = @game + c = g.camera + z = g.currentZoning + s = 3 + ts = 16 + speed = 500 + if z and z.inProgress is false + orientation = @game.zoningOrientation + startValue = endValue = offset = 0 + updateFunc = null + endFunc = null + if orientation is Types.Orientations.LEFT or orientation is Types.Orientations.RIGHT + offset = (c.gridW - 2) * ts + startValue = (if (orientation is Types.Orientations.LEFT) then c.x - ts else c.x + ts) + endValue = (if (orientation is Types.Orientations.LEFT) then c.x - offset else c.x + offset) + updateFunc = (x) -> + c.setPosition x, c.y + g.initAnimatedTiles() + g.renderer.renderStaticCanvases() + + endFunc = -> + c.setPosition z.endValue, c.y + g.endZoning() + else if orientation is Types.Orientations.UP or orientation is Types.Orientations.DOWN + offset = (c.gridH - 2) * ts + startValue = (if (orientation is Types.Orientations.UP) then c.y - ts else c.y + ts) + endValue = (if (orientation is Types.Orientations.UP) then c.y - offset else c.y + offset) + updateFunc = (y) -> + c.setPosition c.x, y + g.initAnimatedTiles() + g.renderer.renderStaticCanvases() + + endFunc = -> + c.setPosition c.x, z.endValue + g.endZoning() + z.start @game.currentTime, updateFunc, endFunc, startValue, endValue, speed + + updateCharacter: (c) -> + self = this + + # Estimate of the movement distance for one update + tick = Math.round(16 / Math.round((c.moveSpeed / (1000 / @game.renderer.FPS)))) + if c.isMoving() and c.movement.inProgress is false + if c.orientation is Types.Orientations.LEFT + c.movement.start @game.currentTime, ((x) -> + c.x = x + c.hasMoved() + ), (-> + c.x = c.movement.endValue + c.hasMoved() + c.nextStep() + ), c.x - tick, c.x - 16, c.moveSpeed + else if c.orientation is Types.Orientations.RIGHT + c.movement.start @game.currentTime, ((x) -> + c.x = x + c.hasMoved() + ), (-> + c.x = c.movement.endValue + c.hasMoved() + c.nextStep() + ), c.x + tick, c.x + 16, c.moveSpeed + else if c.orientation is Types.Orientations.UP + c.movement.start @game.currentTime, ((y) -> + c.y = y + c.hasMoved() + ), (-> + c.y = c.movement.endValue + c.hasMoved() + c.nextStep() + ), c.y - tick, c.y - 16, c.moveSpeed + else if c.orientation is Types.Orientations.DOWN + c.movement.start @game.currentTime, ((y) -> + c.y = y + c.hasMoved() + ), (-> + c.y = c.movement.endValue + c.hasMoved() + c.nextStep() + ), c.y + tick, c.y + 16, c.moveSpeed + + updateKeyboardMovement: -> + return if not @game.player or @game.player.isMoving() + game = @game + player = @game.player + pos = + x: player.gridX + y: player.gridY + + if player.moveUp + pos.y -= 1 + game.keys pos, Types.Orientations.UP + else if player.moveDown + pos.y += 1 + game.keys pos, Types.Orientations.DOWN + else if player.moveRight + pos.x += 1 + game.keys pos, Types.Orientations.RIGHT + else if player.moveLeft + pos.x -= 1 + game.keys pos, Types.Orientations.LEFT + + updateAnimations: -> + t = @game.currentTime + @game.forEachEntity (entity) -> + anim = entity.currentAnimation + entity.setDirty() if anim.update(t) if anim + + sparks = @game.sparksAnimation + sparks.update t if sparks + target = @game.targetAnimation + target.update t if target + + updateAnimatedTiles: -> + self = this + t = @game.currentTime + @game.forEachAnimatedTile (tile) -> + if tile.animate(t) + tile.isDirty = true + tile.dirtyRect = self.game.renderer.getTileBoundingRect(tile) + self.game.checkOtherDirtyRects tile.dirtyRect, tile, tile.x, tile.y if self.game.renderer.mobile or self.game.renderer.tablet + + + updateChatBubbles: -> + t = @game.currentTime + @game.bubbleManager.update t + + updateInfos: -> + t = @game.currentTime + @game.infoManager.update t + ) + Updater + diff --git a/client/js/util.coffee b/client/js/util.coffee new file mode 100644 index 000000000..d95818ce4 --- /dev/null +++ b/client/js/util.coffee @@ -0,0 +1,25 @@ +Function::bind = (bind) -> + self = this + -> + args = Array::slice.call(arguments_) + self.apply bind or null, args + +isInt = (n) -> + (n % 1) is 0 + +TRANSITIONEND = "transitionend webkitTransitionEnd oTransitionEnd" + +# http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +window.requestAnimFrame = (-> + window.requestAnimationFrame or window.webkitRequestAnimationFrame or window.mozRequestAnimationFrame or window.oRequestAnimationFrame or window.msRequestAnimationFrame or (callback, element) -> # function +# DOMElement + window.setTimeout callback, 1000 / 60 +)() +getUrlVars = -> + + #from http://snipplr.com/view/19838/get-url-parameters/ + vars = {} + parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/g, (m, key, value) -> + vars[key] = value + ) + vars diff --git a/client/js/warrior.coffee b/client/js/warrior.coffee new file mode 100644 index 000000000..cda5e04f5 --- /dev/null +++ b/client/js/warrior.coffee @@ -0,0 +1,6 @@ +define ["player"], (Player) -> + Warrior = Player.extend(init: (id, name) -> + @_super id, name, Types.Entities.WARRIOR + ) + Warrior + From 14add0dd33d2ea98db7619f920d70065c3f692a7 Mon Sep 17 00:00:00 2001 From: Justin Clift Date: Sun, 8 Sep 2013 20:38:41 +0100 Subject: [PATCH 13/14] Removed the .js files converted using js2coffee --- client/js/app.js | 653 ------------------------------ client/js/audio.js | 250 ------------ client/js/build.js | 35 -- client/js/character.js | 577 --------------------------- client/js/chest.js | 29 -- client/js/config.js | 20 - client/js/detect.js | 44 -- client/js/entityfactory.js | 212 ---------- client/js/entrypoint.js | 33 -- client/js/exceptions.js | 14 - client/js/guild.js | 24 -- client/js/home.js | 4 - client/js/items.js | 117 ------ client/js/main.js | 606 ---------------------------- client/js/map.js | 310 -------------- client/js/mapworker.js | 68 ---- client/js/mob.js | 14 - client/js/mobs.js | 160 -------- client/js/npc.js | 244 ------------ client/js/npcs.js | 106 ----- client/js/player.js | 302 -------------- client/js/renderer.js | 798 ------------------------------------- client/js/sprite.js | 175 -------- client/js/sprites.js | 82 ---- client/js/storage.js | 181 --------- client/js/text.js | 11 - client/js/timer.js | 22 - client/js/transition.js | 65 --- client/js/updater.js | 290 -------------- client/js/util.js | 34 -- client/js/warrior.js | 11 - 31 files changed, 5491 deletions(-) delete mode 100644 client/js/app.js delete mode 100644 client/js/audio.js delete mode 100644 client/js/build.js delete mode 100644 client/js/character.js delete mode 100644 client/js/chest.js delete mode 100644 client/js/config.js delete mode 100644 client/js/detect.js delete mode 100644 client/js/entityfactory.js delete mode 100644 client/js/entrypoint.js delete mode 100644 client/js/exceptions.js delete mode 100644 client/js/guild.js delete mode 100644 client/js/home.js delete mode 100644 client/js/items.js delete mode 100644 client/js/main.js delete mode 100644 client/js/map.js delete mode 100644 client/js/mapworker.js delete mode 100644 client/js/mob.js delete mode 100644 client/js/mobs.js delete mode 100644 client/js/npc.js delete mode 100644 client/js/npcs.js delete mode 100644 client/js/player.js delete mode 100644 client/js/renderer.js delete mode 100644 client/js/sprite.js delete mode 100644 client/js/sprites.js delete mode 100644 client/js/storage.js delete mode 100644 client/js/text.js delete mode 100644 client/js/timer.js delete mode 100644 client/js/transition.js delete mode 100644 client/js/updater.js delete mode 100644 client/js/util.js delete mode 100644 client/js/warrior.js diff --git a/client/js/app.js b/client/js/app.js deleted file mode 100644 index 63d1866c7..000000000 --- a/client/js/app.js +++ /dev/null @@ -1,653 +0,0 @@ - -define(['jquery', 'storage'], function($, Storage) { - - var App = Class.extend({ - init: function() { - this.currentPage = 1; - this.blinkInterval = null; - this.isParchmentReady = true; - this.ready = false; - this.storage = new Storage(); - this.watchNameInputInterval = setInterval(this.toggleButton.bind(this), 100); - this.initFormFields(); - - if(localStorage && localStorage.data) { - this.frontPage = 'loadcharacter'; - } else { - this.frontPage = 'createcharacter'; - } - }, - - setGame: function(game) { - this.game = game; - this.isMobile = this.game.renderer.mobile; - this.isTablet = this.game.renderer.tablet; - this.isDesktop = !(this.isMobile || this.isTablet); - this.supportsWorkers = !!window.Worker; - this.ready = true; - }, - - initFormFields: function() { - // Play button - this.$playButton = $('.play'), - this.$playDiv = $('.play div'); - - // Login form fields - this.$loginnameinput = $('#loginnameinput'); - this.$loginpwinput = $('#loginpwinput'); - this.loginFormFields = [this.$loginnameinput, this.$loginpwinput]; - - // Create new character form fields - this.$nameinput = $('#nameinput'); - this.$pwinput = $('#pwinput'); - this.$pwinput2 = $('#pwinput2'); - this.$email = $('#emailinput'); - this.createNewCharacterFormFields = [this.$nameinput, this.$pwinput, this.$pwinput2, this.$email]; - - // Functions to return the proper username / password fields to use, depending on which form - // (login or create new character) is currently active. - this.getUsernameField = function() { return this.createNewCharacterFormActive() ? this.$nameinput : this.$loginnameinput; }; - this.getPasswordField = function() { return this.createNewCharacterFormActive() ? this.$pwinput : this.$loginpwinput; }; - }, - - center: function() { - window.scrollTo(0, 1); - }, - - canStartGame: function() { - if(this.isDesktop) { - return (this.game && this.game.map && this.game.map.isLoaded); - } else { - return this.game; - } - }, - - tryStartingGame: function() { - var self = this; - var $play = this.$playButton; - - var username = this.getUsernameField().attr('value'); - var userpw = this.getPasswordField().attr('value'); - var email = ''; - var userpw2; - if(this.createNewCharacterFormActive()) { - email = this.$email.attr('value'); - userpw2 = this.$pwinput2.attr('value'); - } - - if(!this.validateFormFields(username, userpw, userpw2, email)) return; - - if(!this.ready || !this.canStartGame()) { - if(!this.isMobile) { - // on desktop and tablets, add a spinner to the play button - $play.addClass('loading'); - } - this.$playDiv.unbind('click'); - var watchCanStart = setInterval(function() { - log.debug("waiting..."); - if(self.canStartGame()) { - setTimeout(function() { - if(!self.isMobile) { - $play.removeClass('loading'); - } - }, 1500); - clearInterval(watchCanStart); - self.startGame(username, userpw, email); - } - }, 100); - } else { - this.$playDiv.unbind('click'); - this.startGame(username, userpw, email); - } - }, - - startGame: function(username, userpw, email) { - var self = this; - - this.hideIntro(function() { - if(!self.isDesktop) { - // On mobile and tablet we load the map after the player has clicked - // on the PLAY button instead of loading it in a web worker. - self.game.loadMap(); - } - self.start(username, userpw, email); - }); - }, - - start: function(username, userpw, email) { - var self = this, - firstTimePlaying = !self.storage.hasAlreadyPlayed(); - - if(username && !this.game.started) { - var optionsSet = false, - config = this.config; - - //>>includeStart("devHost", pragmas.devHost); - if(config.local) { - log.debug("Starting game with local dev config."); - this.game.setServerOptions(config.local.host, config.local.port, username, userpw, email); - } else { - log.debug("Starting game with default dev config."); - this.game.setServerOptions(config.dev.host, config.dev.port, username, userpw, email); - } - optionsSet = true; - //>>includeEnd("devHost"); - - //>>includeStart("prodHost", pragmas.prodHost); - if(!optionsSet) { - log.debug("Starting game with build config."); - this.game.setServerOptions(config.build.host, config.build.port, username, userpw, email); - } - //>>includeEnd("prodHost"); - - this.center(); - this.game.run(function() { - $('body').addClass('started'); - if(firstTimePlaying) { - self.toggleInstructions(); - } - }); - } - }, - - loginFormActive: function() { - return $('#parchment').hasClass("loadcharacter"); - }, - - createNewCharacterFormActive: function() { - return $('#parchment').hasClass("createcharacter"); - }, - - /** - * Handles the Enter key in the Login / Create New Character forms. (Assumes one of these forms is - * currently active.) - */ - handleEnter: function() { - var fields = this.loginFormActive() ? this.loginFormFields : this.createNewCharacterFormFields; - - var isFieldEmpty = function (field) { return $.trim(field.val()) == 0; }; - var isEmpty = function () { return isFieldEmpty($(this)); }; - var allFieldsFilledOut = function (fields) { - return $.map(fields, isFieldEmpty).every(function (v) { return !v; }); - }; - - if (allFieldsFilledOut(fields)) { - // If all fields have been filled out, then the Enter key should start the game. - this.tryStartingGame(); - } else { - // Otherwise, pressing Enter should switch focus to the first missing field - var firstMissingField = $.grep(fields, isFieldEmpty)[0]; - if (firstMissingField !== undefined) { - firstMissingField.focus(); - } - } - }, - - /** - * Performs some basic validation on the login / create new character forms (required fields are filled - * out, passwords match, email looks valid). Assumes either the login or the create new character form - * is currently active. - */ - validateFormFields: function(username, userpw, userpw2, email) { - if(!username || !userpw) { - return false; - } - - if(this.createNewCharacterFormActive()) { // In Create New Character form (rather than login form) - if(userpw !== userpw2) { - alert('The passwords you entered do not match. Please make sure you typed the password correctly.'); - this.$pwinput.select(); - return false; - } - - // Email field is not required, but if it's filled out, then it should look like a valid email. - if(email && !this.validateEmail(email)) { - alert('The email you entered appears to be invalid. Please enter a valid email (or leave the email blank).'); - this.$email.select(); - return false; - } - } - - return true; - }, - - validateEmail: function(email) { - // Regex borrowed from http://stackoverflow.com/a/46181/393005 - var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(email); - }, - - setMouseCoordinates: function(event) { - var gamePos = $('#container').offset(), - scale = this.game.renderer.getScaleFactor(), - width = this.game.renderer.getWidth(), - height = this.game.renderer.getHeight(), - mouse = this.game.mouse; - - mouse.x = event.pageX - gamePos.left - (this.isMobile ? 0 : 5 * scale); - mouse.y = event.pageY - gamePos.top - (this.isMobile ? 0 : 7 * scale); - - if(mouse.x <= 0) { - mouse.x = 0; - } else if(mouse.x >= width) { - mouse.x = width - 1; - } - - if(mouse.y <= 0) { - mouse.y = 0; - } else if(mouse.y >= height) { - mouse.y = height - 1; - } - }, - //Init the hud that makes it show what creature you are mousing over and attacking - initTargetHud: function(){ - var self = this; - var scale = self.game.renderer.getScaleFactor(), - healthMaxWidth = $("#inspector .health").width() - (12 * scale), - timeout; - - this.game.player.onSetTarget(function(target, name, mouseover){ - var el = '#inspector'; - var sprite = target.sprite, - x = ((sprite.animationData.idle_down.length-1)*sprite.width), - y = ((sprite.animationData.idle_down.row)*sprite.height); - $(el+' .name').text(name); - - //Show how much Health creature has left. Currently does not work. The reason health doesn't currently go down has to do with the lines below down to initExpBar... - if(target.healthPoints){ - $(el+" .health").css('width', Math.round(target.healthPoints/target.maxHp*100)+'%'); - } else{ - $(el+" .health").css('width', '0%'); - } - var level = Types.getMobLevel(Types.getKindFromString(name)); - if(level !== undefined) { - $(el + ' .level').text("Level " + level); - } - else { - $('#inspector .level').text(''); - } - - $(el).fadeIn('fast'); - }); - - self.game.onUpdateTarget(function(target){ - $("#inspector .health").css('width', Math.round(target.healthPoints/target.maxHp*100) + "%"); - }); - - self.game.player.onRemoveTarget(function(targetId){ - $('#inspector').fadeOut('fast'); - $('#inspector .level').text(''); - self.game.player.inspecting = null; - }); - }, - initExpBar: function(){ - var maxHeight = $("#expbar").height(); - - this.game.onPlayerExpChange(function(expInThisLevel, expForLevelUp){ - var barHeight = Math.round((maxHeight / expForLevelUp) * (expInThisLevel > 0 ? expInThisLevel : 0)); - $("#expbar").css('height', barHeight + "px"); - }); - }, - - initHealthBar: function() { - var scale = this.game.renderer.getScaleFactor(), - healthMaxWidth = $("#healthbar").width() - (12 * scale); - - this.game.onPlayerHealthChange(function(hp, maxHp) { - var barWidth = Math.round((healthMaxWidth / maxHp) * (hp > 0 ? hp : 0)); - $("#hitpoints").css('width', barWidth + "px"); - }); - - this.game.onPlayerHurt(this.blinkHealthBar.bind(this)); - }, - - blinkHealthBar: function() { - var $hitpoints = $('#hitpoints'); - - $hitpoints.addClass('white'); - setTimeout(function() { - $hitpoints.removeClass('white'); - }, 500) - }, - - toggleButton: function() { - var name = $('#parchment input').val(), - $play = $('#createcharacter .play'); - - if(name && name.length > 0) { - $play.removeClass('disabled'); - $('#character').removeClass('disabled'); - } else { - $play.addClass('disabled'); - $('#character').addClass('disabled'); - } - }, - - hideIntro: function(hidden_callback) { - clearInterval(this.watchNameInputInterval); - $('body').removeClass('intro'); - setTimeout(function() { - $('body').addClass('game'); - hidden_callback(); - }, 1000); - }, - - showChat: function() { - if(this.game.started) { - $('#chatbox').addClass('active'); - $('#chatinput').focus(); - $('#chatbutton').addClass('active'); - } - }, - - hideChat: function() { - if(this.game.started) { - $('#chatbox').removeClass('active'); - $('#chatinput').blur(); - $('#chatbutton').removeClass('active'); - } - }, - - toggleInstructions: function() { - if($('#achievements').hasClass('active')) { - this.toggleAchievements(); - $('#achievementsbutton').removeClass('active'); - } - $('#instructions').toggleClass('active'); - }, - - toggleAchievements: function() { - if($('#instructions').hasClass('active')) { - this.toggleInstructions(); - $('#helpbutton').removeClass('active'); - } - this.resetPage(); - $('#achievements').toggleClass('active'); - }, - - resetPage: function() { - var self = this, - $achievements = $('#achievements'); - - if($achievements.hasClass('active')) { - $achievements.bind(TRANSITIONEND, function() { - $achievements.removeClass('page' + self.currentPage).addClass('page1'); - self.currentPage = 1; - $achievements.unbind(TRANSITIONEND); - }); - } - }, - - initEquipmentIcons: function() { - var scale = this.game.renderer.getScaleFactor(), - getIconPath = function(spriteName) { - return 'img/'+ scale +'/item-' + spriteName + '.png'; - }, - weapon = this.game.player.getWeaponName(), - armor = this.game.player.getSpriteName(), - weaponPath = getIconPath(weapon), - armorPath = getIconPath(armor); - - $('#weapon').css('background-image', 'url("' + weaponPath + '")'); - if(armor !== 'firefox') { - $('#armor').css('background-image', 'url("' + armorPath + '")'); - } - }, - - hideWindows: function() { - if($('#achievements').hasClass('active')) { - this.toggleAchievements(); - $('#achievementsbutton').removeClass('active'); - } - if($('#instructions').hasClass('active')) { - this.toggleInstructions(); - $('#helpbutton').removeClass('active'); - } - if($('body').hasClass('credits')) { - this.closeInGameScroll('credits'); - } - if($('body').hasClass('legal')) { - this.closeInGameScroll('legal'); - } - if($('body').hasClass('about')) { - this.closeInGameScroll('about'); - } - }, - - showAchievementNotification: function(id, name) { - var $notif = $('#achievement-notification'), - $name = $notif.find('.name'), - $button = $('#achievementsbutton'); - - $notif.removeClass().addClass('active achievement' + id); - $name.text(name); - if(this.game.storage.getAchievementCount() === 1) { - this.blinkInterval = setInterval(function() { - $button.toggleClass('blink'); - }, 500); - } - setTimeout(function() { - $notif.removeClass('active'); - $button.removeClass('blink'); - }, 5000); - }, - - displayUnlockedAchievement: function(id) { - var $achievement = $('#achievements li.achievement' + id), - achievement = this.game.getAchievementById(id); - - if(achievement && achievement.hidden) { - this.setAchievementData($achievement, achievement.name, achievement.desc); - } - $achievement.addClass('unlocked'); - }, - - unlockAchievement: function(id, name) { - this.showAchievementNotification(id, name); - this.displayUnlockedAchievement(id); - - var nb = parseInt($('#unlocked-achievements').text()); - $('#unlocked-achievements').text(nb + 1); - }, - - initAchievementList: function(achievements) { - var self = this, - $lists = $('#lists'), - $page = $('#page-tmpl'), - $achievement = $('#achievement-tmpl'), - page = 0, - count = 0, - $p = null; - - _.each(achievements, function(achievement) { - count++; - - var $a = $achievement.clone(); - $a.removeAttr('id'); - $a.addClass('achievement'+count); - if(!achievement.hidden) { - self.setAchievementData($a, achievement.name, achievement.desc); - } - $a.find('.twitter').attr('href', 'http://twitter.com/share?url=http%3A%2F%2Fbrowserquest.mozilla.org&text=I%20unlocked%20the%20%27'+ achievement.name +'%27%20achievement%20on%20Mozilla%27s%20%23BrowserQuest%21&related=glecollinet:Creators%20of%20BrowserQuest%2Cwhatthefranck'); - $a.show(); - $a.find('a').click(function() { - var url = $(this).attr('href'); - - self.openPopup('twitter', url); - return false; - }); - - if((count - 1) % 4 === 0) { - page++; - $p = $page.clone(); - $p.attr('id', 'page'+page); - $p.show(); - $lists.append($p); - } - $p.append($a); - }); - - $('#total-achievements').text($('#achievements').find('li').length); - }, - - initUnlockedAchievements: function(ids) { - var self = this; - - _.each(ids, function(id) { - self.displayUnlockedAchievement(id); - }); - $('#unlocked-achievements').text(ids.length); - }, - - setAchievementData: function($el, name, desc) { - $el.find('.achievement-name').html(name); - $el.find('.achievement-description').html(desc); - }, - - toggleScrollContent: function(content) { - var currentState = $('#parchment').attr('class'); - - if(this.game.started) { - $('#parchment').removeClass().addClass(content); - - $('body').removeClass('credits legal about').toggleClass(content); - - if(!this.game.player) { - $('body').toggleClass('death'); - } - - if(content !== 'about') { - $('#helpbutton').removeClass('active'); - } - } else { - if(currentState !== 'animate') { - if(currentState === content) { - this.animateParchment(currentState, this.frontPage); - } else { - this.animateParchment(currentState, content); - } - } - } - }, - - closeInGameScroll: function(content) { - $('body').removeClass(content); - $('#parchment').removeClass(content); - if(!this.game.player) { - $('body').addClass('death'); - } - if(content === 'about') { - $('#helpbutton').removeClass('active'); - } - }, - - togglePopulationInfo: function() { - $('#population').toggleClass('visible'); - }, - - openPopup: function(type, url) { - var h = $(window).height(), - w = $(window).width(), - popupHeight, - popupWidth, - top, - left; - - switch(type) { - case 'twitter': - popupHeight = 450; - popupWidth = 550; - break; - case 'facebook': - popupHeight = 400; - popupWidth = 580; - break; - } - - top = (h / 2) - (popupHeight / 2); - left = (w / 2) - (popupWidth / 2); - - newwindow = window.open(url,'name','height=' + popupHeight + ',width=' + popupWidth + ',top=' + top + ',left=' + left); - if (window.focus) {newwindow.focus()} - }, - - animateParchment: function(origin, destination) { - var self = this, - $parchment = $('#parchment'), - duration = 1; - - if(this.isMobile) { - $parchment.removeClass(origin).addClass(destination); - } else { - if(this.isParchmentReady) { - if(this.isTablet) { - duration = 0; - } - this.isParchmentReady = !this.isParchmentReady; - - $parchment.toggleClass('animate'); - $parchment.removeClass(origin); - - setTimeout(function() { - $('#parchment').toggleClass('animate'); - $parchment.addClass(destination); - }, duration * 1000); - - setTimeout(function() { - self.isParchmentReady = !self.isParchmentReady; - }, duration * 1000); - } - } - }, - - animateMessages: function() { - var $messages = $('#notifications div'); - - $messages.addClass('top'); - }, - - resetMessagesPosition: function() { - var message = $('#message2').text(); - - $('#notifications div').removeClass('top'); - $('#message2').text(''); - $('#message1').text(message); - }, - - showMessage: function(message) { - var $wrapper = $('#notifications div'), - $message = $('#notifications #message2'); - - this.animateMessages(); - $message.text(message); - if(this.messageTimer) { - this.resetMessageTimer(); - } - - this.messageTimer = setTimeout(function() { - $wrapper.addClass('top'); - }, 5000); - }, - - resetMessageTimer: function() { - clearTimeout(this.messageTimer); - }, - - resizeUi: function() { - if(this.game) { - if(this.game.started) { - this.game.resize(); - this.initHealthBar(); - this.initTargetHud(); - this.initExpBar(); - this.game.updateBars(); - } else { - var newScale = this.game.renderer.getScaleFactor(); - this.game.renderer.rescale(newScale); - } - } - } - }); - - return App; -}); diff --git a/client/js/audio.js b/client/js/audio.js deleted file mode 100644 index f43d040c4..000000000 --- a/client/js/audio.js +++ /dev/null @@ -1,250 +0,0 @@ - -define(['area'], function(Area) { - - var AudioManager = Class.extend({ - init: function(game) { - var self = this; - - this.enabled = true; - this.extension = Detect.canPlayMP3() ? "mp3" : "ogg"; - this.sounds = {}; - this.game = game; - this.currentMusic = null; - this.areas = []; - this.musicNames = ["village", "beach", "forest", "cave", "desert", "lavaland", "boss"]; - this.soundNames = ["loot", "hit1", "hit2", "hurt", "heal", "chat", "revive", "death", "firefox", "achievement", "kill1", "kill2", "noloot", "teleport", "chest", "npc", "npc-end"]; - - var loadSoundFiles = function() { - var counter = _.size(self.soundNames); - log.info("Loading sound files..."); - _.each(self.soundNames, function(name) { self.loadSound(name, function() { - counter -= 1; - if(counter === 0) { - if(!Detect.isSafari()) { // Disable music on Safari - See bug 738008 - loadMusicFiles(); - } - } - }); - }); - }; - - var loadMusicFiles = function() { - if(!self.game.renderer.mobile) { // disable music on mobile devices - log.info("Loading music files..."); - // Load the village music first, as players always start here - self.loadMusic(self.musicNames.shift(), function() { - // Then, load all the other music files - _.each(self.musicNames, function(name) { - self.loadMusic(name); - }); - }); - } - }; - - if(!(Detect.isSafari() && Detect.isWindows())) { - loadSoundFiles(); - } else { - this.enabled = false; // Disable audio on Safari Windows - } - }, - - toggle: function() { - if(this.enabled) { - this.enabled = false; - - if(this.currentMusic) { - this.resetMusic(this.currentMusic); - } - } else { - this.enabled = true; - - if(this.currentMusic) { - this.currentMusic = null; - } - this.updateMusic(); - } - }, - - load: function (basePath, name, loaded_callback, channels) { - var path = basePath + name + "." + this.extension, - sound = document.createElement('audio'), - self = this; - - sound.addEventListener('canplaythrough', function (e) { - this.removeEventListener('canplaythrough', arguments.callee, false); - log.debug(path + " is ready to play."); - if(loaded_callback) { - loaded_callback(); - } - }, false); - sound.addEventListener('error', function (e) { - log.error("Error: "+ path +" could not be loaded."); - self.sounds[name] = null; - }, false); - - sound.preload = "auto"; - sound.autobuffer = true; - sound.src = path; - sound.load(); - - this.sounds[name] = [sound]; - _.times(channels - 1, function() { - self.sounds[name].push(sound.cloneNode(true)); - }); - }, - - loadSound: function(name, handleLoaded) { - this.load("audio/sounds/", name, handleLoaded, 4); - }, - - loadMusic: function(name, handleLoaded) { - this.load("audio/music/", name, handleLoaded, 1); - var music = this.sounds[name][0]; - music.loop = true; - music.addEventListener('ended', function() { music.play() }, false); - }, - - getSound: function(name) { - if(!this.sounds[name]) { - return null; - } - var sound = _.detect(this.sounds[name], function(sound) { - return sound.ended || sound.paused; - }); - if(sound && sound.ended) { - sound.currentTime = 0; - } else { - sound = this.sounds[name][0]; - } - return sound; - }, - - playSound: function(name) { - var sound = this.enabled && this.getSound(name); - if(sound) { - sound.play(); - } - }, - - addArea: function(x, y, width, height, musicName) { - var area = new Area(x, y, width, height); - area.musicName = musicName; - this.areas.push(area); - }, - - getSurroundingMusic: function(entity) { - var music = null, - area = _.detect(this.areas, function(area) { - return area.contains(entity); - }); - - if(area) { - music = { sound: this.getSound(area.musicName), name: area.musicName }; - } - return music; - }, - - updateMusic: function() { - if(this.enabled) { - var music = this.getSurroundingMusic(this.game.player); - - if(music) { - if(!this.isCurrentMusic(music)) { - if(this.currentMusic) { - this.fadeOutCurrentMusic(); - } - this.playMusic(music); - } - } else { - this.fadeOutCurrentMusic(); - } - } - }, - - isCurrentMusic: function(music) { - return this.currentMusic && (music.name === this.currentMusic.name); - }, - - playMusic: function(music) { - if(this.enabled && music && music.sound) { - if(music.sound.fadingOut) { - this.fadeInMusic(music); - } else { - music.sound.volume = 1; - music.sound.play(); - } - this.currentMusic = music; - } - }, - - resetMusic: function(music) { - if(music && music.sound && music.sound.readyState > 0) { - music.sound.pause(); - music.sound.currentTime = 0; - } - }, - - fadeOutMusic: function(music, ended_callback) { - var self = this; - if(music && !music.sound.fadingOut) { - this.clearFadeIn(music); - music.sound.fadingOut = setInterval(function() { - var step = 0.02, - volume = music.sound.volume - step; - - if(self.enabled && volume >= step) { - music.sound.volume = volume; - } else { - music.sound.volume = 0; - self.clearFadeOut(music); - ended_callback(music); - } - }, 50); - } - }, - - fadeInMusic: function(music) { - var self = this; - if(music && !music.sound.fadingIn) { - this.clearFadeOut(music); - music.sound.fadingIn = setInterval(function() { - var step = 0.01, - volume = music.sound.volume + step; - - if(self.enabled && volume < 1 - step) { - music.sound.volume = volume; - } else { - music.sound.volume = 1; - self.clearFadeIn(music); - } - }, 30); - } - }, - - clearFadeOut: function(music) { - if(music.sound.fadingOut) { - clearInterval(music.sound.fadingOut); - music.sound.fadingOut = null; - } - }, - - clearFadeIn: function(music) { - if(music.sound.fadingIn) { - clearInterval(music.sound.fadingIn); - music.sound.fadingIn = null; - } - }, - - fadeOutCurrentMusic : function() { - var self = this; - if(this.currentMusic) { - this.fadeOutMusic(this.currentMusic, function(music) { - self.resetMusic(music); - }); - this.currentMusic = null; - } - } - }); - - return AudioManager; -}); diff --git a/client/js/build.js b/client/js/build.js deleted file mode 100644 index 2ab628841..000000000 --- a/client/js/build.js +++ /dev/null @@ -1,35 +0,0 @@ -({ - appDir: "../", - baseUrl: "js/", - dir: "../../client-build", - optimize: "uglify", - optimizeCss: "standard.keepLines", - - paths: { - "jquery": "lib/require-jquery" - }, - - modules: [ - //Optimize the require-jquery.js file by applying any minification - //that is desired via the optimize: setting above. - { - name: "jquery" - }, - - { - name: "game", - exclude: ["jquery"] - }, - - { - name: "home", - // Exclude the jquery module since it is included already in require-jquery.js - exclude: ["jquery", "game"] - } - ], - - pragmas: { - devHost: false, - prodHost: true - } -}) diff --git a/client/js/character.js b/client/js/character.js deleted file mode 100644 index 455ea0162..000000000 --- a/client/js/character.js +++ /dev/null @@ -1,577 +0,0 @@ - -define(['entity', 'transition', 'timer'], function(Entity, Transition, Timer) { - - var Character = Entity.extend({ - init: function(id, kind) { - var self = this; - - this._super(id, kind); - - // Position and orientation - this.nextGridX = -1; - this.nextGridY = -1; - this.orientation = Types.Orientations.DOWN; - - // Speeds - this.atkSpeed = 50; - this.moveSpeed = 120; - this.walkSpeed = 100; - this.idleSpeed = 450; - this.setAttackRate(800); - - // Pathing - this.movement = new Transition(); - this.path = null; - this.newDestination = null; - this.adjacentTiles = {}; - - // Combat - this.target = null; - this.unconfirmedTarget = null; - this.attackers = {}; - - // Health - this.hitPoints = 0; - this.maxHitPoints = 0; - - // Modes - this.isDead = false; - this.attackingMode = false; - this.followingMode = false; - - this.inspecting = null; - }, - - clean: function() { - this.forEachAttacker(function(attacker) { - attacker.disengage(); - attacker.idle(); - }); - }, - - setMaxHitPoints: function(hp) { - this.maxHitPoints = hp; - this.hitPoints = hp; - }, - - setDefaultAnimation: function() { - this.idle(); - }, - - hasWeapon: function() { - return false; - }, - - hasShadow: function() { - return true; - }, - - animate: function(animation, speed, count, onEndCount) { - var oriented = ['atk', 'walk', 'idle'], - o = this.orientation; - - if(!(this.currentAnimation && this.currentAnimation.name === "death")) { // don't change animation if the character is dying - this.flipSpriteX = false; - this.flipSpriteY = false; - - if(_.indexOf(oriented, animation) >= 0) { - animation += "_" + (o === Types.Orientations.LEFT ? "right" : Types.getOrientationAsString(o)); - this.flipSpriteX = (this.orientation === Types.Orientations.LEFT) ? true : false; - } - - this.setAnimation(animation, speed, count, onEndCount); - } - }, - - turnTo: function(orientation) { - this.orientation = orientation; - this.idle(); - }, - - setOrientation: function(orientation) { - if(orientation) { - this.orientation = orientation; - } - }, - - idle: function(orientation) { - this.setOrientation(orientation); - this.animate("idle", this.idleSpeed); - }, - - hit: function(orientation) { - this.setOrientation(orientation); - this.animate("atk", this.atkSpeed, 1); - }, - - walk: function(orientation) { - this.setOrientation(orientation); - this.animate("walk", this.walkSpeed); - }, - - moveTo_: function(x, y, callback) { - this.destination = { gridX: x, gridY: y }; - this.adjacentTiles = {}; - - if(this.isMoving()) { - this.continueTo(x, y); - } - else { - var path = this.requestPathfindingTo(x, y); - - this.followPath(path); - } - }, - - requestPathfindingTo: function(x, y) { - if(this.request_path_callback) { - return this.request_path_callback(x, y); - } else { - log.error(this.id + " couldn't request pathfinding to "+x+", "+y); - return []; - } - }, - - onRequestPath: function(callback) { - this.request_path_callback = callback; - }, - - onStartPathing: function(callback) { - this.start_pathing_callback = callback; - }, - - onStopPathing: function(callback) { - this.stop_pathing_callback = callback; - }, - - followPath: function(path) { - if(path.length > 1) { // Length of 1 means the player has clicked on himself - this.path = path; - this.step = 0; - - if(this.followingMode) { // following a character - path.pop(); - } - - if(this.start_pathing_callback) { - this.start_pathing_callback(path); - } - this.nextStep(); - } - }, - - continueTo: function(x, y) { - this.newDestination = { x: x, y: y }; - }, - - updateMovement: function() { - var p = this.path, - i = this.step; - - if(p[i][0] < p[i-1][0]) { - this.walk(Types.Orientations.LEFT); - } - if(p[i][0] > p[i-1][0]) { - this.walk(Types.Orientations.RIGHT); - } - if(p[i][1] < p[i-1][1]) { - this.walk(Types.Orientations.UP); - } - if(p[i][1] > p[i-1][1]) { - this.walk(Types.Orientations.DOWN); - } - }, - - updatePositionOnGrid: function() { - this.setGridPosition(this.path[this.step][0], this.path[this.step][1]); - }, - - nextStep: function() { - var stop = false, - x, y, path; - - if(this.isMoving()) { - if(this.before_step_callback) { - this.before_step_callback(); - } - - this.updatePositionOnGrid(); - this.checkAggro(); - - if(this.interrupted) { // if Character.stop() has been called - stop = true; - this.interrupted = false; - } - else { - if(this.hasNextStep()) { - this.nextGridX = this.path[this.step+1][0]; - this.nextGridY = this.path[this.step+1][1]; - } - - if(this.step_callback) { - this.step_callback(); - } - - if(this.hasChangedItsPath()) { - x = this.newDestination.x; - y = this.newDestination.y; - path = this.requestPathfindingTo(x, y); - - this.newDestination = null; - if(path.length < 2) { - stop = true; - } - else { - this.followPath(path); - } - } - else if(this.hasNextStep()) { - this.step += 1; - this.updateMovement(); - } - else { - stop = true; - } - } - - if(stop) { // Path is complete or has been interrupted - this.path = null; - this.idle(); - - if(this.stop_pathing_callback) { - this.stop_pathing_callback(this.gridX, this.gridY); - } - } - } - }, - - onBeforeStep: function(callback) { - this.before_step_callback = callback; - }, - - onStep: function(callback) { - this.step_callback = callback; - }, - - isMoving: function() { - return !(this.path === null); - }, - - hasNextStep: function() { - return (this.path.length - 1 > this.step); - }, - - hasChangedItsPath: function() { - return !(this.newDestination === null); - }, - - isNear: function(character, distance) { - var dx, dy, near = false; - - dx = Math.abs(this.gridX - character.gridX); - dy = Math.abs(this.gridY - character.gridY); - - if(dx <= distance && dy <= distance) { - near = true; - } - return near; - }, - - onAggro: function(callback) { - this.aggro_callback = callback; - }, - - onCheckAggro: function(callback) { - this.checkaggro_callback = callback; - }, - - checkAggro: function() { - if(this.checkaggro_callback) { - this.checkaggro_callback(); - } - }, - - aggro: function(character) { - if(this.aggro_callback) { - this.aggro_callback(character); - } - }, - - onDeath: function(callback) { - this.death_callback = callback; - }, - - /** - * Changes the character's orientation so that it is facing its target. - */ - lookAtTarget: function() { - if(this.target) { - this.turnTo(this.getOrientationTo(this.target)); - } - }, - - /** - * - */ - go: function(x, y) { - if(this.isAttacking()) { - this.disengage(); - } - else if(this.followingMode) { - this.followingMode = false; - this.target = null; - } - this.moveTo_(x, y); - }, - - /** - * Makes the character follow another one. - */ - follow: function(entity) { - if(entity) { - this.followingMode = true; - this.moveTo_(entity.gridX, entity.gridY); - } - }, - - /** - * Stops a moving character. - */ - stop: function() { - if(this.isMoving()) { - this.interrupted = true; - } - }, - - /** - * Makes the character attack another character. Same as Character.follow but with an auto-attacking behavior. - * @see Character.follow - */ - engage: function(character) { - this.attackingMode = true; - this.setTarget(character); - this.follow(character); - }, - - disengage: function() { - this.attackingMode = false; - this.followingMode = false; - this.removeTarget(); - }, - - /** - * Returns true if the character is currently attacking. - */ - isAttacking: function() { - return this.attackingMode; - }, - - /** - * Gets the right orientation to face a target character from the current position. - * Note: - * In order to work properly, this method should be used in the following - * situation : - * S - * S T S - * S - * (where S is self, T is target character) - * - * @param {Character} character The character to face. - * @returns {String} The orientation. - */ - getOrientationTo: function(character) { - if(this.gridX < character.gridX) { - return Types.Orientations.RIGHT; - } else if(this.gridX > character.gridX) { - return Types.Orientations.LEFT; - } else if(this.gridY > character.gridY) { - return Types.Orientations.UP; - } else { - return Types.Orientations.DOWN; - } - }, - - /** - * Returns true if this character is currently attacked by a given character. - * @param {Character} character The attacking character. - * @returns {Boolean} Whether this is an attacker of this character. - */ - isAttackedBy: function(character) { - return (character.id in this.attackers); - }, - - /** - * Registers a character as a current attacker of this one. - * @param {Character} character The attacking character. - */ - addAttacker: function(character) { - if(!this.isAttackedBy(character)) { - this.attackers[character.id] = character; - } else { - log.error(this.id + " is already attacked by " + character.id); - } - }, - - /** - * Unregisters a character as a current attacker of this one. - * @param {Character} character The attacking character. - */ - removeAttacker: function(character) { - if(this.isAttackedBy(character)) { - delete this.attackers[character.id]; - } else { - log.error(this.id + " is not attacked by " + character.id); - } - }, - - /** - * Loops through all the characters currently attacking this one. - * @param {Function} callback Function which must accept one character argument. - */ - forEachAttacker: function(callback) { - _.each(this.attackers, function(attacker) { - callback(attacker); - }); - }, - - /** - * Sets this character's attack target. It can only have one target at any time. - * @param {Character} character The target character. - */ - setTarget: function(character) { - if(this.target !== character) { // If it's not already set as the target - if(this.hasTarget()) { - this.removeTarget(); // Cleanly remove the previous one - } - this.unconfirmedTarget = null; - this.target = character; - - if(this.settarget_callback){ - var targetName = Types.getKindAsString(character.kind); - this.settarget_callback(character, targetName); - } - - } else { - log.debug(character.id + " is already the target of " + this.id); - } - }, - onSetTarget: function(callback){ - this.settarget_callback = callback; - }, - showTarget: function(character){ - if(this.inspecting !== character){ - this.inspecting = character; - if(this.settarget_callback){ - var targetName = Types.getKindAsString(character.kind); - this.settarget_callback(character, targetName, true); - } - } - }, - - - /** - * Removes the current attack target. - */ - removeTarget: function() { - var self = this; - - if(this.target) { - if(this.target instanceof Character) { - this.target.removeAttacker(this); - } - if(this.removetarget_callback) this.removetarget_callback(this.target.id); - this.target = null; - } - }, - onRemoveTarget: function(callback){ - this.removetarget_callback = callback; - }, - - /** - * Returns true if this character has a current attack target. - * @returns {Boolean} Whether this character has a target. - */ - hasTarget: function() { - return !(this.target === null); - }, - - /** - * Marks this character as waiting to attack a target. - * By sending an "attack" message, the server will later confirm (or not) - * that this character is allowed to acquire this target. - * - * @param {Character} character The target character - */ - waitToAttack: function(character) { - this.unconfirmedTarget = character; - }, - - /** - * Returns true if this character is currently waiting to attack the target character. - * @param {Character} character The target character. - * @returns {Boolean} Whether this character is waiting to attack. - */ - isWaitingToAttack: function(character) { - return (this.unconfirmedTarget === character); - }, - - /** - * - */ - canAttack: function(time) { - if(this.canReachTarget() && this.attackCooldown.isOver(time)) { - return true; - } - return false; - }, - - canReachTarget: function() { - if(this.hasTarget() && this.isAdjacentNonDiagonal(this.target)) { - return true; - } - return false; - }, - - /** - * - */ - die: function() { - this.removeTarget(); - this.isDead = true; - - if(this.death_callback) { - this.death_callback(); - } - }, - - onHasMoved: function(callback) { - this.hasmoved_callback = callback; - }, - - hasMoved: function() { - this.setDirty(); - if(this.hasmoved_callback) { - this.hasmoved_callback(this); - } - }, - - hurt: function() { - var self = this; - - this.stopHurting(); - this.sprite = this.hurtSprite; - this.hurting = setTimeout(this.stopHurting.bind(this), 75); - }, - - stopHurting: function() { - this.sprite = this.normalSprite; - clearTimeout(this.hurting); - }, - - setAttackRate: function(rate) { - this.attackCooldown = new Timer(rate); - } - }); - - return Character; -}); diff --git a/client/js/chest.js b/client/js/chest.js deleted file mode 100644 index 211811a30..000000000 --- a/client/js/chest.js +++ /dev/null @@ -1,29 +0,0 @@ - -define(['entity'], function(Entity) { - - var Chest = Entity.extend({ - init: function(id, kind) { - this._super(id, Types.Entities.CHEST); - }, - - getSpriteName: function() { - return "chest"; - }, - - isMoving: function() { - return false; - }, - - open: function() { - if(this.open_callback) { - this.open_callback(); - } - }, - - onOpen: function(callback) { - this.open_callback = callback; - } - }); - - return Chest; -}); diff --git a/client/js/config.js b/client/js/config.js deleted file mode 100644 index 16278a7a7..000000000 --- a/client/js/config.js +++ /dev/null @@ -1,20 +0,0 @@ - -define(['text!../config/config_build.json'], -function(build) { - var config = { - dev: { host: "localhost", port: 8000, dispatcher: false }, - build: JSON.parse(build) - }; - - //>>excludeStart("prodHost", pragmas.prodHost); - require(['text!../config/config_local.json'], function(local) { - try { - config.local = JSON.parse(local); - } catch(e) { - // Exception triggered when config_local.json does not exist. Nothing to do here. - } - }); - //>>excludeEnd("prodHost"); - - return config; -}); diff --git a/client/js/detect.js b/client/js/detect.js deleted file mode 100644 index da9d34419..000000000 --- a/client/js/detect.js +++ /dev/null @@ -1,44 +0,0 @@ - -var Detect = {}; - -Detect.supportsWebSocket = function() { - return window.WebSocket || window.MozWebSocket; -}; - -Detect.userAgentContains = function(string) { - return navigator.userAgent.indexOf(string) != -1; -}; - -Detect.isTablet = function(screenWidth) { - if(screenWidth > 640) { - if((Detect.userAgentContains('Android') && Detect.userAgentContains('Firefox')) - || Detect.userAgentContains('Mobile')) { - return true; - } - } - return false; -}; - -Detect.isWindows = function() { - return Detect.userAgentContains('Windows'); -} - -Detect.isChromeOnWindows = function() { - return Detect.userAgentContains('Chrome') && Detect.userAgentContains('Windows'); -}; - -Detect.canPlayMP3 = function() { - return Modernizr.audio.mp3; -}; - -Detect.isSafari = function() { - return Detect.userAgentContains('Safari') && !Detect.userAgentContains('Chrome'); -}; - -Detect.isOpera = function() { - return Detect.userAgentContains('Opera'); -}; - -Detect.isFirefoxAndroid = function() { - return Detect.userAgentContains('Android') && Detect.userAgentContains('Firefox'); -}; diff --git a/client/js/entityfactory.js b/client/js/entityfactory.js deleted file mode 100644 index e9c119d77..000000000 --- a/client/js/entityfactory.js +++ /dev/null @@ -1,212 +0,0 @@ - -define(['mobs', 'items', 'npcs', 'warrior', 'chest'], function(Mobs, Items, NPCs, Warrior, Chest) { - - var EntityFactory = {}; - - EntityFactory.createEntity = function(kind, id, name) { - if(!kind) { - log.error("kind is undefined", true); - return; - } - - if(!_.isFunction(EntityFactory.builders[kind])) { - throw Error(kind + " is not a valid Entity type"); - } - - return EntityFactory.builders[kind](id, name); - }; - - //===== mobs ====== - - EntityFactory.builders = []; - - EntityFactory.builders[Types.Entities.WARRIOR] = function(id, name) { - return new Warrior(id, name); - }; - - EntityFactory.builders[Types.Entities.RAT] = function(id) { - return new Mobs.Rat(id); - }; - - EntityFactory.builders[Types.Entities.SKELETON] = function(id) { - return new Mobs.Skeleton(id); - }; - - EntityFactory.builders[Types.Entities.SKELETON2] = function(id) { - return new Mobs.Skeleton2(id); - }; - - EntityFactory.builders[Types.Entities.SPECTRE] = function(id) { - return new Mobs.Spectre(id); - }; - - EntityFactory.builders[Types.Entities.DEATHKNIGHT] = function(id) { - return new Mobs.Deathknight(id); - }; - - EntityFactory.builders[Types.Entities.GOBLIN] = function(id) { - return new Mobs.Goblin(id); - }; - - EntityFactory.builders[Types.Entities.OGRE] = function(id) { - return new Mobs.Ogre(id); - }; - - EntityFactory.builders[Types.Entities.CRAB] = function(id) { - return new Mobs.Crab(id); - }; - - EntityFactory.builders[Types.Entities.SNAKE] = function(id) { - return new Mobs.Snake(id); - }; - - EntityFactory.builders[Types.Entities.EYE] = function(id) { - return new Mobs.Eye(id); - }; - - EntityFactory.builders[Types.Entities.BAT] = function(id) { - return new Mobs.Bat(id); - }; - - EntityFactory.builders[Types.Entities.WIZARD] = function(id) { - return new Mobs.Wizard(id); - }; - - EntityFactory.builders[Types.Entities.BOSS] = function(id) { - return new Mobs.Boss(id); - }; - - //===== items ====== - - EntityFactory.builders[Types.Entities.SWORD2] = function(id) { - return new Items.Sword2(id); - }; - - EntityFactory.builders[Types.Entities.AXE] = function(id) { - return new Items.Axe(id); - }; - - EntityFactory.builders[Types.Entities.REDSWORD] = function(id) { - return new Items.RedSword(id); - }; - - EntityFactory.builders[Types.Entities.BLUESWORD] = function(id) { - return new Items.BlueSword(id); - }; - - EntityFactory.builders[Types.Entities.GOLDENSWORD] = function(id) { - return new Items.GoldenSword(id); - }; - - EntityFactory.builders[Types.Entities.MORNINGSTAR] = function(id) { - return new Items.MorningStar(id); - }; - - EntityFactory.builders[Types.Entities.MAILARMOR] = function(id) { - return new Items.MailArmor(id); - }; - - EntityFactory.builders[Types.Entities.LEATHERARMOR] = function(id) { - return new Items.LeatherArmor(id); - }; - - EntityFactory.builders[Types.Entities.PLATEARMOR] = function(id) { - return new Items.PlateArmor(id); - }; - - EntityFactory.builders[Types.Entities.REDARMOR] = function(id) { - return new Items.RedArmor(id); - }; - - EntityFactory.builders[Types.Entities.GOLDENARMOR] = function(id) { - return new Items.GoldenArmor(id); - }; - - EntityFactory.builders[Types.Entities.FLASK] = function(id) { - return new Items.Flask(id); - }; - - EntityFactory.builders[Types.Entities.FIREPOTION] = function(id) { - return new Items.FirePotion(id); - }; - - EntityFactory.builders[Types.Entities.BURGER] = function(id) { - return new Items.Burger(id); - }; - - EntityFactory.builders[Types.Entities.CAKE] = function(id) { - return new Items.Cake(id); - }; - - EntityFactory.builders[Types.Entities.CHEST] = function(id) { - return new Chest(id); - }; - - //====== NPCs ====== - - EntityFactory.builders[Types.Entities.GUARD] = function(id) { - return new NPCs.Guard(id); - }; - - EntityFactory.builders[Types.Entities.KING] = function(id) { - return new NPCs.King(id); - }; - - EntityFactory.builders[Types.Entities.VILLAGEGIRL] = function(id) { - return new NPCs.VillageGirl(id); - }; - - EntityFactory.builders[Types.Entities.VILLAGER] = function(id) { - return new NPCs.Villager(id); - }; - - EntityFactory.builders[Types.Entities.CODER] = function(id) { - return new NPCs.Coder(id); - }; - - EntityFactory.builders[Types.Entities.AGENT] = function(id) { - return new NPCs.Agent(id); - }; - - EntityFactory.builders[Types.Entities.RICK] = function(id) { - return new NPCs.Rick(id); - }; - - EntityFactory.builders[Types.Entities.SCIENTIST] = function(id) { - return new NPCs.Scientist(id); - }; - - EntityFactory.builders[Types.Entities.NYAN] = function(id) { - return new NPCs.Nyan(id); - }; - - EntityFactory.builders[Types.Entities.PRIEST] = function(id) { - return new NPCs.Priest(id); - }; - - EntityFactory.builders[Types.Entities.SORCERER] = function(id) { - return new NPCs.Sorcerer(id); - }; - - EntityFactory.builders[Types.Entities.OCTOCAT] = function(id) { - return new NPCs.Octocat(id); - }; - - EntityFactory.builders[Types.Entities.BEACHNPC] = function(id) { - return new NPCs.BeachNpc(id); - }; - - EntityFactory.builders[Types.Entities.FORESTNPC] = function(id) { - return new NPCs.ForestNpc(id); - }; - - EntityFactory.builders[Types.Entities.DESERTNPC] = function(id) { - return new NPCs.DesertNpc(id); - }; - - EntityFactory.builders[Types.Entities.LAVANPC] = function(id) { - return new NPCs.LavaNpc(id); - }; - - return EntityFactory; -}); diff --git a/client/js/entrypoint.js b/client/js/entrypoint.js deleted file mode 100644 index dfc5b90ac..000000000 --- a/client/js/entrypoint.js +++ /dev/null @@ -1,33 +0,0 @@ -define(['lib/sha1', 'util'],function() { - - var EntryPoint = Class.extend({ - init: function(){ - //"hashedID" ← use tools/sha1_encode.html to generate: function(){} ← action - this.hashes = { - "Obda3tBpL9VXsXsSsv5xB4QKNo4=": function(aGame){ - aGame.player.switchArmor(aGame.sprites["firefox"]); - aGame.showNotification("You enter the game as a fox, but not invincible…"); - } - }; - }, - - execute: function(game){ - var res = false; - var ID = getUrlVars()["entrance"]; - if(ID!=undefined){ - var shaObj = new jsSHA(ID, 'TEXT'); - var hash = shaObj.getHash("SHA-1", 'B64'); - if(this.hashes[hash]==undefined){ - game.showNotification("Nice try little scoundrel… bad code, though"); - } - else{ - this.hashes[hash](game); - res = true; - } - } - return res; - } - }); - - return EntryPoint; -}); diff --git a/client/js/exceptions.js b/client/js/exceptions.js deleted file mode 100644 index bd2ed74f0..000000000 --- a/client/js/exceptions.js +++ /dev/null @@ -1,14 +0,0 @@ - -define(function() { - - var Exceptions = { - - LootException: Class.extend({ - init: function(message) { - this.message = message; - } - }) - }; - - return Exceptions; -}); diff --git a/client/js/guild.js b/client/js/guild.js deleted file mode 100644 index 0711904e8..000000000 --- a/client/js/guild.js +++ /dev/null @@ -1,24 +0,0 @@ -define(function() { - var Guild = Class.extend({ - init: function(id, name) { - this.members = [];//name - this.id = id; - this.name = name; - }/*, Maybe useful later… see #updateguild tag - - addMembers: function(membersList) { - //maybe we could have tested the form of the array… - this.members = _.union(this.members, membersList); - }, - - removeMembers: function(membersList) { - this.members = _.difference(this.members, membersList); - }, - - listMembers: function(iterator) { - return _.filter(this.members, iterator); - }*/ - }); - - return Guild; -}); diff --git a/client/js/home.js b/client/js/home.js deleted file mode 100644 index 811faf49f..000000000 --- a/client/js/home.js +++ /dev/null @@ -1,4 +0,0 @@ - -define(['lib/class', 'lib/underscore.min', 'lib/stacktrace', 'util'], function() { - require(["main"]); -}); diff --git a/client/js/items.js b/client/js/items.js deleted file mode 100644 index 316f80ad7..000000000 --- a/client/js/items.js +++ /dev/null @@ -1,117 +0,0 @@ - -define(['item'], function(Item) { - - var Items = { - - Sword2: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.SWORD2, "weapon"); - this.lootMessage = "You pick up a steel sword"; - }, - }), - - Axe: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.AXE, "weapon"); - this.lootMessage = "You pick up an axe"; - }, - }), - - RedSword: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.REDSWORD, "weapon"); - this.lootMessage = "You pick up a blazing sword"; - }, - }), - - BlueSword: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.BLUESWORD, "weapon"); - this.lootMessage = "You pick up a magic sword"; - }, - }), - - GoldenSword: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.GOLDENSWORD, "weapon"); - this.lootMessage = "You pick up the ultimate sword"; - }, - }), - - MorningStar: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.MORNINGSTAR, "weapon"); - this.lootMessage = "You pick up a morning star"; - }, - }), - - LeatherArmor: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.LEATHERARMOR, "armor"); - this.lootMessage = "You equip a leather armor"; - }, - }), - - MailArmor: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.MAILARMOR, "armor"); - this.lootMessage = "You equip a mail armor"; - }, - }), - - PlateArmor: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.PLATEARMOR, "armor"); - this.lootMessage = "You equip a plate armor"; - }, - }), - - RedArmor: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.REDARMOR, "armor"); - this.lootMessage = "You equip a ruby armor"; - }, - }), - - GoldenArmor: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.GOLDENARMOR, "armor"); - this.lootMessage = "You equip a golden armor"; - }, - }), - - Flask: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.FLASK, "object"); - this.lootMessage = "You drink a health potion"; - }, - }), - - Cake: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.CAKE, "object"); - this.lootMessage = "You eat a cake"; - }, - }), - - Burger: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.BURGER, "object"); - this.lootMessage = "You can haz rat burger"; - }, - }), - - FirePotion: Item.extend({ - init: function(id) { - this._super(id, Types.Entities.FIREPOTION, "object"); - this.lootMessage = "You feel the power of Firefox!"; - }, - - onLoot: function(player) { - player.startInvincibility(); - }, - }), - }; - - return Items; -}); diff --git a/client/js/main.js b/client/js/main.js deleted file mode 100644 index 43ba856ae..000000000 --- a/client/js/main.js +++ /dev/null @@ -1,606 +0,0 @@ - -define(['jquery', 'app', 'entrypoint'], function($, App, EntryPoint) { - var app, game; - - var initApp = function() { - $(document).ready(function() { - app = new App(); - app.center(); - - if(Detect.isWindows()) { - // Workaround for graphical glitches on text - $('body').addClass('windows'); - } - - if(Detect.isOpera()) { - // Fix for no pointer events - $('body').addClass('opera'); - } - - if(Detect.isFirefoxAndroid()) { - // Remove chat placeholder - $('#chatinput').removeAttr('placeholder'); - } - - $('body').click(function(event) { - if($('#parchment').hasClass('credits')) { - app.toggleScrollContent('credits'); - } - - if($('#parchment').hasClass('legal')) { - app.toggleScrollContent('legal'); - } - - if($('#parchment').hasClass('about')) { - app.toggleScrollContent('about'); - } - }); - - $('.barbutton').click(function() { - $(this).toggleClass('active'); - }); - - $('#chatbutton').click(function() { - if($('#chatbutton').hasClass('active')) { - app.showChat(); - } else { - app.hideChat(); - } - }); - - $('#helpbutton').click(function() { - if($('body').hasClass('about')) { - app.closeInGameScroll('about'); - $('#helpbutton').removeClass('active'); - } else { - app.toggleScrollContent('about'); - } - }); - - $('#achievementsbutton').click(function() { - app.toggleAchievements(); - if(app.blinkInterval) { - clearInterval(app.blinkInterval); - } - $(this).removeClass('blink'); - }); - - $('#instructions').click(function() { - app.hideWindows(); - }); - - $('#playercount').click(function() { - app.togglePopulationInfo(); - }); - - $('#population').click(function() { - app.togglePopulationInfo(); - }); - - $('.clickable').click(function(event) { - event.stopPropagation(); - }); - - $('#toggle-credits').click(function() { - app.toggleScrollContent('credits'); - }); - - $('#toggle-legal').click(function() { - app.toggleScrollContent('legal'); - if(game.renderer.mobile) { - if($('#parchment').hasClass('legal')) { - $(this).text('close'); - } else { - $(this).text('Privacy'); - } - }; - }); - - $('#create-new span').click(function() { - app.animateParchment('loadcharacter', 'confirmation'); - }); - - $('#continue span').click(function() { - app.storage.clear(); - app.animateParchment('confirmation', 'createcharacter'); - $('body').removeClass('returning'); - }); - - $('#cancel span').click(function() { - app.animateParchment('confirmation', 'loadcharacter'); - }); - - $('.ribbon').click(function() { - app.toggleScrollContent('about'); - }); - - $('#nameinput').bind("keyup", function() { - app.toggleButton(); - }); - $('#pwinput').bind("keyup", function() { - app.toggleButton(); - }); - $('#pwinput2').bind("keyup", function() { - app.toggleButton(); - }); - $('#emailinput').bind("keyup", function() { - app.toggleButton(); - }); - - $('#previous').click(function() { - var $achievements = $('#achievements'); - - if(app.currentPage === 1) { - return false; - } else { - app.currentPage -= 1; - $achievements.removeClass().addClass('active page' + app.currentPage); - } - }); - - $('#next').click(function() { - var $achievements = $('#achievements'), - $lists = $('#lists'), - nbPages = $lists.children('ul').length; - - if(app.currentPage === nbPages) { - return false; - } else { - app.currentPage += 1; - $achievements.removeClass().addClass('active page' + app.currentPage); - } - }); - - $('#notifications div').bind(TRANSITIONEND, app.resetMessagesPosition.bind(app)); - - $('.close').click(function() { - app.hideWindows(); - }); - - $('.twitter').click(function() { - var url = $(this).attr('href'); - - app.openPopup('twitter', url); - return false; - }); - - $('.facebook').click(function() { - var url = $(this).attr('href'); - - app.openPopup('facebook', url); - return false; - }); - - var data = app.storage.data; - if(data.hasAlreadyPlayed) { - if(data.player.name && data.player.name !== "") { - $('#playername').html(data.player.name); - $('#playerimage').attr('src', data.player.image); - } - } - - $('#playbutton span').click(function(event) { - app.tryStartingGame(); - }); - - document.addEventListener("touchstart", function() {},false); - - $('#resize-check').bind("transitionend", app.resizeUi.bind(app)); - $('#resize-check').bind("webkitTransitionEnd", app.resizeUi.bind(app)); - $('#resize-check').bind("oTransitionEnd", app.resizeUi.bind(app)); - - log.info("App initialized."); - - initGame(); - }); - }; - - var initGame = function() { - require(['game'], function(Game) { - - var canvas = document.getElementById("entities"), - background = document.getElementById("background"), - foreground = document.getElementById("foreground"), - input = document.getElementById("chatinput"); - - game = new Game(app); - game.setup('#bubbles', canvas, background, foreground, input); - game.setStorage(app.storage); - app.setGame(game); - - if(app.isDesktop && app.supportsWorkers) { - game.loadMap(); - } - - game.onGameStart(function() { - app.initEquipmentIcons(); - var entry = new EntryPoint(); - entry.execute(game); - }); - - game.onDisconnect(function(message) { - $('#death').find('p').html(message+"Please reload the page."); - $('#respawn').hide(); - }); - - game.onPlayerDeath(function() { - if($('body').hasClass('credits')) { - $('body').removeClass('credits'); - } - $('body').addClass('death'); - }); - - game.onPlayerEquipmentChange(function() { - app.initEquipmentIcons(); - }); - - game.onPlayerInvincible(function() { - $('#hitpoints').toggleClass('invincible'); - }); - - game.onNbPlayersChange(function(worldPlayers, totalPlayers) { - var setWorldPlayersString = function(string) { - $("#instance-population").find("span:nth-child(2)").text(string); - $("#playercount").find("span:nth-child(2)").text(string); - }, - setTotalPlayersString = function(string) { - $("#world-population").find("span:nth-child(2)").text(string); - }; - - $("#playercount").find("span.count").text(worldPlayers); - - $("#instance-population").find("span").text(worldPlayers); - if(worldPlayers == 1) { - setWorldPlayersString("player"); - } else { - setWorldPlayersString("players"); - } - - $("#world-population").find("string").text(Types.getLevel); - if(totalPlayers == 1) { - setTotalPlayersString("player"); - } else { - setTotalPlayersString("players"); - } - }); - - game.onGuildPopulationChange( function(guildName, guildPopulation) { - var setGuildPlayersString = function(string) { - $("#guild-population").find("span:nth-child(2)").text(string); - }; - $('#guild-population').addClass("visible"); - $("#guild-population").find("span").text(guildPopulation); - $('#guild-name').text(guildName); - if(guildPopulation == 1) { - setGuildPlayersString("player"); - } else { - setGuildPlayersString("players"); - } - }); - - game.onAchievementUnlock(function(id, name, description) { - app.unlockAchievement(id, name); - }); - - game.onNotification(function(message) { - app.showMessage(message); - }); - - app.initHealthBar(); - app.initTargetHud(); - app.initExpBar(); - $('#nameinput').attr('value', ''); - $('#pwinput').attr('value', ''); - $('#pwinput2').attr('value', ''); - $('#emailinput').attr('value', ''); - $('#chatbox').attr('value', ''); - - if(game.renderer.mobile || game.renderer.tablet) { - $('#foreground').bind('touchstart', function(event) { - app.center(); - app.setMouseCoordinates(event.originalEvent.touches[0]); - game.click(); - app.hideWindows(); - }); - } else { - $('#foreground').click(function(event) { - app.center(); - app.setMouseCoordinates(event); - if(game && !app.dropDialogPopuped) { - game.pvpFlag = event.shiftKey; - game.click(); - } - app.hideWindows(); - }); - } - - $('body').unbind('click'); - $('body').click(function(event) { - var hasClosedParchment = false; - - if($('#parchment').hasClass('credits')) { - if(game.started) { - app.closeInGameScroll('credits'); - hasClosedParchment = true; - } else { - app.toggleScrollContent('credits'); - } - } - - if($('#parchment').hasClass('legal')) { - if(game.started) { - app.closeInGameScroll('legal'); - hasClosedParchment = true; - } else { - app.toggleScrollContent('legal'); - } - } - - if($('#parchment').hasClass('about')) { - if(game.started) { - app.closeInGameScroll('about'); - hasClosedParchment = true; - } else { - app.toggleScrollContent('about'); - } - } - - if(game.started && !game.renderer.mobile && game.player && !hasClosedParchment) { - game.click(); - } - }); - - $('#respawn').click(function(event) { - game.audioManager.playSound("revive"); - game.restart(); - $('body').removeClass('death'); - }); - - $(document).mousemove(function(event) { - app.setMouseCoordinates(event); - if(game.started) { - game.pvpFlag = event.shiftKey; - game.movecursor(); - } - }); - - $(document).keyup(function(e) { - var key = e.which; - - if (game.started && !$('#chatbox').hasClass('active')) - { - switch(key) { - case Types.Keys.LEFT: - case Types.Keys.A: - case Types.Keys.KEYPAD_4: - game.player.moveLeft = false; - game.player.disableKeyboardNpcTalk = false; - break; - case Types.Keys.RIGHT: - case Types.Keys.D: - case Types.Keys.KEYPAD_6: - game.player.moveRight = false; - game.player.disableKeyboardNpcTalk = false; - break; - case Types.Keys.UP: - case Types.Keys.W: - case Types.Keys.KEYPAD_8: - game.player.moveUp = false; - game.player.disableKeyboardNpcTalk = false; - break; - case Types.Keys.DOWN: - case Types.Keys.S: - case Types.Keys.KEYPAD_2: - game.player.moveDown = false; - game.player.disableKeyboardNpcTalk = false; - break; - default: - break; - } - } - }); - - $(document).keydown(function(e) { - var key = e.which, - $chat = $('#chatinput'); - - if(key === Types.Keys.ENTER) { - if($('#chatbox').hasClass('active')) { - app.hideChat(); - } else { - app.showChat(); - } - } - else if(key === 16) - game.pvpFlag = true; - if (game.started && !$('#chatbox').hasClass('active')) { - pos = { - x: game.player.gridX, - y: game.player.gridY - }; - switch(key) { - case Types.Keys.LEFT: - case Types.Keys.A: - case Types.Keys.KEYPAD_4: - game.player.moveLeft = true; - break; - case Types.Keys.RIGHT: - case Types.Keys.D: - case Types.Keys.KEYPAD_6: - game.player.moveRight = true; - break; - case Types.Keys.UP: - case Types.Keys.W: - case Types.Keys.KEYPAD_8: - game.player.moveUp = true; - break; - case Types.Keys.DOWN: - case Types.Keys.S: - case Types.Keys.KEYPAD_2: - game.player.moveDown = true; - break; - case Types.Keys.SPACE: - game.makePlayerAttackNext(); - break; - case Types.Keys.I: - $('#achievementsbutton').click(); - break; - case Types.Keys.H: - $('#helpbutton').click(); - break; - case Types.Keys.M: - $('#mutebutton').click(); - break; - case Types.Keys.P: - $('#playercount').click(); - break; - default: - break; - } - } - }); - - $(document).keyup(function(e) { - var key = e.which; - - if(key === 16) - game.pvpFlag = false; - }); - $('#chatinput').keydown(function(e) { - var key = e.which, - $chat = $('#chatinput'), - placeholder = $(this).attr("placeholder"); - - // if (!(e.shiftKey && e.keyCode === 16) && e.keyCode !== 9) { - // if ($(this).val() === placeholder) { - // $(this).val(''); - // $(this).removeAttr('placeholder'); - // $(this).removeClass('placeholder'); - // } - // } - - if(key === 13) { - if($chat.attr('value') !== '') { - if(game.player) { - game.say($chat.attr('value')); - } - $chat.attr('value', ''); - app.hideChat(); - $('#foreground').focus(); - return false; - } else { - app.hideChat(); - return false; - } - } - - if(key === 27) { - app.hideChat(); - return false; - } - }); - - $('#chatinput').focus(function(e) { - var placeholder = $(this).attr("placeholder"); - - if(!Detect.isFirefoxAndroid()) { - $(this).val(placeholder); - } - - if ($(this).val() === placeholder) { - this.setSelectionRange(0, 0); - } - }); - - $('#nameinput').focusin(function() { - $('#name-tooltip').addClass('visible'); - }); - - $('#nameinput').focusout(function() { - $('#name-tooltip').removeClass('visible'); - }); - - $('#nameinput').keypress(function(event) { - $('#name-tooltip').removeClass('visible'); - }); - - $('#mutebutton').click(function() { - game.audioManager.toggle(); - }); - - $(document).bind("keydown", function(e) { - var key = e.which, - $chat = $('#chatinput'); - - if(key === 13) { // Enter - if(game.ready) { - $chat.focus(); - return false; - } else { - if(app.loginFormActive() || app.createNewCharacterFormActive()) { - $('input').blur(); // exit keyboard on mobile - app.handleEnter(); - return false; // prevent form submit - } - } - } - - if($('#chatinput:focus').size() == 0 && $('#nameinput:focus').size() == 0) { - if(key === 27) { // ESC - app.hideWindows(); - _.each(game.player.attackers, function(attacker) { - attacker.stop(); - }); - return false; - } - - // The following may be uncommented for debugging purposes. - // - // if(key === 32 && game.ready) { // Space - // game.togglePathingGrid(); - // return false; - // } - // if(key === 70 && game.ready) { // F - // game.toggleDebugInfo(); - // return false; - // } - } - }); - - $('#healthbar').click(function(e) { - var hb = $('#healthbar'), - hp = $('#hitpoints'), - hpg = $('#hpguide'); - - var hbp = hb.position(), - hpp = hp.position(); - - if((e.offsetX >= hpp.left) && (e.offsetX < hb.width())) { - if(hpg.css('display') === 'none') { - hpg.css('display', 'block'); - - setInterval(function () { - if(((game.player.hitPoints / game.player.maxHitPoints) <= game.hpGuide) && - (game.healShortCut >= 0) && - Types.isHealingItem(game.player.inventory[game.healShortCut]) && - (game.player.inventoryCount[game.healShortCut] > 0) - ) { - game.eat(game.healShortCut); - } - }, 100); - } - hpg.css('left', e.offsetX + 'px'); - - game.hpGuide = (e.offsetX - hpp.left) / (hb.width()- hpp.left); - } - - return false; - }); - if(game.renderer.tablet) { - $('body').addClass('tablet'); - } - }); - }; - - initApp(); -}); diff --git a/client/js/map.js b/client/js/map.js deleted file mode 100644 index f86cf6c48..000000000 --- a/client/js/map.js +++ /dev/null @@ -1,310 +0,0 @@ - -define(['jquery', 'area'], function($, Area) { - - var Map = Class.extend({ - init: function(loadMultiTilesheets, game) { - this.game = game; - this.data = []; - this.isLoaded = false; - this.tilesetsLoaded = false; - this.mapLoaded = false; - this.loadMultiTilesheets = loadMultiTilesheets; - - var useWorker = !(this.game.renderer.mobile || this.game.renderer.tablet); - - this._loadMap(useWorker); - this._initTilesets(); - }, - - _checkReady: function() { - if(this.tilesetsLoaded && this.mapLoaded) { - this.isLoaded = true; - if(this.ready_func) { - this.ready_func(); - } - } - }, - - _loadMap: function(useWorker) { - var self = this, - filepath = "maps/world_client.json"; - - if(useWorker) { - log.info("Loading map with web worker."); - var worker = new Worker('js/mapworker.js'); - worker.postMessage(1); - - worker.onmessage = function(event) { - var map = event.data; - self._initMap(map); - self.grid = map.grid; - self.plateauGrid = map.plateauGrid; - self.mapLoaded = true; - self._checkReady(); - }; - } else { - log.info("Loading map via Ajax."); - $.get(filepath, function (data) { - self._initMap(data); - self._generateCollisionGrid(); - self._generatePlateauGrid(); - self.mapLoaded = true; - self._checkReady(); - }, 'json'); - } - }, - - _initTilesets: function() { - var tileset1, tileset2, tileset3; - - if(!this.loadMultiTilesheets) { - this.tilesetCount = 1; - tileset1 = this._loadTileset('img/1/tilesheet.png'); - } else { - if(this.game.renderer.mobile || this.game.renderer.tablet) { - this.tilesetCount = 1; - tileset2 = this._loadTileset('img/2/tilesheet.png'); - } else { - this.tilesetCount = 2; - tileset2 = this._loadTileset('img/2/tilesheet.png'); - tileset3 = this._loadTileset('img/3/tilesheet.png'); - } - } - - this.tilesets = [tileset1, tileset2, tileset3]; - }, - - _initMap: function(map) { - this.width = map.width; - this.height = map.height; - this.tilesize = map.tilesize; - this.data = map.data; - this.blocking = map.blocking || []; - this.plateau = map.plateau || []; - this.musicAreas = map.musicAreas || []; - this.collisions = map.collisions; - this.high = map.high; - this.animated = map.animated; - - this.doors = this._getDoors(map); - this.checkpoints = this._getCheckpoints(map); - }, - - _getDoors: function(map) { - var doors = {}, - self = this; - - _.each(map.doors, function(door) { - var o; - - switch(door.to) { - case 'u': o = Types.Orientations.UP; - break; - case 'd': o = Types.Orientations.DOWN; - break; - case 'l': o = Types.Orientations.LEFT; - break; - case 'r': o = Types.Orientations.RIGHT; - break; - default : o = Types.Orientations.DOWN; - } - - doors[self.GridPositionToTileIndex(door.x, door.y)] = { - x: door.tx, - y: door.ty, - orientation: o, - cameraX: door.tcx, - cameraY: door.tcy, - portal: door.p === 1, - }; - }); - - return doors; - }, - - _loadTileset: function(filepath) { - var self = this, - tileset = new Image(); - - tileset.crossOrigin = "Anonymous"; - tileset.src = filepath; - - log.info("Loading tileset: "+filepath); - - tileset.onload = function() { - if(tileset.width % self.tilesize > 0) { - throw Error("Tileset size should be a multiple of "+ self.tilesize); - } - log.info("Map tileset loaded."); - - self.tilesetCount -= 1; - if(self.tilesetCount === 0) { - log.debug("All map tilesets loaded.") - - self.tilesetsLoaded = true; - self._checkReady(); - } - }; - - return tileset; - }, - - ready: function(f) { - this.ready_func = f; - }, - - tileIndexToGridPosition: function(tileNum) { - var x = 0, - y = 0; - - var getX = function(num, w) { - if(num == 0) { - return 0; - } - return (num % w == 0) ? w - 1 : (num % w) - 1; - }; - - tileNum -= 1; - x = getX(tileNum + 1, this.width); - y = Math.floor(tileNum / this.width); - - return { x: x, y: y }; - }, - - GridPositionToTileIndex: function(x, y) { - return (y * this.width) + x + 1; - }, - - isColliding: function(x, y) { - if(this.isOutOfBounds(x, y) || !this.grid) { - return false; - } - return (this.grid[y][x] === 1); - }, - - isPlateau: function(x, y) { - if(this.isOutOfBounds(x, y) || !this.plateauGrid) { - return false; - } - return (this.plateauGrid[y][x] === 1); - }, - - _generateCollisionGrid: function() { - var tileIndex = 0, - self = this; - - this.grid = []; - for(var j, i = 0; i < this.height; i++) { - this.grid[i] = []; - for(j = 0; j < this.width; j++) { - this.grid[i][j] = 0; - } - } - - _.each(this.collisions, function(tileIndex) { - var pos = self.tileIndexToGridPosition(tileIndex+1); - self.grid[pos.y][pos.x] = 1; - }); - - _.each(this.blocking, function(tileIndex) { - var pos = self.tileIndexToGridPosition(tileIndex+1); - if(self.grid[pos.y] !== undefined) { - self.grid[pos.y][pos.x] = 1; - } - }); - log.debug("Collision grid generated."); - }, - - _generatePlateauGrid: function() { - var tileIndex = 0; - - this.plateauGrid = []; - for(var j, i = 0; i < this.height; i++) { - this.plateauGrid[i] = []; - for(j = 0; j < this.width; j++) { - if(_.include(this.plateau, tileIndex)) { - this.plateauGrid[i][j] = 1; - } else { - this.plateauGrid[i][j] = 0; - } - tileIndex += 1; - } - } - log.info("Plateau grid generated."); - }, - - /** - * Returns true if the given position is located within the dimensions of the map. - * - * @returns {Boolean} Whether the position is out of bounds. - */ - isOutOfBounds: function(x, y) { - return isInt(x) && isInt(y) && (x < 0 || x >= this.width || y < 0 || y >= this.height); - }, - - /** - * Returns true if the given tile id is "high", i.e. above all entities. - * Used by the renderer to know which tiles to draw after all the entities - * have been drawn. - * - * @param {Number} id The tile id in the tileset - * @see Renderer.drawHighTiles - */ - isHighTile: function(id) { - return _.indexOf(this.high, id+1) >= 0; - }, - - /** - * Returns true if the tile is animated. Used by the renderer. - * @param {Number} id The tile id in the tileset - */ - isAnimatedTile: function(id) { - return id+1 in this.animated; - }, - - /** - * - */ - getTileAnimationLength: function(id) { - return this.animated[id+1].l; - }, - - /** - * - */ - getTileAnimationDelay: function(id) { - var animProperties = this.animated[id+1]; - if(animProperties.d) { - return animProperties.d; - } else { - return 100; - } - }, - - isDoor: function(x, y) { - return this.doors[this.GridPositionToTileIndex(x, y)] !== undefined; - }, - - getDoorDestination: function(x, y) { - return this.doors[this.GridPositionToTileIndex(x, y)]; - }, - - _getCheckpoints: function(map) { - var checkpoints = []; - _.each(map.checkpoints, function(cp) { - var area = new Area(cp.x, cp.y, cp.w, cp.h); - area.id = cp.id; - checkpoints.push(area); - }); - return checkpoints; - }, - - getCurrentCheckpoint: function(entity) { - return _.detect(this.checkpoints, function(checkpoint) { - return checkpoint.contains(entity); - }); - } - }); - - return Map; -}); diff --git a/client/js/mapworker.js b/client/js/mapworker.js deleted file mode 100644 index 0a9123cee..000000000 --- a/client/js/mapworker.js +++ /dev/null @@ -1,68 +0,0 @@ - -importScripts('../maps/world_client.js', 'lib/underscore.min.js'); - -onmessage = function (event) { - generateCollisionGrid(); - generatePlateauGrid(); - - postMessage(mapData); -}; - -function generateCollisionGrid() { - var tileIndex = 0; - - mapData.grid = []; - for(var j, i = 0; i < mapData.height; i++) { - mapData.grid[i] = []; - for(j = 0; j < mapData.width; j++) { - mapData.grid[i][j] = 0; - } - } - - _.each(mapData.collisions, function(tileIndex) { - var pos = tileIndexToGridPosition(tileIndex+1); - mapData.grid[pos.y][pos.x] = 1; - }); - - _.each(mapData.blocking, function(tileIndex) { - var pos = tileIndexToGridPosition(tileIndex+1); - if(mapData.grid[pos.y] !== undefined) { - mapData.grid[pos.y][pos.x] = 1; - } - }); -} - -function generatePlateauGrid() { - var tileIndex = 0; - - mapData.plateauGrid = []; - for(var j, i = 0; i < mapData.height; i++) { - mapData.plateauGrid[i] = []; - for(j = 0; j < mapData.width; j++) { - if(_.include(mapData.plateau, tileIndex)) { - mapData.plateauGrid[i][j] = 1; - } else { - mapData.plateauGrid[i][j] = 0; - } - tileIndex += 1; - } - } -} - -function tileIndexToGridPosition(tileNum) { - var x = 0, - y = 0; - - var getX = function(num, w) { - if(num == 0) { - return 0; - } - return (num % w == 0) ? w - 1 : (num % w) - 1; - }; - - tileNum -= 1; - x = getX(tileNum + 1, mapData.width); - y = Math.floor(tileNum / mapData.width); - - return { x: x, y: y }; -} diff --git a/client/js/mob.js b/client/js/mob.js deleted file mode 100644 index 3906c0656..000000000 --- a/client/js/mob.js +++ /dev/null @@ -1,14 +0,0 @@ - -define(['character'], function(Character) { - - var Mob = Character.extend({ - init: function(id, kind) { - this._super(id, kind); - - this.aggroRange = 1; - this.isAggressive = true; - } - }); - - return Mob; -}); diff --git a/client/js/mobs.js b/client/js/mobs.js deleted file mode 100644 index 1f2d4abe5..000000000 --- a/client/js/mobs.js +++ /dev/null @@ -1,160 +0,0 @@ - -define(['mob', 'timer'], function(Mob, Timer) { - - var Mobs = { - Rat: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.RAT); - this.moveSpeed = 350; - this.idleSpeed = 700; - this.shadowOffsetY = -2; - this.isAggressive = false; - } - }), - - Skeleton: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.SKELETON); - this.moveSpeed = 350; - this.atkSpeed = 100; - this.idleSpeed = 800; - this.shadowOffsetY = 1; - this.setAttackRate(1300); - } - }), - - Skeleton2: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.SKELETON2); - this.moveSpeed = 200; - this.atkSpeed = 100; - this.idleSpeed = 800; - this.walkSpeed = 200; - this.shadowOffsetY = 1; - this.setAttackRate(1300); - } - }), - - Spectre: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.SPECTRE); - this.moveSpeed = 150; - this.atkSpeed = 50; - this.idleSpeed = 200; - this.walkSpeed = 200; - this.shadowOffsetY = 1; - this.setAttackRate(900); - } - }), - - Deathknight: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.DEATHKNIGHT); - this.atkSpeed = 50; - this.moveSpeed = 220; - this.walkSpeed = 100; - this.idleSpeed = 450; - this.setAttackRate(800); - this.aggroRange = 3; - }, - - idle: function(orientation) { - if(!this.hasTarget()) { - this._super(Types.Orientations.DOWN); - } else { - this._super(orientation); - } - } - }), - - Goblin: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.GOBLIN); - this.moveSpeed = 150; - this.atkSpeed = 60; - this.idleSpeed = 600; - this.setAttackRate(700); - } - }), - - Ogre: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.OGRE); - this.moveSpeed = 300; - this.atkSpeed = 100; - this.idleSpeed = 600; - } - }), - - Crab: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.CRAB); - this.moveSpeed = 200; - this.atkSpeed = 40; - this.idleSpeed = 500; - } - }), - - Snake: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.SNAKE); - this.moveSpeed = 200; - this.atkSpeed = 40; - this.idleSpeed = 250; - this.walkSpeed = 100; - this.shadowOffsetY = -4; - } - }), - - Eye: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.EYE); - this.moveSpeed = 200; - this.atkSpeed = 40; - this.idleSpeed = 50; - } - }), - - Bat: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.BAT); - this.moveSpeed = 120; - this.atkSpeed = 90; - this.idleSpeed = 90; - this.walkSpeed = 85; - this.isAggressive = false; - } - }), - - Wizard: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.WIZARD); - this.moveSpeed = 200; - this.atkSpeed = 100; - this.idleSpeed = 150; - } - }), - - Boss: Mob.extend({ - init: function(id) { - this._super(id, Types.Entities.BOSS); - this.moveSpeed = 300; - this.atkSpeed = 50; - this.idleSpeed = 400; - this.atkRate = 2000; - this.attackCooldown = new Timer(this.atkRate); - this.aggroRange = 3; - }, - - idle: function(orientation) { - if(!this.hasTarget()) { - this._super(Types.Orientations.DOWN); - } else { - this._super(orientation); - } - } - }) - }; - - return Mobs; -}); diff --git a/client/js/npc.js b/client/js/npc.js deleted file mode 100644 index a0937f5c6..000000000 --- a/client/js/npc.js +++ /dev/null @@ -1,244 +0,0 @@ -define(['character'], function(Character) { - - var NpcTalk = { - "guard": [ - "Hello there", - "We don't need to see your identification", - "You are not the player we're looking for", - "Move along, move along..." - ], - - "king": [ - "Hi, I'm the King", - "I run this place", - "Like a boss", - "I talk to people", - "Like a boss", - "I wear a crown", - "Like a boss", - "I do nothing all day", - "Like a boss", - "Now leave me alone", - "Like a boss" - ], - - "villagegirl": [ - "Hi there, adventurer!", - "How do you like this game?", - "It's all happening in a single web page! Isn't it crazy?", - "It's all made possible thanks to WebSockets.", - "I don't know much about it, after all I'm just a program.", - 'Why don't you read this blog post and learn all about it?' - ], - - "villager": [ - "Howdy stranger. Do you like poetry?", - "Roses are red, violets are blue...", - "I like hunting rats, and so do you...", - "The rats are dead, now what to do?", - "To be honest, I have no clue.", - "Maybe the forest, could interest you...", - "or instead, cook a rat stew." - ], - - "agent": [ - "Do not try to bend the sword", - "That's impossible", - "Instead, only try to realize the truth...", - "There is no sword." - ], - - "rick": [ - "We're no strangers to love", - "You know the rules and so do I", - "A full commitment's what I'm thinking of", - "You wouldn't get this from any other guy", - "I just wanna tell you how I'm feeling", - "Gotta make you understand", - "Never gonna give you up", - "Never gonna let you down", - "Never gonna run around and desert you", - "Never gonna make you cry", - "Never gonna say goodbye", - "Never gonna tell a lie and hurt you" - ], - - "scientist": [{ - "text": [//default - "Greetings.", - "I am the inventor of these two potions.", - "The red one will replenish your health points...", - "The orange one will turn you into a firefox and make you invincible...", - "But it only lasts for a short while.", - "So make good use of it!", - "Now if you'll excuse me, I need to get back to my experiments..." - ]}, - {"condition": function(game){return (game.player.invincible);}, - "text": [ - "Did you not listen to what I said?!!", - "the famous fire-potion only lasts a few seconds", - "You shouldn't be wasting them talking to me…" - ]}, - {"condition": function(game){return ((game.player.getSpriteName() == "firefox") - && !(game.player.invincible));}, - "text": [ - "Ha ha ha, *name*", - "All that glitters is not gold…", - "-sigh-", - "Did you really think you could abuse me with your disguise?", - "I conceived that f…, that potion.", - "Better not use your outfit as a deterrent,", - "The goons you'll meet will attack you whatever you look like." - ]} - - ], - - "nyan": [ - "nyan nyan nyan nyan nyan", - "nyan nyan nyan nyan nyan nyan nyan", - "nyan nyan nyan nyan nyan nyan", - "nyan nyan nyan nyan nyan nyan nyan nyan" - ], - - "beachnpc": [ - "lorem ipsum dolor sit amet", - "consectetur adipisicing elit, sed do eiusmod tempor" - ], - - "forestnpc": [ - "lorem ipsum dolor sit amet", - "consectetur adipisicing elit, sed do eiusmod tempor" - ], - - "desertnpc": [ - "lorem ipsum dolor sit amet", - "consectetur adipisicing elit, sed do eiusmod tempor" - ], - - "lavanpc": [ - "lorem ipsum dolor sit amet", - "consectetur adipisicing elit, sed do eiusmod tempor" - ], - - "priest": [ - "Oh, hello, young man.", - "Wisdom is everything, so I'll share a few guidelines with you.", - "You are free to go wherever you like in this world", - "but beware of the many foes that await you.", - "You can find many weapons and armors by killing enemies.", - "The tougher the enemy, the higher the potential rewards.", - "You can also unlock achievements by exploring and hunting.", - "Click on the small cup icon to see a list of all the achievements.", - "Please stay a while and enjoy the many surprises of BrowserQuest", - "Farewell, young friend." - ], - - "sorcerer": [ - "Ah... I had foreseen you would come to see me.", - "Well? How do you like my new staff?", - "Pretty cool, eh?", - "Where did I get it, you ask?", - "I understand. It's easy to get envious.", - "I actually crafted it myself, using my mad wizard skills.", - "But let me tell you one thing...", - "There are lots of items in this game.", - "Some more powerful than others.", - "In order to find them, exploration is key.", - "Good luck." - ], - - "octocat": [ - "Welcome to BrowserQuest!", - "Want to see the source code?", - 'Check out the repository on GitHub' - ], - - "coder": [ - "Hi! Do you know that you can also play BrowserQuest on your tablet or mobile?", - "That's the beauty of HTML5!", - "Give it a try..." - ], - - "beachnpc": [ - "Don't mind me, I'm just here on vacation.", - "I have to say...", - "These giant crabs are somewhat annoying.", - "Could you please get rid of them for me?" - ], - - "desertnpc": [ - "One does not simply walk into these mountains...", - "An ancient undead lord is said to dwell here.", - "Nobody knows exactly what he looks like...", - "...for none has lived to tell the tale.", - "It's not too late to turn around and go home, kid." - ], - - "othernpc": [ - "lorem ipsum", - "lorem ipsum" - ] - }; - - var Npc = Character.extend({ - init: function(id, kind) { - this._super(id, kind, 1); - this.itemKind = Types.getKindAsString(this.kind); - if(typeof NpcTalk[this.itemKind][0] === 'string'){ - this.discourse = -1; - this.talkCount = NpcTalk[this.itemKind].length; - } - else{ - this.discourse = 0; - this.talkCount = NpcTalk[this.itemKind][this.discourse]["text"].length; - } - this.talkIndex = 0; - }, - - selectTalk: function(game){ - var change = false; - if(this.discourse != -1){ - var found = false; - for(var i = 1; !found && i this.talkCount) ){ - this.talkIndex = 0; - } - if(this.talkIndex < this.talkCount) { - if(this.discourse == -1){ - msg = NpcTalk[this.itemKind][this.talkIndex]; - } - else{ - msg = NpcTalk[this.itemKind][this.discourse]["text"][this.talkIndex]; - } - } - this.talkIndex += 1; - - return msg.replace('*name*',game.player.name); - } - }); - - return Npc; -}); diff --git a/client/js/npcs.js b/client/js/npcs.js deleted file mode 100644 index d3748aa46..000000000 --- a/client/js/npcs.js +++ /dev/null @@ -1,106 +0,0 @@ - -define(['npc'], function(Npc) { - - var NPCs = { - - Guard: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.GUARD, 1); - } - }), - - King: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.KING, 1); - } - }), - - Agent: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.AGENT, 1); - } - }), - - Rick: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.RICK, 1); - } - }), - - VillageGirl: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.VILLAGEGIRL, 1); - } - }), - - Villager: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.VILLAGER, 1); - } - }), - - Coder: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.CODER, 1); - } - }), - - Scientist: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.SCIENTIST, 1); - } - }), - - Nyan: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.NYAN, 1); - this.idleSpeed = 50; - } - }), - - Sorcerer: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.SORCERER, 1); - this.idleSpeed = 150; - } - }), - - Priest: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.PRIEST, 1); - } - }), - - BeachNpc: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.BEACHNPC, 1); - } - }), - - ForestNpc: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.FORESTNPC, 1); - } - }), - - DesertNpc: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.DESERTNPC, 1); - } - }), - - LavaNpc: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.LAVANPC, 1); - } - }), - - Octocat: Npc.extend({ - init: function(id) { - this._super(id, Types.Entities.OCTOCAT, 1); - } - }) - }; - - return NPCs; -}); diff --git a/client/js/player.js b/client/js/player.js deleted file mode 100644 index 8cca0fdcb..000000000 --- a/client/js/player.js +++ /dev/null @@ -1,302 +0,0 @@ - -define(['character', 'exceptions'], function(Character, Exceptions) { - - var Player = Character.extend({ - MAX_LEVEL: 10, - - init: function(id, name, pw, kind, guild) { - this._super(id, kind); - - this.name = name; - this.pw = pw; - - if (typeof guild !== 'undefined') { - this.setGuild(guild); - } - - // Renderer - this.nameOffsetY = -10; - - // sprites - this.spriteName = "clotharmor"; - this.armorName = "clotharmor"; - this.weaponName = "sword1"; - - // modes - this.isLootMoving = false; - this.isSwitchingWeapon = true; - - // PVP Flag - this.pvpFlag = true; - }, - - getGuild: function() { - return this.guild; - }, - - setGuild: function(guild) { - this.guild = guild; - $('#guild-population').addClass("visible"); - $('#guild-name').html(guild.name); - }, - - unsetGuild: function(){ - delete this.guild; - $('#guild-population').removeClass("visible"); - }, - - hasGuild: function(){ - return (typeof this.guild !== 'undefined'); - }, - - - addInvite: function(inviteGuildId){ - this.invite = {time:new Date().valueOf(), guildId: inviteGuildId}; - }, - - deleteInvite: function(){ - delete this.invite; - }, - - checkInvite: function(){ - if(this.invite && ( (new Date().valueOf() - this.invite.time) < 595000)){ - return this.invite.guildId; - } - else{ - if(this.invite){ - this.deleteInvite(); - return -1; - } - else{ - return false; - } - } - }, - - loot: function(item) { - if(item) { - var rank, currentRank, msg, currentArmorName; - - if(this.currentArmorSprite) { - currentArmorName = this.currentArmorSprite.name; - } else { - currentArmorName = this.spriteName; - } - - if(item.type === "armor") { - rank = Types.getArmorRank(item.kind); - currentRank = Types.getArmorRank(Types.getKindFromString(currentArmorName)); - msg = "You are wearing a better armor"; - } else if(item.type === "weapon") { - rank = Types.getWeaponRank(item.kind); - currentRank = Types.getWeaponRank(Types.getKindFromString(this.weaponName)); - msg = "You are wielding a better weapon"; - } - - if(rank && currentRank) { - if(rank === currentRank) { - throw new Exceptions.LootException("You already have this "+item.type); - } else if(rank <= currentRank) { - throw new Exceptions.LootException(msg); - } - } - - log.info('Player '+this.id+' has looted '+item.id); - if(Types.isArmor(item.kind) && this.invincible) { - this.stopInvincibility(); - } - item.onLoot(this); - } - }, - - /** - * Returns true if the character is currently walking towards an item in order to loot it. - */ - isMovingToLoot: function() { - return this.isLootMoving; - }, - - getSpriteName: function() { - return this.spriteName; - }, - - setSpriteName: function(name) { - this.spriteName = name; - }, - - getArmorName: function() { - var sprite = this.getArmorSprite(); - return sprite.id; - }, - - getArmorSprite: function() { - if(this.invincible) { - return this.currentArmorSprite; - } else { - return this.sprite; - } - }, - setArmorName: function(name){ - this.armorName = name; - }, - - getWeaponName: function() { - return this.weaponName; - }, - - setWeaponName: function(name) { - this.weaponName = name; - }, - - hasWeapon: function() { - return this.weaponName !== null; - }, - equipFromInventory: function(type, inventoryNumber, sprites){ - var itemString = Types.getKindAsString(this.inventory[inventoryNumber]); - - if(itemString){ - var itemSprite = sprites[itemString]; - if(itemSprite){ - if(type === "armor"){ - this.inventory[inventoryNumber] = Types.getKindFromString(this.getArmorName()); - this.setSpriteName(itemString); - this.setSprite(itemSprite); - this.setArmorName(itemString); - } else if(type === "avatar"){ - this.inventory[inventoryNumber] = null; - this.setSpriteName(itemString); - this.setSprite(itemSprite); - } - } - } - }, - switchArmor: function(armorName, sprite){ - this.setSpriteName(armorName); - this.setSprite(sprite); - this.setArmorName(armorName); - if(this.switch_callback) { - this.switch_callback(); - } - }, - switchWeapon: function(newWeaponName) { - var count = 14, - value = false, - self = this; - - var toggle = function() { - value = !value; - return value; - }; - - if(newWeaponName !== this.getWeaponName()) { - if(this.isSwitchingWeapon) { - clearInterval(blanking); - } - - this.switchingWeapon = true; - var blanking = setInterval(function() { - if(toggle()) { - self.setWeaponName(newWeaponName); - } else { - self.setWeaponName(null); - } - - count -= 1; - if(count === 1) { - clearInterval(blanking); - self.switchingWeapon = false; - - if(self.switch_callback) { - self.switch_callback(); - } - } - }, 90); - } - }, - - switchArmor: function(newArmorSprite) { - var count = 14, - value = false, - self = this; - - var toggle = function() { - value = !value; - return value; - }; - - if(newArmorSprite && newArmorSprite.id !== this.getSpriteName()) { - if(this.isSwitchingArmor) { - clearInterval(blanking); - } - - this.isSwitchingArmor = true; - self.setSprite(newArmorSprite); - self.setSpriteName(newArmorSprite.id); - var blanking = setInterval(function() { - self.setVisible(toggle()); - - count -= 1; - if(count === 1) { - clearInterval(blanking); - self.isSwitchingArmor = false; - - if(self.switch_callback) { - self.switch_callback(); - } - } - }, 90); - } - }, - - onArmorLoot: function(callback) { - this.armorloot_callback = callback; - }, - - onSwitchItem: function(callback) { - this.switch_callback = callback; - }, - - onInvincible: function(callback) { - this.invincible_callback = callback; - }, - - startInvincibility: function() { - var self = this; - - if(!this.invincible) { - this.currentArmorSprite = this.getSprite(); - this.invincible = true; - this.invincible_callback(); - } else { - // If the player already has invincibility, just reset its duration. - if(this.invincibleTimeout) { - clearTimeout(this.invincibleTimeout); - } - } - - this.invincibleTimeout = setTimeout(function() { - self.stopInvincibility(); - self.idle(); - }, 15000); - }, - - stopInvincibility: function() { - this.invincible_callback(); - this.invincible = false; - - if(this.currentArmorSprite) { - this.setSprite(this.currentArmorSprite); - this.setSpriteName(this.currentArmorSprite.id); - this.currentArmorSprite = null; - } - if(this.invincibleTimeout) { - clearTimeout(this.invincibleTimeout); - } - }, - flagPVP: function(pvpFlag){ - this.pvpFlag = pvpFlag; - } - }); - - return Player; -}); diff --git a/client/js/renderer.js b/client/js/renderer.js deleted file mode 100644 index 40c2495b9..000000000 --- a/client/js/renderer.js +++ /dev/null @@ -1,798 +0,0 @@ - -define(['camera', 'item', 'character', 'player', 'timer'], -function(Camera, Item, Character, Player, Timer) { - - var Renderer = Class.extend({ - init: function(game, canvas, background, foreground) { - this.game = game; - this.context = (canvas && canvas.getContext) ? canvas.getContext("2d") : null; - this.background = (background && background.getContext) ? background.getContext("2d") : null; - this.foreground = (foreground && foreground.getContext) ? foreground.getContext("2d") : null; - - this.canvas = canvas; - this.backcanvas = background; - this.forecanvas = foreground; - - this.initFPS(); - this.tilesize = 16; - - this.upscaledRendering = this.context.mozImageSmoothingEnabled !== undefined; - this.supportsSilhouettes = this.upscaledRendering; - - this.rescale(this.getScaleFactor()); - - this.lastTime = new Date(); - this.frameCount = 0; - this.maxFPS = this.FPS; - this.realFPS = 0; - //Turn on or off Debuginfo (FPS Counter) - this.isDebugInfoVisible = false; - - this.animatedTileCount = 0; - this.highTileCount = 0; - - this.tablet = Detect.isTablet(window.innerWidth); - - this.fixFlickeringTimer = new Timer(100); - }, - - getWidth: function() { - return this.canvas.width; - }, - - getHeight: function() { - return this.canvas.height; - }, - - setTileset: function(tileset) { - this.tileset = tileset; - }, - - getScaleFactor: function() { - var w = window.innerWidth, - h = window.innerHeight, - scale; - - this.mobile = false; - - if(w <= 1000) { - scale = 2; - this.mobile = true; - } - else if(w <= 1500 || h <= 870) { - scale = 2; - } - else { - scale = 3; - } - - return scale; - }, - - rescale: function(factor) { - this.scale = this.getScaleFactor(); - - this.createCamera(); - - this.context.mozImageSmoothingEnabled = false; - this.background.mozImageSmoothingEnabled = false; - this.foreground.mozImageSmoothingEnabled = false; - - this.initFont(); - this.initFPS(); - - if(!this.upscaledRendering && this.game.map && this.game.map.tilesets) { - this.setTileset(this.game.map.tilesets[this.scale - 1]); - } - if(this.game.renderer) { - this.game.setSpriteScale(this.scale); - } - }, - - createCamera: function() { - this.camera = new Camera(this); - this.camera.rescale(); - - this.canvas.width = this.camera.gridW * this.tilesize * this.scale; - this.canvas.height = this.camera.gridH * this.tilesize * this.scale; - log.debug("#entities set to "+this.canvas.width+" x "+this.canvas.height); - - this.backcanvas.width = this.canvas.width; - this.backcanvas.height = this.canvas.height; - log.debug("#background set to "+this.backcanvas.width+" x "+this.backcanvas.height); - - this.forecanvas.width = this.canvas.width; - this.forecanvas.height = this.canvas.height; - log.debug("#foreground set to "+this.forecanvas.width+" x "+this.forecanvas.height); - }, - - initFPS: function() { - this.FPS = this.mobile ? 50 : 50; - }, - - initFont: function() { - var fontsize; - - switch(this.scale) { - case 1: - fontsize = 10; break; - case 2: - fontsize = Detect.isWindows() ? 10 : 13; break; - case 3: - fontsize = 20; - } - this.setFontSize(fontsize); - }, - - setFontSize: function(size) { - var font = size+"px GraphicPixel"; - - this.context.font = font; - this.background.font = font; - }, - - drawText: function(text, x, y, centered, color, strokeColor) { - var ctx = this.context, - strokeSize; - - switch(this.scale) { - case 1: - strokeSize = 3; break; - case 2: - strokeSize = 3; break; - case 3: - strokeSize = 5; - } - - if(text && x && y) { - ctx.save(); - if(centered) { - ctx.textAlign = "center"; - } - ctx.strokeStyle = strokeColor || "#373737"; - ctx.lineWidth = strokeSize; - ctx.strokeText(text, x, y); - ctx.fillStyle = color || "white"; - ctx.fillText(text, x, y); - ctx.restore(); - } - }, - - drawCellRect: function(x, y, color) { - this.context.save(); - this.context.lineWidth = 2*this.scale; - this.context.strokeStyle = color; - this.context.translate(x+2, y+2); - this.context.strokeRect(0, 0, (this.tilesize * this.scale) - 4, (this.tilesize * this.scale) - 4); - this.context.restore(); - }, - drawRectStroke: function(x, y, width, height, color) { - this.context.fillStyle = color; - this.context.fillRect(x, y, (this.tilesize * this.scale)*width, (this.tilesize * this.scale)*height); - this.context.fill(); - this.context.lineWidth = 5; - this.context.strokeStyle = 'black'; - this.context.strokeRect(x, y, (this.tilesize * this.scale)*width, (this.tilesize * this.scale)*height); - }, - drawRect: function(x, y, width, height, color) { - this.context.fillStyle = color; - this.context.fillRect(x, y, (this.tilesize * this.scale)*width, (this.tilesize * this.scale)*height); - }, - - drawCellHighlight: function(x, y, color) { - var s = this.scale, - ts = this.tilesize, - tx = x * ts * s, - ty = y * ts * s; - - this.drawCellRect(tx, ty, color); - }, - - drawTargetCell: function() { - var mouse = this.game.getMouseGridPosition(); - - if(this.game.targetCellVisible && !(mouse.x === this.game.selectedX && mouse.y === this.game.selectedY)) { - this.drawCellHighlight(mouse.x, mouse.y, this.game.targetColor); - } - }, - - drawAttackTargetCell: function() { - var mouse = this.game.getMouseGridPosition(), - entity = this.game.getEntityAt(mouse.x, mouse.y), - s = this.scale; - - if(entity) { - this.drawCellRect(entity.x * s, entity.y * s, "rgba(255, 0, 0, 0.5)"); - } - }, - - drawOccupiedCells: function() { - var positions = this.game.entityGrid; - - if(positions) { - for(var i=0; i < positions.length; i += 1) { - for(var j=0; j < positions[i].length; j += 1) { - if(!_.isNull(positions[i][j])) { - this.drawCellHighlight(i, j, "rgba(50, 50, 255, 0.5)"); - } - } - } - } - }, - - drawPathingCells: function() { - var grid = this.game.pathingGrid; - - if(grid && this.game.debugPathing) { - for(var y=0; y < grid.length; y += 1) { - for(var x=0; x < grid[y].length; x += 1) { - if(grid[y][x] === 1 && this.game.camera.isVisiblePosition(x, y)) { - this.drawCellHighlight(x, y, "rgba(50, 50, 255, 0.5)"); - } - } - } - } - }, - - drawSelectedCell: function() { - var sprite = this.game.cursors["target"], - anim = this.game.targetAnimation, - os = this.upscaledRendering ? 1 : this.scale, - ds = this.upscaledRendering ? this.scale : 1; - - if(this.game.selectedCellVisible) { - if(this.mobile || this.tablet) { - if(this.game.drawTarget) { - var x = this.game.selectedX, - y = this.game.selectedY; - - this.drawCellHighlight(this.game.selectedX, this.game.selectedY, "rgb(51, 255, 0)"); - this.lastTargetPos = { x: x, - y: y }; - this.game.drawTarget = false; - } - } else { - if(sprite && anim) { - var frame = anim.currentFrame, - s = this.scale, - x = frame.x * os, - y = frame.y * os, - w = sprite.width * os, - h = sprite.height * os, - ts = 16, - dx = this.game.selectedX * ts * s, - dy = this.game.selectedY * ts * s, - dw = w * ds, - dh = h * ds; - - this.context.save(); - this.context.translate(dx, dy); - this.context.drawImage(sprite.image, x, y, w, h, 0, 0, dw, dh); - this.context.restore(); - } - } - } - }, - - clearScaledRect: function(ctx, x, y, w, h) { - var s = this.scale; - - ctx.clearRect(x * s, y * s, w * s, h * s); - }, - - drawCursor: function() { - var mx = this.game.mouse.x, - my = this.game.mouse.y, - s = this.scale, - os = this.upscaledRendering ? 1 : this.scale; - - this.context.save(); - if(this.game.currentCursor && this.game.currentCursor.isLoaded) { - this.context.drawImage(this.game.currentCursor.image, 0, 0, 14 * os, 14 * os, mx, my, 14*s, 14*s); - } - this.context.restore(); - }, - - drawScaledImage: function(ctx, image, x, y, w, h, dx, dy) { - var s = this.upscaledRendering ? 1 : this.scale; - _.each(arguments, function(arg) { - if(_.isUndefined(arg) || _.isNaN(arg) || _.isNull(arg) || arg < 0) { - log.error("x:"+x+" y:"+y+" w:"+w+" h:"+h+" dx:"+dx+" dy:"+dy, true); - throw Error("A problem occured when trying to draw on the canvas"); - } - }); - - ctx.drawImage(image, - x * s, - y * s, - w * s, - h * s, - dx * this.scale, - dy * this.scale, - w * this.scale, - h * this.scale); - }, - - drawTile: function(ctx, tileid, tileset, setW, gridW, cellid) { - var s = this.upscaledRendering ? 1 : this.scale; - if(tileid !== -1) { // -1 when tile is empty in Tiled. Don't attempt to draw it. - this.drawScaledImage(ctx, - tileset, - getX(tileid + 1, (setW / s)) * this.tilesize, - Math.floor(tileid / (setW / s)) * this.tilesize, - this.tilesize, - this.tilesize, - getX(cellid + 1, gridW) * this.tilesize, - Math.floor(cellid / gridW) * this.tilesize); - } - }, - - clearTile: function(ctx, gridW, cellid) { - var s = this.scale, - ts = this.tilesize, - x = getX(cellid + 1, gridW) * ts * s, - y = Math.floor(cellid / gridW) * ts * s, - w = ts * s, - h = w; - - ctx.clearRect(x, y, h, w); - }, - - drawEntity: function(entity) { - var sprite = entity.sprite, - shadow = this.game.shadows["small"], - anim = entity.currentAnimation, - os = this.upscaledRendering ? 1 : this.scale, - ds = this.upscaledRendering ? this.scale : 1; - - if(anim && sprite) { - var frame = anim.currentFrame, - s = this.scale, - x = frame.x * os, - y = frame.y * os, - w = sprite.width * os, - h = sprite.height * os, - ox = sprite.offsetX * s, - oy = sprite.offsetY * s, - dx = entity.x * s, - dy = entity.y * s, - dw = w * ds, - dh = h * ds; - - if(entity.isFading) { - this.context.save(); - this.context.globalAlpha = entity.fadingAlpha; - } - - if(!this.mobile && !this.tablet) { - this.drawEntityName(entity); - } - - this.context.save(); - if(entity.flipSpriteX) { - this.context.translate(dx + this.tilesize*s, dy); - this.context.scale(-1, 1); - } - else if(entity.flipSpriteY) { - this.context.translate(dx, dy + dh); - this.context.scale(1, -1); - } - else { - this.context.translate(dx, dy); - } - - if(entity.isVisible()) { - if(entity.hasShadow()) { - this.context.drawImage(shadow.image, 0, 0, shadow.width * os, shadow.height * os, - 0, - entity.shadowOffsetY * ds, - shadow.width * os * ds, shadow.height * os * ds); - } - - this.context.drawImage(sprite.image, x, y, w, h, ox, oy, dw, dh); - - if(entity instanceof Item && entity.kind !== Types.Entities.CAKE) { - var sparks = this.game.sprites["sparks"], - anim = this.game.sparksAnimation, - frame = anim.currentFrame, - sx = sparks.width * frame.index * os, - sy = sparks.height * anim.row * os, - sw = sparks.width * os, - sh = sparks.width * os; - - this.context.drawImage(sparks.image, sx, sy, sw, sh, - sparks.offsetX * s, - sparks.offsetY * s, - sw * ds, sh * ds); - } - } - - if(entity instanceof Character && !entity.isDead && entity.hasWeapon()) { - var weapon = this.game.sprites[entity.getWeaponName()]; - - if(weapon) { - var weaponAnimData = weapon.animationData[anim.name], - index = frame.index < weaponAnimData.length ? frame.index : frame.index % weaponAnimData.length, - wx = weapon.width * index * os, - wy = weapon.height * anim.row * os, - ww = weapon.width * os, - wh = weapon.height * os; - - this.context.drawImage(weapon.image, wx, wy, ww, wh, - weapon.offsetX * s, - weapon.offsetY * s, - ww * ds, wh * ds); - } - } - - this.context.restore(); - - if(entity.isFading) { - this.context.restore(); - } - } - }, - - drawEntities: function(dirtyOnly) { - var self = this; - - this.game.forEachVisibleEntityByDepth(function(entity) { - if(entity.isLoaded) { - if(dirtyOnly) { - if(entity.isDirty) { - self.drawEntity(entity); - - entity.isDirty = false; - entity.oldDirtyRect = entity.dirtyRect; - entity.dirtyRect = null; - } - } else { - self.drawEntity(entity); - } - } - }); - }, - - drawDirtyEntities: function() { - this.drawEntities(true); - }, - - clearDirtyRect: function(r) { - this.context.clearRect(r.x, r.y, r.w, r.h); - }, - - clearDirtyRects: function() { - var self = this, - count = 0; - - this.game.forEachVisibleEntityByDepth(function(entity) { - if(entity.isDirty && entity.oldDirtyRect) { - self.clearDirtyRect(entity.oldDirtyRect); - count += 1; - } - }); - - this.game.forEachAnimatedTile(function(tile) { - if(tile.isDirty) { - self.clearDirtyRect(tile.dirtyRect); - count += 1; - } - }); - - if(this.game.clearTarget && this.lastTargetPos) { - var last = this.lastTargetPos, - rect = this.getTargetBoundingRect(last.x, last.y); - - this.clearDirtyRect(rect); - this.game.clearTarget = false; - count += 1; - } - - if(count > 0) { - //log.debug("count:"+count); - } - }, - - getEntityBoundingRect: function(entity) { - var rect = {}, - s = this.scale, - spr; - - if(entity instanceof Player && entity.hasWeapon()) { - var weapon = this.game.sprites[entity.getWeaponName()]; - spr = weapon; - } else { - spr = entity.sprite; - } - - if(spr) { - rect.x = (entity.x + spr.offsetX - this.camera.x) * s; - rect.y = (entity.y + spr.offsetY - this.camera.y) * s; - rect.w = spr.width * s; - rect.h = spr.height * s; - rect.left = rect.x; - rect.right = rect.x + rect.w; - rect.top = rect.y; - rect.bottom = rect.y + rect.h; - } - return rect; - }, - - getTileBoundingRect: function(tile) { - var rect = {}, - gridW = this.game.map.width, - s = this.scale, - ts = this.tilesize, - cellid = tile.index; - - rect.x = ((getX(cellid + 1, gridW) * ts) - this.camera.x) * s; - rect.y = ((Math.floor(cellid / gridW) * ts) - this.camera.y) * s; - rect.w = ts * s; - rect.h = ts * s; - rect.left = rect.x; - rect.right = rect.x + rect.w; - rect.top = rect.y; - rect.bottom = rect.y + rect.h; - - return rect; - }, - - getTargetBoundingRect: function(x, y) { - var rect = {}, - s = this.scale, - ts = this.tilesize, - tx = x || this.game.selectedX, - ty = y || this.game.selectedY; - - rect.x = ((tx * ts) - this.camera.x) * s; - rect.y = ((ty * ts) - this.camera.y) * s; - rect.w = ts * s; - rect.h = ts * s; - rect.left = rect.x; - rect.right = rect.x + rect.w; - rect.top = rect.y; - rect.bottom = rect.y + rect.h; - - return rect; - }, - - isIntersecting: function(rect1, rect2) { - return !((rect2.left > rect1.right) || - (rect2.right < rect1.left) || - (rect2.top > rect1.bottom) || - (rect2.bottom < rect1.top)); - }, - - drawEntityName: function(entity) { - this.context.save(); - if(entity.name && entity instanceof Player) { - var color = (entity.id === this.game.playerId) ? "#fcda5c" : "white"; - var name = (entity.level) ? "lv." + entity.level + " " + entity.name : entity.name; - this.drawText(entity.name, - (entity.x + 8) * this.scale, - (entity.y + entity.nameOffsetY) * this.scale, - true, - color); - } - this.context.restore(); - }, - - drawTerrain: function() { - var self = this, - m = this.game.map, - tilesetwidth = this.tileset.width / m.tilesize; - - this.game.forEachVisibleTile(function (id, index) { - if(!m.isHighTile(id) && !m.isAnimatedTile(id)) { // Don't draw unnecessary tiles - self.drawTile(self.background, id, self.tileset, tilesetwidth, m.width, index); - } - }, 1); - }, - - drawAnimatedTiles: function(dirtyOnly) { - var self = this, - m = this.game.map, - tilesetwidth = this.tileset.width / m.tilesize; - - this.animatedTileCount = 0; - this.game.forEachAnimatedTile(function (tile) { - if(dirtyOnly) { - if(tile.isDirty) { - self.drawTile(self.context, tile.id, self.tileset, tilesetwidth, m.width, tile.index); - tile.isDirty = false; - } - } else { - self.drawTile(self.context, tile.id, self.tileset, tilesetwidth, m.width, tile.index); - self.animatedTileCount += 1; - } - }); - }, - - drawDirtyAnimatedTiles: function() { - this.drawAnimatedTiles(true); - }, - - drawHighTiles: function(ctx) { - var self = this, - m = this.game.map, - tilesetwidth = this.tileset.width / m.tilesize; - - this.highTileCount = 0; - this.game.forEachVisibleTile(function (id, index) { - if(m.isHighTile(id)) { - self.drawTile(ctx, id, self.tileset, tilesetwidth, m.width, index); - self.highTileCount += 1; - } - }, 1); - }, - - drawBackground: function(ctx, color) { - ctx.fillStyle = color; - ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); - }, - - drawFPS: function() { - var nowTime = new Date(), - diffTime = nowTime.getTime() - this.lastTime.getTime(); - - if (diffTime >= 1000) { - this.realFPS = this.frameCount; - this.frameCount = 0; - this.lastTime = nowTime; - } - this.frameCount++; - - //this.drawText("FPS: " + this.realFPS + " / " + this.maxFPS, 30, 30, false); - this.drawText("FPS: " + this.realFPS, 30, 30, false); - }, - - drawDebugInfo: function() { - if(this.isDebugInfoVisible) { - this.drawFPS(); - //this.drawText("A: " + this.animatedTileCount, 100, 30, false); - //this.drawText("H: " + this.highTileCount, 140, 30, false); - } - }, - - drawCombatInfo: function() { - var self = this; - - switch(this.scale) { - case 2: this.setFontSize(20); break; - case 3: this.setFontSize(30); break; - } - this.game.infoManager.forEachInfo(function(info) { - self.context.save(); - self.context.globalAlpha = info.opacity; - self.drawText(info.value, (info.x + 8) * self.scale, Math.floor(info.y * self.scale), true, info.fillColor, info.strokeColor); - self.context.restore(); - }); - this.initFont(); - }, - - setCameraView: function(ctx) { - ctx.translate(-this.camera.x * this.scale, -this.camera.y * this.scale); - }, - - clearScreen: function(ctx) { - ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - }, - - getPlayerImage: function() { - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - os = this.upscaledRendering ? 1 : this.scale, - player = this.game.player, - sprite = player.getArmorSprite(), - spriteAnim = sprite.animationData["idle_down"], - // character - row = spriteAnim.row, - w = sprite.width * os, - h = sprite.height * os, - y = row * h, - // weapon - weapon = this.game.sprites[this.game.player.getWeaponName()], - ww = weapon.width * os, - wh = weapon.height * os, - wy = wh * row, - offsetX = (weapon.offsetX - sprite.offsetX) * os, - offsetY = (weapon.offsetY - sprite.offsetY) * os, - // shadow - shadow = this.game.shadows["small"], - sw = shadow.width * os, - sh = shadow.height * os, - ox = -sprite.offsetX * os, - oy = -sprite.offsetY * os; - - canvas.width = w; - canvas.height = h; - - ctx.clearRect(0, 0, w, h); - ctx.drawImage(shadow.image, 0, 0, sw, sh, ox, oy, sw, sh); - ctx.drawImage(sprite.image, 0, y, w, h, 0, 0, w, h); - ctx.drawImage(weapon.image, 0, wy, ww, wh, offsetX, offsetY, ww, wh); - - return canvas.toDataURL("image/png"); - }, - - renderStaticCanvases: function() { - this.background.save(); - this.setCameraView(this.background); - this.drawTerrain(); - this.background.restore(); - - if(this.mobile || this.tablet) { - this.clearScreen(this.foreground); - this.foreground.save(); - this.setCameraView(this.foreground); - this.drawHighTiles(this.foreground); - this.foreground.restore(); - } - }, - - renderFrame: function() { - if(this.mobile || this.tablet) { - this.renderFrameMobile(); - } - else { - this.renderFrameDesktop(); - } - }, - - renderFrameDesktop: function() { - this.clearScreen(this.context); - - this.context.save(); - this.setCameraView(this.context); - this.drawAnimatedTiles(); - - if(this.game.started && this.game.cursorVisible) { - this.drawSelectedCell(); - this.drawTargetCell(); - } - - //this.drawOccupiedCells(); - this.drawPathingCells(); - this.drawEntities(); - this.drawCombatInfo(); - this.drawHighTiles(this.context); - this.context.restore(); - - // Overlay UI elements - if(this.game.cursorVisible) - this.drawCursor(); - - this.drawDebugInfo(); - }, - - renderFrameMobile: function() { - this.clearDirtyRects(); - this.preventFlickeringBug(); - - this.context.save(); - this.setCameraView(this.context); - - this.drawDirtyAnimatedTiles(); - this.drawSelectedCell(); - this.drawDirtyEntities(); - this.context.restore(); - }, - - preventFlickeringBug: function() { - if(this.fixFlickeringTimer.isOver(this.game.currentTime)) { - this.background.fillRect(0, 0, 0, 0); - this.context.fillRect(0, 0, 0, 0); - this.foreground.fillRect(0, 0, 0, 0); - } - } - }); - - var getX = function(id, w) { - if(id == 0) { - return 0; - } - return (id % w == 0) ? w - 1 : (id % w) - 1; - }; - - return Renderer; -}); diff --git a/client/js/sprite.js b/client/js/sprite.js deleted file mode 100644 index 3fea6ae5b..000000000 --- a/client/js/sprite.js +++ /dev/null @@ -1,175 +0,0 @@ - -define(['jquery', 'animation', 'sprites'], function($, Animation, sprites) { - - var Sprite = Class.extend({ - init: function(name, scale) { - this.name = name; - this.scale = scale; - this.isLoaded = false; - this.offsetX = 0; - this.offsetY = 0; - this.loadJSON(sprites[name]); - }, - - loadJSON: function(data) { - this.id = data.id; - this.filepath = "img/" + this.scale + "/" + this.id + ".png"; - this.animationData = data.animations; - this.width = data.width; - this.height = data.height; - this.offsetX = (data.offset_x !== undefined) ? data.offset_x : -16; - this.offsetY = (data.offset_y !== undefined) ? data.offset_y : -16; - - this.load(); - }, - - load: function() { - var self = this; - - this.image = new Image(); - this.image.crossOrigin = "Anonymous"; - this.image.src = this.filepath; - - this.image.onload = function() { - self.isLoaded = true; - - if(self.onload_func) { - self.onload_func(); - } - }; - }, - - createAnimations: function() { - var animations = {}; - - for(var name in this.animationData) { - var a = this.animationData[name]; - animations[name] = new Animation(name, a.length, a.row, this.width, this.height); - } - - return animations; - }, - - createHurtSprite: function() { - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - width = this.image.width, - height = this.image.height, - spriteData, data; - - canvas.width = width; - canvas.height = height; - ctx.drawImage(this.image, 0, 0, width, height); - - try { - spriteData = ctx.getImageData(0, 0, width, height); - - data = spriteData.data; - - for(var i=0; i < data.length; i += 4) { - data[i] = 255; - data[i+1] = data[i+2] = 75; - } - spriteData.data = data; - - ctx.putImageData(spriteData, 0, 0); - - this.whiteSprite = { - image: canvas, - isLoaded: true, - offsetX: this.offsetX, - offsetY: this.offsetY, - width: this.width, - height: this.height - }; - } catch(e) { - log.error("Error getting image data for sprite : "+this.name); - } - }, - - getHurtSprite: function() { - return this.whiteSprite; - }, - - createSilhouette: function() { - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - width = this.image.width, - height = this.image.height, - spriteData, finalData, data; - - canvas.width = width; - canvas.height = height; - - try { - ctx.drawImage(this.image, 0, 0, width, height); - data = ctx.getImageData(0, 0, width, height).data; - finalData = ctx.getImageData(0, 0, width, height); - fdata = finalData.data; - - var getIndex = function(x, y) { - return ((width * (y-1)) + x - 1) * 4; - }; - - var getPosition = function(i) { - var x, y; - - i = (i / 4) + 1; - x = i % width; - y = ((i - x) / width) + 1; - - return { x: x, y: y }; - }; - - var hasAdjacentPixel = function(i) { - var pos = getPosition(i); - - if(pos.x < width && !isBlankPixel(getIndex(pos.x + 1, pos.y))) { - return true; - } - if(pos.x > 1 && !isBlankPixel(getIndex(pos.x - 1, pos.y))) { - return true; - } - if(pos.y < height && !isBlankPixel(getIndex(pos.x, pos.y + 1))) { - return true; - } - if(pos.y > 1 && !isBlankPixel(getIndex(pos.x, pos.y - 1))) { - return true; - } - return false; - }; - - var isBlankPixel = function(i) { - if(i < 0 || i >= data.length) { - return true; - } - return data[i] === 0 && data[i+1] === 0 && data[i+2] === 0 && data[i+3] === 0; - }; - - for(var i=0; i < data.length; i += 4) { - if(isBlankPixel(i) && hasAdjacentPixel(i)) { - fdata[i] = fdata[i+1] = 255; - fdata[i+2] = 150; - fdata[i+3] = 150; - } - } - - finalData.data = fdata; - ctx.putImageData(finalData, 0, 0); - - this.silhouetteSprite = { - image: canvas, - isLoaded: true, - offsetX: this.offsetX, - offsetY: this.offsetY, - width: this.width, - height: this.height - }; - } catch(e) { - this.silhouetteSprite = this; - } - } - }); - - return Sprite; -}); diff --git a/client/js/sprites.js b/client/js/sprites.js deleted file mode 100644 index b59d6fc67..000000000 --- a/client/js/sprites.js +++ /dev/null @@ -1,82 +0,0 @@ - -define(['text!../sprites/agent.json', - 'text!../sprites/arrow.json', - 'text!../sprites/axe.json', - 'text!../sprites/bat.json', - 'text!../sprites/beachnpc.json', - 'text!../sprites/bluesword.json', - 'text!../sprites/boss.json', - 'text!../sprites/chest.json', - 'text!../sprites/clotharmor.json', - 'text!../sprites/coder.json', - 'text!../sprites/crab.json', - 'text!../sprites/death.json', - 'text!../sprites/deathknight.json', - 'text!../sprites/desertnpc.json', - 'text!../sprites/eye.json', - 'text!../sprites/firefox.json', - 'text!../sprites/forestnpc.json', - 'text!../sprites/goblin.json', - 'text!../sprites/goldenarmor.json', - 'text!../sprites/goldensword.json', - 'text!../sprites/guard.json', - 'text!../sprites/hand.json', - 'text!../sprites/impact.json', - 'text!../sprites/item-axe.json', - 'text!../sprites/item-bluesword.json', - 'text!../sprites/item-burger.json', - 'text!../sprites/item-cake.json', - 'text!../sprites/item-firepotion.json', - 'text!../sprites/item-flask.json', - 'text!../sprites/item-goldenarmor.json', - 'text!../sprites/item-goldensword.json', - 'text!../sprites/item-leatherarmor.json', - 'text!../sprites/item-mailarmor.json', - 'text!../sprites/item-morningstar.json', - 'text!../sprites/item-platearmor.json', - 'text!../sprites/item-redarmor.json', - 'text!../sprites/item-redsword.json', - 'text!../sprites/item-sword1.json', - 'text!../sprites/item-sword2.json', - 'text!../sprites/king.json', - 'text!../sprites/lavanpc.json', - 'text!../sprites/leatherarmor.json', - 'text!../sprites/loot.json', - 'text!../sprites/mailarmor.json', - 'text!../sprites/morningstar.json', - 'text!../sprites/nyan.json', - 'text!../sprites/octocat.json', - 'text!../sprites/ogre.json', - 'text!../sprites/platearmor.json', - 'text!../sprites/priest.json', - 'text!../sprites/rat.json', - 'text!../sprites/redarmor.json', - 'text!../sprites/redsword.json', - 'text!../sprites/rick.json', - 'text!../sprites/scientist.json', - 'text!../sprites/shadow16.json', - 'text!../sprites/skeleton.json', - 'text!../sprites/skeleton2.json', - 'text!../sprites/snake.json', - 'text!../sprites/sorcerer.json', - 'text!../sprites/sparks.json', - 'text!../sprites/spectre.json', - 'text!../sprites/sword.json', - 'text!../sprites/sword1.json', - 'text!../sprites/sword2.json', - 'text!../sprites/talk.json', - 'text!../sprites/target.json', - 'text!../sprites/villagegirl.json', - 'text!../sprites/villager.json', - 'text!../sprites/wizard.json'], function() { - - var sprites = {}; - - _.each(arguments, function(spriteJson) { - var sprite = JSON.parse(spriteJson); - - sprites[sprite.id] = sprite; - }); - - return sprites; -}); diff --git a/client/js/storage.js b/client/js/storage.js deleted file mode 100644 index 11c707f46..000000000 --- a/client/js/storage.js +++ /dev/null @@ -1,181 +0,0 @@ - -define(function() { - - var Storage = Class.extend({ - init: function() { - if(this.hasLocalStorage() && localStorage.data) { - this.data = JSON.parse(localStorage.data); - } else { - this.resetData(); - } - }, - - resetData: function() { - this.data = { - hasAlreadyPlayed: false, - player: { - name: "", - weapon: "", - armor: "", - guild: "", - image: "" - }, - achievements: { - unlocked: [], - ratCount: 0, - skeletonCount: 0, - totalKills: 0, - totalDmg: 0, - totalRevives: 0 - } - }; - }, - - hasLocalStorage: function() { - return Modernizr.localstorage; - }, - - save: function() { - if(this.hasLocalStorage()) { - localStorage.data = JSON.stringify(this.data); - } - }, - - clear: function() { - if(this.hasLocalStorage()) { - localStorage.data = ""; - this.resetData(); - } - }, - - // Player - - hasAlreadyPlayed: function() { - return this.data.hasAlreadyPlayed; - }, - - initPlayer: function(name) { - this.data.hasAlreadyPlayed = true; - this.setPlayerName(name); - }, - - setPlayerName: function(name) { - this.data.player.name = name; - this.save(); - }, - - setPlayerImage: function(img) { - this.data.player.image = img; - this.save(); - }, - - setPlayerArmor: function(armor) { - this.data.player.armor = armor; - this.save(); - }, - - setPlayerWeapon: function(weapon) { - this.data.player.weapon = weapon; - this.save(); - }, - - setPlayerGuild: function(guild) { - if(typeof guild !== "undefined") { - this.data.player.guild={id:guild.id, name:guild.name,members:JSON.stringify(guild.members)}; - this.save(); - } - else{ - delete this.data.player.guild; - this.save(); - } - }, - - savePlayer: function(img, armor, weapon, guild) { - this.setPlayerImage(img); - this.setPlayerArmor(armor); - this.setPlayerWeapon(weapon); - this.setPlayerGuild(guild); - }, - - // Achievements - - hasUnlockedAchievement: function(id) { - return _.include(this.data.achievements.unlocked, id); - }, - - unlockAchievement: function(id) { - if(!this.hasUnlockedAchievement(id)) { - this.data.achievements.unlocked.push(id); - this.save(); - return true; - } - return false; - }, - - getAchievementCount: function() { - return _.size(this.data.achievements.unlocked); - }, - - // Angry rats - getRatCount: function() { - return this.data.achievements.ratCount; - }, - - incrementRatCount: function() { - if(this.data.achievements.ratCount < 10) { - this.data.achievements.ratCount++; - this.save(); - } - }, - - // Skull Collector - getSkeletonCount: function() { - return this.data.achievements.skeletonCount; - }, - - incrementSkeletonCount: function() { - if(this.data.achievements.skeletonCount < 10) { - this.data.achievements.skeletonCount++; - this.save(); - } - }, - - // Meatshield - getTotalDamageTaken: function() { - return this.data.achievements.totalDmg; - }, - - addDamage: function(damage) { - if(this.data.achievements.totalDmg < 5000) { - this.data.achievements.totalDmg += damage; - this.save(); - } - }, - - // Hunter - getTotalKills: function() { - return this.data.achievements.totalKills; - }, - - incrementTotalKills: function() { - if(this.data.achievements.totalKills < 50) { - this.data.achievements.totalKills++; - this.save(); - } - }, - - // Still Alive - getTotalRevives: function() { - return this.data.achievements.totalRevives; - }, - - incrementRevives: function() { - if(this.data.achievements.totalRevives < 5) { - this.data.achievements.totalRevives++; - this.save(); - } - }, - }); - - return Storage; -}); diff --git a/client/js/text.js b/client/js/text.js deleted file mode 100644 index f60ecd8da..000000000 --- a/client/js/text.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - RequireJS text 0.26.0 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved. - Available via the MIT or new BSD license. - see: http://github.com/jrburke/requirejs for details -*/ -(function(){var j=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"],l=/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,m=/]*>\s*([\s\S]+)\s*<\/body>/im,n=typeof location!=="undefined"&&location.href,i=[];define(function(){var e,h,k;typeof window!=="undefined"&&window.navigator&&window.document?h=function(a,b){var c=e.createXhr();c.open("GET",a,!0);c.onreadystatechange=function(){c.readyState===4&&b(c.responseText)};c.send(null)}:typeof process!=="undefined"&&process.versions&& -process.versions.node?(k=require.nodeRequire("fs"),h=function(a,b){b(k.readFileSync(a,"utf8"))}):typeof Packages!=="undefined"&&(h=function(a,b){var c=new java.io.File(a),g=java.lang.System.getProperty("line.separator"),c=new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(c),"utf-8")),d,f,e="";try{d=new java.lang.StringBuffer;(f=c.readLine())&&f.length()&&f.charAt(0)===65279&&(f=f.substring(1));for(d.append(f);(f=c.readLine())!==null;)d.append(g),d.append(f);e=String(d.toString())}finally{c.close()}b(e)}); -return e={version:"0.26.0",strip:function(a){if(a){var a=a.replace(l,""),b=a.match(m);b&&(a=b[1])}else a="";return a},jsEscape:function(a){return a.replace(/(['\\])/g,"\\$1").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r")},createXhr:function(){var a,b,c;if(typeof XMLHttpRequest!=="undefined")return new XMLHttpRequest;else for(b=0;b<3;b++){c=j[b];try{a=new ActiveXObject(c)}catch(e){}if(a){j=[c];break}}if(!a)throw Error("createXhr(): XMLHttpRequest not available"); -return a},get:h,parseName:function(a){var b=!1,c=a.indexOf("."),e=a.substring(0,c),a=a.substring(c+1,a.length),c=a.indexOf("!");c!==-1&&(b=a.substring(c+1,a.length),b=b==="strip",a=a.substring(0,c));return{moduleName:e,ext:a,strip:b}},xdRegExp:/^((\w+)\:)?\/\/([^\/\\]+)/,canUseXhr:function(a,b,c,g){var d=e.xdRegExp.exec(a),f;if(!d)return!0;a=d[2];d=d[3];d=d.split(":");f=d[1];d=d[0];return(!a||a===b)&&(!d||d===c)&&(!f&&!d||f===g)},finishLoad:function(a,b,c,g,d){c=b?e.strip(c):c;d.isBuild&&d.inlineText&& -(i[a]=c);g(c)},load:function(a,b,c,g){var d=e.parseName(a),f=d.moduleName+"."+d.ext,h=b.toUrl(f);!n||e.canUseXhr(h)?e.get(h,function(b){e.finishLoad(a,d.strip,b,c,g)}):b([f],function(a){e.finishLoad(d.moduleName+"."+d.ext,d.strip,a,c,g)})},write:function(a,b,c){if(b in i){var g=e.jsEscape(i[b]);c("define('"+a+"!"+b+"', function () { return '"+g+"';});\n")}},writeFile:function(a,b,c,g,d){var b=e.parseName(b),f=b.moduleName+"."+b.ext,h=c.toUrl(b.moduleName+"."+b.ext)+".js";e.load(f,c,function(){e.write(a, -f,function(a){g(h,a)},d)},d)}}})})(); diff --git a/client/js/timer.js b/client/js/timer.js deleted file mode 100644 index 7f2e80c1c..000000000 --- a/client/js/timer.js +++ /dev/null @@ -1,22 +0,0 @@ - -define(function() { - - var Timer = Class.extend({ - init: function(duration, startTime) { - this.lastTime = startTime || 0; - this.duration = duration; - }, - - isOver: function(time) { - var over = false; - - if((time - this.lastTime) > this.duration) { - over = true; - this.lastTime = time; - } - return over; - } - }); - - return Timer; -}); diff --git a/client/js/transition.js b/client/js/transition.js deleted file mode 100644 index 72ba6aee6..000000000 --- a/client/js/transition.js +++ /dev/null @@ -1,65 +0,0 @@ - -define(function() { - - var Transition = Class.extend({ - init: function() { - this.startValue = 0; - this.endValue = 0; - this.duration = 0; - this.inProgress = false; - }, - - start: function(currentTime, updateFunction, stopFunction, startValue, endValue, duration) { - this.startTime = currentTime; - this.updateFunction = updateFunction; - this.stopFunction = stopFunction; - this.startValue = startValue; - this.endValue = endValue; - this.duration = duration; - this.inProgress = true; - this.count = 0; - }, - - step: function(currentTime) { - if(this.inProgress) { - if(this.count > 0) { - this.count -= 1; - log.debug(currentTime + ": jumped frame"); - } - else { - var elapsed = currentTime - this.startTime; - - if(elapsed > this.duration) { - elapsed = this.duration; - } - - var diff = this.endValue - this.startValue; - var i = this.startValue + ((diff / this.duration) * elapsed); - - i = Math.round(i); - - if(elapsed === this.duration || i === this.endValue) { - this.stop(); - if(this.stopFunction) { - this.stopFunction(); - } - } - else if(this.updateFunction) { - this.updateFunction(i); - } - } - } - }, - - restart: function(currentTime, startValue, endValue) { - this.start(currentTime, this.updateFunction, this.stopFunction, startValue, endValue, this.duration); - this.step(currentTime); - }, - - stop: function() { - this.inProgress = false; - } - }); - - return Transition; -}); diff --git a/client/js/updater.js b/client/js/updater.js deleted file mode 100644 index ebfef61e7..000000000 --- a/client/js/updater.js +++ /dev/null @@ -1,290 +0,0 @@ - -define(['character', 'timer'], function(Character, Timer) { - - var Updater = Class.extend({ - init: function(game) { - this.game = game; - this.playerAggroTimer = new Timer(1000); - }, - - update: function() { - this.updateZoning(); - this.updateCharacters(); - this.updatePlayerAggro(); - this.updateTransitions(); - this.updateAnimations(); - this.updateAnimatedTiles(); - this.updateChatBubbles(); - this.updateInfos(); - this.updateKeyboardMovement(); - }, - - updateCharacters: function() { - var self = this; - - this.game.forEachEntity(function(entity) { - var isCharacter = entity instanceof Character; - - if(entity.isLoaded) { - if(isCharacter) { - self.updateCharacter(entity); - self.game.onCharacterUpdate(entity); - } - self.updateEntityFading(entity); - } - }); - }, - - updatePlayerAggro: function() { - var t = this.game.currentTime, - player = this.game.player; - - // Check player aggro every 1s when not moving nor attacking - if(player && !player.isMoving() && !player.isAttacking() && this.playerAggroTimer.isOver(t)) { - player.checkAggro(); - } - }, - - updateEntityFading: function(entity) { - if(entity && entity.isFading) { - var duration = 1000, - t = this.game.currentTime, - dt = t - entity.startFadingTime; - - if(dt > duration) { - this.isFading = false; - entity.fadingAlpha = 1; - } else { - entity.fadingAlpha = dt / duration; - } - } - }, - - updateTransitions: function() { - var self = this, - m = null, - z = this.game.currentZoning; - - this.game.forEachEntity(function(entity) { - m = entity.movement; - if(m) { - if(m.inProgress) { - m.step(self.game.currentTime); - } - } - }); - - if(z) { - if(z.inProgress) { - z.step(this.game.currentTime); - } - } - }, - - updateZoning: function() { - var g = this.game, - c = g.camera, - z = g.currentZoning, - s = 3, - ts = 16, - speed = 500; - - if(z && z.inProgress === false) { - var orientation = this.game.zoningOrientation, - startValue = endValue = offset = 0, - updateFunc = null, - endFunc = null; - - if(orientation === Types.Orientations.LEFT || orientation === Types.Orientations.RIGHT) { - offset = (c.gridW - 2) * ts; - startValue = (orientation === Types.Orientations.LEFT) ? c.x - ts : c.x + ts; - endValue = (orientation === Types.Orientations.LEFT) ? c.x - offset : c.x + offset; - updateFunc = function(x) { - c.setPosition(x, c.y); - g.initAnimatedTiles(); - g.renderer.renderStaticCanvases(); - } - endFunc = function() { - c.setPosition(z.endValue, c.y); - g.endZoning(); - } - } else if(orientation === Types.Orientations.UP || orientation === Types.Orientations.DOWN) { - offset = (c.gridH - 2) * ts; - startValue = (orientation === Types.Orientations.UP) ? c.y - ts : c.y + ts; - endValue = (orientation === Types.Orientations.UP) ? c.y - offset : c.y + offset; - updateFunc = function(y) { - c.setPosition(c.x, y); - g.initAnimatedTiles(); - g.renderer.renderStaticCanvases(); - } - endFunc = function() { - c.setPosition(c.x, z.endValue); - g.endZoning(); - } - } - - z.start(this.game.currentTime, updateFunc, endFunc, startValue, endValue, speed); - } - }, - - updateCharacter: function(c) { - var self = this; - - // Estimate of the movement distance for one update - var tick = Math.round(16 / Math.round((c.moveSpeed / (1000 / this.game.renderer.FPS)))); - - if(c.isMoving() && c.movement.inProgress === false) { - if(c.orientation === Types.Orientations.LEFT) { - c.movement.start(this.game.currentTime, - function(x) { - c.x = x; - c.hasMoved(); - }, - function() { - c.x = c.movement.endValue; - c.hasMoved(); - c.nextStep(); - }, - c.x - tick, - c.x - 16, - c.moveSpeed); - } - else if(c.orientation === Types.Orientations.RIGHT) { - c.movement.start(this.game.currentTime, - function(x) { - c.x = x; - c.hasMoved(); - }, - function() { - c.x = c.movement.endValue; - c.hasMoved(); - c.nextStep(); - }, - c.x + tick, - c.x + 16, - c.moveSpeed); - } - else if(c.orientation === Types.Orientations.UP) { - c.movement.start(this.game.currentTime, - function(y) { - c.y = y; - c.hasMoved(); - }, - function() { - c.y = c.movement.endValue; - c.hasMoved(); - c.nextStep(); - }, - c.y - tick, - c.y - 16, - c.moveSpeed); - } - else if(c.orientation === Types.Orientations.DOWN) { - c.movement.start(this.game.currentTime, - function(y) { - c.y = y; - c.hasMoved(); - }, - function() { - c.y = c.movement.endValue; - c.hasMoved(); - c.nextStep(); - }, - c.y + tick, - c.y + 16, - c.moveSpeed); - } - } - }, - - updateKeyboardMovement: function() - { - if(!this.game.player || this.game.player.isMoving()) - return; - - var game = this.game; - var player = this.game.player; - - var pos = { - x: player.gridX, - y: player.gridY - }; - - if(player.moveUp) - { - pos.y -= 1; - game.keys(pos, Types.Orientations.UP); - } - else if(player.moveDown) - { - pos.y += 1; - game.keys(pos, Types.Orientations.DOWN); - } - else if(player.moveRight) - { - pos.x += 1; - game.keys(pos, Types.Orientations.RIGHT); - } - else if(player.moveLeft) - { - pos.x -= 1; - game.keys(pos, Types.Orientations.LEFT); - } - - }, - - updateAnimations: function() { - var t = this.game.currentTime; - - this.game.forEachEntity(function(entity) { - var anim = entity.currentAnimation; - - if(anim) { - if(anim.update(t)) { - entity.setDirty(); - } - } - }); - - var sparks = this.game.sparksAnimation; - if(sparks) { - sparks.update(t); - } - - var target = this.game.targetAnimation; - if(target) { - target.update(t); - } - }, - - updateAnimatedTiles: function() { - var self = this, - t = this.game.currentTime; - - this.game.forEachAnimatedTile(function (tile) { - if(tile.animate(t)) { - tile.isDirty = true; - tile.dirtyRect = self.game.renderer.getTileBoundingRect(tile); - - if(self.game.renderer.mobile || self.game.renderer.tablet) { - self.game.checkOtherDirtyRects(tile.dirtyRect, tile, tile.x, tile.y); - } - } - }); - }, - - updateChatBubbles: function() { - var t = this.game.currentTime; - - this.game.bubbleManager.update(t); - }, - - updateInfos: function() { - var t = this.game.currentTime; - - this.game.infoManager.update(t); - } - }); - - return Updater; -}); diff --git a/client/js/util.js b/client/js/util.js deleted file mode 100644 index 060fe8a86..000000000 --- a/client/js/util.js +++ /dev/null @@ -1,34 +0,0 @@ -Function.prototype.bind = function (bind) { - var self = this; - return function () { - var args = Array.prototype.slice.call(arguments); - return self.apply(bind || null, args); - }; -}; - -var isInt = function(n) { - return (n % 1) === 0; -}; - -var TRANSITIONEND = 'transitionend webkitTransitionEnd oTransitionEnd'; - -// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ -window.requestAnimFrame = (function(){ - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(/* function */ callback, /* DOMElement */ element){ - window.setTimeout(callback, 1000 / 60); - }; -})(); - -var getUrlVars = function() { - //from http://snipplr.com/view/19838/get-url-parameters/ - var vars = {}; - var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { - vars[key] = value; - }); - return vars; -} diff --git a/client/js/warrior.js b/client/js/warrior.js deleted file mode 100644 index 6bf049acc..000000000 --- a/client/js/warrior.js +++ /dev/null @@ -1,11 +0,0 @@ - -define(['player'], function(Player) { - - var Warrior = Player.extend({ - init: function(id, name) { - this._super(id, name, Types.Entities.WARRIOR); - }, - }); - - return Warrior; -}); From d9b658e329682b4854e2a337a32d458723d38727 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 8 Sep 2013 15:56:34 -0400 Subject: [PATCH 14/14] Temporarily disable testing on CoffeeScript branch --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 102825f98..1947e156c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,10 @@ node_js: - "0.8" - "0.10" +branches: + except: + - coffeescript + deploy: provider: openshift user: "aa1ronham@gmail.com"