├── logs └── .gitignore ├── doc ├── demo.png ├── drug.png ├── gun.png ├── hide.png ├── mine.png ├── demo1.gif ├── demo2.gif ├── demo3.gif ├── error.png ├── flypack.png ├── grenade.png ├── power.png ├── random.png └── socket.io vs ws.png ├── static ├── imgs │ ├── arm.png │ ├── bomb.png │ ├── jet.png │ ├── mine.png │ ├── sign.png │ ├── grenade.png │ ├── head │ │ ├── girl.png │ │ ├── win.png │ │ ├── wtf.png │ │ ├── alone.png │ │ ├── alone2.png │ │ ├── alone3.png │ │ ├── danger.png │ │ ├── happy.png │ │ ├── normal.png │ │ ├── throll.png │ │ └── danger3.png │ ├── item │ │ ├── drug.png │ │ ├── gun.png │ │ ├── hide.png │ │ ├── mine.png │ │ ├── power.png │ │ ├── random.png │ │ ├── flypack.png │ │ └── grenade.png │ ├── itemGate.png │ └── tile │ │ ├── door.png │ │ ├── sign.png │ │ ├── dooropen.png │ │ └── itemGate.png ├── js │ ├── const.js │ ├── pcController.js │ ├── mobileController.js │ ├── socket.js │ ├── JPack.js │ ├── effect.js │ ├── game.js │ └── zepto.js ├── rooms.html ├── canvas.html ├── index.html ├── css │ └── game.css ├── game.css ├── admin.html ├── td.html └── tree.html ├── .gitignore ├── game ├── ai │ ├── AIController.js │ ├── Path.js │ └── AI.js ├── struct │ ├── sign.js │ ├── itemGate.js │ └── door.js ├── entity │ └── grenade.js ├── item.js ├── room.js ├── maps │ ├── lesson1.js │ └── lesson2.js ├── ai.js ├── lib │ └── DataSync.js ├── collide.js ├── client.js ├── map.js ├── game.js └── user.js ├── changelog.md ├── gulpfile.js ├── package.json ├── LICENSE ├── README.md └── app.js /logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /doc/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/demo.png -------------------------------------------------------------------------------- /doc/drug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/drug.png -------------------------------------------------------------------------------- /doc/gun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/gun.png -------------------------------------------------------------------------------- /doc/hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/hide.png -------------------------------------------------------------------------------- /doc/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/mine.png -------------------------------------------------------------------------------- /doc/demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/demo1.gif -------------------------------------------------------------------------------- /doc/demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/demo2.gif -------------------------------------------------------------------------------- /doc/demo3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/demo3.gif -------------------------------------------------------------------------------- /doc/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/error.png -------------------------------------------------------------------------------- /doc/flypack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/flypack.png -------------------------------------------------------------------------------- /doc/grenade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/grenade.png -------------------------------------------------------------------------------- /doc/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/power.png -------------------------------------------------------------------------------- /doc/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/random.png -------------------------------------------------------------------------------- /static/imgs/arm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/arm.png -------------------------------------------------------------------------------- /static/imgs/bomb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/bomb.png -------------------------------------------------------------------------------- /static/imgs/jet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/jet.png -------------------------------------------------------------------------------- /static/imgs/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/mine.png -------------------------------------------------------------------------------- /static/imgs/sign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/sign.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | node_modules/ 8 | npm-debug.log 9 | -------------------------------------------------------------------------------- /doc/socket.io vs ws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/doc/socket.io vs ws.png -------------------------------------------------------------------------------- /static/imgs/grenade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/grenade.png -------------------------------------------------------------------------------- /static/imgs/head/girl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/girl.png -------------------------------------------------------------------------------- /static/imgs/head/win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/win.png -------------------------------------------------------------------------------- /static/imgs/head/wtf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/wtf.png -------------------------------------------------------------------------------- /static/imgs/item/drug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/item/drug.png -------------------------------------------------------------------------------- /static/imgs/item/gun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/item/gun.png -------------------------------------------------------------------------------- /static/imgs/item/hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/item/hide.png -------------------------------------------------------------------------------- /static/imgs/item/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/item/mine.png -------------------------------------------------------------------------------- /static/imgs/itemGate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/itemGate.png -------------------------------------------------------------------------------- /static/imgs/tile/door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/tile/door.png -------------------------------------------------------------------------------- /static/imgs/tile/sign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/tile/sign.png -------------------------------------------------------------------------------- /static/imgs/head/alone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/alone.png -------------------------------------------------------------------------------- /static/imgs/head/alone2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/alone2.png -------------------------------------------------------------------------------- /static/imgs/head/alone3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/alone3.png -------------------------------------------------------------------------------- /static/imgs/head/danger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/danger.png -------------------------------------------------------------------------------- /static/imgs/head/happy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/happy.png -------------------------------------------------------------------------------- /static/imgs/head/normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/normal.png -------------------------------------------------------------------------------- /static/imgs/head/throll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/throll.png -------------------------------------------------------------------------------- /static/imgs/item/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/item/power.png -------------------------------------------------------------------------------- /static/imgs/item/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/item/random.png -------------------------------------------------------------------------------- /static/imgs/head/danger3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/head/danger3.png -------------------------------------------------------------------------------- /static/imgs/item/flypack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/item/flypack.png -------------------------------------------------------------------------------- /static/imgs/item/grenade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/item/grenade.png -------------------------------------------------------------------------------- /static/imgs/tile/dooropen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/tile/dooropen.png -------------------------------------------------------------------------------- /static/imgs/tile/itemGate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanyuxin/baogame/HEAD/static/imgs/tile/itemGate.png -------------------------------------------------------------------------------- /static/js/const.js: -------------------------------------------------------------------------------- 1 | var C = { 2 | TW: 40, //tile width 3 | TH: 40, //tile height 4 | IS: 15, //item size 5 | 6 | GAME_STATUS_INIT: 1, 7 | GAME_STATUS_RUNNING: 2, 8 | GAME_STATUS_PAUSE: 3, 9 | GAME_STATUS_OVER: 4 10 | } 11 | 12 | if (!this.CSS) { 13 | module.exports = C; 14 | } -------------------------------------------------------------------------------- /game/ai/AIController.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var AI = require('./AI.js'); 4 | var Path = require('./Path.js'); 5 | 6 | function AIController (game) { 7 | this.game = game; 8 | this.path = new Path(game.map, game.props); 9 | } 10 | 11 | AIController.prototype = { 12 | userAI: function (user, config) { 13 | return new AI(this, user, config); 14 | } 15 | } 16 | 17 | 18 | module.exports = AIController; -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | #v1.3.3 2017-03-06 2 | 增加第一种可操作建筑:AI传送门 3 | AI数量不再由房主控制,所有玩家可以选择开启或关闭传送门 4 | 5 | #v1.3.2 2017-02-28 6 | 增加了地图控制,游戏时间最长的玩家可以修改地图配置 7 | AI看不到隐形单位 8 | 9 | #v1.3.1 2017-02-25 10 | 优化了展示面板 11 | 12 | #v1.3.0 2017-02-24 13 | 增加了AI 14 | 15 | #v1.2.0 2016-01-28 16 | 增加了单人教学 17 | 18 | #v1.1.0 2016-01-17 19 | 增加了手雷 按住act扔出,按住的时间越久,扔的越远,扔出后一段时间爆炸;蹲下时扔出的手雷会贴着地滚。 20 | 21 | #v1.0.0 2016-01-15 22 | 初始版本 23 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var minifyCSS = require('gulp-csso'); 3 | var uglify = require('gulp-uglify'); 4 | var pump = require('pump'); 5 | var concat = require('gulp-concat'); 6 | 7 | gulp.task('css', function(){ 8 | return gulp.src('static/js/*.css') 9 | .pipe(minifyCSS()) 10 | .pipe(gulp.dest('build/css')) 11 | }); 12 | 13 | gulp.task('js', function (cb) { 14 | pump([ 15 | gulp.src('static/js/*.js'), 16 | concat('all.js'), 17 | uglify(), 18 | gulp.dest('build/js') 19 | ], 20 | cb 21 | ); 22 | }); 23 | 24 | gulp.task('default', [ 'css', 'js' ]); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuzion-game", 3 | "description": "a html5 mutiplayer game", 4 | "version": "1.3.0", 5 | "private": false, 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:guanyuxin/baogame.git" 9 | }, 10 | "engines": { 11 | "node": ">=4" 12 | }, 13 | "dependencies": { 14 | "compression": ">=1.0.0", 15 | "cookieparser": "^0.1.0", 16 | "express": ">=4.0", 17 | "serve-static": ">=0.0.0", 18 | "ws": "^1.0.1" 19 | }, 20 | "devDependencies": { 21 | "gulp": "^3.9.1", 22 | "gulp-concat": "^2.6.1", 23 | "gulp-csso": "^2.0.0", 24 | "gulp-uglify": "^2.0.1", 25 | "pump": "^1.0.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /game/struct/sign.js: -------------------------------------------------------------------------------- 1 | 2 | var DataSync = require('../lib/DataSync.js'); 3 | 4 | var Sign = function (game, data) { 5 | this.game = game; 6 | 7 | this.sync = new DataSync({ 8 | id: data.id, 9 | type: "sign", 10 | x: data.x, 11 | y: data.y, 12 | working: 0, //运行 13 | workingTime: data.workingTime || 20, //工作耗时 14 | coolingTime: data.coolingTime || 200, //冷却耗时 15 | cooling: 0, //冷却 16 | openMax: data.openMax || 200, 17 | opening: data.opening || data.openMax || 200, //开启状态 18 | }, this); 19 | } 20 | Sign.prototype.update = function () { 21 | 22 | } 23 | Sign.prototype.getData = function () { 24 | return { 25 | id: this.id, 26 | type: "sign", 27 | x: this.x, 28 | y: this.y, 29 | message: this.message 30 | } 31 | } 32 | module.exports = Sign; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) <2015> 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /game/entity/grenade.js: -------------------------------------------------------------------------------- 1 | var Grenade = function (user) { 2 | this.x = 0; 3 | this.y = 0; 4 | this.creater = user; 5 | this.vx = 0; 6 | this.vy = 0; 7 | this.life = 100; 8 | this.r = 0; 9 | this.dead = false; 10 | this.game = user.game; 11 | } 12 | 13 | Grenade.prototype.update = function () { 14 | this.x += this.vx; 15 | this.r += this.vx / 5; 16 | if (this.x < 0 || this.x > this.game.props.w) { 17 | this.vx *= -1; 18 | } 19 | 20 | this.vy -= .2; 21 | this.vy = Math.max(this.vy, -6); 22 | 23 | if (this.vy > 0) { 24 | this.y += Math.floor(this.vy); 25 | } else { 26 | for (var i = 0; i < -this.vy; i++) { 27 | if(this.game.map.onFloor(this.x, this.y)) { 28 | if (this.game.map.onPilla(this.x, this.y)) { 29 | this.vx *=.7; 30 | } else { 31 | this.vy *= -.85; 32 | break; 33 | } 34 | } 35 | this.y--; 36 | } 37 | } 38 | if (this.y < 0) { 39 | this.dead = true; 40 | } 41 | this.life--; 42 | if (this.life < 0) { 43 | this.dead = true; 44 | this.game.explode(this.x, this.y, this.creater, 100); 45 | } 46 | } 47 | 48 | module.exports = Grenade; -------------------------------------------------------------------------------- /game/struct/itemGate.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var C = require('../../static/js/const.js'); 4 | var DataSync = require('../lib/DataSync.js'); 5 | 6 | var ItemGate = function (game, data) { 7 | this.itemType = data.itemType; 8 | 9 | this.sync = new DataSync({ 10 | id: data.id, 11 | type: "itemGate", 12 | x: data.x, 13 | y: data.y, 14 | working: 0, //运行 15 | workingTime: data.workingTime || 20, //工作耗时 16 | coolingTime: data.coolingTime || 200, //冷却耗时 17 | cooling: 0, //冷却 18 | openMax: data.openMax || 200, 19 | opening: data.opening || data.openMax || 200, //开启状态 20 | }, this); 21 | 22 | this.game = game; 23 | } 24 | ItemGate.prototype.update = function () { 25 | if (this.itemType !== undefined) { 26 | if ((this.game.tick + 120) % 150 == 0 && (!this.targetItem || this.targetItem.dead)) { 27 | var item = this.game.createItem(this.itemType); 28 | item.x = (this.x + .5) * C.TW; 29 | item.y = (this.y + .5) * C.TH; 30 | item.vx = 0; 31 | item.vy = 0; 32 | this.targetItem = item; 33 | } 34 | } else { 35 | //生成物品(如果需要) 36 | if (this.game.items.length < this.game.users.length && Math.random() * 100 < this.game.users.length) { 37 | var item = this.game.createItem(); 38 | item.x = (this.x + .5) * C.TW; 39 | item.y = (this.y + .5) * C.TH; 40 | } 41 | } 42 | } 43 | module.exports = ItemGate; -------------------------------------------------------------------------------- /game/struct/door.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var C = require('../../static/js/const.js'); 4 | var DataSync = require('../lib/DataSync.js'); 5 | 6 | 7 | var Door = function (game, data) { 8 | this.game = game; 9 | 10 | this.sync = new DataSync({ 11 | id: data.id, 12 | type: "door", 13 | x: data.x, 14 | y: data.y, 15 | message: "AI传送门,使用空格键开启/关闭", 16 | working: 0, //运行 17 | workingTime: data.workingTime || 80, //工作耗时 18 | coolingTime: data.coolingTime || 2000, //冷却耗时 19 | cooling: 0, //冷却 20 | opening: data.opening == undefined ? true : data.opening, //开启状态 21 | }, this); 22 | 23 | //最多使用几次 24 | this.count = data.count; 25 | 26 | //同时最多控制多少npc 27 | this.liveCount = data.liveCount; 28 | 29 | this.npcConfig = data.npcConfig; 30 | 31 | this.users = []; 32 | } 33 | Door.prototype.act = function () { 34 | this.opening = !this.opening; 35 | } 36 | Door.prototype.createMob = function () { 37 | var npc = this.game.createNPC({name: "萌萌的AI", npc: true, AI: "auto"}); 38 | npc.x = (this.x + .5) * C.TW; 39 | npc.y = (this.y + .5) * C.TH; 40 | return npc; 41 | } 42 | Door.prototype.update = function () { 43 | if ((!this.targetMob || this.targetMob.dead) && this.opening) { 44 | this.working++; 45 | if (this.working >= this.workingTime) { 46 | var item = this.createMob(); 47 | this.targetMob = item; 48 | this.working = 0; 49 | } 50 | } 51 | } 52 | module.exports = Door; -------------------------------------------------------------------------------- /static/js/pcController.js: -------------------------------------------------------------------------------- 1 | //pc的控制器 2 | var p1 = { 3 | upDown: 0, 4 | downDown: 0, 5 | leftDown: 0, 6 | rightDown: 0, 7 | itemDown: false, 8 | team: 0 9 | } 10 | 11 | document.addEventListener('keydown', function (e) { 12 | if (e.keyCode == 87) { 13 | if (!p1.upDown) {p1.upPress = true} 14 | p1.upDown = true; 15 | } else if (e.keyCode == 83) { 16 | if (!p1.downDown) {p1.downPress = true} 17 | p1.downDown = true; 18 | } else if (e.keyCode == 65) { 19 | if (!p1.leftDown) {p1.leftPress = true} 20 | p1.leftDown = true; 21 | } else if (e.keyCode == 68) { 22 | if (!p1.rightDown) {p1.rightPress = true} 23 | p1.rightDown = true; 24 | } else if (e.keyCode == 81) { 25 | if (!p1.itemDown) {p1.itemPress = true} 26 | p1.itemDown = true; 27 | } else if (e.keyCode == 32) { 28 | if (!p1.spaceDown) {p1.spacePress = true} 29 | p1.spaceDown = true; 30 | e.preventDefault(); 31 | } 32 | }); 33 | document.addEventListener('keyup', function (e) { 34 | if (e.keyCode == 87) { 35 | p1.upDown = false; 36 | } else if (e.keyCode == 83) { 37 | p1.downDown = false; 38 | } else if (e.keyCode == 65) { 39 | p1.leftDown = false; 40 | } else if (e.keyCode == 68) { 41 | p1.rightDown = false; 42 | } else if (e.keyCode == 81) { 43 | p1.itemDown = false; 44 | } else if (e.keyCode == 32) { 45 | p1.spaceDown = false; 46 | e.preventDefault(); 47 | } else if (e.keyCode == 69) { 48 | p1.onJoin && p1.onJoin(); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /game/item.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var Pack = require('../static/js/JPack.js'); 3 | var C = require('../static/js/const.js'); 4 | 5 | var Items = []; 6 | for (let key in Pack.items) { 7 | Items.push(Pack.items[key]); 8 | } 9 | 10 | var Item = function (game, type) { 11 | this.game = game; 12 | if (type === undefined) { 13 | type = Math.floor(Math.random() * Items.length); 14 | } 15 | this.id = Items[type].id; 16 | this.count = Items[type].count || 0; 17 | this.lifetime = 3000; 18 | this.slowdown = 0; 19 | this.vx = Math.random()+.5; 20 | this.vy = Math.random()+.5; 21 | this.dead = false; 22 | } 23 | Item.prototype.update = function () { 24 | this.slowdown++; 25 | if (this.x >= this.game.props.w - C.IS || this.x <= C.IS) { 26 | this.vx *= -1 27 | } 28 | 29 | if (this.y >= this.game.props.h - C.IS || this.y <= C.IS) { 30 | this.vy *= -1 31 | } 32 | this.lifetime--; 33 | if (this.lifetime < 0) { 34 | this.dead = true; 35 | } 36 | if (this.slowdown < 100) { 37 | this.x += this.vx * this.slowdown/100; 38 | this.y += this.vy * this.slowdown/100; 39 | } else { 40 | this.x += this.vx; 41 | this.y += this.vy; 42 | } 43 | } 44 | Item.prototype.touchUser = function (u) { 45 | if (this.id == Pack.items.drug.id) { 46 | this.dead = true; 47 | u.killed('drug'); 48 | } else { 49 | this.dead = true; 50 | u.carry = this.id; 51 | u.carryCount = this.count; 52 | } 53 | } 54 | Item.prototype.getData = function () { 55 | return Pack.itemPack.encode(this); 56 | } 57 | 58 | module.exports = Item; -------------------------------------------------------------------------------- /static/js/mobileController.js: -------------------------------------------------------------------------------- 1 | //移动的控制器 2 | var p1 = { 3 | upDown: 0, 4 | downDown: 0, 5 | leftDown: 0, 6 | rightDown: 0, 7 | itemDown: 0, 8 | team: 0 9 | } 10 | 11 | document.body.addEventListener('touchmove', function (e) { 12 | e.preventDefault(); 13 | }); 14 | 15 | $('.notice').hide(); 16 | $('.mobileController').show(); 17 | 18 | 19 | $('.mobileController .moreBtn').on('touchstart', function (e) { 20 | var t = $(e.currentTarget).data('act'); 21 | if (t == 'a') { 22 | if (!p1.itemDown) { 23 | p1.itemPress = true; 24 | } 25 | p1.itemDown = 20000; 26 | } else if (t == 'l') { 27 | if (!p1.leftDown) { 28 | p1.leftPress = true; 29 | } 30 | p1.leftDown = 20000; 31 | } else if (t == 'r') { 32 | if (!p1.rightDown) { 33 | p1.rightPress = true; 34 | } 35 | p1.rightDown = 20000; 36 | } else if (t == 'u') { 37 | if (!p1.upDown) { 38 | p1.upPress = true; 39 | } 40 | p1.upDown = 20000; 41 | } else if (t == 'd') { 42 | if (!p1.downDown) { 43 | p1.downPress = true; 44 | } 45 | p1.downDown = 20000; 46 | } 47 | }); 48 | 49 | $('.mobileController .moreBtn').on('touchend', function (e) { 50 | var t = $(e.currentTarget).data('act'); 51 | if (t == 'a') { 52 | p1.itemDown = 0; 53 | } else if (t == 'l') { 54 | p1.leftDown = 0; 55 | } else if (t == 'r') { 56 | p1.rightDown = 0; 57 | } else if (t == 'u') { 58 | p1.upDown = 0; 59 | } else if (t == 'd') { 60 | p1.downDown = 0; 61 | } 62 | }); 63 | 64 | $('.joining .joinBtn').click(function () {joing(true)}); 65 | $('.joining .dismissBtn').click(function () { 66 | $('.joining').hide(); 67 | }); 68 | 69 | 70 | initDone && initDone(); -------------------------------------------------------------------------------- /game/room.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var Game = require('./game.js'); 3 | 4 | var roomID = 1; 5 | var rooms = []; 6 | 7 | //定期回收房间 8 | setInterval(function () { 9 | for (var i = 0; i < rooms.length; i++) { 10 | if (rooms[i].game.clients.length == 0 && !rooms[i].presist) { 11 | if (rooms[i].dead > 10) { 12 | rooms.splice(i, 1); 13 | } else if (rooms[i].dead > 0) { 14 | rooms[i].dead++; 15 | } else { 16 | rooms[i].dead = 1; 17 | } 18 | } 19 | } 20 | }, 1000); 21 | 22 | var Room = { 23 | setConfig: function (code) { 24 | this.code = code; 25 | }, 26 | createRoom: function (type, presist) { 27 | var maxUser = 6; 28 | 29 | var room = { 30 | id: roomID++, 31 | presist: presist, 32 | game: new Game(this.code, maxUser, type, Room.removeRoom), 33 | name: type 34 | } 35 | rooms.push(room); 36 | return room; 37 | }, 38 | userCount: function () { 39 | var c = 0; 40 | for (var i = 0; i < rooms.length; i++) { 41 | c += rooms.game.clients.length; 42 | } 43 | return c; 44 | }, 45 | removeRoom: function (game) { 46 | for (var i = 0; i < rooms.length; i++) { 47 | if (rooms[i].game == game) { 48 | rooms.splice(i, 1); 49 | break; 50 | } 51 | } 52 | }, 53 | findRoom: function (roomID) { 54 | for (var i = 0; i < rooms.length; i++) { 55 | if (rooms[i].id == roomID) { 56 | return rooms[i]; 57 | } 58 | } 59 | }, 60 | getRoomData: function () { 61 | var rdata = []; 62 | for (let room of rooms) { 63 | var users = 0; 64 | for (let user of room.game.users) { 65 | if (!user.npc) { 66 | users++; 67 | } 68 | } 69 | rdata.push({ 70 | id: room.id, 71 | maxUser: room.game.maxUser, 72 | users: users, 73 | name: room.name 74 | }) 75 | } 76 | return rdata; 77 | } 78 | } 79 | module.exports = Room; -------------------------------------------------------------------------------- /game/maps/lesson1.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var map = { 4 | type: "rpg", 5 | w: 22, 6 | h: 12, 7 | npcMAX: 0, 8 | floor: [ 9 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 10 | [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0], 11 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 12 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 13 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 14 | [0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,0], 15 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 16 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 17 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 18 | [0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0], 19 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 20 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 21 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 22 | ], 23 | pilla: [{ 24 | x: 19.5, 25 | y1: 1, 26 | y2: 6, 27 | }, { 28 | x: 6.5, 29 | y1: 5, 30 | y2: 10, 31 | }], 32 | borns: [{ 33 | x: 2, 34 | y: 1 35 | }], 36 | npcs: [{ 37 | x: 14, 38 | y: 9, 39 | name: "王二狗" 40 | }], 41 | structs: [ 42 | { 43 | type: "sign", 44 | x: 2, 45 | y: 1, 46 | message: "A,D 左右移动" 47 | }, { 48 | type: "sign", 49 | x: 10, 50 | y: 1, 51 | message: "如果你掉进水中就会失败,你需要按W跳过去" 52 | }, { 53 | type: "sign", 54 | x: 17, 55 | y: 1, 56 | message: "走到梯子上,按W,S上下层" 57 | }, { 58 | type: "sign", 59 | x: 14, 60 | y: 5, 61 | message: "对于宽阔的缝隙,你需要助跑并跳过去" 62 | }, { 63 | type: "sign", 64 | x: 8, 65 | y: 5, 66 | message: "目标:消灭王二狗" 67 | }, { 68 | type: "sign", 69 | x: 7, 70 | y: 9, 71 | message: "利用与对手的碰撞,把敌人推进水中" 72 | }, { 73 | type: "sign", 74 | x: 10, 75 | y: 9, 76 | message: "碰撞的效果取决于双方的姿势,通常下蹲和跳起方可以获得优势" 77 | } 78 | ], 79 | hooks: { 80 | onKilled: function (game, u) { 81 | if (u.npc) { 82 | game.win(u); 83 | } 84 | } 85 | } 86 | } 87 | 88 | module.exports = map; -------------------------------------------------------------------------------- /static/rooms.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 暴走大乱斗 5 | 6 | 40 | 41 | 42 |
43 |

乱斗:

44 |
45 |
46 |

教程:

47 |
48 | 基础教学 49 | 物品教学 50 |
51 |

竞技:

52 |
53 | 54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /static/js/socket.js: -------------------------------------------------------------------------------- 1 | var socket = { 2 | open: false, 3 | error: null, 4 | queueData: [], 5 | ws: null, 6 | begin: function (roomID) { 7 | var _this = this; 8 | _this.ws = new WebSocket("ws://"+location.host+"?roomID="+(roomID || 1)); 9 | _this.ws.onopen = function () { 10 | _this.open = true; 11 | for (var i = 0; i < _this.queueData.length; i++) { 12 | _this.emit(_this.queueData[i].name, _this.queueData[i].data); 13 | } 14 | }; 15 | 16 | // 消息处理 17 | _this.ws.onmessage = function (evt) { 18 | function processData (str) { 19 | var $s = str.indexOf('$'); 20 | if ($s == -1) { 21 | var name = str; 22 | } else { 23 | var name = str.substring(0, $s); 24 | var data = JSON.parse(str.substring($s + 1)); 25 | } 26 | //被服务器主动关闭 27 | if (name == "close") { 28 | this.open = false; 29 | this.error = data; 30 | } 31 | _this.listeners[name] && _this.listeners[name](data); 32 | } 33 | 34 | // 接收处理的数据 35 | if (evt.data instanceof Blob) { 36 | // 二进制 37 | var reader = new FileReader(); 38 | reader.addEventListener("loadend", function () { 39 | var x = new Uint8Array(reader.result); 40 | var res = LZString.decompressFromUint8Array(x); 41 | processData(res); 42 | }); 43 | reader.readAsArrayBuffer(evt.data); 44 | } else { 45 | // 文本数据 46 | processData(evt.data); 47 | } 48 | }; 49 | // 断线重连,1.5s 50 | _this.ws.onclose = function (evt) { 51 | if (_this.open) { 52 | setTimeout(function () { 53 | socket.begin(); 54 | }, 1500); 55 | } 56 | }; 57 | // 打印异常 58 | _this.ws.onerror = function (evt) { 59 | console.log("WebSocketError"); 60 | }; 61 | }, 62 | 63 | // 发送消息 64 | emit: function (name, data) { 65 | if (!this.open) { 66 | this.queueData.push({name: name, data: data}); 67 | } else { 68 | this.ws.send(name+"$"+JSON.stringify(data || {})); 69 | } 70 | }, 71 | 72 | // 回调功能 73 | on: function (name, callback) { 74 | this.listeners[name] = callback; 75 | }, 76 | close: function () { 77 | this.open = false; 78 | this.ws.close(); 79 | }, 80 | listeners: {} 81 | } 82 | -------------------------------------------------------------------------------- /game/ai.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var Pack = require('../static/js/JPack.js'); 3 | var C = require('../static/js/const.js'); 4 | 5 | function userCanGoLeft (user, map) { 6 | var x = Math.floor((user.x - 5) / C.TW); 7 | var y = Math.floor(user.y / C.TH); 8 | return user.game.map.floor[y][x]; 9 | } 10 | function userCanGoRight (user, map) { 11 | var x = Math.floor((user.x + 5) / C.TW); 12 | var y = Math.floor(user.y / C.TH); 13 | return user.game.map.floor[y][x]; 14 | } 15 | function userCanJumpLeft (user) { 16 | if (user.vx > -1) return false; 17 | var x = Math.floor((user.x + 5) / C.TW); 18 | var y = Math.floor(user.y / C.TH); 19 | return user.game.map.floor[y][x - 3] || false; 20 | } 21 | function userCanJumpRight (user) { 22 | if (user.vx < 1) return false; 23 | var x = Math.floor((user.x + 5) / C.TW); 24 | var y = Math.floor(user.y / C.TH); 25 | return user.game.map.floor[y][x + 3] || false; 26 | } 27 | function playerAI (user) { 28 | user.upDown = false; 29 | 30 | if (user.status == "standing") { 31 | if (user.carry == 1 || true) { 32 | if (user.goleft || !user.goright) { 33 | user.goleft = true; 34 | if (userCanGoLeft(user)) { 35 | user.leftDown = 200; 36 | } else if (userCanJumpLeft(user)) { 37 | user.upDown = 200; 38 | user.leftDown = 200; 39 | } else { 40 | user.goleft = false; 41 | user.goright = true; 42 | user.leftDown = 0; 43 | } 44 | } 45 | if (user.goright) { 46 | if (userCanGoRight(user)) { 47 | user.rightDown = 200; 48 | } else if (userCanJumpRight(user)) { 49 | user.upDown = 200; 50 | user.rightDown = 200; 51 | } else { 52 | user.goleft = true; 53 | user.goright = false; 54 | user.rightDown = 0; 55 | } 56 | } 57 | } else if (user.carry == 2) { 58 | var find = false; 59 | for (let other of user.game.users) { 60 | if (user == other || other.dieing) {continue} 61 | if (Math.abs(other.y - user.y) < 10 && other.carry != Pack.items.hide.id) { 62 | if (user.facing && other.x < user.x || !user.facing && other.x > user.x) { 63 | find = true; 64 | break; 65 | } 66 | } 67 | } 68 | user.itemPress = find; 69 | } 70 | } else { 71 | 72 | } 73 | } 74 | module.exports = playerAI; -------------------------------------------------------------------------------- /game/lib/DataSync.js: -------------------------------------------------------------------------------- 1 | //数据增量修改记录器 2 | 3 | function DataSync (schema, obj) { 4 | this.schema = schema; 5 | this.obj = obj || this; 6 | this.store = {}; 7 | this.dist = {}; 8 | this.clean = true; 9 | 10 | var shce = {}; 11 | for (var key in schema) {((key) => { 12 | this.store[key] = schema[key]; 13 | shce[key] = { 14 | get: () => { 15 | return this.store[key]; 16 | }, 17 | set: (value) => { 18 | if (typeof value == "number" || typeof value == "string" || typeof value == "boolean") { 19 | if (this.store[key] === value) { 20 | return; 21 | } 22 | this.store[key] = value; 23 | } else { 24 | //throw "DataSync not support direct update obj" 25 | this.store[key] = value; 26 | } 27 | this.dist[key] = value; 28 | this.clean = false; 29 | } 30 | } 31 | })(key)} 32 | Object.defineProperties(obj || this, shce); 33 | } 34 | 35 | DataSync.prototype.isClean = function () { 36 | if (!this.clean) { 37 | return false; 38 | } 39 | for (var key in this.schema) { 40 | if (Array.isArray(this.store[key])) { 41 | for (var i = 0; i < this.store[key].length; i++) { 42 | if (this.store[key][i].sync && !this.store[key][i].sync.isClean()) { 43 | return false; 44 | } 45 | } 46 | } 47 | } 48 | return true; 49 | } 50 | DataSync.prototype.flush = function () { 51 | if (this.isClean() == false) { 52 | this.clean = true; 53 | var res = this.dist; 54 | for (var key in this.schema) { 55 | if (Array.isArray(this.store[key])) { 56 | for (var i = 0; i < this.store[key].length; i++) { 57 | if (this.store[key][i].sync && !this.store[key][i].sync.isClean()) { 58 | res[key+":"+i] = this.store[key][i].sync.flush(); 59 | } 60 | } 61 | } 62 | } 63 | this.dist = {}; 64 | return res; 65 | } else { 66 | return null; 67 | } 68 | } 69 | 70 | DataSync.prototype.all = function () { 71 | var data = {}; 72 | for (var key in this.schema) { 73 | if (Array.isArray(this.store[key])) { 74 | data[key] = []; 75 | for (var i = 0; i < this.store[key].length; i++) { 76 | data[key].push(this.store[key][i].sync.all()); 77 | } 78 | } else { 79 | data[key] = this.store[key]; 80 | } 81 | } 82 | return data; 83 | } 84 | 85 | module.exports = DataSync; -------------------------------------------------------------------------------- /static/canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Obj design 4 | 5 | 33 | 34 |
35 |
36 |
37 |
38 | [.,] 39 |
40 |
41 |
42 | 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![demo](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/demo1.gif) 2 | # 在线游戏 3 | 访问: http://guanyuxin.com/ 4 | 5 | 由于官方服务器带宽和延迟较大,想玩的同学可以通过搭建私服在局域网中和好朋友一起玩。搭建私服方法在末尾给出。 6 |
7 | *2016-01-17修改了手雷的控制方式:按住act扔出,按住的时间越久,扔的越远,扔出后一段时间爆炸;蹲下时扔出的手雷会贴着地滚。* 8 | 9 | -- 10 | # 游戏简介 11 | 12 | ### 游戏目标: 13 | 把其他人推下水 14 | 使用道具消灭其他人 15 | have fun 16 | 17 | ### 加入: 18 | 通过浏览器打开后可以查看目前的游戏状态,选择加入后可以进行游戏 19 | 20 | 加入前给自己起个有个性的名字吧 21 | 22 | 自己的角色顶部有黄色名称,敌人角色顶部有红色名称 23 | 24 | ### 移动碰撞: 25 | wasd控制移动,当玩家处于平台上时,w为跳跃,d为下蹲;当玩家处于梯子附近时(头顶出现上下箭头),w,d为爬梯子上下 26 | 27 | ** 手机上使用虚拟按键控制 ** 28 | 29 | 两个玩家接触后会产生碰撞,将两个玩家弹开,使用这个机制把敌人推下平台吧 30 | 31 | 通常情况下,两个玩家碰撞时,跳起或者蹲下的一方会有优势 32 | 33 | ### 道具: 34 | 游戏中有紫色的能量球,玩家吃到后会产生各种能力或者效果,有些道具的效果能力强大,好好使用他们。详情在道具部分介绍。 35 | 36 | ![demo](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/demo2.gif) 37 | 38 | # 道具 39 | ### 毒药: 40 | ![drug](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/drug.png) 41 | 42 | *大部分道具是强大而有益的,但是看到毒药你还是离他远一些为好,他会让吃到他的玩家立即死亡* 43 | 44 | ### 手枪: 45 | ![drug](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/gun.png) 46 | 47 | *按q向面前发射一颗子弹,消灭任何敢于正面对抗你的敌人,注意:只有三发子弹,请节约使用。无法消灭下蹲或者跳起的敌人* 48 | 49 | ### 无敌: 50 | ![drug](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/power.png) 51 | 52 | *并不是真正的无敌,但是会让你直接消灭所有敢于触碰你的敌人,并且他们无法给你造成碰撞* 53 | 54 | ### 隐身: 55 | ![drug](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/hide.png) 56 | 57 | *使用后慢慢从你的敌人视野里面消失,谁能和看不见的敌人战斗呢?* 58 | 59 | ### 惊喜: 60 | ![drug](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/random.png) 61 | 62 | *surprise !* 63 | 64 | ### 喷气背包: 65 | ![drug](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/flypack.png) 66 | 67 | *跳的不够高?干嘛不飞呢!跳起后再次按w进入飞行模式,让那些只会蹦跶的人羡慕吧。等等,好像没油了...* 68 | 69 | ### 手雷: 70 | ![drug](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/grenade.png) 71 | 72 | *按住act扔出,按住的时间越久,扔的越远,扔出后一段时间爆炸;蹲下时扔出的手雷会贴着地滚。手雷扔出的速度受角色影响,所以跑动时手雷速度更快,跳起时仍的更高* 73 | 74 | -- 75 | 76 | ![demo](https://raw.githubusercontent.com/guanyuxin/baogame/master/doc/demo3.gif) 77 | 78 | # 搭建私服方法--通过npm [稳定版] 79 | 80 | 1.安装node4.2.4(或者以上版本)和npm 81 | # **如果安装出现问题请尝试将node升级至最新版本 82 | 83 | 2.shell中执行以下代码: 84 | 85 | ``` 86 | npm install fuzion-game && 87 | cd node_modules/fuzion-game/ && 88 | node app.js 89 | ``` 90 | 91 | 3.打开http://localhost:8030 就可以开始玩了 92 | 93 | 4.把localhost替换成你的域名或者ip,然后分享给你的朋友,一起玩吧 94 | 95 | -- 96 | 97 | # 搭建私服方法--使用github [最新版] 98 | 99 | 将上面方法的第二部替换为: 100 | 101 | ``` 102 | git clone https://github.com/guanyuxin/baogame 103 | cd baogame 104 | npm install 105 | node app.js 106 | ``` 107 | -- 108 | 109 | # 服务器管理 110 | 111 | ``` 112 | #启动参数: 113 | node app.js [port=端口,默认8030] [code=管理员口令,默认admin] [room=房间数目,默认1] 114 | ``` 115 | http://localhost:port/admin 可以进入管理界面,需要localStorage中设置code=管理员口令,然后可以创建物品或者封禁用户ip 116 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 暴走大乱斗 7 | 8 | 9 |
10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 | 返回 22 |
23 | 24 |
25 |

加入游戏

26 | 你的名字: 27 |
28 | 加入 29 |
房间已满
30 | 其他房间 31 | 观战 32 |
33 |
34 | 35 | 46 | 47 |
48 | 49 |
50 |
W,A,S,D:移动 Q:使用物品
51 |
点击查看帮助详情
52 |
------------------
53 |
[2017-03-06]
54 | 增加了AI传送门
55 |
56 |
57 |
58 | 59 |
60 | 返回房间列表 61 |
62 |
63 |
64 | 65 |
66 |
67 | 更换地图 68 |
69 |
玩家数:{{clientCount}}
70 |
71 | 72 |
73 |
74 |
75 | 76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /static/js/JPack.js: -------------------------------------------------------------------------------- 1 | var enumCache = {}; 2 | var enumCacheList = []; 3 | function JPack (schema) { 4 | this.schema = schema; 5 | this.bits = []; 6 | for (var key in schema) { 7 | this.bits.push({ 8 | name: key, 9 | type: schema[key] 10 | }); 11 | } 12 | } 13 | JPack.prototype.encode = function (data) { 14 | var res = []; 15 | for (var i = 0; i < this.bits.length; i++) { 16 | var b = this.bits[i]; 17 | var v = data[b.name]; 18 | if (b.type == Boolean) { 19 | res.push(v ? 1 : 0); 20 | } else if (b.type == "ENUM") { 21 | if (enumCache[v]) { 22 | res.push(enumCache[v]); 23 | } else { 24 | res.push(v); 25 | enumCache[v] = enumCacheList.length; 26 | enumCacheList.push(v); 27 | } 28 | } else if (b.type == "INT") { 29 | res.push(Math.floor(v)); 30 | } else if (b.type == "JSON") { 31 | res.push(v); 32 | } else { 33 | res.push(v); 34 | } 35 | } 36 | return res; 37 | } 38 | JPack.prototype.decode = function (arr) { 39 | var data = {}; 40 | for (var i = 0; i < this.bits.length; i++) { 41 | var n = this.bits[i].name; 42 | var t = this.bits[i].type; 43 | var v = arr[i]; 44 | if (t == "ENUM") { 45 | if (typeof(v) == 'string') { 46 | data[n] = v; 47 | enumCacheList.push(v); 48 | } else { 49 | data[n] = enumCacheList[v]; 50 | } 51 | } else { 52 | data[n] = v; 53 | } 54 | } 55 | return data; 56 | } 57 | 58 | var Packs = { 59 | items: { 60 | power: { 61 | id: 1, 62 | name: "无敌", 63 | count: 1000 64 | }, 65 | gun: { 66 | id: 2, 67 | name: "枪", 68 | count: 3 69 | }, 70 | mine: { 71 | id: 3, 72 | name: "地雷", 73 | count: 2 74 | }, 75 | drug: { 76 | id: 4, 77 | name: "毒药" 78 | }, 79 | hide: { 80 | id: 5, 81 | name: "隐身", 82 | count: 1000 83 | }, 84 | bomb: { 85 | id: 6, 86 | name: "惊喜!", 87 | count: 550 88 | }, 89 | doublejump: { 90 | id: 7, 91 | name: "二段跳" 92 | }, 93 | flypack: { 94 | id: 8, 95 | name: "喷气背包", 96 | count: 250 97 | }, 98 | grenade: { 99 | id: 9, 100 | name: "手雷", 101 | count: 3 102 | } 103 | }, 104 | userPack: new JPack({ 105 | id: "INT", 106 | name: "Str", 107 | x: "INT", 108 | y: "INT", 109 | vy: "INT", 110 | faceing: "INT", 111 | 112 | danger: Boolean, 113 | status: "Str", 114 | dead: Boolean, 115 | 116 | carry: "Str", 117 | carryCount: "INT", 118 | 119 | fireing: "INT", 120 | grenadeing: "INT", 121 | doubleJumping: Boolean, 122 | flying: "INT", 123 | 124 | score: "INT", 125 | npc: Boolean, 126 | team: "INT", 127 | 128 | watchData: "JSON" 129 | }), 130 | controlPack: new JPack({ 131 | leftDown: Boolean, 132 | rightDown: Boolean, 133 | upDown: Boolean, 134 | downDown: Boolean, 135 | itemDown: Boolean, 136 | spaceDown: Boolean, 137 | 138 | leftPress: Boolean, 139 | rightPress: Boolean, 140 | upPress: Boolean, 141 | downPress: Boolean, 142 | itemPress: Boolean, 143 | spacePress: Boolean, 144 | }), 145 | itemPack: new JPack({ 146 | x: "INT", 147 | y: "INT", 148 | id: "INT", 149 | dead: Boolean 150 | }), 151 | minePack: new JPack({ 152 | x: "INT", 153 | y: "INT", 154 | dead: Boolean 155 | }), 156 | entityPack: new JPack({ 157 | x: "INT", 158 | y: "INT", 159 | r: "INT" 160 | }) 161 | } 162 | 163 | if (!this.CSS) { 164 | module.exports = Packs; 165 | } else { 166 | this.Packs = Packs 167 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var url = require('url'); 3 | var express = require('express'); 4 | var app = express(); 5 | var server = require('http').Server(app); 6 | var WebSocketServer = require('ws').Server; 7 | var wss = new WebSocketServer({server: server}); 8 | var Room = require('./game/room.js'); 9 | var cookieparser = require("cookieparser"); 10 | 11 | var opts = {}; 12 | for (var key of process.argv.splice(2)) { 13 | var keys = key.split('='); 14 | opts[keys[0]] = keys[1]; 15 | } 16 | 17 | server.listen(opts.port || 8030, function () { 18 | console.log('Listening on ' + server.address().port); 19 | }); 20 | 21 | app.use('/static', express.static('static')); 22 | app.use('/invite', express.static('invite')); 23 | app.use('/build', express.static('build')); 24 | 25 | //app.use(cookieParser()); 26 | //游戏地址 27 | app.get('/', function (req, res) { 28 | // var UUID = req.cookies.UUID; 29 | // if (!UUID || true) { 30 | // UUID = Math.floor(Math.random()*2322423432); 31 | // res.cookie('UUID',UUID, { maxAge: 90000000, httpOnly: true }); 32 | // } 33 | res.sendFile(__dirname + '/static/index.html'); 34 | }); 35 | //游戏地址 36 | app.get('/rooms', function (req, res) { 37 | res.sendFile(__dirname + '/static/rooms.html'); 38 | }); 39 | //管理地址 40 | app.get('/admin', function (req, res) { 41 | res.sendFile(__dirname + '/static/admin.html'); 42 | }); 43 | 44 | 45 | //游戏地址 46 | app.get('/createRoom', function (req, res) { 47 | var type = req.query.type; 48 | var room = Room.createRoom(type); 49 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 50 | res.end(room.id+""); 51 | }); 52 | 53 | //获取房间列表 54 | app.get('/roomsData', function (req, res) { 55 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 56 | res.end(JSON.stringify(Room.getRoomData())); 57 | }); 58 | 59 | var adminCode = opts.code || 'admin'; 60 | Room.setConfig(adminCode); 61 | for (var i = 0; i < (opts.room || 1); i++) { 62 | Room.createRoom("大乱斗", true); 63 | } 64 | 65 | wss.on('connection', function (ws) { 66 | //var UUID = cookieparser.parse(ws.upgradeReq.headers.cookie).UUID; 67 | var location = url.parse(ws.upgradeReq.url, true); 68 | 69 | var roomID = location.query.roomID || 1; 70 | var room = Room.findRoom(roomID); 71 | 72 | 73 | var socket = { 74 | emit: function (name, data) { 75 | try { 76 | var c = name + "$" + JSON.stringify(data || {}); 77 | ws.send(c); 78 | } catch (e) { 79 | console.log(e); 80 | } 81 | }, 82 | on: function (name, callback) { 83 | this.listeners[name] = callback; 84 | }, 85 | ip: ws.upgradeReq.connection.remoteAddress, 86 | listeners: {} 87 | } 88 | 89 | if (!room) { 90 | socket.emit('close', '未找到房间'); 91 | ws.close(); 92 | return; 93 | } 94 | 95 | //房间最多30个链接 96 | if (room.game.clients.length > 30) { 97 | socket.emit('close', '房间链接已满'); 98 | ws.close(); 99 | return; 100 | } 101 | ws.on('message', function (message) { 102 | var $s = message.indexOf('$'); 103 | if ($s == -1) { 104 | var name = message; 105 | var data = {}; 106 | } else { 107 | var name = message.substring(0, $s); 108 | var data = JSON.parse(message.substring($s + 1)); 109 | } 110 | socket.listeners[name] && socket.listeners[name](data); 111 | }); 112 | 113 | ws.on('close', function () { 114 | room.game.removeClient(socket); 115 | socket = null; 116 | ws = null; 117 | room = null; 118 | }); 119 | 120 | room.game.addClient(socket, Math.random()); 121 | }); 122 | 123 | -------------------------------------------------------------------------------- /game/collide.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var Pack = require('../static/js/JPack.js'); 3 | var C = require('../static/js/const.js'); 4 | 5 | function userCollide(a, b, game) { 6 | //不碰撞情况 7 | if (a.dead || b.dead) {return} 8 | if((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) > game.props.userWidth*game.props.userWidth) {return;} 9 | 10 | //带电情况 11 | if (a.carry == Pack.items.power.id && b.carry != Pack.items.power.id) { 12 | b.killed('power', a); 13 | b.vx = (b.x - a.x)/2; 14 | if (b.carry == Pack.items.bomb.id) { 15 | a.carry = b.carry; 16 | a.carryCount = b.carryCount; 17 | b.carry = ''; 18 | } 19 | return; 20 | } else if (a.carry != Pack.items.power.id && b.carry == Pack.items.power.id) { 21 | a.killed('power', b); 22 | a.vx = (a.x - b.x)/2; 23 | if (a.carry == Pack.items.bomb.id) { 24 | b.carry = a.carry; 25 | b.carryCount = a.carryCount; 26 | a.carry = ''; 27 | } 28 | return; 29 | } else if (a.carry == Pack.items.power.id && b.carry == Pack.items.power.id) { 30 | a.carry = ''; 31 | b.carry = ''; 32 | } 33 | //排除刚刚碰撞 34 | if (a.ignore[b.id] > 0 || b.ignore[a.id] > 0) {return} 35 | 36 | if (b.carry == Pack.items.bomb.id && a.carry != Pack.items.bomb.id) { 37 | a.carry = b.carry; 38 | a.carryCount = b.carryCount; 39 | b.carry = ''; 40 | } else if (a.carry == Pack.items.bomb.id && b.carry != Pack.items.bomb.id) { 41 | b.carry = a.carry; 42 | b.carryCount = a.carryCount; 43 | a.carry = ''; 44 | } 45 | //正常情况 46 | if (a.onFloor && b.onFloor) { 47 | if (a.crawl && !b.crawl) { 48 | b.vy = 5; 49 | b.danger = true; 50 | } else if (!a.crawl && b.crawl) { 51 | a.vy = 5; 52 | a.danger = true; 53 | } else { 54 | if (a.crawl && b.crawl) { 55 | a.crawl = false; 56 | b.crawl = false; 57 | } 58 | var tmp = a.vx; 59 | a.vx = b.vx; 60 | b.vx = tmp; 61 | 62 | a.vy = 2.5; 63 | b.vy = 2.5; 64 | } 65 | } else if (a.onFloor && !b.onFloor) { 66 | if (a.crawl) { 67 | a.vx = b.vx / 2; 68 | b.vx = -b.vx / 2; 69 | a.vy = 2.5; 70 | b.vy = 2.5; 71 | } else { 72 | a.vx = b.vx; 73 | b.vx /= 2; 74 | a.vy = 2.5; 75 | a.danger = true; 76 | } 77 | } else if (!a.onFloor && b.onFloor) { 78 | if (b.crawl) { 79 | b.vx = a.vx / 2; 80 | a.vx = -a.vx / 2; 81 | b.vy = 2.5; 82 | a.vy = 2.5; 83 | } else { 84 | b.vx = a.vx; 85 | a.vx /= 2; 86 | b.vy = 2.5; 87 | b.danger = true; 88 | } 89 | } else { 90 | var tmp = a.vx; 91 | a.vx = b.vx; 92 | b.vx = tmp; 93 | a.danger = true; 94 | b.danger = true; 95 | } 96 | //自然抗拒 97 | if (a.x < b.x) { 98 | if (!a.crawl) { 99 | a.vx -= 1; 100 | } 101 | if (!b.crawl) { 102 | b.vx += 1; 103 | } 104 | } else { 105 | if (!a.crawl) { 106 | a.vx += 1; 107 | } 108 | if (!b.crawl) { 109 | b.vx -= 1; 110 | } 111 | } 112 | //阻止近期碰撞 113 | a.ignore[b.id] = 40; 114 | b.ignore[a.id] = 40; 115 | a.fireing = false; 116 | b.fireing = false; 117 | a.mining = false; 118 | b.mining = false; 119 | a.onPilla = false; 120 | b.onPilla = false; 121 | a.lastTouch = b.id; 122 | b.lastTouch = a.id; 123 | } 124 | 125 | function eatItem (a, b, game) { 126 | if (a.dead || b.dead) {return} 127 | if (a.carry == Pack.items.bomb.id) {return} 128 | if((a.x-b.x)*(a.x-b.x) + (a.y+game.props.userHeight/2-b.y)*(a.y+game.props.userHeight/2-b.y) > 129 | (game.props.userWidth+C.IS)*(game.props.userWidth+C.IS)/4) { 130 | return; 131 | } 132 | b.touchUser(a); 133 | } 134 | 135 | module.exports = { 136 | userCollide: userCollide, 137 | eatItem: eatItem 138 | } -------------------------------------------------------------------------------- /game/maps/lesson2.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var map = { 3 | type: "rpg", 4 | w: 28, 5 | h: 13, 6 | npcMAX: 0, 7 | floor: [ 8 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 9 | [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0], 10 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 11 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 12 | [0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0], 13 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 14 | [0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0], 15 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 16 | [0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0], 17 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 18 | [0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,0], 19 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 20 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 21 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 22 | ], 23 | pilla: [{ 24 | x: 23.5, 25 | y1: 1, 26 | y2: 5, 27 | }, { 28 | x: 6.5, 29 | y1: 4, 30 | y2: 7, 31 | }, { 32 | x: 16.5, 33 | y1: 6, 34 | y2: 9, 35 | }, { 36 | x: 6.5, 37 | y1: 8, 38 | y2: 11, 39 | }], 40 | borns: [{ 41 | x: 5, 42 | y: 1 43 | }], 44 | npcs: [{ 45 | x: 13, 46 | y: 1, 47 | name: "坏人甲", 48 | carry: 1, 49 | carryCount: 9999999, 50 | }, { 51 | x: 15, 52 | y: 1, 53 | name: "坏人乙", 54 | carry: 1, 55 | carryCount: 9999999, 56 | }, { 57 | x: 7, 58 | y: 4, 59 | name: "坏人丙", 60 | carry: 1, 61 | carryCount: 9999999, 62 | AI: "walking" 63 | }, { 64 | x: 7, 65 | y: 8, 66 | name: "坏人丁", 67 | carry: 2, 68 | carryCount: 9999999, 69 | AI: "walking" 70 | }, { 71 | x: 9, 72 | y: 10, 73 | name: "龙套A", 74 | }, { 75 | x: 14, 76 | y: 10, 77 | name: "龙套B", 78 | }, { 79 | x: 25, 80 | y: 10, 81 | name: "龙套C", 82 | }, { 83 | x: 23, 84 | y: 10, 85 | name: "龙套D", 86 | }, { 87 | x: 21, 88 | y: 10, 89 | name: "龙套E", 90 | }], 91 | structs: [{ 92 | type: "sign", 93 | x: 5, 94 | y: 1, 95 | message: "使用各种物品可以帮你更有效的消灭敌人" 96 | }, { 97 | type: "sign", 98 | x: 7, 99 | y: 1, 100 | message: "手枪是一种强力的远程武器,可以用于消灭你正前方的敌人" 101 | }, { 102 | type: "sign", 103 | x: 9, 104 | y: 1, 105 | message: "拿到手枪后,按q使用开火,注意只有三发子弹" 106 | }, { 107 | type: "itemGate", 108 | x: 8, 109 | y: 1, 110 | itemType: 1 111 | }, { 112 | type: "itemGate", 113 | x: 19, 114 | y: 4, 115 | itemType: 2 116 | }, { 117 | type: "sign", 118 | x: 21, 119 | y: 4, 120 | message: "地雷可以被埋在地上,碰到他的人会被炸飞" 121 | }, { 122 | type: "sign", 123 | x: 20, 124 | y: 4, 125 | message: "你只能看到自己埋的地雷" 126 | }, { 127 | type: "sign", 128 | x: 17, 129 | y: 4, 130 | message: "按q可以在面前埋雷,注意自己不要踩到地雷" 131 | }, { 132 | type: "itemGate", 133 | x: 10, 134 | y: 6, 135 | itemType: 3 136 | }, { 137 | type: "sign", 138 | x: 8, 139 | y: 6, 140 | message: "碰到毒药的人会死亡,离他们远一些" 141 | }, { 142 | type: "sign", 143 | x: 15, 144 | y: 6, 145 | message: "隐身将会使你的敌人无法看到你" 146 | }, { 147 | type: "itemGate", 148 | x: 17, 149 | y: 6, 150 | itemType: 4 151 | }, { 152 | type: "itemGate", 153 | x: 4, 154 | y: 10, 155 | itemType: 0 156 | }, { 157 | type: "sign", 158 | x: 7, 159 | y: 10, 160 | message: "无敌是一件强大的武器,其他弱者们碰到你会被弹飞" 161 | }, { 162 | type: "sign", 163 | x: 11, 164 | y: 10, 165 | message: "目标:消灭其他所有人" 166 | } 167 | ], 168 | hooks: { 169 | onKilled: function (game, u) { 170 | for (let user of game.users) { 171 | if (user.npc == true && !user.dieing && !user.dead) { 172 | return; 173 | } 174 | } 175 | game.win({id: 0}); 176 | } 177 | } 178 | } 179 | 180 | module.exports = map; -------------------------------------------------------------------------------- /game/client.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var Pack = require('../static/js/JPack.js'); 3 | var C = require('../static/js/const.js'); 4 | var DataSync = require('./lib/DataSync.js'); 5 | 6 | var banedip = {}; 7 | 8 | var concount = 0; 9 | var Client = function (socket, game, UUID) { 10 | this.id = concount++; 11 | this.p1 = null; 12 | this.socket = socket; 13 | this.game = game; 14 | this.UUID = UUID; 15 | this.admin = false; 16 | this.name = '无名小卒'; 17 | this.joinTime = new Date().getTime(); 18 | this.ip = socket.ip; 19 | 20 | this.kill = 0; 21 | this.death = 0; 22 | this.highestKill = 0; 23 | 24 | //会与客户端同步的数据 25 | this.sync = new DataSync({ 26 | me: null, 27 | onStruct: null, 28 | canClimb: false 29 | }, this); 30 | 31 | if (banedip[this.ip]) { 32 | this.banned = true; 33 | } else { 34 | this.banned = false; 35 | } 36 | this.connect(); 37 | } 38 | Client.prototype.sendMap = function () { 39 | 40 | this.socket.emit("init", { 41 | props: this.game.props, 42 | map: this.game.map.getData(), 43 | bodies: [], 44 | }); 45 | 46 | this.socket.emit("globalSync", this.game.sync.all()); 47 | } 48 | Client.prototype.isAdmin = function () { 49 | var admin = null; 50 | for (let client of this.game.clients) { 51 | if (client.p1 && !client.p1.dieing && !client.p1.dead) { 52 | admin = client; 53 | break; 54 | } 55 | } 56 | return this == admin; 57 | } 58 | Client.prototype.connect = function () { 59 | var socket = this.socket; 60 | //接收初始化数据 61 | socket.on('init', data => { 62 | //admin 63 | if (data.code != undefined) { 64 | if (data.code != this.game.adminCode) { 65 | socket.emit('initFail'); 66 | return; 67 | } else { 68 | this.admin = true; 69 | socket.on('createItem', type => { 70 | var item = this.game.createItem(type); 71 | item.x = Math.random()*C.TW; 72 | item.y = Math.random()*C.TH; 73 | }); 74 | socket.on('ban', cliID => { 75 | var client = this.game.getClient(cliID); 76 | client.banned = true; 77 | banedip[client.ip] = true; 78 | }); 79 | socket.on('unban', cliID => { 80 | var client = this.game.getClient(cliID); 81 | client.banned = false; 82 | banedip[client.ip] = false; 83 | }); 84 | } 85 | } 86 | if (data.userName) { 87 | this.name = data.userName.replace(/[<>]/g, '').substring(0, 8); 88 | } 89 | 90 | //初始化数据 91 | var bodiesData = []; 92 | for (let body of this.game.bodies) { 93 | bodiesData.push(body.getData()); 94 | } 95 | this.sendMap(); 96 | }); 97 | 98 | //加入 99 | socket.on('join', data => { 100 | if (this.banned) { 101 | socket.emit('joinFail', "you are banned"); 102 | return; 103 | } 104 | var playerCount = 0; 105 | for (let user of this.game.users) { 106 | if (!user.npc) { 107 | playerCount++; 108 | } 109 | } 110 | if (playerCount >= this.game.maxUser) { 111 | socket.emit('joinFail', "加入失败,服务器已满"); 112 | return; 113 | } 114 | if (this.p1 && !this.p1.dieing && !this.p1.dead) {return} 115 | 116 | this.name = data.userName.replace(/[<>]/g, '').substring(0, 8); 117 | this.team = data.team; 118 | this.p1 = this.game.createUser(this); 119 | this.me = this.p1.id; 120 | socket.emit('joinSuccess'); 121 | }); 122 | //接收控制 123 | socket.on("control", data => { 124 | if (this.p1 && data) { 125 | var p1 = Pack.controlPack.decode(data); 126 | this.p1.leftDown = p1.leftDown; 127 | this.p1.rightDown = p1.rightDown; 128 | this.p1.upDown = p1.upDown; 129 | this.p1.downDown = p1.downDown; 130 | this.p1.itemDown = p1.itemDown; 131 | this.p1.spaceDown = p1.spaceDown; 132 | 133 | this.p1.leftPress = p1.leftPress; 134 | this.p1.rightPress = p1.rightPress; 135 | this.p1.upPress = p1.upPress; 136 | this.p1.downPress = p1.downPress; 137 | this.p1.itemPress = p1.itemPress; 138 | this.p1.spacePress = p1.spacePress; 139 | } 140 | }); 141 | 142 | socket.on("changeMap", data => { 143 | if (this.isAdmin()) { 144 | this.game.createMap(); 145 | } else { 146 | this.game.announce('message', [this.p1.id,"希望更换地图"]); 147 | } 148 | }) 149 | } 150 | Client.prototype.getData = function () { 151 | return { 152 | p1: this.p1 && this.p1.id, 153 | id: this.id, 154 | admin: this.admin, 155 | name: this.name, 156 | banned: this.banned, 157 | joinTime: this.joinTime, 158 | ip: this.ip, 159 | kill: this.kill, 160 | death: this.death, 161 | highestKill: this.highestKill 162 | } 163 | } 164 | module.exports = Client; -------------------------------------------------------------------------------- /game/ai/Path.js: -------------------------------------------------------------------------------- 1 | 2 | var C = require('../../static/js/const.js'); 3 | 4 | //get All possible dest in one move from location [i,j] 5 | // return [y,x,distance,method] 6 | function getDest (map, i, j) { 7 | var res = []; 8 | var f = map.floor 9 | //move 10 | if (f[i][j] && f[i][j + 1]) { 11 | res.push([i, j + 1, 1, 'moveRight']); 12 | } 13 | if (f[i][j] && f[i][j - 1]) { 14 | res.push([i, j - 1, 1, 'moveLeft']); 15 | } 16 | //climb 17 | for (var pilla of map.pilla) { 18 | if (pilla.x > j && pilla.x < j + 1 && i >= pilla.y1 && i <= pilla.y2) { 19 | for (var k = pilla.y1; k <= pilla.y2; k++) { 20 | if (k > i) { 21 | res.push([k, j, k - i, 'climbUp']); 22 | } else { 23 | res.push([k, j, i - k, 'climbDown']); 24 | } 25 | } 26 | } 27 | } 28 | //jump on floor 29 | if (f[i][j]) { 30 | if (f[i][j - 1] && f[i][j + 3]) { 31 | res.push([i, j + 3, 4, 'jumpRight3']); 32 | } 33 | if (f[i][j + 1] && f[i][j - 3]) { 34 | res.push([i, j - 3, 4, 'jumpLeft3']); 35 | } 36 | if (f[i][j - 1] && f[i][j + 4]) { 37 | res.push([i, j + 4, 5, 'jumpRight4']); 38 | } 39 | if (f[i][j + 1] && f[i][j - 4]) { 40 | res.push([i, j - 4, 5, 'jumpLeft4']); 41 | } 42 | } 43 | //jump down 44 | if (!f[i][j - 1]) { 45 | for (var k = 1; k <= 2; k++) { 46 | if (f[i - k] && f[i - k][j - 1]) { 47 | res.push([i - k, j - 1, k + 1, 'jumpDownLeft']); 48 | break; 49 | } 50 | } 51 | for (var k = 3; k <= 4; k++) { 52 | if (f[i - k] && f[i - k][j - 2]) { 53 | res.push([i - k, j - 2, k + 1, 'jumpDownLeft']); 54 | break; 55 | } 56 | } 57 | for (var k = 5; k <= 7; k++) { 58 | if (f[i - k] && f[i - k][j - 3]) { 59 | res.push([i - k, j - 3, k + 1, 'jumpDownLeft']); 60 | break; 61 | } 62 | } 63 | } 64 | if (!f[i][j + 1]) { 65 | for (var k = 1; k < 2; k++) { 66 | if (f[i - k] && f[i - k][j + 1]) { 67 | res.push([i - k, j + 1, k + 1, 'jumpDownRight']); 68 | break; 69 | } 70 | } 71 | for (var k = 3; k <= 4; k++) { 72 | if (f[i - k] && f[i - k][j + 2]) { 73 | res.push([i - k, j + 2, k + 1, 'jumpDownRight']); 74 | break; 75 | } 76 | } 77 | for (var k = 5; k <= 7; k++) { 78 | if (f[i - k] && f[i - k][j + 3]) { 79 | res.push([i - k, j + 3, k + 1, 'jumpDownRight']); 80 | break; 81 | } 82 | } 83 | } 84 | return res; 85 | } 86 | 87 | function onPillar(map, i, j) { 88 | for (var pilla of map.pilla) { 89 | if (pilla.x > j && pilla.x < j + 1 && i >= pilla.y1 && i <= pilla.y2) { 90 | return true; 91 | } 92 | } 93 | return false; 94 | } 95 | 96 | 97 | function deepSearch (map, lengthMap, dirMap) { 98 | var t = 0; 99 | while (t < 1000) { 100 | var find = false; 101 | for (var i = 0; i < lengthMap.length; i++) { 102 | for (var j = 0; j < lengthMap[i].length; j++) { 103 | if (lengthMap[i][j] == -1) { 104 | continue; 105 | } 106 | dest = getDest(map, i, j); 107 | for (var k = 0; k < dest.length; k++) { 108 | if (lengthMap[dest[k][0]][dest[k][1]] == -1 || lengthMap[dest[k][0]][dest[k][1]] > lengthMap[i][j] + dest[k][2]) { 109 | lengthMap[dest[k][0]][dest[k][1]] = lengthMap[i][j] + dest[k][2]; 110 | dirMap[dest[k][0]][dest[k][1]] = dirMap[i][j] || dest[k][3]; 111 | find = true; 112 | } 113 | } 114 | } 115 | } 116 | t++; 117 | if (!find) { 118 | break; 119 | } 120 | } 121 | } 122 | 123 | function DR_MAP (dirMap) { 124 | console.log('\n\n'); 125 | for (var dd = 0; dd < dirMap.length; dd++){ 126 | console.log(dirMap[dd].join(' ')) 127 | } 128 | console.log('\n\n'); 129 | } 130 | 131 | var Path = function (map, P) { 132 | this.lengthMap = []; 133 | this.dirMap = []; 134 | //init 135 | var wc = P.w/C.TW; 136 | var hc = P.h/C.TH; 137 | for (var i = 0; i < hc; i++) { 138 | this.lengthMap[i] = []; 139 | this.dirMap[i] = []; 140 | for (var j = 0; j < wc; j++) { 141 | this.lengthMap[i][j] = []; 142 | this.dirMap[i][j] = []; 143 | if (!map.floor[i][j] && !onPillar(map, i, j)) { 144 | continue; 145 | } 146 | var lengthMap = []; 147 | var dirMap = []; 148 | for (var ix = 0; ix < hc; ix++) { 149 | lengthMap[ix] = []; 150 | dirMap[ix] = []; 151 | for (var jx = 0; jx < wc; jx++) { 152 | if (ix == i && jx == j) { 153 | lengthMap[ix][jx] = 0; 154 | } else { 155 | lengthMap[ix][jx] = -1; 156 | } 157 | dirMap[ix][jx] = ''; 158 | } 159 | } 160 | deepSearch(map, lengthMap, dirMap); 161 | this.lengthMap[i][j] = lengthMap; 162 | this.dirMap[i][j] = dirMap; 163 | } 164 | } 165 | } 166 | module.exports = Path; -------------------------------------------------------------------------------- /game/ai/AI.js: -------------------------------------------------------------------------------- 1 | 2 | var C = require('../../static/js/const.js'); 3 | var Pack = require('../../static/js/JPack.js'); 4 | 5 | 6 | function userCanGoLeft (user) { 7 | var x = Math.floor((user.x - 5) / C.TW); 8 | var y = user.ty; 9 | return user.game.map.floor[y][x]; 10 | } 11 | function userCanGoRight (user) { 12 | var x = Math.floor((user.x + 5) / C.TW); 13 | var y = user.ty; 14 | return user.game.map.floor[y][x]; 15 | } 16 | function userCanJumpLeft (user) { 17 | if (user.vx > -1) return false; 18 | var x = Math.floor((user.x + 5) / C.TW); 19 | var y = user.ty; 20 | return user.game.map.floor[y][x - 3] || false; 21 | } 22 | function userCanJumpRight (user) { 23 | if (user.vx < 1) return false; 24 | var x = Math.floor((user.x + 5) / C.TW); 25 | var y = user.ty; 26 | return user.game.map.floor[y][x + 3] || false; 27 | } 28 | 29 | var AI = function (AIController, user, config) { 30 | this.user = user; 31 | this.AIController = AIController; 32 | this.path = AIController.path; 33 | } 34 | 35 | 36 | AI.prototype.findUser = function () { 37 | var users = this.AIController.game.users; 38 | for (var user of users) { 39 | if (!user.npc && user.carry !== Pack.items.hide.id) { 40 | this.targetUser = user; 41 | return user; 42 | } 43 | } 44 | } 45 | 46 | AI.prototype.findItem = function () { 47 | var items = this.AIController.game.items; 48 | for (var item of items) { 49 | if (item.id !== 4 && item.id !== 6) { 50 | this.targetItem = item; 51 | return item; 52 | } 53 | } 54 | } 55 | 56 | AI.prototype.getAction = function () { 57 | var user = this.user; 58 | var y = user.ty; 59 | var x = user.tx; 60 | 61 | if (this.tempDestX && this.tempDestY) { 62 | this.destY = this.tempDestY; 63 | this.destX = this.tempDestX; 64 | } else { 65 | var target = this.findUser(); 66 | if (!target) { 67 | target = this.findItem(); 68 | } 69 | if (!target) { 70 | return; 71 | } 72 | 73 | this.destY = Math.floor(target.y/C.TH); 74 | this.destX = Math.floor(target.x/C.TW); 75 | var floor = user.game.map.floor 76 | for (var k = 0; k < 4 && this.destY > 0; k++) { 77 | if (floor[this.destY] && !floor[this.destY][this.destX]) { 78 | this.destY--; 79 | } else { 80 | break; 81 | } 82 | } 83 | } 84 | 85 | if (this.path.lengthMap[y] && this.path.lengthMap[y][x] && this.path.dirMap[y][x][this.destY]) { 86 | return this.path.dirMap[y][x][this.destY][this.destX]; 87 | } 88 | return ''; 89 | } 90 | 91 | AI.prototype.fight = function () { 92 | if (this.targetUser) { 93 | if (this.targetUser.ty == this.user.ty || this.targetUser.ty == this.user.ty + 1) { 94 | if (Math.abs(this.targetUser.tx - this.user.tx) < 2) { 95 | if (this.user.vx > 2 || this.user.vx < -2) { 96 | this.user.upDown = true; 97 | } else { 98 | this.user.downDown = true; 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | AI.prototype.update = function () { 106 | 107 | var user = this.user; 108 | var dir = this.getAction(); 109 | this.dir = dir; 110 | var ty = user.y%C.TH; 111 | var tx = user.x%C.TW; 112 | 113 | user.leftDown = 0; 114 | user.rightDown = 0; 115 | user.upDown = 0; 116 | user.downDown = 0; 117 | 118 | user.leftPress = false; 119 | user.rightPress = false; 120 | if (!dir) { 121 | this.tempDestX = 0; 122 | this.tempDestY = 0; 123 | return; 124 | } 125 | if (dir == "moveLeft") { 126 | user.leftDown = true; 127 | user.leftPress = true; 128 | this.fight(); 129 | } else if (dir == "moveRight") { 130 | user.rightDown = true; 131 | user.rightPress = true; 132 | this.fight(); 133 | } else if (dir == "climbUp" || dir == "climbDown") { 134 | if (user.onPilla) { 135 | if (dir == "climbUp") { 136 | user.upDown = true; 137 | } 138 | if (dir == "climbDown") { 139 | user.downDown = true; 140 | } 141 | } else if (tx - C.TW/2 > 4) { 142 | user.leftDown = true; 143 | } else if (C.TW/2 - tx > 4) { 144 | user.rightDown = true; 145 | } else { 146 | if (user.vx == 0 && user.vy == 0) { 147 | if (dir == "climbUp") { 148 | user.upDown = true; 149 | } 150 | if (dir == "climbDown") { 151 | user.downDown = true; 152 | } 153 | } 154 | } 155 | } else if (dir.indexOf('jumpLeft') == 0) { 156 | if (user.onPilla) { 157 | user.leftPress = true; 158 | } else { 159 | if (user.vx < -3) { 160 | user.upDown = true; 161 | user.leftDown = true; 162 | } else { 163 | if (userCanGoLeft(user)) { 164 | user.leftDown = true; 165 | } else { 166 | this.tempDestX = user.tx + 2; 167 | this.tempDestY = user.ty; 168 | } 169 | } 170 | } 171 | } else if (dir.indexOf('jumpRight') == 0) { 172 | if (user.onPilla) { 173 | user.rightPress = true 174 | } else { 175 | if (user.vx > 3) { 176 | user.upDown = true; 177 | user.rightDown = true; 178 | } else { 179 | if (userCanGoRight(user)) { 180 | user.rightDown = true; 181 | } else { 182 | this.tempDestX = user.tx - 2; 183 | this.tempDestY = user.ty; 184 | } 185 | } 186 | } 187 | } else if (dir == "jumpDownLeft") { 188 | user.leftDown = true; 189 | user.leftPress = true; 190 | } else if (dir == "jumpDownRight") { 191 | user.rightDown = true; 192 | user.rightPress = true; 193 | } 194 | } 195 | 196 | module.exports = AI; -------------------------------------------------------------------------------- /static/css/game.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes popup { 2 | 0% { 3 | opacity: 0; 4 | -webkit-transform: scale(1.4) 5 | } 6 | 70% { 7 | opacity: 1; 8 | -webkit-transform: scale(.9) 9 | } 10 | 100% { 11 | -webkit-transform: scale(1) 12 | } 13 | } 14 | body { 15 | background: #000; 16 | -webkit-user-select: none; 17 | margin: 0; 18 | } 19 | canvas { 20 | width: 100%; 21 | position: absolute; 22 | } 23 | .frame { 24 | position: fixed; 25 | width: 100%; 26 | top: 0; 27 | background: black; 28 | } 29 | .middle { 30 | margin: auto; 31 | position: relative; 32 | background: #333; 33 | background: -webkit-radial-gradient(#444, #444, #222) 34 | } 35 | .joining { 36 | position: absolute; 37 | left: 0; 38 | right: 0; 39 | top: 0; 40 | bottom: 0; 41 | background: rgba(0,0,0,.5); 42 | background: -webkit-radial-gradient(rgba(0,0,0,.3), rgba(0,0,0,.3), rgba(0,0,0,.7)); 43 | color: #ff0; 44 | display: -webkit-box; 45 | -webkit-box-align: center; 46 | -webkit-box-pack: center; 47 | } 48 | .joining .center { 49 | padding: 40px 80px; 50 | background: rgba(0,0,0,.6); 51 | border-radius: 20px; 52 | -webkit-animation: popup .5s; 53 | position: relative; 54 | } 55 | .center h4 { 56 | margin-top: 0; 57 | text-align: center; 58 | font-size: 30px; 59 | } 60 | .btn { 61 | display: block; 62 | border: 1px solid #ff0; 63 | padding: 10px 20px; 64 | border-radius: 5px; 65 | margin-top: 20px; 66 | text-align: center; 67 | color: #ff0; 68 | cursor: pointer; 69 | text-decoration: none; 70 | } 71 | .btn.btn-weak { 72 | padding: 5px; 73 | font-size: 12px; 74 | background: rgba(200,200,200,.1) 75 | } 76 | .btn-block { 77 | display: block; 78 | width: 400px; 79 | } 80 | .btn:active { 81 | background: #ff0; 82 | color: black; 83 | } 84 | 85 | .btn[disabled] { 86 | background: rgba(100, 100, 100, .5); 87 | border: 1px solid rgba(190, 190, 190, .5); 88 | color: rgba(190, 190, 190, .5); 89 | } 90 | .btn[disabled]:after { 91 | content: attr(disabledText) 92 | } 93 | .txt-input { 94 | background: rgba(255, 255, 255, .8); 95 | border: 1px solid #ff0; 96 | padding: 10px; 97 | } 98 | input::-webkit-input-placeholder { 99 | color: #f00 !important; 100 | } 101 | 102 | /*** notice GUI ***/ 103 | .notice { 104 | background: rgba(255,255,255,.5); 105 | position: relative; 106 | z-index: 10; 107 | } 108 | .noticeItem { 109 | padding: 3px 10px; 110 | border-bottom: 1px solid #ccc; 111 | color: #333; 112 | } 113 | .noticeItem b { 114 | font-weight: bolder; 115 | color: #f00; 116 | } 117 | /*** user GUI ***/ 118 | .clients { 119 | position: relative; 120 | background: rgba(255,255,255,.8); 121 | display: flex; 122 | align-items: center; 123 | } 124 | .clients .controls { 125 | display: flex; 126 | } 127 | .clients .btn { 128 | padding: 3px 5px; 129 | margin: 10px; 130 | } 131 | .clients .counter { 132 | border: 1px solid #999; 133 | color: #999; 134 | border-radius: 4px; 135 | padding: 3px 5px; 136 | margin: 10px; 137 | } 138 | /*** help GUI ***/ 139 | .help { 140 | position: absolute; 141 | right: 20px; 142 | top: 20px; 143 | } 144 | .help-icon { 145 | color: #fff; 146 | border: 2px solid #fff; 147 | border-radius: 50%; 148 | width: 20px; 149 | height: 20px; 150 | text-align: center; 151 | line-height: 20px; 152 | font-weight: bold; 153 | } 154 | .help-content { 155 | pointer-events: none; 156 | display: none; 157 | position: absolute; 158 | top: 30px; 159 | right: 0px; 160 | padding: 20px; 161 | background: rgba(255,255,255,.6); 162 | border-radius: 10px; 163 | width: 200px; 164 | } 165 | .help-content b { 166 | color: #ffff33; 167 | } 168 | .help:hover .help-content { 169 | display: block; 170 | } 171 | /*** leave GUI ***/ 172 | .leave { 173 | position: absolute; 174 | left: 0px; 175 | top: 0px; 176 | } 177 | .leave-icon { 178 | display: block; 179 | color: #fff; 180 | width: 100px; 181 | height: 40px; 182 | background: rgba(255,255,255,.1); 183 | transform: rotate(-45deg) translate(-22px, -30px); 184 | text-align: center; 185 | line-height: 40px; 186 | font-weight: bold; 187 | } 188 | .leave-content { 189 | pointer-events: none; 190 | display: none; 191 | position: absolute; 192 | top: 30px; 193 | left: 30px; 194 | padding: 10px; 195 | background: rgba(255,255,255,.6); 196 | border-radius: 10px; 197 | width: 100px; 198 | } 199 | .leave:hover .leave-icon { 200 | background: rgba(255,255,255,.4); 201 | } 202 | .leave:hover .leave-content { 203 | display: block; 204 | } 205 | 206 | .win { 207 | display: none; 208 | position: absolute; 209 | left: 0; 210 | right: 0; 211 | top: 0; 212 | bottom: 0; 213 | background: rgba(0,0,0,.5); 214 | -webkit-box-align: center; 215 | -webkit-box-pack: center; 216 | -webkit-box-orient: vertical; 217 | } 218 | .win .popup { 219 | color: #fa0; 220 | font-size: 100; 221 | font-weight: bolder; 222 | -webkit-text-stroke: 3px #fff; 223 | -webkit-animation: popup 1s; 224 | } 225 | .mobileController { 226 | position: absolute; 227 | left: 0; 228 | right: 0; 229 | top: 0; 230 | bottom: 0; 231 | } 232 | .mobileController .left { 233 | position: absolute; 234 | left: 20px; 235 | bottom: 20px; 236 | display: -webkit-box; 237 | } 238 | .mobileController .right { 239 | position: absolute; 240 | right: 20px; 241 | bottom: 20px; 242 | } 243 | .mobileController .moreBtn { 244 | padding: 45px; 245 | color: #fff; 246 | border: 1px solid #fff; 247 | background: rgba(255,255,255,.3); 248 | text-align: center; 249 | margin-top: -1px; 250 | margin-left: -1px; 251 | } 252 | .mobileController .moreBtn:active { 253 | background: rgba(255,255,255,.6); 254 | } 255 | 256 | a { 257 | color: inherit; 258 | text-decoration: none; 259 | } -------------------------------------------------------------------------------- /static/game.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes popup { 2 | 0% { 3 | opacity: 0; 4 | -webkit-transform: scale(1.4) 5 | } 6 | 70% { 7 | opacity: 1; 8 | -webkit-transform: scale(.9) 9 | } 10 | 100% { 11 | -webkit-transform: scale(1) 12 | } 13 | } 14 | body { 15 | background: #000; 16 | -webkit-user-select: none; 17 | margin: 0; 18 | } 19 | canvas { 20 | width: 100%; 21 | position: absolute; 22 | } 23 | 24 | .frame { 25 | position: fixed; 26 | width: 100%; 27 | top: 0; 28 | background: black; 29 | } 30 | .middle { 31 | margin: auto; 32 | position: relative; 33 | background: #333; 34 | background: -webkit-radial-gradient(#444, #444, #222) 35 | } 36 | .joining { 37 | position: absolute; 38 | left: 0; 39 | right: 0; 40 | top: 0; 41 | bottom: 0; 42 | background: rgba(0,0,0,.5); 43 | background: -webkit-radial-gradient(rgba(0,0,0,.3), rgba(0,0,0,.3), rgba(0,0,0,.7)); 44 | color: #ff0; 45 | display: -webkit-box; 46 | -webkit-box-align: center; 47 | -webkit-box-pack: center; 48 | } 49 | .joining .center { 50 | padding: 40px 80px; 51 | background: rgba(0,0,0,.6); 52 | border-radius: 20px; 53 | -webkit-animation: popup .5s; 54 | position: relative; 55 | } 56 | .center h4 { 57 | margin-top: 0; 58 | text-align: center; 59 | font-size: 30px; 60 | } 61 | .btn { 62 | display: block; 63 | border: 1px solid #ff0; 64 | padding: 10px 20px; 65 | border-radius: 5px; 66 | margin-top: 20px; 67 | text-align: center; 68 | color: #ff0; 69 | cursor: pointer; 70 | text-decoration: none; 71 | } 72 | .btn.btn-weak { 73 | padding: 5px; 74 | font-size: 12px; 75 | background: rgba(200,200,200,.1) 76 | } 77 | .btn-block { 78 | display: block; 79 | width: 400px; 80 | } 81 | .btn:active { 82 | background: #ff0; 83 | color: black; 84 | } 85 | 86 | .btn[disabled] { 87 | background: rgba(100, 100, 100, .5); 88 | border: 1px solid rgba(190, 190, 190, .5); 89 | color: rgba(190, 190, 190, .5); 90 | } 91 | .btn[disabled]:after { 92 | content: attr(disabledText) 93 | } 94 | .txt-input { 95 | background: rgba(255, 255, 255, .8); 96 | border: 1px solid #ff0; 97 | padding: 10px; 98 | } 99 | input::-webkit-input-placeholder { 100 | color: #f00 !important; 101 | } 102 | 103 | /*** notice GUI ***/ 104 | .notice { 105 | background: rgba(255,255,255,.5); 106 | position: relative; 107 | z-index: 10; 108 | } 109 | .noticeItem { 110 | padding: 3px 10px; 111 | border-bottom: 1px solid #ccc; 112 | color: #333; 113 | } 114 | .noticeItem b { 115 | font-weight: bolder; 116 | color: #f00; 117 | } 118 | /*** user GUI ***/ 119 | .clients { 120 | position: relative; 121 | background: rgba(255,255,255,.8); 122 | display: flex; 123 | align-items: center; 124 | } 125 | .clients .controls { 126 | display: flex; 127 | } 128 | .clients .btn { 129 | padding: 3px 5px; 130 | margin: 10px; 131 | } 132 | .clients .counter { 133 | border: 1px solid #999; 134 | color: #999; 135 | border-radius: 4px; 136 | padding: 3px 5px; 137 | margin: 10px; 138 | } 139 | /*** help GUI ***/ 140 | .help { 141 | position: absolute; 142 | right: 20px; 143 | top: 20px; 144 | } 145 | .help-icon { 146 | color: #fff; 147 | border: 2px solid #fff; 148 | border-radius: 50%; 149 | width: 20px; 150 | height: 20px; 151 | text-align: center; 152 | line-height: 20px; 153 | font-weight: bold; 154 | } 155 | .help-content { 156 | pointer-events: none; 157 | display: none; 158 | position: absolute; 159 | top: 30px; 160 | right: 0px; 161 | padding: 20px; 162 | background: rgba(255,255,255,.6); 163 | border-radius: 10px; 164 | width: 200px; 165 | } 166 | .help-content b { 167 | color: #ffff33; 168 | } 169 | .help:hover .help-content { 170 | display: block; 171 | } 172 | /*** leave GUI ***/ 173 | .leave { 174 | position: absolute; 175 | left: 0px; 176 | top: 0px; 177 | } 178 | .leave-icon { 179 | display: block; 180 | color: #fff; 181 | width: 100px; 182 | height: 40px; 183 | background: rgba(255,255,255,.1); 184 | transform: rotate(-45deg) translate(-22px, -30px); 185 | text-align: center; 186 | line-height: 40px; 187 | font-weight: bold; 188 | } 189 | .leave-content { 190 | pointer-events: none; 191 | display: none; 192 | position: absolute; 193 | top: 30px; 194 | left: 30px; 195 | padding: 10px; 196 | background: rgba(255,255,255,.6); 197 | border-radius: 10px; 198 | width: 100px; 199 | } 200 | .leave:hover .leave-icon { 201 | background: rgba(255,255,255,.4); 202 | } 203 | .leave:hover .leave-content { 204 | display: block; 205 | } 206 | 207 | .win { 208 | display: none; 209 | position: absolute; 210 | left: 0; 211 | right: 0; 212 | top: 0; 213 | bottom: 0; 214 | background: rgba(0,0,0,.5); 215 | -webkit-box-align: center; 216 | -webkit-box-pack: center; 217 | -webkit-box-orient: vertical; 218 | } 219 | .win .popup { 220 | color: #fa0; 221 | font-size: 100; 222 | font-weight: bolder; 223 | -webkit-text-stroke: 3px #fff; 224 | -webkit-animation: popup 1s; 225 | } 226 | .mobileController { 227 | position: absolute; 228 | left: 0; 229 | right: 0; 230 | top: 0; 231 | bottom: 0; 232 | } 233 | .mobileController .left { 234 | position: absolute; 235 | left: 20px; 236 | bottom: 20px; 237 | display: -webkit-box; 238 | } 239 | .mobileController .right { 240 | position: absolute; 241 | right: 20px; 242 | bottom: 20px; 243 | } 244 | .mobileController .moreBtn { 245 | padding: 45px; 246 | color: #fff; 247 | border: 1px solid #fff; 248 | background: rgba(255,255,255,.3); 249 | text-align: center; 250 | margin-top: -1px; 251 | margin-left: -1px; 252 | } 253 | .mobileController .moreBtn:active { 254 | background: rgba(255,255,255,.6); 255 | } 256 | 257 | a { 258 | color: inherit; 259 | text-decoration: none; 260 | } -------------------------------------------------------------------------------- /game/map.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var C = require('../static/js/const.js'); 3 | 4 | var structs = { 5 | door: require('./struct/door.js'), 6 | sign: require('./struct/sign.js'), 7 | itemGate: require('./struct/itemGate.js') 8 | } 9 | 10 | var Map = function (game, data) { 11 | this.game = game; 12 | this.structID = 1; 13 | if (data) { 14 | this.type = data.type; 15 | this.w = data.w; 16 | this.h = data.h; 17 | this.floor = data.floor; 18 | this.pilla = data.pilla; 19 | this.borns = data.borns; 20 | this.hooks = data.hooks || {}; 21 | this.npcMAX = data.npcMAX; 22 | for(let struct of data.structs) { 23 | var s = new structs[struct.type](this.game, struct); 24 | this.game.structs.push(s); 25 | } 26 | for (let struct of this.game.structs) { 27 | struct.id = this.structID++; 28 | } 29 | if (data.npcs) { 30 | for (let npcData of data.npcs) { 31 | var npc = this.game.createNPC({name: npcData.name || "npc", AI: npcData.AI, npc: true}); 32 | npc.x = (npcData.x + .5) * C.TW; 33 | npc.y = (npcData.y + .5) * C.TH; 34 | npc.carryCount = npcData.carryCount; 35 | npc.carry = npcData.carry; 36 | } 37 | } 38 | } else { 39 | this.type = "world"; 40 | //random map 41 | var w = this.w = 28; 42 | var h = this.h = 15; 43 | this.floor = []; 44 | this.pilla = []; 45 | this.hooks = {}; 46 | this.npcMAX = 2; 47 | 48 | for (var i = 0; i < h; i++) { 49 | this.floor[i] = []; 50 | var on = Math.random()> .5 ? 1 : 0; 51 | if (i % 2 == 1 && i < h - 2) { 52 | for (var j = 0; j < w; j++) { 53 | if (i < 2 || i == h - 1 || j == 0 || j == w - 1) { 54 | this.floor[i][j] = 0; 55 | } else { 56 | this.floor[i][j] = on; 57 | if (Math.random() > .8) {on = 1 - on} 58 | } 59 | } 60 | } 61 | } 62 | 63 | this.pilla.push({ 64 | x: 4.5, 65 | y1: 1, 66 | y2: h - 1 67 | }); 68 | 69 | this.pilla.push({ 70 | x: w - 3.5, 71 | y1: 1, 72 | y2: h - 1 73 | }); 74 | 75 | for (var j = 6; j < w - 6; j++) { 76 | var start = 0; 77 | var end = 0; 78 | for (var i = 0; i < 4; i++) { 79 | if (this.floor[i][j]) { 80 | start = i; 81 | break; 82 | } 83 | } 84 | if (start) { 85 | for (var i = start + 1; i < h; i++) { 86 | if (this.floor[i][j]) { 87 | end = i; 88 | } 89 | } 90 | if (end) { 91 | this.pilla.push({ 92 | x: j + .5, 93 | y1: start, 94 | y2: end+1 95 | }); 96 | } 97 | j+=3; 98 | } 99 | } 100 | 101 | this.floor[1][3] = 1; 102 | this.floor[1][4] = 1; 103 | this.floor[1][5] = 1; 104 | 105 | this.floor[1][w - 3] = 1; 106 | this.floor[1][w - 4] = 1; 107 | this.floor[1][w - 5] = 1; 108 | 109 | this.floor[3][2] = 1; 110 | this.floor[3][3] = 1; 111 | this.floor[3][4] = 1; 112 | this.floor[3][5] = 1; 113 | 114 | this.floor[3][w - 2] = 1; 115 | this.floor[3][w - 3] = 1; 116 | this.floor[3][w - 4] = 1; 117 | this.floor[3][w - 5] = 1; 118 | 119 | this.floor[h - 2][3] = 1; 120 | this.floor[h - 2][4] = 1; 121 | this.floor[h - 2][5] = 1; 122 | 123 | this.floor[h - 2][w - 3] = 1; 124 | this.floor[h - 2][w - 4] = 1; 125 | this.floor[h - 2][w - 5] = 1; 126 | 127 | this.borns = []; 128 | var count = 0; 129 | for (var i = 0; i < 80; i++) { 130 | var x = Math.floor(Math.random()*(this.w - 2)) + 1; 131 | var y = Math.floor(Math.random()*(this.h - 2)) + 1; 132 | if (this.floor[y][x]) { 133 | this.borns.push({id: this.structID, x: x, y: y}); 134 | this.game.structs.push(new structs.door(this.game, {id: this.structID++, x: x, y: y,opening:false})); 135 | count++; 136 | if (count > 4) { 137 | break; 138 | } 139 | } 140 | } 141 | 142 | this.game.structs.push(new structs.itemGate(this.game, {id: this.structID++, x: 0, y: this.h/2})); 143 | this.game.structs.push(new structs.itemGate(this.game, {id: this.structID++, x: this.w - 1, y: this.h/2})); 144 | this.game.structs.push(new structs.itemGate(this.game, {id: this.structID++, x: this.w/2, y: this.h - 1})); 145 | } 146 | } 147 | Map.prototype.onStruct = function (u) { 148 | for (let struct of this.game.structs) { 149 | if (u.tx == struct.x && u.ty == struct.y) { 150 | return struct; 151 | } 152 | } 153 | return null; 154 | } 155 | Map.prototype.born = function () { 156 | var i = Math.floor(Math.random()*this.borns.length); 157 | var x = this.borns[i].x; 158 | var y = this.borns[i].y; 159 | return {x: (x+.5) * C.TW, y: y * C.TH} 160 | } 161 | Map.prototype.onFloor = function (x, y) { 162 | x = Math.floor(x/C.TW); 163 | if (y % C.TH != 0) {return false} 164 | y = y / C.TH; 165 | if (x < 0 || y < 0 || x >= this.w || y >= this.h || !this.floor[y]) {return false} 166 | return this.floor[y][x]; 167 | } 168 | Map.prototype.nearPilla = function (u) { 169 | if (this.onFloor(u.x, u.y) == false) {return false} 170 | if (Math.abs(u.vx) > 1 || Math.abs(u.vy) > 1 || u.dieing) {return false} 171 | var x = u.x, y = u.y; 172 | for (let pilla of this.pilla) { 173 | if (Math.abs(x - pilla.x * C.TW) < 8 && y >= pilla.y1*C.TH && y <= pilla.y2*C.TH) { 174 | return pilla; 175 | } 176 | } 177 | return false; 178 | } 179 | Map.prototype.onPilla = function (x, y) { 180 | for (let pilla of this.pilla) { 181 | if (Math.abs(x - pilla.x * C.TW) < 8 && y >= pilla.y1*C.TH && y <= pilla.y2*C.TH) { 182 | return true; 183 | } 184 | } 185 | return false; 186 | } 187 | Map.prototype.update = function () { 188 | for (let struct of this.game.structs) { 189 | struct.update(); 190 | } 191 | } 192 | Map.prototype.getData = function () { 193 | return { 194 | floor: this.floor, 195 | pilla: this.pilla, 196 | } 197 | } 198 | 199 | module.exports = Map; -------------------------------------------------------------------------------- /static/js/effect.js: -------------------------------------------------------------------------------- 1 | //5毛钱特效 2 | //effect 是由事件触发的,临时的,逻辑无关效果。如爆炸后的效果、喷出气体等 3 | var Smoke = function (x, y, size, life) { 4 | this.life = Math.floor(life); 5 | this.totalLife = Math.random()*60 + 30; 6 | this.x = x; 7 | this.y = y; 8 | this.chaos = Math.random()*4 - 2; 9 | this.size = size; 10 | } 11 | Smoke.prototype.draw = function (ctx, t) { 12 | t += this.chaos; 13 | var g = t < 5 ? 255 - Math.floor(t*50) : Math.min(255, Math.floor(t*20)); 14 | var b = t < 5 ? 0 : Math.min(255, Math.floor(t*20)); 15 | var a = (this.totalLife - this.life)/this.totalLife; 16 | ctx.fillStyle = "rgba(255, "+g+", "+b+", "+a+")"; 17 | ctx.beginPath(); 18 | ctx.arc(this.x, this.y, this.size, 0, Math.PI*2); 19 | ctx.fill(); 20 | } 21 | 22 | var Flare = function (mine, large) { 23 | this.x = mine.x; 24 | this.y = mine.y; 25 | this.txt = " 嘭!"; 26 | this.life = 70; 27 | this.smokes = []; 28 | 29 | if (large) { 30 | for (var j = 0; j < 15; j++) { 31 | var r = j/3 - .8 + Math.random()*.5 - .25 ; 32 | var len = Math.random() * 30 + 15 33 | for (var i = 0; i < len; i++) { 34 | var r1 = r + (Math.random()*1 - .5) * (len - i) / len; 35 | this.smokes.push(new Smoke( 36 | i * .04 * mine.power * Math.sin(r), 37 | -i * .04 * mine.power * Math.cos(r) + i*i*mine.power/3000, 38 | 5 * Math.pow((len - i)/len, .6) * (Math.random() + 2), 39 | -i + Math.random()*len 40 | )); 41 | } 42 | } 43 | } else { 44 | for (var j = 0; j < 5; j++) { 45 | var r = j/3 - .8 + Math.random()*.5 - .25 ; 46 | var len = Math.random() * 20 + 10 47 | for (var i = 0; i < len; i++) { 48 | var r1 = r + (Math.random()*1 - .5) * (len - i) / len; 49 | this.smokes.push(new Smoke( 50 | i * 5 * Math.sin(r), 51 | -i * 5 * Math.cos(r) + i*i/10, 52 | 3 * Math.pow((len - i)/len, .8) * (Math.random() + 2), 53 | -i + Math.random()*len 54 | )); 55 | } 56 | } 57 | } 58 | } 59 | Flare.prototype.draw = function (ctx) { 60 | ctx.save(); 61 | ctx.translate(this.x, P.h - this.y); 62 | ctx.fillStyle = "#fff"; 63 | ctx.font="34px 宋体"; 64 | ctx.fillText(this.txt, 0, this.life - 80); 65 | ctx.font="14px 宋体"; 66 | var _this = this; 67 | this.smokes.forEach(function (smoke) { 68 | smoke.life++; 69 | if (smoke.life > 0 && smoke.life < smoke.totalLife) { 70 | smoke.draw(ctx, 70 - _this.life); 71 | } 72 | }); 73 | ctx.restore(); 74 | } 75 | 76 | 77 | var Toast = function (user) { 78 | this.x = user.x; 79 | this.y = user.y; 80 | this.dy = -60; 81 | this.size = user.size; 82 | this.txt = user.txt; 83 | this.life = 40; 84 | this.t = 0; 85 | } 86 | Toast.prototype.draw = function (ctx) { 87 | this.t++; 88 | ctx.save(); 89 | if (this.life < 10) { 90 | this.dy -= (10 - this.life)/4; 91 | ctx.globalAlpha = this.life/10; 92 | } 93 | ctx.translate(this.x, P.h - this.y); 94 | ctx.scale(Math.min(this.t/5, 1),Math.min(this.t/5, 1)); 95 | ctx.fillStyle = "#fff"; 96 | ctx.font = this.size + "px 宋体"; 97 | ctx.fillText(this.txt, 0, this.dy); 98 | ctx.font ="14px 宋体"; 99 | ctx.restore(); 100 | } 101 | 102 | var Brust = function (u, count, w, dx, dy, size) { 103 | this.x = u.x; 104 | this.y = u.y; 105 | this.size = size || 6 106 | this.life = 100; 107 | this.drops = []; 108 | this.vy = 2; 109 | dx = dx || 0; 110 | dy = dy || 0; 111 | for (var i = 0; i < count; i++) { 112 | this.drops.push({ 113 | x: Math.random()*w - w/2 + dx, y: Math.random()*10 - 5 + dy 114 | }); 115 | } 116 | } 117 | 118 | Brust.prototype.draw = function (ctx) { 119 | ctx.save(); 120 | ctx.fillStyle = "#fff"; 121 | ctx.globalAlpha = this.life/100; 122 | var _this = this; 123 | this.vy *= .95; 124 | this.drops.forEach(function (drop) { 125 | drop.y -= _this.vy; 126 | ctx.beginPath(); 127 | ctx.arc(drop.x + _this.x, P.h - drop.y - _this.y, _this.size, 0, Math.PI * 2); 128 | ctx.fill(); 129 | }); 130 | ctx.restore(); 131 | } 132 | 133 | var WaterDrops = function (u) { 134 | this.x = u.x; 135 | this.y = u.y; 136 | this.life = 100; 137 | this.drops = []; 138 | 139 | for (var i = 0; i < 1 - u.vy*2; i++) { 140 | this.drops.push({ 141 | x: 0, y:0, vx: Math.random()*4 - 2, vy: -Math.random()*u.vy/1.5 + 1 142 | }); 143 | } 144 | } 145 | WaterDrops.prototype.draw = function (ctx) { 146 | ctx.save(); 147 | ctx.fillStyle = "#95a"; 148 | var _this = this; 149 | this.drops.forEach(function (drop) { 150 | if (drop.y >= 0) { 151 | drop.y += drop.vy; 152 | drop.vy -= .2; 153 | drop.x += drop.vx; 154 | ctx.beginPath(); 155 | ctx.arc(drop.x + _this.x, P.h - drop.y - _this.y, 6, 0, Math.PI * 2); 156 | ctx.fill(); 157 | } 158 | }); 159 | ctx.restore(); 160 | } 161 | 162 | 163 | var ItemDead = function (item, name) { 164 | this.life = 40; 165 | this.item = item; 166 | this.name = name 167 | } 168 | ItemDead.prototype.draw = function (ctx) { 169 | ctx.strokeStyle = "rgba(255,255,255,"+(this.life)/40+")"; 170 | ctx.beginPath(); 171 | ctx.arc(this.item.x, P.h - this.item.y, P.itemSize + (40 - this.life)/2, 0, 2*Math.PI); 172 | ctx.stroke(); 173 | 174 | 175 | ctx.font = (1600 - this.life*this.life)/160 + 12 + "px 宋体"; 176 | ctx.fillStyle = "#fff"; 177 | ctx.fillText(this.name, this.item.x, P.h - this.item.y); 178 | ctx.font="14px 宋体"; 179 | } 180 | 181 | var ShotLine = function (x, y ,dir) { 182 | this.life = 20; 183 | this.x = x; 184 | this.y = y; 185 | this.dir = dir; 186 | } 187 | ShotLine.prototype.draw = function (ctx) { 188 | ctx.strokeStyle = "rgba(255,255,0,"+(this.life)/20+")"; 189 | ctx.beginPath(); 190 | if (this.dir == 1) { 191 | ctx.moveTo(this.x + 40, P.h - this.y); 192 | ctx.lineTo(P.w, P.h - this.y); 193 | } else { 194 | ctx.moveTo(this.x - 40, P.h - this.y); 195 | ctx.lineTo(0, P.h - this.y); 196 | } 197 | ctx.stroke(); 198 | } 199 | 200 | var Effect = { 201 | lists: [], 202 | create: function (name, val) { 203 | this.effects[name] = val; 204 | }, 205 | trigger: function (effect) { 206 | this.lists.push(effect); 207 | }, 208 | render: function (ctx) { 209 | for (var i = this.lists.length - 1; i >= 0; i--) { 210 | var eff = this.lists[i]; 211 | if (eff.life < 0) { 212 | this.lists.splice(i, 1); 213 | } else { 214 | eff.draw(ctx); 215 | eff.life--; 216 | } 217 | } 218 | }, 219 | clean: function () { 220 | this.lists = []; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /static/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 暴走后台 5 | 6 | 7 | 8 | 58 | 59 | 60 |
61 |
62 |
63 | 道具 64 | 用户 65 | 人物 66 | 地图 67 | 配置 68 |
69 |
70 |
71 |
72 | 生成道具: 73 | 无敌 74 | 75 | 地雷 76 | 毒药 77 | 隐身 78 | ? 79 | 二段跳 80 | 喷气背包 81 | 手雷 82 |
83 |
84 |
85 |
86 |
87 |
88 | 89 |
90 |
91 | 92 |
93 |
94 | heapdump 95 |
96 |
97 |
98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /static/td.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | endlesstd 5 | 6 | 7 | 104 | 105 | 106 |
107 |
108 |
109 | 110 |
111 |
112 |
113 |
114 |
115 |
116 | 117 | 118 | -------------------------------------------------------------------------------- /game/game.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var Pack = require('../static/js/JPack.js'); 3 | var C = require('../static/js/const.js'); 4 | 5 | var Map = require('./map.js'); 6 | var User = require('./user.js'); 7 | var Item = require('./item.js'); 8 | var Client = require('./client.js'); 9 | var Collide = require('./collide.js'); 10 | 11 | var map1 = require('./maps/lesson1.js'); 12 | var map2 = require('./maps/lesson2.js'); 13 | var AIController = require('./ai/AIController.js'); 14 | 15 | var DataSync = require('./lib/DataSync.js'); 16 | 17 | var Game = function (adminCode, maxUser, map, remove) { 18 | this.status = C.GAME_STATUS_INIT; 19 | this.adminCode = adminCode; 20 | this.mapConfig = map; 21 | //其他人 22 | this.users = []; 23 | //A方面 24 | this.teams = []; 25 | //所有连接,包括ob 26 | this.clients = []; 27 | this.deadClients = []; 28 | //物品 29 | this.items = []; 30 | //尸体 31 | this.bodies = []; 32 | //地雷 33 | this.mines = []; 34 | //投掷物 35 | this.entitys = []; 36 | this.tick = 0; 37 | this.remove = remove; 38 | this.structs = []; //预先建筑列表,map使用 39 | this.createMap(); 40 | //会与客户端同步的数据 41 | this.sync = new DataSync({ 42 | clientCount: 0, // 当前玩家数量 43 | type: this.map.type, 44 | structs: this.structs, // 建筑列表 45 | maxUser: maxUser //最多玩家数量 46 | }, this); 47 | 48 | this.runningTimer = setInterval(() => { 49 | this.update(); 50 | }, 17); 51 | } 52 | 53 | Game.prototype.createMap = function () { 54 | this.mines = []; 55 | this.entitys = []; 56 | this.structs = []; 57 | for (var i = 0; i < this.users.length; i++) { 58 | this.users[i].killed('system') 59 | } 60 | 61 | if (this.mapConfig == "lesson1") { 62 | this.map = new Map(this, map1); 63 | } else if (this.mapConfig == "lesson2") { 64 | this.map = new Map(this, map2); 65 | } else { 66 | this.map = new Map(this); 67 | } 68 | 69 | this.props = { 70 | userHeight: 40, 71 | userWidth: 40, 72 | mode: "", 73 | w: C.TW * this.map.w, 74 | h: C.TH * this.map.h 75 | } 76 | 77 | this.AIController = new AIController(this); 78 | 79 | for (var i = 0; i < this.clients.length; i++) { 80 | this.clients[i].sendMap(); 81 | } 82 | 83 | } 84 | // todo remove 85 | Game.prototype.createNPC = function (data) { 86 | data = data || {name: "萌萌的AI", npc: true, AI: "auto"}; 87 | var u = new User(this, data); 88 | if (!this.map) { 89 | u.x = data.x; 90 | u.y = data.y; 91 | } else { 92 | var p = this.map.born(); 93 | u.x = p.x; 94 | u.y = p.y; 95 | } 96 | this.users.push(u); 97 | return u; 98 | } 99 | //增加玩家 100 | Game.prototype.createUser = function (client) { 101 | var u = new User(this, client); 102 | var place = this.map.born(); 103 | u.x = place.x; 104 | u.y = place.y + C.TH/2; 105 | this.users.push(u); 106 | return u; 107 | } 108 | //获得玩家(或者尸体) 109 | Game.prototype.getUser = function (uid) { 110 | for (let user of this.users) { 111 | if (user.id == uid) { 112 | return user; 113 | } 114 | } 115 | for (let user of this.bodies) { 116 | if (user.id == uid) { 117 | return user; 118 | } 119 | } 120 | } 121 | //增加物品 122 | Game.prototype.createItem = function (type) { 123 | var item = new Item(this, type); 124 | this.items.push(item); 125 | return item; 126 | } 127 | //发生爆炸 128 | Game.prototype.explode = function (x, y, byUser, power) { 129 | for (let user of this.users) { 130 | var ux = user.x; 131 | var uy = user.y + this.props.userHeight; 132 | var dist = (ux - x)*(ux - x) + (uy - y)*(uy - y); 133 | if (dist < power*power) { 134 | user.killed('bomb', byUser); 135 | } 136 | if (dist < 2.25*power*power) { 137 | var r = Math.atan2(uy - y, ux - x); 138 | var force = 450 * power / (dist + 2500); 139 | user.vx += force * Math.cos(r); 140 | user.vy += force * Math.sin(r); 141 | user.danger = true; 142 | } 143 | }; 144 | this.announce('explode', {x: x, y: y, power: power}); 145 | } 146 | 147 | //发生枪击 148 | Game.prototype.checkShot = function (u) { 149 | var game = this; 150 | var x = u.x; 151 | var y = u.y + game.props.userHeight*2/3; 152 | var f = u.faceing; 153 | 154 | for (let user of this.users) { 155 | var uh = game.props.userHeight; 156 | if (user.crawl) { 157 | uh /= 2; 158 | } 159 | if (f < 0 && x > user.x && user.y <= y && user.y + uh >= y) { 160 | user.killed('gun', u); 161 | user.vx = 6 * f; 162 | } 163 | 164 | if (f > 0 && x < user.x && user.y <= y && user.y + uh >= y) { 165 | user.killed('gun', u); 166 | user.vx = 6 * f; 167 | } 168 | } 169 | } 170 | 171 | Game.prototype.addMine = function (user) { 172 | var x = user.x + user.faceing * 40; 173 | if (this.map.onFloor(x, user.y)) { 174 | this.mines.push({ 175 | x: x, 176 | y: user.y, 177 | creater: user, 178 | }); 179 | return true; 180 | } 181 | return false; 182 | } 183 | Game.prototype.checkMine = function (user) { 184 | for (var i = this.mines.length - 1; i >= 0; i--) { 185 | var mine = this.mines[i]; 186 | if (Math.abs(user.x - mine.x) < 10 && Math.abs(user.y - mine.y) < 5) { 187 | user.killed('mine', mine.creater); 188 | mine.dead = true; 189 | return true; 190 | } 191 | } 192 | return false; 193 | } 194 | 195 | //链接 196 | Game.prototype.addClient = function (socket, UUID) { 197 | for (var i = 0; i < this.deadClients.length; i++) { 198 | if (this.deadClients[i].UUID == UUID) { 199 | var client = this.deadClients[i]; 200 | client.socket = socket; 201 | this.deadClients.splice(i, 1); 202 | client.connect(); 203 | } 204 | } 205 | if (!client) { 206 | client = new Client(socket, this, UUID); 207 | } 208 | this.clients.push(client); 209 | this.clientCount++; 210 | } 211 | //链接关闭 212 | Game.prototype.removeClient = function (socket) { 213 | for (var i = 0; i < this.clients.length; i++) { 214 | if (this.clients[i].socket == socket) { 215 | var client = this.clients[i]; 216 | client.leaveTime = new Date().getTime(); 217 | console.log('User <' + client.name + '> ' 218 | + ' ['+client.joinTime+':'+client.leaveTime+':'+Math.floor((client.joinTime-client.leaveTime)/60)+']' 219 | + ' ['+client.kill+','+client.death+','+client.highestKill+']'); 220 | this.clients.splice(i, 1); 221 | this.deadClients.push(client); 222 | 223 | this.clientCount--; 224 | return; 225 | } 226 | } 227 | } 228 | //获得链接 229 | Game.prototype.getClient = function (cid) { 230 | for (let client of this.clients) { 231 | if (client.id == cid) { 232 | return client; 233 | } 234 | } 235 | } 236 | 237 | //分发事件 238 | Game.prototype.announce = function (type, data) { 239 | for (let client of this.clients) { 240 | client.socket.emit(type, data); 241 | } 242 | } 243 | 244 | Game.prototype.win = function (user) { 245 | this.announce('win', user.id); 246 | setTimeout(() => { 247 | clearInterval(this.runningTimer); 248 | this.remove && this.remove(this); 249 | }, 1000); 250 | } 251 | 252 | //游戏主流程 253 | Game.prototype.update = function () { 254 | this.tick++; 255 | this.map.update(); 256 | //物品更新 257 | for(let item of this.items) { 258 | item.update(); 259 | } 260 | //实体更新 261 | for(let entity of this.entitys) { 262 | entity.update(); 263 | } 264 | //碰撞检测 265 | for (var i = 0; i < this.users.length; i++) { 266 | for (var j = i + 1; j < this.users.length; j++) { 267 | Collide.userCollide(this.users[i], this.users[j], this); 268 | } 269 | for (var j = 0; j < this.items.length; j++) { 270 | Collide.eatItem(this.users[i], this.items[j], this); 271 | } 272 | } 273 | //user更新 274 | 275 | var npcCount = 0; 276 | for(let user of this.users) { 277 | user.update(); 278 | if (user.npc) { 279 | npcCount++; 280 | } 281 | }; 282 | 283 | //分发状态 284 | this.sendTick(); 285 | //清理死亡的人物/物品 286 | this.clean(); 287 | } 288 | Game.prototype.clean = function () { 289 | for(var i = this.items.length - 1; i >= 0; i--) { 290 | var item = this.items[i]; 291 | if (!item.dead) { 292 | } else { 293 | this.items.splice(i, 1); 294 | } 295 | } 296 | for(var i = this.mines.length - 1; i >= 0; i--) { 297 | var mine = this.mines[i]; 298 | if (!mine.dead) { 299 | } else { 300 | this.mines.splice(i, 1); 301 | } 302 | } 303 | for(var i = this.entitys.length - 1; i >= 0; i--) { 304 | var entity = this.entitys[i]; 305 | if (!entity.dead) { 306 | } else { 307 | this.entitys.splice(i, 1); 308 | } 309 | } 310 | for(var i = this.users.length - 1; i >= 0; i--) { 311 | var user = this.users[i]; 312 | if (!user.dead) { 313 | } else { 314 | this.users.splice(i, 1); 315 | this.bodies.push(user); 316 | if (this.bodies.length > 100) { 317 | this.bodies = this.bodies.slice(0, 50); 318 | } 319 | } 320 | } 321 | } 322 | Game.prototype.sendTick = function () { 323 | var itemdata = []; 324 | for (let item of this.items) { 325 | itemdata.push(item.getData()); 326 | } 327 | var userdata = []; 328 | for (let user of this.users) { 329 | userdata.push(user.getData()); 330 | } 331 | var clientsdata = []; 332 | for (let client of this.clients) { 333 | clientsdata.push(client.getData()); 334 | } 335 | var entitydata = []; 336 | for (let entity of this.entitys) { 337 | entitydata.push(Pack.entityPack.encode(entity)); 338 | } 339 | 340 | //team info 341 | // var team1 = { 342 | // users: [], 343 | // score: this.team1.score 344 | // } 345 | // var team2 = { 346 | // users: [], 347 | // score: this.team2.score 348 | // } 349 | 350 | 351 | for (let client of this.clients) { 352 | var p1 = client.p1 && client.p1.id; 353 | var minedata = []; 354 | for (let mine of this.mines) { 355 | if (mine.creater.id == p1 || mine.dead) { 356 | minedata.push(Pack.minePack.encode(mine)); 357 | } 358 | }; 359 | 360 | if (client.admin) { 361 | client.socket.emit('tick', { 362 | users: userdata, 363 | items: itemdata, 364 | mines: minedata, 365 | clients: clientsdata 366 | }); 367 | } else { 368 | client.socket.emit('tick', { 369 | users: userdata, 370 | items: itemdata, 371 | mines: minedata, 372 | entitys: entitydata 373 | }); 374 | } 375 | } 376 | 377 | 378 | var sync = this.sync.flush(); 379 | for (let client of this.clients) { 380 | if (sync) { 381 | client.socket.emit('globalSync', sync); 382 | } 383 | var userSync = client.sync.flush(); 384 | if (userSync) { 385 | client.socket.emit('userSync', userSync); 386 | } 387 | } 388 | } 389 | 390 | 391 | module.exports = Game; -------------------------------------------------------------------------------- /game/user.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var Pack = require('../static/js/JPack.js'); 3 | var C = require('../static/js/const.js'); 4 | var Grenade = require('./entity/grenade.js'); 5 | 6 | var userCount = 0; 7 | var User = function (game, client) { 8 | this.id = userCount++; 9 | this.game = game; 10 | this.client = client; 11 | this.name = client.name; 12 | this.team = client.team; 13 | 14 | //状态 15 | this.onFloor = false; 16 | this.onPilla = false; 17 | this.nearPilla = false; 18 | //滚动 19 | this.rolling = false; 20 | //下蹲 21 | this.crawl = false; 22 | //僵直中 23 | this.danger = false; 24 | //已死亡,尸体下落中 25 | this.dieing = false; 26 | //已死亡,躺尸 27 | this.dead = false; 28 | this.faceing = 1; 29 | 30 | //刚刚碰撞过哪些用户 31 | this.ignore = []; 32 | 33 | //携带物品 34 | this.carry = ''; 35 | this.carryCount = 0; 36 | 37 | //所在的设施 38 | this.onStruct = null; 39 | 40 | //施法动作(倒计时时间) 41 | this.fireing = 0; 42 | this.mining = 0; 43 | this.grenadeing = 0; 44 | 45 | this.score = 0; 46 | this.canDoubleJump = false; 47 | this.lastTouch = null; 48 | 49 | this.npc = client.npc; 50 | this.AIConfig = client.AI; 51 | 52 | 53 | //坐标 54 | this.x = 0; 55 | this.y = 0; 56 | //栅格坐标 57 | this.tx = 0; 58 | this.ty = 0; 59 | //栅格偏移坐标 60 | this.tox = 0; 61 | this.toy = 0; 62 | //速度 63 | this.vx = 0; 64 | this.vy = 0; 65 | 66 | //管理员下的监控指标 67 | this.watchData = {} 68 | } 69 | User.prototype.throwGrenade = function () { 70 | var g = new Grenade(this); 71 | var vx = this.faceing * (15 + this.grenadeing) / 5; 72 | var vy = this.grenadeing / 3; 73 | 74 | if (this.crawl) { 75 | vy = 0; 76 | } 77 | 78 | g.x = this.x - this.faceing * 20; 79 | g.y = this.y + this.game.props.userHeight; 80 | g.vx = this.vx + vx; 81 | g.vy = this.vy + vy; 82 | 83 | this.game.entitys.push(g); 84 | } 85 | User.prototype.getStatus = function () { 86 | this.crawl = false; 87 | if (this.dieing) {return "dieing";} 88 | if ((this.vy <= 0 || this.onPilla) && this.game.checkMine(this)) { 89 | return "dieing"; 90 | } 91 | if (this.onPilla && this.vx == 0 && this.vy == 0) { 92 | return "climbing"; 93 | } else { 94 | var onFloor = this.game.map.onFloor(this.x, this.y); 95 | this.onFloor = onFloor; 96 | this.nearPilla = this.game.map.nearPilla(this); 97 | this.client.canClimb = this.nearPilla ? true : false; 98 | if (onFloor && this.vy <= 0) { 99 | if (this.rolling) { 100 | this.rollPoint--; 101 | if (this.rollPoint <= 0) { 102 | this.vx = 0; 103 | this.rolling = false; 104 | } else { 105 | this.crawl = true; 106 | return "rolling2"; 107 | } 108 | } 109 | if (this.danger) { 110 | if (Math.abs(this.vx) < .2) { 111 | this.danger = false; 112 | return "standing"; 113 | } else { 114 | return "rolling"; 115 | } 116 | } 117 | if (this.mining > 0) { 118 | this.mining--; 119 | if (this.mining == 0) { 120 | this.game.addMine(this) && this.carryCount--; 121 | } else { 122 | return 'mining'; 123 | } 124 | } 125 | if ((this.upDown || this.downDown) && this.nearPilla) { 126 | this.onPilla = true; 127 | this.onFloor = false; 128 | this.vx = 0; 129 | this.pilla = this.nearPilla; 130 | this.x = this.pilla.x * C.TW; 131 | return "climbing"; 132 | } else if (this.downDown) { 133 | this.crawl = true; 134 | if (Math.abs(this.vx) < .2) { 135 | return "crawling"; 136 | } else { 137 | this.rolling = true; 138 | this.rollPoint = 20; 139 | if (this.vx > 0) {this.vx += 2} 140 | if (this.vx < 0) {this.vx -= 2} 141 | return "rolling2"; 142 | } 143 | 144 | } else if (this.itemPress && this.vx == 0 && this.carry == Pack.items.mine.id && this.carryCount > 0) { 145 | this.mining = 20; 146 | return 'mining'; 147 | } else { 148 | this.lastTouch = null; 149 | if (this.carry == Pack.items.doublejump.id) { 150 | this.canDoubleJump = true; 151 | } 152 | return "standing"; 153 | } 154 | } else { 155 | return "falling"; 156 | } 157 | } 158 | } 159 | User.prototype.update = function () { 160 | 161 | if (this.npc && this.AIConfig && !this.AI) { 162 | this.AI = this.game.AIController.userAI(this, this.AI); 163 | } 164 | if (this.AI) { 165 | this.AI.update(); 166 | } 167 | 168 | this.doubleJumping = false; 169 | this.flying = 0; 170 | 171 | for (var key in this.ignore) { 172 | this.ignore[key]--; 173 | } 174 | //时限 175 | if (this.carry == Pack.items.power.id || this.carry == Pack.items.hide.id || this.carry == Pack.items.bomb.id) { 176 | this.carryCount--; 177 | if (this.carryCount <= 0) { 178 | if (this.carry == Pack.items.bomb.id) { 179 | this.game.explode(this.x + this.faceing * 20, this.y + this.game.props.userHeight/2, this, 120); 180 | } 181 | this.carry = 0; 182 | this.carryCount = 0; 183 | } 184 | } 185 | 186 | this.onStruct = this.game.map.onStruct(this); 187 | if (this.onStruct) { 188 | this.client.onStruct = this.onStruct.id; 189 | } else { 190 | this.client.onStruct = 0; 191 | } 192 | 193 | if (this.spacePress && this.onStruct) { 194 | this.onStruct.act && this.onStruct.act(this); 195 | } 196 | if (this.spaceDown && this.onStruct) { 197 | this.onStruct.acting && this.onStruct.acting(this); 198 | } 199 | 200 | this.status = this.getStatus(); 201 | 202 | 203 | 204 | 205 | if (this.status == "falling" || this.status == "standing" || this.status == "climbing") { 206 | //开枪 207 | if (this.fireing > 0) { 208 | this.fireing--; 209 | if (this.fireing == 5) { 210 | this.carryCount--; 211 | if (this.carryCount == 0) { 212 | this.carry = 0; 213 | } 214 | this.game.checkShot(this); 215 | } 216 | } else if (this.itemPress && this.carry == Pack.items.gun.id && this.carryCount > 0) { 217 | this.fireing = 25; 218 | } 219 | } else { 220 | this.fireing = 0; 221 | } 222 | 223 | if (this.status == "falling" || this.status == "standing" || this.status == "climbing" || this.status == "crawling") { 224 | //grenade 225 | if (this.grenadeing > 0 && this.itemDown) { 226 | this.grenadeing++; 227 | this.grenadeing = Math.min(25, this.grenadeing); 228 | } else if (this.grenadeing > 0 && !this.itemDown) { 229 | this.throwGrenade(); 230 | this.grenadeing = 0; 231 | this.carryCount--; 232 | if (this.carryCount == 0) { 233 | this.carry = 0; 234 | } 235 | } else if (this.grenadeing == 0 && this.itemPress && this.carry == Pack.items.grenade.id && this.carryCount > 0) { 236 | this.grenadeing = 1; 237 | } 238 | } else { 239 | this.grenadeing = 0; 240 | } 241 | 242 | 243 | if (this.status == "dieing") { 244 | this.vx *= .98; 245 | this.vy -= .2; 246 | this.vy = Math.max(-9, this.vy); 247 | this.r += this.vr; 248 | this.vr *= .96; 249 | } if (this.status == "climbing") { 250 | if (this.upDown && !this.downDown && this.y < this.pilla.y2*C.TH - this.game.props.userHeight) { 251 | this.y += 3; 252 | } else if (this.downDown && !this.upDown && this.y > this.pilla.y1*C.TH + 3) { 253 | this.y -= 3; 254 | } 255 | if (this.leftPress) { 256 | if (this.faceing != -1) { 257 | this.faceing = -1; 258 | } else { 259 | this.vx = -2; 260 | this.onPilla = false; 261 | } 262 | } else if (this.rightPress) { 263 | if (this.faceing != 1) { 264 | this.faceing = 1; 265 | } else { 266 | this.vx = 2; 267 | this.onPilla = false; 268 | } 269 | } 270 | } else if (this.status == "standing") { 271 | if (this.leftDown && !this.rightDown) { 272 | if (this.vx > 0) { 273 | if (this.carry == Pack.items.power.id) { 274 | this.vx = -.4; 275 | } else { 276 | this.vx = -1; 277 | } 278 | } else { 279 | if (this.carry == Pack.items.power.id) { 280 | this.vx -= .08; 281 | } else { 282 | this.vx -= .2; 283 | } 284 | } 285 | this.faceing = -1; 286 | this.vx = Math.max(this.vx, -4); 287 | } else if (!this.leftDown && this.rightDown) { 288 | if (this.vx < 0) { 289 | if (this.carry == Pack.items.power.id) { 290 | this.vx = .4; 291 | } else { 292 | this.vx = 1; 293 | } 294 | } else { 295 | if (this.carry == Pack.items.power.id) { 296 | this.vx += .08; 297 | } else { 298 | this.vx += .2; 299 | } 300 | } 301 | this.faceing = 1; 302 | this.vx = Math.min(this.vx, 4); 303 | } else { 304 | this.vx = 0; 305 | } 306 | if (this.upDown && !this.downDown) { 307 | this.vy = 5; 308 | this.flypackActive = false; 309 | } else { 310 | this.vy = 0; 311 | } 312 | } else if (this.status == "rolling2") { 313 | this.vx *= .96; 314 | } else if (this.status == "rolling") { 315 | this.vx *= .9; 316 | } else if (this.status == "falling") { 317 | if (this.upPress && this.canDoubleJump) { 318 | this.doubleJumping = true; 319 | this.canDoubleJump = false; 320 | this.vy = 5; 321 | } 322 | if (this.upPress && this.carry == Pack.items.flypack.id) { 323 | this.flypackActive = true; 324 | } 325 | if (this.upDown && this.carry == Pack.items.flypack.id && this.carryCount > 0 && this.flypackActive) { 326 | this.vy += .3; 327 | this.flying += 1; 328 | this.carryCount--; 329 | } 330 | if (this.leftPress && this.faceing == 1) { 331 | this.faceing = -1; 332 | } 333 | if (this.rightPress && this.faceing == -1) { 334 | this.faceing = 1; 335 | } 336 | if (this.leftDown && this.carry == Pack.items.flypack.id && this.carryCount > 0) { 337 | this.vx -= .15; 338 | this.flying += 2; 339 | this.carryCount-=.2; 340 | } 341 | if (this.rightDown && this.carry == Pack.items.flypack.id && this.carryCount > 0) { 342 | this.vx += .15; 343 | this.flying += 4; 344 | this.carryCount-=.2; 345 | } 346 | this.vy -= .2; 347 | this.vy = Math.max(-9, this.vy); 348 | this.vy = Math.min(10, this.vy); 349 | this.vx = Math.max(-8, this.vx); 350 | this.vx = Math.min(8, this.vx); 351 | if (this.vy == -9) { 352 | this.danger = true; 353 | } 354 | } else if (this.status == "crawling") { 355 | this.vx = 0; 356 | } 357 | 358 | //final process 359 | this.x += this.vx; 360 | if (this.x <= 0) {this.vx = Math.abs(this.vx)} 361 | if (this.x >= this.game.props.w) {this.vx = -Math.abs(this.vx)} 362 | if (this.y < 0) { 363 | this.dead = true; 364 | if (!this.dieing) { 365 | this.killed('fall'); 366 | } 367 | } else { 368 | if (this.vy > 0) { 369 | this.y += Math.floor(this.vy); 370 | } else { 371 | for (var i = 0; i < -this.vy; i++) { 372 | this.y--; 373 | if(!this.dieing && this.game.map.onFloor(this.x, this.y)) { 374 | this.vy = 0; 375 | break; 376 | } 377 | } 378 | } 379 | } 380 | 381 | 382 | this.updateCommon(); 383 | if (this.AI) { 384 | this.watchData.dir = this.AI.dir; 385 | } 386 | } 387 | 388 | User.prototype.updateCommon = function () { 389 | this.tx = Math.floor(this.x / C.TW); 390 | this.ty = Math.floor(this.y / C.TH); 391 | this.tox = this.x % C.TW; 392 | this.toy = this.y % C.TH; 393 | } 394 | 395 | User.prototype.scoreing = function () { 396 | this.score++; 397 | this.client.kill++; 398 | if (this.score > this.client.highestKill) { 399 | this.client.highestKill = this.score; 400 | } 401 | if (this.game.map.hooks.onKill) { 402 | this.game.map.hooks.onKill(this.game, this); 403 | } 404 | } 405 | User.prototype.killed = function (action, byUser) { 406 | if (this.dieing) {return} 407 | this.killer = byUser && byUser.id; 408 | this.dieing = true; 409 | this.killedBy = action; 410 | this.client.death++; 411 | 412 | if (action == 'power') { 413 | this.vy = 10; 414 | } else if (action == 'drug') { 415 | this.vy = 3; 416 | this.killer = this.lastTouch; 417 | } else if (action == 'gun') { 418 | this.vy = 1; 419 | } else if (action == 'mine') { 420 | this.vy = 10; 421 | } else if (action == 'bomb') { 422 | } else if (action == 'system') { 423 | this.killer = null; 424 | } else { 425 | this.killer = this.lastTouch; 426 | } 427 | 428 | if (this.game.map.hooks.onKilled) { 429 | this.game.map.hooks.onKilled(this.game, this); 430 | } 431 | if (this.killer && this.killer != this.id) { 432 | var killer = this.game.getUser(this.killer); 433 | if (killer) { 434 | killer.scoreing(); 435 | } 436 | } 437 | 438 | if (killer) { 439 | if (action == 'drug') { 440 | var message = "" + killer.name + "" + this.name + "品尝到了毒药的滋味"; 441 | } else if (action == 'mine') { 442 | if (this.killer == this.id) { 443 | var message = "" + killer.name + "用自己的身体检验了地雷的可靠性,结果很成功"; 444 | } else { 445 | var message = "" + killer.name + "的地雷让" + this.name + "的菊花一紧"; 446 | } 447 | } else if (action == 'gun') { 448 | var message = "" + killer.name + "开枪了," + this.name + "应声倒地"; 449 | } else if (action == 'power') { 450 | var message = "" + killer.name + "" + this.name + "扔进了泥潭"; 451 | } else if (action == 'bomb') { 452 | var message = "" + this.name + "没能从爆炸中逃生"; 453 | } else { 454 | var message = "" + killer.name + "" + this.name + "扔进了泥潭"; 455 | } 456 | } else { 457 | if (action == 'drug') { 458 | var message = "" + this.name + "尝了一口毒药"; 459 | } else if (action == 'system') { 460 | var message = "" + this.name + "被时空管理局消灭"; 461 | } else { 462 | var message = "" + this.name + "完成了华丽的一跃"; 463 | } 464 | } 465 | 466 | this.game.announce('userDead', { 467 | user: this.getData(), 468 | killer: killer && killer.getData(), 469 | message: message 470 | }); 471 | } 472 | User.prototype.getData = function () { 473 | return Pack.userPack.encode(this); 474 | } 475 | module.exports = User; -------------------------------------------------------------------------------- /static/tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 133 | 134 | 135 | 136 | 137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | 148 | 149 | 150 | 151 |
152 | 153 | 728 | 729 | -------------------------------------------------------------------------------- /static/js/game.js: -------------------------------------------------------------------------------- 1 | p1.onJoin = joining; 2 | 3 | var app = new Vue({ 4 | el: '#gameView', 5 | methods: { 6 | changeMap: function () { 7 | socket.emit('changeMap'); 8 | }, 9 | input: function (e) { 10 | e.stopPropagation(); 11 | }, 12 | joining: function () { 13 | joining(); 14 | }, 15 | ob: function () { 16 | app.playing = true; 17 | } 18 | }, 19 | data: { 20 | message: 'INIT DONE', 21 | playing: false, 22 | myName: localStorage.userName || "无名小卒", 23 | type: "world", 24 | clientCount: 0, 25 | playerCount: 0, 26 | maxUser: 0, 27 | win: false, 28 | logs: [], 29 | viewport: { 30 | scale: 1, 31 | w: 1, 32 | h: 1 33 | } 34 | } 35 | }) 36 | 37 | 38 | 39 | function parseParam () { 40 | var query = location.search; 41 | if (query.indexOf('?') == 0) { 42 | query = query.substring(1); 43 | } 44 | if (!query) { 45 | return {} 46 | } 47 | var arr = query.split('&'); 48 | var res = {}; 49 | for (var i = arr.length - 1; i >= 0; i--) { 50 | var pair = arr[i].split('='); 51 | if (pair[1]) { 52 | res[pair[0]] = decodeURIComponent(pair[1].replace(/\+/g, " ")); 53 | } else { 54 | res[pair[0]] = null; 55 | } 56 | } 57 | return res; 58 | } 59 | var param = parseParam(); 60 | 61 | var scoreText = [ 62 | '小试牛刀', 63 | '势不可挡', 64 | '正在大杀特杀', 65 | '已经主宰比赛', 66 | '已经杀人如麻', 67 | '已经无人能挡', 68 | '已经变态杀戮', 69 | '已经妖怪般的杀戮', 70 | '已经如同神一般', 71 | '已经超越神了' 72 | ] 73 | 74 | function notice (str) { 75 | app.logs.unshift(str); 76 | if (app.logs.length > 20) { 77 | app.logs.pop(); 78 | } 79 | } 80 | 81 | var P; 82 | var game = { 83 | t: 0, 84 | beginRender: false, 85 | env: { 86 | cdx: 0, 87 | cdy: 0, 88 | }, 89 | userWords: {}, 90 | display: { 91 | ctx: { 92 | fg: document.getElementById('fg').getContext("2d"), 93 | bg: document.getElementById('bg').getContext("2d"), 94 | mark: document.getElementById('mark').getContext("2d"), 95 | structs: document.getElementById('structs').getContext("2d"), 96 | }, 97 | scale: 1 98 | } 99 | }; 100 | 101 | 102 | function joining (team) { 103 | if (!app.playerCount >= app.maxUser || app.playing) {return} 104 | localStorage.userName = app.myName; 105 | p1.team = team; 106 | socket.emit('join', { 107 | userName: app.myName, 108 | team: team 109 | }); 110 | 111 | } 112 | 113 | function initViewPort () { 114 | if (!P) {return} 115 | var wh = window.innerHeight; 116 | var ww = window.innerWidth; 117 | if (P.w / ww > P.h / wh) { 118 | var s = ww / P.w; 119 | } else { 120 | var s = wh / P.h; 121 | } 122 | app.viewport = { 123 | scale: s, 124 | w: P.w*s, 125 | h: P.h*s 126 | } 127 | 128 | setTimeout(function () { 129 | game.beginRender = true; 130 | game.display.ctx.fg.setTransform(1, 0, 0, 1, 0, 0); 131 | game.display.ctx.mark.setTransform(1, 0, 0, 1, 0, 0); 132 | game.display.ctx.bg.setTransform(1, 0, 0, 1, 0, 0); 133 | game.display.ctx.structs.setTransform(1, 0, 0, 1, 0, 0); 134 | game.display.ctx.fg.scale(s, s); 135 | game.display.ctx.mark.scale(s, s); 136 | game.display.ctx.bg.scale(s, s); 137 | game.display.ctx.structs.scale(s, s); 138 | game.display.ctx.fg.font="14px 宋体"; 139 | game.display.ctx.fg.textBaseline = 'middle';//设置文本的垂直对齐方式 140 | game.display.ctx.fg.textAlign = 'center'; //设置文本的水平对对齐方式 141 | game.display.ctx.mark.font="14px 宋体"; 142 | game.display.ctx.mark.textBaseline = 'middle';//设置文本的垂直对齐方式 143 | game.display.ctx.mark.textAlign = 'center'; //设置文本的水平对对齐方式 144 | 145 | //绘制背景 146 | drawBg(game.display.ctx.bg, game.map); 147 | drawAllStructs(game.display.ctx.structs); 148 | //初始化尸体 149 | for (var i = 0; i < game.bodies.length; i++) { 150 | var user = Packs.userPack.decode(game.bodies[i]); 151 | drawUser(game.display.ctx.mark, user); 152 | } 153 | }, 1); 154 | } 155 | window.addEventListener('resize', initViewPort); 156 | 157 | 158 | function initDone () { 159 | socket.emit('init', { 160 | userName: app.myName 161 | }); 162 | //初始化链接,监听事件(打开或刷新页面触发,也可能由后台重启触发) 163 | socket.on('init', function (data) { 164 | Effect.clean(); 165 | P = data.props //常量 166 | 167 | game.map = data.map; 168 | initViewPort(); 169 | game.bodies = data.bodies 170 | 171 | }); 172 | socket.on('message', function (data) { 173 | notice(data[1]); 174 | game.userWords[data[0]] = { 175 | t: game.t, 176 | txt: data[1] 177 | } 178 | }) 179 | //加入 180 | socket.on('joinSuccess', function (p1) { 181 | app.playing = true; 182 | }); 183 | socket.on('joinFail', function (message) { 184 | alert(message); 185 | }); 186 | //主流程 187 | var controlCache = ''; 188 | socket.on('tick', function (data) { 189 | if (!P) {return} 190 | game.t++; 191 | var playerCount = 0; 192 | for (var i = 0; i < data.users.length; i++) { 193 | data.users[i] = Packs.userPack.decode(data.users[i]); 194 | if (!data.users[i].npc) { 195 | playerCount++; 196 | } 197 | if (data.users[i].id == p1) { 198 | p1.data = data.users[i]; 199 | } 200 | } 201 | app.playerCount = playerCount; 202 | for (var i = 0; i < data.items.length; i++) { 203 | data.items[i] = Packs.itemPack.decode(data.items[i]); 204 | } 205 | for (var i = 0; i < data.mines.length; i++) { 206 | data.mines[i] = Packs.minePack.decode(data.mines[i]); 207 | } 208 | for (var i = 0; i < data.entitys.length; i++) { 209 | data.entitys[i] = Packs.entityPack.decode(data.entitys[i]); 210 | } 211 | //更新游戏渲染 212 | 213 | if (!game.beginRender) {return} 214 | render(game.display.ctx.fg, data); 215 | drawStructs(game.display.ctx.structs); 216 | //发送控制 217 | var control = JSON.stringify(Packs.controlPack.encode(p1)); 218 | if (controlCache != control) { 219 | controlCache = control; 220 | socket.emit('control', Packs.controlPack.encode(p1)); 221 | } 222 | 223 | 224 | p1.leftPress = false; 225 | p1.rightPress = false; 226 | p1.upPress = false; 227 | p1.downPress = false; 228 | p1.itemPress = false; 229 | p1.spacePress = false; 230 | }); 231 | 232 | socket.on('explode', function (data) { 233 | game.env.cdx = 8; 234 | game.env.cdy = 9; 235 | Effect.trigger(new Flare(data, true)); 236 | }); 237 | 238 | socket.on('userDead', function (data) { 239 | var user = Packs.userPack.decode(data.user); 240 | notice(data.message); 241 | //p1 dead 242 | if (user.id == app.me) { 243 | app.playing = false; 244 | } 245 | if (data.killer) { 246 | var killer = Packs.userPack.decode(data.killer); 247 | if (killer.score <= 10) { 248 | Effect.trigger(new Toast({ 249 | x: killer.x, 250 | y: killer.y, 251 | size: killer.score * 1.5 + 14, 252 | txt: killer.name + scoreText[killer.score - 1] 253 | })); 254 | } 255 | } 256 | }); 257 | 258 | socket.on('win', function () { 259 | app.win = true; 260 | }) 261 | 262 | socket.on('globalSync', function (data) { 263 | if (data.structs) { 264 | game.structsData = []; 265 | for (var struct of data.structs) { 266 | if (game.structsData[struct.y] == undefined) { 267 | game.structsData[struct.y] = []; 268 | } 269 | game.structsData[struct.y][struct.x] = struct; 270 | } 271 | } 272 | syncData(data, app); 273 | }) 274 | 275 | socket.on('userSync', function (data) { 276 | syncData(data, app); 277 | }); 278 | 279 | function syncData (data, dest) { 280 | for (var key in data) { 281 | if (key.indexOf(':') != -1) { 282 | var keys = key.split(':'); 283 | var old = dest[keys[0]]; 284 | if (Array.isArray(old)) { 285 | syncData(data[key], old[parseInt(keys[1])]); 286 | old[parseInt(keys[1])].clean = false; 287 | } else { 288 | syncData(data[key], old[keys[1]]); 289 | old[(keys[1])].clean = false; 290 | } 291 | } else { 292 | var value = data[key]; 293 | if (typeof value == "number" || typeof value == "string" || typeof value == "boolean") { 294 | dest[key] = value; 295 | } else { 296 | dest[key] = value; 297 | } 298 | } 299 | } 300 | } 301 | 302 | var listeners = { 303 | structs: function (oldVal, newVal) { 304 | 305 | }, 306 | struct_i: function (i, data) { 307 | 308 | } 309 | } 310 | socket.begin(param.roomID); 311 | } 312 | 313 | var imgUrls = { 314 | happy: "/head/happy.png", 315 | throll: "/head/throll.png", 316 | danger: "/head/danger3.png", 317 | alone: "/head/alone.png", 318 | alone2: "/head/alone2.png", 319 | alone3: "/head/alone3.png", 320 | normal: "/head/normal.png", 321 | win: "/head/win.png", 322 | wtf: "/head/wtf.png", 323 | items: [ 324 | "/item/power.png", 325 | "/item/gun.png", 326 | "/item/mine.png", 327 | "/item/drug.png", 328 | "/item/hide.png", 329 | "/item/random.png", 330 | "/item/random.png", 331 | "/item/flypack.png", 332 | "/item/grenade.png", 333 | ], 334 | sign: "/tile/sign.png", 335 | door: "/tile/door.png", 336 | dooropen: "/tile/dooropen.png", 337 | itemGate: "/tile/itemGate.png", 338 | bomb: "/bomb.png", 339 | arm: "/arm.png", 340 | grenade: "/grenade.png", 341 | minePlaced: "/mine.png", 342 | jet: "/jet.png" 343 | }; 344 | 345 | var imgs = {}; 346 | for (var key in imgUrls) { 347 | if (typeof(imgUrls[key]) == "string") { 348 | var Img = new Image(); 349 | Img.src = "/static/imgs/" + imgUrls[key]; 350 | imgs[key] = Img; 351 | } else { 352 | var arr = []; 353 | for (var i = 0; i < imgUrls[key].length; i++) { 354 | var Img = new Image(); 355 | Img.src = "/static/imgs/" + imgUrls[key][i]; 356 | arr.push(Img); 357 | } 358 | imgs[key] = arr; 359 | } 360 | } 361 | 362 | //绘制背景 363 | function drawBg (ctx, map) { 364 | ctx.clearRect(0, 0, P.w, P.h); 365 | //绘制柱子 366 | map.pilla.forEach(function (pilla) { 367 | ctx.fillStyle = "#888"; 368 | for (var j = P.h - pilla.y2*C.TH + 10; j < P.h - pilla.y1*C.TH; j += 20) { 369 | ctx.fillRect(pilla.x * C.TW - 10, j, 20, 4); 370 | } 371 | 372 | ctx.fillStyle = "#aaa"; 373 | ctx.beginPath(); 374 | ctx.rect(pilla.x * C.TW - 12, P.h - pilla.y2*C.TH, 4, (pilla.y2 - pilla.y1)*C.TH); 375 | ctx.stroke(); 376 | ctx.fill(); 377 | ctx.beginPath(); 378 | ctx.arc(pilla.x * C.TW - 10, P.h - pilla.y2*C.TH - 4, 4, 0, 2*Math.PI); 379 | ctx.stroke(); 380 | ctx.fill(); 381 | 382 | ctx.beginPath(); 383 | ctx.rect(pilla.x * C.TW + 8, P.h - pilla.y2*C.TH, 4, (pilla.y2 - pilla.y1)*C.TH); 384 | ctx.stroke(); 385 | ctx.fill(); 386 | ctx.beginPath(); 387 | ctx.arc(pilla.x * C.TW + 10, P.h - pilla.y2*C.TH - 4, 4, 0, 2*Math.PI); 388 | ctx.stroke(); 389 | ctx.fill(); 390 | }); 391 | 392 | //绘制地板 393 | for (var i = 0; i < map.floor.length; i++) { 394 | for (var j = 0; j < map.floor[i].length; j++) { 395 | var x = j * C.TW; 396 | var y = P.h - (i) * C.TH 397 | if (map.floor[i][j]) { 398 | ctx.beginPath(); 399 | ctx.fillStyle = "#aa8"; 400 | ctx.moveTo(x + 1, y) 401 | ctx.lineTo(x + C.TW - 1, y); 402 | ctx.lineTo(x + C.TW - 1, y + 4); 403 | ctx.lineTo(x + C.TW - 5, y + 8); 404 | ctx.lineTo(x + 5, y + 8); 405 | ctx.lineTo(x + 1, y + 4); 406 | ctx.fill(); 407 | 408 | ctx.fillStyle = "#982"; 409 | ctx.beginPath(); 410 | ctx.fillRect(x + 1, y, C.TW - 2, 4); 411 | ctx.fill(); 412 | } 413 | } 414 | } 415 | } 416 | 417 | function drawDoor (ctx, struct) { 418 | var w = 1- (struct.working / struct.workingTime); 419 | 420 | ctx.fillStyle = "#c52"; 421 | ctx.fillRect(struct.x * C.TW, P.h - (struct.y + 1) * C.TH, C.TW, C.TH); 422 | 423 | ctx.fillStyle = "#333"; 424 | ctx.fillRect((struct.x + .1) * C.TW, P.h - (struct.y + 1) * C.TH + 4, C.TW * .8, C.TH - 4); 425 | 426 | ctx.save(); 427 | ctx.translate((struct.x+.1) * C.TW, P.h - (struct.y + 1) * C.TH); 428 | ctx.scale(w, 1); 429 | ctx.fillStyle = "#ca9"; 430 | ctx.fillRect(0, 4, C.TW*.4, C.TH - 4); 431 | if (!struct.opening) { 432 | ctx.fillStyle = "#000"; 433 | } else { 434 | ctx.fillStyle = "#ffa"; 435 | } 436 | ctx.fillRect(C.TW/8, 2 + C.TH/4, C.TW*.2, C.TH/4); 437 | ctx.restore(); 438 | 439 | 440 | ctx.save(); 441 | ctx.translate((struct.x + .9) * C.TW, P.h - (struct.y + 1) * C.TH); 442 | ctx.scale(-w, 1); 443 | ctx.fillStyle = "#ca9"; 444 | ctx.fillRect(0, 4, C.TW*.4, C.TH - 4); 445 | if (!struct.opening) { 446 | ctx.fillStyle = "#000"; 447 | } else { 448 | ctx.fillStyle = "#ffa"; 449 | } 450 | ctx.fillRect(C.TW/8, 2 + C.TH/4, C.TW*.2, C.TH/4); 451 | ctx.restore(); 452 | } 453 | function drawStruct (ctx, struct) { 454 | if (struct.type == "sign") { 455 | if (!imgs.itemGate.sign) {return false} 456 | ctx.drawImage(imgs.sign, struct.x * C.TW, P.h - (struct.y+1)*C.TH, C.TW, C.TH); 457 | } else if (struct.type == "itemGate") { 458 | if (!imgs.itemGate.complete) {return false} 459 | ctx.drawImage(imgs.itemGate, struct.x * C.TW, P.h - (struct.y+1)*C.TH, C.TW, C.TH); 460 | } else { 461 | drawDoor(ctx, struct); 462 | } 463 | return true; 464 | } 465 | function drawStructs (ctx) { 466 | ctx.save(); 467 | for (var i = 0; i < app.structs.length; i++) { 468 | 469 | var struct = app.structs[i]; 470 | if (struct.clean == true) { 471 | continue; 472 | } 473 | 474 | ctx.clearRect((struct.x) * C.TW, P.h - (struct.y+1)*C.TH, C.TW , C.TH); 475 | // if (game.structsData[struct.y] && game.structsData[struct.y][struct.x - 2]) { 476 | // drawStruct(ctx, game.structsData[struct.y][struct.x - 2]); 477 | // } 478 | // if (game.structsData[struct.y] && game.structsData[struct.y][struct.x - 1]) { 479 | // drawStruct(ctx, game.structsData[struct.y][struct.x - 1]); 480 | // } 481 | if (drawStruct(ctx, struct)) { 482 | struct.clean = true; 483 | } 484 | // if (game.structsData[struct.y] && game.structsData[struct.y][struct.x + 1]) { 485 | // drawStruct(ctx, game.structsData[struct.y][struct.x + 1]); 486 | // } 487 | // if (game.structsData[struct.y] && game.structsData[struct.y][struct.x + 2]) { 488 | // drawStruct(ctx, game.structsData[struct.y][struct.x + 2]); 489 | // } 490 | 491 | } 492 | ctx.restore(); 493 | } 494 | function drawAllStructs (ctx) { 495 | ctx.clearRect(0, 0, P.w, P.h); 496 | for (var i = 0; i < app.structs.length; i++) { 497 | app.structs[i].clean = false; 498 | } 499 | drawStructs(ctx); 500 | } 501 | 502 | function drawWater (ctx, height, color) { 503 | var waveLen = 20; 504 | var waveHeight = height/5; 505 | var c = waveLen / 2; 506 | var b = P.h - height; 507 | var offset = Math.sin(game.t/50 + height) * 10; 508 | var start = offset - 10 509 | ctx.fillStyle = color; 510 | ctx.beginPath(); 511 | ctx.moveTo(start, P.h); 512 | ctx.lineTo(start, P.h - height); 513 | while (start < P.w) { 514 | ctx.bezierCurveTo(start + c, b + waveHeight, start + waveLen - c, b + waveHeight, start + waveLen, b); 515 | start += waveLen; 516 | waveHeight *= -1; 517 | } 518 | ctx.lineTo(P.w, P.h); 519 | ctx.fill(); 520 | } 521 | 522 | function drawWeapon (ctx, index) { 523 | ctx.strokeStyle = "#fff"; 524 | ctx.lineWidth = 6; 525 | if (index < 10) { 526 | ctx.rotate(1 - index/10); 527 | } 528 | ctx.beginPath(); 529 | ctx.moveTo(10, 0); 530 | if (index < 10) { 531 | ctx.lineTo(10 + index * 2, 10 - index); 532 | ctx.lineTo(10 + index * 3, -5); 533 | var endx = 10 + index * 3; 534 | } else { 535 | ctx.lineTo(10 + 20, 0); 536 | ctx.lineTo(10 + 30, -5); 537 | var endx = 10 + 30; 538 | } 539 | ctx.stroke(); 540 | 541 | ctx.strokeStyle = "#F00"; 542 | ctx.beginPath(); 543 | ctx.moveTo(endx, 0); 544 | ctx.lineTo(endx, -6); 545 | ctx.lineTo(endx + 9, -6); 546 | ctx.stroke(); 547 | 548 | ctx.lineWidth = 1; 549 | } 550 | 551 | function drawUser (ctx, user) { 552 | if (user.doubleJumping) { 553 | Effect.trigger(new Brust(user, 10, 40)) 554 | } 555 | 556 | if (app.onStruct) { 557 | var struct = app.structs.find(function (st) {return st.id == app.onStruct}); 558 | if (struct && struct.message) { 559 | ctx.fillStyle = "#fff"; 560 | ctx.fillText(struct.message, struct.x * C.TW, P.h - (struct.y + 1)*C.TH - 30); 561 | } 562 | } 563 | ctx.save(); 564 | ctx.translate(user.x, P.h - user.y); 565 | if (user.dead) { 566 | var img = imgs.alone; 567 | } else if (user.status == "dieing") { 568 | var img = imgs.alone; 569 | } else if (user.carry == Packs.items.power.id) { 570 | var img = imgs.win; 571 | } else if (user.danger) { 572 | var img = imgs.danger; 573 | } else if (user.status == "crawling" || user.status == "mining" || user.status == "rolling2") { 574 | var img = imgs.throll; 575 | } else if (user.carry == Packs.items.bomb.id) { 576 | var img = imgs.wtf; 577 | } else if (user.status == "falling" || user.status == "climbing") { 578 | var img = imgs.happy; 579 | } else { 580 | var img = imgs.normal; 581 | } 582 | 583 | //用户说话 584 | 585 | 586 | 587 | if (game.userWords[user.id] && game.t - game.userWords[user.id].t < 60) { 588 | var t = game.t - game.userWords[user.id].t; 589 | ctx.save(); 590 | 591 | if (t < 10) { 592 | ctx.scale((t)/10,(t)/10); 593 | } 594 | 595 | var txt = game.userWords[user.id].txt; 596 | var rect = ctx.measureText(txt); 597 | ctx.fillStyle = "#fff"; 598 | 599 | ctx.beginPath(); 600 | ctx.moveTo(20,-20); 601 | ctx.lineTo(60, -30); 602 | ctx.lineTo(60, -40); 603 | ctx.fill(); 604 | ctx.fillRect(55, -50, rect.width + 10, 30); 605 | 606 | ctx.fillStyle = "#000"; 607 | ctx.fillText(game.userWords[user.id].txt, 60, -30); 608 | 609 | 610 | ctx.restore(); 611 | } 612 | 613 | //用户指示器 614 | if (user.id == app.me) { 615 | ctx.fillStyle = "#ffa"; 616 | } else if (user.team && user.team == app.team) { 617 | ctx.fillStyle = "#aaf"; 618 | } else { 619 | ctx.fillStyle = "#f77"; 620 | } 621 | if (user.carry != Packs.items.hide.id || user.id == app.me) { 622 | ctx.fillText(user.name, 0, -50); 623 | if (app.canClimb && user.id == app.me) { 624 | ctx.fillText("上", 0, -70); 625 | } 626 | } 627 | 628 | ctx.scale(user.faceing, 1); 629 | 630 | if (user.fireing) { 631 | if (user.fireing == 5) { 632 | Effect.trigger(new ShotLine(user.x, user.y + 25, user.faceing)); 633 | } 634 | ctx.save(); 635 | ctx.translate(0, -P.userWidth/2); 636 | drawWeapon(ctx, 25 - user.fireing); 637 | ctx.restore(); 638 | } 639 | 640 | if (user.carry == Packs.items.hide.id) { 641 | ctx.globalAlpha = user.carryCount > 900 ? (user.carryCount - 900)/100 : user.carryCount > 100 ? 0 : (100 - user.carryCount)/100 642 | } 643 | 644 | if (user.status == "crawling" || user.status == "mining" || user.status == "rolling2") { 645 | ctx.drawImage(img, -P.userWidth/2, -25, P.userWidth, P.userHeight); 646 | } else { 647 | ctx.drawImage(img, -P.userWidth/2, -P.userHeight, P.userWidth, P.userHeight); 648 | } 649 | if (user.carry == Packs.items.flypack.id) { 650 | var bottleWidth = 10; 651 | var bottleHeight = 30; 652 | var wPadding = -P.userWidth/2 - bottleWidth; 653 | ctx.lineWidth = 1; 654 | ctx.beginPath(); 655 | ctx.fillStyle = "rgb(100,160,255)"; 656 | ctx.fillRect(wPadding, -bottleHeight * user.carryCount / Packs.items.flypack.count, bottleWidth, bottleHeight * user.carryCount / Packs.items.flypack.count); 657 | ctx.stroke(); 658 | 659 | ctx.drawImage(imgs.jet, wPadding - 16, -bottleHeight - 10, 40, bottleHeight + 20); 660 | if (user.flying) { 661 | Effect.trigger(new Brust(user, 1, 5, user.faceing * (wPadding + bottleWidth/2), -10)); 662 | } 663 | } 664 | if (user.carry == Packs.items.bomb.id) { 665 | ctx.drawImage(imgs.bomb, P.userWidth/2 - 10, -P.userHeight, 30, 40); 666 | if (!user.dead) { 667 | ctx.scale(user.faceing, 1); 668 | var x = user.faceing * (P.userWidth/2 + 10) 669 | ctx.font="28px 宋体"; 670 | ctx.fillStyle = "#ff0"; 671 | ctx.fillText(Math.floor(user.carryCount*17/1000) + 1, x, -P.userHeight - 10); 672 | ctx.font="14px 宋体"; 673 | ctx.scale(user.faceing, 1); 674 | } 675 | } 676 | if (user.grenadeing > 0) { 677 | ctx.save(); 678 | 679 | ctx.translate(-10, -20); 680 | ctx.rotate((25 - user.grenadeing)/10); 681 | ctx.translate(-40, -P.userHeight/2); 682 | 683 | ctx.drawImage(imgs.arm, 0, 0, 40, 40); 684 | ctx.drawImage(imgs.grenade, -10, -13, 30, 40); 685 | ctx.restore(); 686 | } 687 | ctx.restore(); 688 | } 689 | 690 | function drawItem (ctx, item) { 691 | var s = C.IS; 692 | ctx.strokeStyle = "rgba(255,255,255,"+Math.abs((game.t%300)/150 - 1)+")"; 693 | ctx.lineWidth = 3; 694 | ctx.save(); 695 | ctx.translate(item.x, P.h - item.y); 696 | ctx.beginPath(); 697 | ctx.arc(0, 0, s, 0, 2*Math.PI); 698 | ctx.stroke(); 699 | ctx.fill(); 700 | ctx.lineWidth = 1; 701 | ctx.drawImage(imgs.items[item.id - 1], -s, -s, s*2, s*2); 702 | ctx.restore(); 703 | } 704 | 705 | function drawEntity (ctx, entity) { 706 | var w = 15; 707 | var h = 18; 708 | ctx.save(); 709 | ctx.translate(entity.x, P.h - entity.y); 710 | ctx.rotate(entity.r/10); 711 | ctx.drawImage(imgs.grenade, -w, -h, w*2, h*2); 712 | ctx.restore(); 713 | } 714 | 715 | function render (ctx, data) { 716 | ctx.clearRect(0, 0, P.w, P.h); 717 | ctx.save(); 718 | ctx.translate(game.env.cdx, game.env.cdy); 719 | if (game.env.cdx > .5) { 720 | game.env.cdx *= -.98; 721 | } else { 722 | game.env.cdx = 0; 723 | } 724 | if (game.env.cdy > .5) { 725 | game.env.cdy *= -.97; 726 | } else { 727 | game.env.cdy = 0; 728 | } 729 | drawWater(ctx, 20, "#758"); 730 | 731 | 732 | data.mines.forEach(function (mine) { 733 | ctx.drawImage(imgs.minePlaced, mine.x - 12, P.h - mine.y - 3, 23, 5); 734 | if (mine.dead) { 735 | game.env.cdx = 3; 736 | game.env.cdy = 11; 737 | Effect.trigger(new Flare(mine)); 738 | } 739 | }); 740 | 741 | for (var i = 0; i < data.users.length; i++) { 742 | var user = data.users[i]; 743 | if (user.dead == true) { 744 | Effect.trigger(new WaterDrops(user)); 745 | drawUser(game.display.ctx.mark, user); 746 | } else { 747 | drawUser(ctx, user); 748 | } 749 | } 750 | 751 | drawWater(ctx, 10, "#95a"); 752 | 753 | for (var i = 0; i < data.items.length; i++) { 754 | var item = data.items[i]; 755 | drawItem(ctx, item); 756 | if (item.dead) { 757 | var itemName = ''; 758 | for (var key in Packs.items) { 759 | if (Packs.items[key].id == item.id) { 760 | itemName = Packs.items[key].name; 761 | } 762 | } 763 | Effect.trigger(new ItemDead(item, itemName)); 764 | } 765 | } 766 | 767 | for (var i = 0; i < data.entitys.length; i++) { 768 | var entity = data.entitys[i]; 769 | drawEntity(ctx, entity); 770 | } 771 | 772 | Effect.render(ctx); 773 | ctx.restore(); 774 | } 775 | initDone(); 776 | -------------------------------------------------------------------------------- /static/js/zepto.js: -------------------------------------------------------------------------------- 1 | /* Zepto v1.1.6 - zepto event ajax form ie - zeptojs.com/license 2 | Copyright (c) 2010-2014 Thomas Fuchs 3 | http://zeptojs.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | */ 12 | var Zepto=function(){function L(t){return null==t?String(t):j[S.call(t)]||"object"}function Z(t){return"function"==L(t)}function _(t){return null!=t&&t==t.window}function $(t){return null!=t&&t.nodeType==t.DOCUMENT_NODE}function D(t){return"object"==L(t)}function M(t){return D(t)&&!_(t)&&Object.getPrototypeOf(t)==Object.prototype}function R(t){return"number"==typeof t.length}function k(t){return s.call(t,function(t){return null!=t})}function z(t){return t.length>0?n.fn.concat.apply([],t):t}function F(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function q(t){return t in f?f[t]:f[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function H(t,e){return"number"!=typeof e||c[F(t)]?e:e+"px"}function I(t){var e,n;return u[t]||(e=a.createElement(t),a.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),u[t]=n),u[t]}function V(t){return"children"in t?o.call(t.children):n.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function B(n,i,r){for(e in i)r&&(M(i[e])||A(i[e]))?(M(i[e])&&!M(n[e])&&(n[e]={}),A(i[e])&&!A(n[e])&&(n[e]=[]),B(n[e],i[e],r)):i[e]!==t&&(n[e]=i[e])}function U(t,e){return null==e?n(t):n(t).filter(e)}function J(t,e,n,i){return Z(e)?e.call(t,n,i):e}function X(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function W(e,n){var i=e.className||"",r=i&&i.baseVal!==t;return n===t?r?i.baseVal:i:void(r?i.baseVal=n:e.className=n)}function Y(t){try{return t?"true"==t||("false"==t?!1:"null"==t?null:+t+""==t?+t:/^[\[\{]/.test(t)?n.parseJSON(t):t):t}catch(e){return t}}function G(t,e){e(t);for(var n=0,i=t.childNodes.length;i>n;n++)G(t.childNodes[n],e)}var t,e,n,i,C,N,r=[],o=r.slice,s=r.filter,a=window.document,u={},f={},c={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},l=/^\s*<(\w+|!)[^>]*>/,h=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,p=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,d=/^(?:body|html)$/i,m=/([A-Z])/g,g=["val","css","html","text","data","width","height","offset"],v=["after","prepend","before","append"],y=a.createElement("table"),x=a.createElement("tr"),b={tr:a.createElement("tbody"),tbody:y,thead:y,tfoot:y,td:x,th:x,"*":a.createElement("div")},w=/complete|loaded|interactive/,E=/^[\w-]*$/,j={},S=j.toString,T={},O=a.createElement("div"),P={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},A=Array.isArray||function(t){return t instanceof Array};return T.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var i,r=t.parentNode,o=!r;return o&&(r=O).appendChild(t),i=~T.qsa(r,e).indexOf(t),o&&O.removeChild(t),i},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return s.call(t,function(e,n){return t.indexOf(e)==n})},T.fragment=function(e,i,r){var s,u,f;return h.test(e)&&(s=n(a.createElement(RegExp.$1))),s||(e.replace&&(e=e.replace(p,"<$1>")),i===t&&(i=l.test(e)&&RegExp.$1),i in b||(i="*"),f=b[i],f.innerHTML=""+e,s=n.each(o.call(f.childNodes),function(){f.removeChild(this)})),M(r)&&(u=n(s),n.each(r,function(t,e){g.indexOf(t)>-1?u[t](e):u.attr(t,e)})),s},T.Z=function(t,e){return t=t||[],t.__proto__=n.fn,t.selector=e||"",t},T.isZ=function(t){return t instanceof T.Z},T.init=function(e,i){var r;if(!e)return T.Z();if("string"==typeof e)if(e=e.trim(),"<"==e[0]&&l.test(e))r=T.fragment(e,RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=T.qsa(a,e)}else{if(Z(e))return n(a).ready(e);if(T.isZ(e))return e;if(A(e))r=k(e);else if(D(e))r=[e],e=null;else if(l.test(e))r=T.fragment(e.trim(),RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=T.qsa(a,e)}}return T.Z(r,e)},n=function(t,e){return T.init(t,e)},n.extend=function(t){var e,n=o.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){B(t,n,e)}),t},T.qsa=function(t,e){var n,i="#"==e[0],r=!i&&"."==e[0],s=i||r?e.slice(1):e,a=E.test(s);return $(t)&&a&&i?(n=t.getElementById(s))?[n]:[]:1!==t.nodeType&&9!==t.nodeType?[]:o.call(a&&!i?r?t.getElementsByClassName(s):t.getElementsByTagName(e):t.querySelectorAll(e))},n.contains=a.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},n.type=L,n.isFunction=Z,n.isWindow=_,n.isArray=A,n.isPlainObject=M,n.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},n.inArray=function(t,e,n){return r.indexOf.call(e,t,n)},n.camelCase=C,n.trim=function(t){return null==t?"":String.prototype.trim.call(t)},n.uuid=0,n.support={},n.expr={},n.map=function(t,e){var n,r,o,i=[];if(R(t))for(r=0;r=0?e:e+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return r.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return Z(t)?this.not(this.not(t)):n(s.call(this,function(e){return T.matches(e,t)}))},add:function(t,e){return n(N(this.concat(n(t,e))))},is:function(t){return this.length>0&&T.matches(this[0],t)},not:function(e){var i=[];if(Z(e)&&e.call!==t)this.each(function(t){e.call(this,t)||i.push(this)});else{var r="string"==typeof e?this.filter(e):R(e)&&Z(e.item)?o.call(e):n(e);this.forEach(function(t){r.indexOf(t)<0&&i.push(t)})}return n(i)},has:function(t){return this.filter(function(){return D(t)?n.contains(this,t):n(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!D(t)?t:n(t)},last:function(){var t=this[this.length-1];return t&&!D(t)?t:n(t)},find:function(t){var e,i=this;return e=t?"object"==typeof t?n(t).filter(function(){var t=this;return r.some.call(i,function(e){return n.contains(e,t)})}):1==this.length?n(T.qsa(this[0],t)):this.map(function(){return T.qsa(this,t)}):n()},closest:function(t,e){var i=this[0],r=!1;for("object"==typeof t&&(r=n(t));i&&!(r?r.indexOf(i)>=0:T.matches(i,t));)i=i!==e&&!$(i)&&i.parentNode;return n(i)},parents:function(t){for(var e=[],i=this;i.length>0;)i=n.map(i,function(t){return(t=t.parentNode)&&!$(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return U(e,t)},parent:function(t){return U(N(this.pluck("parentNode")),t)},children:function(t){return U(this.map(function(){return V(this)}),t)},contents:function(){return this.map(function(){return o.call(this.childNodes)})},siblings:function(t){return U(this.map(function(t,e){return s.call(V(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return n.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=I(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=Z(t);if(this[0]&&!e)var i=n(t).get(0),r=i.parentNode||this.length>1;return this.each(function(o){n(this).wrapAll(e?t.call(this,o):r?i.cloneNode(!0):i)})},wrapAll:function(t){if(this[0]){n(this[0]).before(t=n(t));for(var e;(e=t.children()).length;)t=e.first();n(t).append(this)}return this},wrapInner:function(t){var e=Z(t);return this.each(function(i){var r=n(this),o=r.contents(),s=e?t.call(this,i):t;o.length?o.wrapAll(s):r.append(s)})},unwrap:function(){return this.parent().each(function(){n(this).replaceWith(n(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(e){return this.each(function(){var i=n(this);(e===t?"none"==i.css("display"):e)?i.show():i.hide()})},prev:function(t){return n(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return n(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var i=this.innerHTML;n(this).empty().append(J(this,t,e,i))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=J(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this[0].textContent:null},attr:function(n,i){var r;return"string"!=typeof n||1 in arguments?this.each(function(t){if(1===this.nodeType)if(D(n))for(e in n)X(this,e,n[e]);else X(this,n,J(this,i,t,this.getAttribute(n)))}):this.length&&1===this[0].nodeType?!(r=this[0].getAttribute(n))&&n in this[0]?this[0][n]:r:t},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){X(this,t)},this)})},prop:function(t,e){return t=P[t]||t,1 in arguments?this.each(function(n){this[t]=J(this,e,n,this[t])}):this[0]&&this[0][t]},data:function(e,n){var i="data-"+e.replace(m,"-$1").toLowerCase(),r=1 in arguments?this.attr(i,n):this.attr(i);return null!==r?Y(r):t},val:function(t){return 0 in arguments?this.each(function(e){this.value=J(this,t,e,this.value)}):this[0]&&(this[0].multiple?n(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(t){if(t)return this.each(function(e){var i=n(this),r=J(this,t,e,i.offset()),o=i.offsetParent().offset(),s={top:r.top-o.top,left:r.left-o.left};"static"==i.css("position")&&(s.position="relative"),i.css(s)});if(!this.length)return null;var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(t,i){if(arguments.length<2){var r,o=this[0];if(!o)return;if(r=getComputedStyle(o,""),"string"==typeof t)return o.style[C(t)]||r.getPropertyValue(t);if(A(t)){var s={};return n.each(t,function(t,e){s[e]=o.style[C(e)]||r.getPropertyValue(e)}),s}}var a="";if("string"==L(t))i||0===i?a=F(t)+":"+H(t,i):this.each(function(){this.style.removeProperty(F(t))});else for(e in t)t[e]||0===t[e]?a+=F(e)+":"+H(e,t[e])+";":this.each(function(){this.style.removeProperty(F(e))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(n(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?r.some.call(this,function(t){return this.test(W(t))},q(t)):!1},addClass:function(t){return t?this.each(function(e){if("className"in this){i=[];var r=W(this),o=J(this,t,e,r);o.split(/\s+/g).forEach(function(t){n(this).hasClass(t)||i.push(t)},this),i.length&&W(this,r+(r?" ":"")+i.join(" "))}}):this},removeClass:function(e){return this.each(function(n){if("className"in this){if(e===t)return W(this,"");i=W(this),J(this,e,n,i).split(/\s+/g).forEach(function(t){i=i.replace(q(t)," ")}),W(this,i.trim())}})},toggleClass:function(e,i){return e?this.each(function(r){var o=n(this),s=J(this,e,r,W(this));s.split(/\s+/g).forEach(function(e){(i===t?!o.hasClass(e):i)?o.addClass(e):o.removeClass(e)})}):this},scrollTop:function(e){if(this.length){var n="scrollTop"in this[0];return e===t?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=e}:function(){this.scrollTo(this.scrollX,e)})}},scrollLeft:function(e){if(this.length){var n="scrollLeft"in this[0];return e===t?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=e}:function(){this.scrollTo(e,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),i=this.offset(),r=d.test(e[0].nodeName)?{top:0,left:0}:e.offset();return i.top-=parseFloat(n(t).css("margin-top"))||0,i.left-=parseFloat(n(t).css("margin-left"))||0,r.top+=parseFloat(n(e[0]).css("border-top-width"))||0,r.left+=parseFloat(n(e[0]).css("border-left-width"))||0,{top:i.top-r.top,left:i.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||a.body;t&&!d.test(t.nodeName)&&"static"==n(t).css("position");)t=t.offsetParent;return t})}},n.fn.detach=n.fn.remove,["width","height"].forEach(function(e){var i=e.replace(/./,function(t){return t[0].toUpperCase()});n.fn[e]=function(r){var o,s=this[0];return r===t?_(s)?s["inner"+i]:$(s)?s.documentElement["scroll"+i]:(o=this.offset())&&o[e]:this.each(function(t){s=n(this),s.css(e,J(this,r,t,s[e]()))})}}),v.forEach(function(t,e){var i=e%2;n.fn[t]=function(){var t,o,r=n.map(arguments,function(e){return t=L(e),"object"==t||"array"==t||null==e?e:T.fragment(e)}),s=this.length>1;return r.length<1?this:this.each(function(t,u){o=i?u:u.parentNode,u=0==e?u.nextSibling:1==e?u.firstChild:2==e?u:null;var f=n.contains(a.documentElement,o);r.forEach(function(t){if(s)t=t.cloneNode(!0);else if(!o)return n(t).remove();o.insertBefore(t,u),f&&G(t,function(t){null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src||window.eval.call(window,t.innerHTML)})})})},n.fn[i?t+"To":"insert"+(e?"Before":"After")]=function(e){return n(e)[t](this),this}}),T.Z.prototype=n.fn,T.uniq=N,T.deserializeValue=Y,n.zepto=T,n}();window.Zepto=Zepto,void 0===window.$&&(window.$=Zepto),function(t){function l(t){return t._zid||(t._zid=e++)}function h(t,e,n,i){if(e=p(e),e.ns)var r=d(e.ns);return(s[l(t)]||[]).filter(function(t){return!(!t||e.e&&t.e!=e.e||e.ns&&!r.test(t.ns)||n&&l(t.fn)!==l(n)||i&&t.sel!=i)})}function p(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function d(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function m(t,e){return t.del&&!u&&t.e in f||!!e}function g(t){return c[t]||u&&f[t]||t}function v(e,i,r,o,a,u,f){var h=l(e),d=s[h]||(s[h]=[]);i.split(/\s/).forEach(function(i){if("ready"==i)return t(document).ready(r);var s=p(i);s.fn=r,s.sel=a,s.e in c&&(r=function(e){var n=e.relatedTarget;return!n||n!==this&&!t.contains(this,n)?s.fn.apply(this,arguments):void 0}),s.del=u;var l=u||r;s.proxy=function(t){if(t=j(t),!t.isImmediatePropagationStopped()){t.data=o;var i=l.apply(e,t._args==n?[t]:[t].concat(t._args));return i===!1&&(t.preventDefault(),t.stopPropagation()),i}},s.i=d.length,d.push(s),"addEventListener"in e&&e.addEventListener(g(s.e),s.proxy,m(s,f))})}function y(t,e,n,i,r){var o=l(t);(e||"").split(/\s/).forEach(function(e){h(t,e,n,i).forEach(function(e){delete s[o][e.i],"removeEventListener"in t&&t.removeEventListener(g(e.e),e.proxy,m(e,r))})})}function j(e,i){return(i||!e.isDefaultPrevented)&&(i||(i=e),t.each(E,function(t,n){var r=i[t];e[t]=function(){return this[n]=x,r&&r.apply(i,arguments)},e[n]=b}),(i.defaultPrevented!==n?i.defaultPrevented:"returnValue"in i?i.returnValue===!1:i.getPreventDefault&&i.getPreventDefault())&&(e.isDefaultPrevented=x)),e}function S(t){var e,i={originalEvent:t};for(e in t)w.test(e)||t[e]===n||(i[e]=t[e]);return j(i,t)}var n,e=1,i=Array.prototype.slice,r=t.isFunction,o=function(t){return"string"==typeof t},s={},a={},u="onfocusin"in window,f={focus:"focusin",blur:"focusout"},c={mouseenter:"mouseover",mouseleave:"mouseout"};a.click=a.mousedown=a.mouseup=a.mousemove="MouseEvents",t.event={add:v,remove:y},t.proxy=function(e,n){var s=2 in arguments&&i.call(arguments,2);if(r(e)){var a=function(){return e.apply(n,s?s.concat(i.call(arguments)):arguments)};return a._zid=l(e),a}if(o(n))return s?(s.unshift(e[n],e),t.proxy.apply(null,s)):t.proxy(e[n],e);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,i){return this.on(t,e,n,i,1)};var x=function(){return!0},b=function(){return!1},w=/^([A-Z]|returnValue$|layer[XY]$)/,E={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,s,a,u,f){var c,l,h=this;return e&&!o(e)?(t.each(e,function(t,e){h.on(t,s,a,e,f)}),h):(o(s)||r(u)||u===!1||(u=a,a=s,s=n),(r(a)||a===!1)&&(u=a,a=n),u===!1&&(u=b),h.each(function(n,r){f&&(c=function(t){return y(r,t.type,u),u.apply(this,arguments)}),s&&(l=function(e){var n,o=t(e.target).closest(s,r).get(0);return o&&o!==r?(n=t.extend(S(e),{currentTarget:o,liveFired:r}),(c||u).apply(o,[n].concat(i.call(arguments,1)))):void 0}),v(r,e,u,a,s,l||c)}))},t.fn.off=function(e,i,s){var a=this;return e&&!o(e)?(t.each(e,function(t,e){a.off(t,i,e)}),a):(o(i)||r(s)||s===!1||(s=i,i=n),s===!1&&(s=b),a.each(function(){y(this,e,s,i)}))},t.fn.trigger=function(e,n){return e=o(e)||t.isPlainObject(e)?t.Event(e):j(e),e._args=n,this.each(function(){e.type in f&&"function"==typeof this[e.type]?this[e.type]():"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,n){var i,r;return this.each(function(s,a){i=S(o(e)?t.Event(e):e),i._args=n,i.target=a,t.each(h(a,e.type||e),function(t,e){return r=e.proxy(i),i.isImmediatePropagationStopped()?!1:void 0})}),r},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return 0 in arguments?this.bind(e,t):this.trigger(e)}}),t.Event=function(t,e){o(t)||(e=t,t=e.type);var n=document.createEvent(a[t]||"Events"),i=!0;if(e)for(var r in e)"bubbles"==r?i=!!e[r]:n[r]=e[r];return n.initEvent(t,i,!0),j(n)}}(Zepto),function(t){function h(e,n,i){var r=t.Event(n);return t(e).trigger(r,i),!r.isDefaultPrevented()}function p(t,e,i,r){return t.global?h(e||n,i,r):void 0}function d(e){e.global&&0===t.active++&&p(e,null,"ajaxStart")}function m(e){e.global&&!--t.active&&p(e,null,"ajaxStop")}function g(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||p(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void p(e,n,"ajaxSend",[t,e])}function v(t,e,n,i){var r=n.context,o="success";n.success.call(r,t,o,e),i&&i.resolveWith(r,[t,o,e]),p(n,r,"ajaxSuccess",[e,n,t]),x(o,e,n)}function y(t,e,n,i,r){var o=i.context;i.error.call(o,n,e,t),r&&r.rejectWith(o,[n,e,t]),p(i,o,"ajaxError",[n,i,t||e]),x(e,n,i)}function x(t,e,n){var i=n.context;n.complete.call(i,e,t),p(n,i,"ajaxComplete",[e,n]),m(n)}function b(){}function w(t){return t&&(t=t.split(";",2)[0]),t&&(t==f?"html":t==u?"json":s.test(t)?"script":a.test(t)&&"xml")||"text"}function E(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function j(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()||(e.url=E(e.url,e.data),e.data=void 0)}function S(e,n,i,r){return t.isFunction(n)&&(r=i,i=n,n=void 0),t.isFunction(i)||(r=i,i=void 0),{url:e,data:n,success:i,dataType:r}}function C(e,n,i,r){var o,s=t.isArray(n),a=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),r&&(n=i?r:r+"["+(a||"object"==o||"array"==o?n:"")+"]"),!r&&s?e.add(u.name,u.value):"array"==o||!i&&"object"==o?C(e,u,i,n):e.add(n,u)})}var i,r,e=0,n=window.document,o=/)<[^<]*)*<\/script>/gi,s=/^(?:text|application)\/javascript/i,a=/^(?:text|application)\/xml/i,u="application/json",f="text/html",c=/^\s*$/,l=n.createElement("a");l.href=window.location.href,t.active=0,t.ajaxJSONP=function(i,r){if(!("type"in i))return t.ajax(i);var f,h,o=i.jsonpCallback,s=(t.isFunction(o)?o():o)||"jsonp"+ ++e,a=n.createElement("script"),u=window[s],c=function(e){t(a).triggerHandler("error",e||"abort")},l={abort:c};return r&&r.promise(l),t(a).on("load error",function(e,n){clearTimeout(h),t(a).off().remove(),"error"!=e.type&&f?v(f[0],l,i,r):y(null,n||"error",l,i,r),window[s]=u,f&&t.isFunction(u)&&u(f[0]),u=f=void 0}),g(l,i)===!1?(c("abort"),l):(window[s]=function(){f=arguments},a.src=i.url.replace(/\?(.+)=\?/,"?$1="+s),n.head.appendChild(a),i.timeout>0&&(h=setTimeout(function(){c("timeout")},i.timeout)),l)},t.ajaxSettings={type:"GET",beforeSend:b,success:b,error:b,complete:b,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:u,xml:"application/xml, text/xml",html:f,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},t.ajax=function(e){var a,o=t.extend({},e||{}),s=t.Deferred&&t.Deferred();for(i in t.ajaxSettings)void 0===o[i]&&(o[i]=t.ajaxSettings[i]);d(o),o.crossDomain||(a=n.createElement("a"),a.href=o.url,a.href=a.href,o.crossDomain=l.protocol+"//"+l.host!=a.protocol+"//"+a.host),o.url||(o.url=window.location.toString()),j(o);var u=o.dataType,f=/\?.+=\?/.test(o.url);if(f&&(u="jsonp"),o.cache!==!1&&(e&&e.cache===!0||"script"!=u&&"jsonp"!=u)||(o.url=E(o.url,"_="+Date.now())),"jsonp"==u)return f||(o.url=E(o.url,o.jsonp?o.jsonp+"=?":o.jsonp===!1?"":"callback=?")),t.ajaxJSONP(o,s);var C,h=o.accepts[u],p={},m=function(t,e){p[t.toLowerCase()]=[t,e]},x=/^([\w-]+:)\/\//.test(o.url)?RegExp.$1:window.location.protocol,S=o.xhr(),T=S.setRequestHeader;if(s&&s.promise(S),o.crossDomain||m("X-Requested-With","XMLHttpRequest"),m("Accept",h||"*/*"),(h=o.mimeType||h)&&(h.indexOf(",")>-1&&(h=h.split(",",2)[0]),S.overrideMimeType&&S.overrideMimeType(h)),(o.contentType||o.contentType!==!1&&o.data&&"GET"!=o.type.toUpperCase())&&m("Content-Type",o.contentType||"application/x-www-form-urlencoded"),o.headers)for(r in o.headers)m(r,o.headers[r]);if(S.setRequestHeader=m,S.onreadystatechange=function(){if(4==S.readyState){S.onreadystatechange=b,clearTimeout(C);var e,n=!1;if(S.status>=200&&S.status<300||304==S.status||0==S.status&&"file:"==x){u=u||w(o.mimeType||S.getResponseHeader("content-type")),e=S.responseText;try{"script"==u?(1,eval)(e):"xml"==u?e=S.responseXML:"json"==u&&(e=c.test(e)?null:t.parseJSON(e))}catch(i){n=i}n?y(n,"parsererror",S,o,s):v(e,S,o,s)}else y(S.statusText||null,S.status?"error":"abort",S,o,s)}},g(S,o)===!1)return S.abort(),y(null,"abort",S,o,s),S;if(o.xhrFields)for(r in o.xhrFields)S[r]=o.xhrFields[r];var N="async"in o?o.async:!0;S.open(o.type,o.url,N,o.username,o.password);for(r in p)T.apply(S,p[r]);return o.timeout>0&&(C=setTimeout(function(){S.onreadystatechange=b,S.abort(),y(null,"timeout",S,o,s)},o.timeout)),S.send(o.data?o.data:null),S},t.get=function(){return t.ajax(S.apply(null,arguments))},t.post=function(){var e=S.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=S.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,i){if(!this.length)return this;var a,r=this,s=e.split(/\s/),u=S(e,n,i),f=u.success;return s.length>1&&(u.url=s[0],a=s[1]),u.success=function(e){r.html(a?t("
").html(e.replace(o,"")).find(a):e),f&&f.apply(r,arguments)},t.ajax(u),this};var T=encodeURIComponent;t.param=function(e,n){var i=[];return i.add=function(e,n){t.isFunction(n)&&(n=n()),null==n&&(n=""),this.push(T(e)+"="+T(n))},C(i,e,n),i.join("&").replace(/%20/g,"+")}}(Zepto),function(t){t.fn.serializeArray=function(){var e,n,i=[],r=function(t){return t.forEach?t.forEach(r):void i.push({name:e,value:t})};return this[0]&&t.each(this[0].elements,function(i,o){n=o.type,e=o.name,e&&"fieldset"!=o.nodeName.toLowerCase()&&!o.disabled&&"submit"!=n&&"reset"!=n&&"button"!=n&&"file"!=n&&("radio"!=n&&"checkbox"!=n||o.checked)&&r(t(o).val())}),i},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(0 in arguments)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(Zepto),function(t){"__proto__"in{}||t.extend(t.zepto,{Z:function(e,n){return e=e||[],t.extend(e,t.fn),e.selector=n||"",e.__Z=!0,e},isZ:function(e){return"array"===t.type(e)&&"__Z"in e}});try{getComputedStyle(void 0)}catch(e){var n=getComputedStyle;window.getComputedStyle=function(t){try{return n(t)}catch(e){return null}}}}(Zepto); --------------------------------------------------------------------------------