├── .babelrc ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── favicon.ico ├── game.gif ├── index.html ├── package.json ├── src ├── api │ └── login.js ├── app.js ├── assets │ ├── font │ │ └── mainfont.otf │ ├── hero-1.png │ ├── menu-fight.png │ ├── menu-forge.png │ ├── menu-setting.png │ └── menu-shop.png ├── components │ ├── App.vue │ ├── component-item.vue │ ├── game-config.vue │ ├── game-fight-drop-list.vue │ ├── game-fight-event-log.vue │ ├── game-fight-unit-info.vue │ ├── game-fight.vue │ ├── game-home-info.vue │ ├── game-home-menu.vue │ ├── game-home.vue │ ├── game-hot-key-item.vue │ ├── game-house.vue │ ├── game-login.vue │ ├── game-map-active.vue │ ├── game-map-block.vue │ ├── game-map.vue │ ├── game-package.vue │ ├── game-progress.vue │ ├── game-range-select.vue │ ├── game-shop.vue │ ├── game-skill-item.vue │ ├── game-smithy-blueprint.vue │ ├── game-smithy-intensify.vue │ ├── game-smithy.vue │ ├── game-state-item.vue │ └── game-switch-button.vue ├── css │ ├── animate.css │ ├── item-tool-tip.css │ ├── main.css │ └── map-dialog-modal.css ├── data │ ├── blueprint-data.js │ ├── constant.js │ ├── event-data.js │ ├── hero-data.js │ ├── item-data.js │ ├── map-data.js │ ├── monster-data.js │ ├── skill-data.js │ └── state-data.js ├── directive │ ├── drop-item.js │ └── item-tool-tip.js ├── filter.js ├── js │ ├── astar.js │ ├── audio.js │ ├── blueprint.js │ ├── cool-time-event.js │ ├── create-hero.js │ ├── create-monster.js │ ├── different-item-move-class.js │ ├── drag-drop.js │ ├── dungeon-creater.js │ ├── event-class.js │ ├── fight-action-class.js │ ├── fight.js │ ├── hero │ │ └── update-attribute.js │ ├── intensify.js │ ├── map-hero-move.js │ ├── map-init.js │ ├── monster-ai.js │ ├── public-function.js │ ├── public-random-range.js │ ├── public-static-get.js │ ├── release-skill.js │ ├── save-load.js │ ├── skill-available.js │ └── unit-class.js ├── router.js ├── store.js └── store │ ├── config-store.js │ ├── fight-store.js │ ├── hero-store.js │ ├── map-store.js │ └── smithy-store.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | bower_components/ 5 | npm-debug.log 6 | .vscode 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // 将设置放入此文件中以覆盖默认值和用户设置。 2 | { 3 | "editor.tabSize": 2, 4 | "files.associations": { 5 | "*.vue": "vue" 6 | }, 7 | "vsicons.presets.angular": true 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Endless[文档持续更新中...请往下看] 2 | 3 | > 一个用 Vue.js 开发的冒险类游戏; 4 | 5 | ![image](https://github.com/bastarder/Endless/blob/master/game.gif) 6 | 7 | ## 项目安装&启动 8 | 9 | ``` bash 10 | git clone https://github.com/bastarder/Endless.git 11 | cd Endless 12 | npm install 13 | npm run dev 14 | ``` 15 | 16 | ## 你好 17 | - 如果你和我一样, 是一名学生, 喜欢前端开发, 苦于没有练手的项目, 或是您已经是独当一面的大牛, 不妨可以与我一起完成这样一个小游戏, 在这个游戏中最复杂的战斗系统已经基本完成, 剩下一些 相对容易 的功能, 能让你得到很好的锻炼。 18 | 19 | - 这是一款 简单的冒险类游戏, 我也希望在编写过程中能得到更好的锻炼; 20 | 21 | - 数据设计, UI设计 方面, 我真的不擅长, 如果你觉得你闲来无事的时候愿意帮忙, 可以联系我; 22 | - `QQ:85257684`,`Wechat: I85257` 23 | 24 | ## 游戏详细文档 25 | 26 | ## 战斗逻辑 27 | 28 | 游戏中的战斗系统采用`技能冷却`的模式,一次向一个目标发送一个技能,计算一次行动的效果为战斗逻辑 29 | 30 | 首先是大致逻辑:(Fight.js) 31 | 32 | ### 1. 判断技能是否可以释放 (skill-available.js) 33 | 34 | 当`技能被释放`后会优先进入这个方法,此方法接受3个参数,`技能对象`,`攻击方`,`被攻击方`,可根据这3者定制,判断此次技能是否能够被释放; 35 | 36 | ### 2. 触发冷却时间 (cool-time-event.js) 37 | 38 | 当技能被判定为能够成功释放以后, 技能将进入冷却时间; 39 | 40 | ### 3. 提取人物状态与敌人状态; 41 | 42 | 从攻击者身上提取 `type : 1` 的状态, 从被攻击者身上提取 `type : 2`的状态; 43 | 44 | `type : 1` 为所有能够影响到此次技能的状态,并且自定义状态时应该按照这一规则创建,否则将不被认可; 45 | 46 | `type : 2` 为所有能够影响此次技能对敌人造成的改变的状态,并且自定义状态时应该按照这一规则创建,否则将不被认可; 47 | 48 | ### 4. 拼接技能,状态效果排序; 49 | 50 | 将技能与状态对象中的事件提取并且合并,按照优先级进行排序; 51 | 52 | ### 5. 行动对象计算(关键); 53 | 54 | 技能行动对象,由所有的状态与技能行动效果进行计算而出,根据权重排序; 55 | 56 | *权重暂定的规则* 57 | 58 | 1. `1-10` Action层, 与 影响本次 `暴击`,`命中`,`闪避` 等前置条件相关的事件,权重值需要定义在这个范围; 59 | 2. `11-89` 普通层, 普通的效果,例如技能造成的伤害,状态添加,移除,更新等,大多数都在普通层; 60 | 3. `>90` 在`90`层中,将会计算本次技能在所有可识别加成下的最终结果(如,造成伤害,恢复血量等等), 如果你的状态需要造成一个 `最终加成`, 例如最终伤害增加50%,那么此事件必须 定义在90层, 在计算结果确定后; 61 | 4. `9999` 最终层, 所有权重都不应该超出9999, 在计算此层时, 行动对象将被转换为可被`unit`接口直接执行的参数,并且不再改变; 62 | 63 | ### 6. 判断行动对像是否有效; 64 | 行动对象生成完毕后, 判定此次攻击是否有效(如必中,miss,等 ps: 必中将优先于100%miss); 65 | 66 | ### 7. 执行变更 67 | 确定有效以后,将行动对象代理给`攻击者`与`被攻击者`处理; 68 | 69 | ### 8. 触发全局冷却 70 | 至此,技能完全释放完毕,每次释放技能为`1s`,也就是说释放一个技能以后所有技能都将获得1秒的冷却(如果技能冷却时间不满1秒,将被强制提升至1秒); 71 | 72 | **一次技能释放结束,如果攻击方和被攻击方,有一方死亡, 则返回 false** 73 | 74 | ## 强化逻辑 75 | 76 | 1. 消耗计算 77 | 78 | 目前采用的公式是: (等级 * 基础金额) * (强化等级 + 1) * 品级参数 79 | 80 | 2. 几率计算 81 | 82 | 几率采用固定值: [1, 1, 1, 0.95, 0.9, 0.8, 0.75, 0.62, 0.54, 0.41, 0.33, 0.28, 0.2, 0.17, 0.13, 0.1, 0.04, 0.03, 0.01, 0.01] 83 | 84 | 3. 属性增幅计算 85 | - **武器计算公式** [等级 + 品级参数A/ 8 ] * 强化参数 * 品级参数B * 位置参数 86 | - **防具计算公式** 基础值 * 强化参数 * 品级参数 * 位置参数 87 | 4. 强化失败后的处理 88 | - 1-3 无效果 89 | - 3-8 随机退级 1-2 90 | - 8-10 掉为0级 91 | - 11以上 装备被破坏 92 | 93 | 大致流程: **判断前置条件 -> 判断强化几率-> 重新计算属性** 94 | 95 | 96 | 97 | ## 如何自定义事件 98 | 99 | 在地图中,我们总可以触发事件,从而获得特定的效果,目前内置的事件的有2种: 100 | 101 | 1. **战斗事件** : 当英雄走入此事件格子以后,将会利用事件对象创建一个战斗事件, 并且跳转到战斗界面,当战斗完毕,在返回地图; 102 | 103 | 2. **对话事件** : 当英雄走入此事件格子以后, 将会利用事件对象创建一个对话事件,与英雄进行一些对话交互,分支事件,物品交换等; 104 | 105 | **事件定义的规则:** 106 | 107 | ``` 108 | 1. 事件将在判定允许被移动后,直接执行; 109 | 2. 在执行阶段可捕获的参数有: 当前格子对象,目标格子对象,英雄对象 110 | 3. 事件必须为一个可执行的函数; 111 | 4. 详细可以参考内置事件(event-class.js) 112 | ``` 113 | 114 | ## 如何自定义技能(状态) 115 | 116 | 在这里,我们引用实例来解释,技能对象的定义规则; 117 | 118 | *数据(skill-data.js,state-data.js),所有字符串形式将在`战斗技能释放,计算行动对象时`被转换为可执行函数;* 119 | 120 | **范例** 121 | 122 | ``` 123 | { 124 | id: 1000002, 125 | name: '净化', 126 | dsc : '净化中毒效果~', 127 | label : ['测试2','伤害2'], 128 | defaultTime : 3000, 129 | restrict : [ 130 | "[attacker]{$mp} >= {60}", 131 | "[attacker]{$hp} <= {250}", 132 | "[attacker]{$skills} nothas {1000003,1000001}", 133 | "[attacker]{$status} has {2000001}", 134 | "[skill]{coolTime} > {0}", 135 | function(skill, attacker, enemy){ 136 | return true; 137 | } 138 | ], 139 | eventList : [ 140 | `[1]enemy@changeHp@attacker.$atk`, 141 | `[2]attacker@changeHp@2`, 142 | `[3] 143 | action@{action.state.isCritical === true}; 144 | attacker@{attacker.$hp > (attacker.$hp * 0.5)} 145 | # 146 | enemy@changeState@[{ id: 2000001, state: "ADD" }]; 147 | enemy@changeHp@attacker.$atk 148 | ` 149 | , 150 | `[4]action@{action.state.isCritical = true}`, 151 | ], 152 | // 当为状态时还可以添加下面2个属性; 153 | stateEvent : function(hero) { 154 | var self = this; 155 | var duration = 5; 156 | var per = 1; 157 | var current = 1; 158 | self.stateEventTimer = setInterval(function(){ 159 | hero.changeHp(-30); 160 | current +=1; 161 | if(current > 5){ 162 | clearInterval(self.stateEventTimer); 163 | hero.removeList('$status',self); 164 | } 165 | }, per * 1000); 166 | this.actived = true; 167 | }, 168 | powerUp : { 169 | $maxHp : [0,0,0,0], 170 | }, 171 | }, 172 | ``` 173 | 174 | | key | value | dsc | 175 | | ------------- |:-------------:| -----| 176 | | id | Number | 技能的唯一标识 | 177 | | name | String | 技能的名称 | 178 | | label | [String] | 技能的标签 | 179 | | defaultTime | Number|技能冷却时间,单位毫秒| 180 | | restrict | [String or function] | 技能释放的前置条件,当解析为函数时,能访问3个参数,`技能`,`攻击者`,`被攻击者`,当函数返回为true时表示此规则通过,字符串解析规则: `[对象]{属性名1} 标识符 {值}`, 标识符支持 `> < >= <= nothas has`,表达式的最终值将被判定为真与假| 181 | | eventList |[String or function]|事件列表, 可以为一个函数,函数中接受3个参数,`行动对象`,`攻击者`,`被攻击者`,字符串形式解析规则`[权重]参数名@{前置条件语句}#参数名@事件@事件参数`,可多条应该使用`;`分开,并且末尾条目不需要添加分隔符| 182 | | stateEvent |function| 持续状态, 一个状态被添加到对象身上时将被执行此函数,在此函数内可以访问被作用的单位,做一些处理| 183 | | powerUp |object| 状态能带来的属性提升, Array值解析,`[值, 类型: 0:基础值 1:基础百分 2:高级值 3:高级百分]`, 属性计算公式`((默认 + 基础值) * (1 + 基础百分) + 高级值) * (1 + 高级百分)`| 184 | 185 | *恭喜,至此你已经可以制作一个属于自己的技能了* 186 | 187 | ## 如何自定义装备(物品) 188 | 189 | ``` 190 | // 装备 191 | { 192 | id: 30000012, 193 | name: '精致的铁剑', 194 | level: 1, 195 | grade: 1, 196 | equipType : 0, 197 | label: [ 198 | '武器' 199 | ], 200 | intensify : 1, 201 | intPowerUp : { 202 | $atk: 123 203 | }, 204 | equip : { 205 | $def: 2, 206 | $atk: 15, 207 | $maxHp : 5, 208 | $maxMp : 5, 209 | }, 210 | dsc : '用野草编制的手镯' 211 | } 212 | // 物品 213 | { 214 | id: 3000001, 215 | name: '野草', 216 | pile : true, 217 | price : 10, 218 | use : { 219 | defaultTime : 1000, 220 | restrict :[ 221 | function(){ 222 | return this.$hp > 500; 223 | } 224 | ], 225 | effect :[ 226 | function(){ 227 | this.changeHp(30); 228 | } 229 | ] 230 | }, 231 | label : [ 232 | '材料' 233 | ], 234 | dsc : '很常见的东西,或许能用来做一些东西' 235 | }, 236 | ``` 237 | | key | value | dsc | 238 | | ------------- |:-------------:| -----| 239 | | level | Number | 装备的等级 | 240 | | grade |Number| 装备的品级 `白0`,`绿1`,`蓝2`,`紫3`,`橙4`| 241 | | equipType |Number|装备类型 `武器0`, `护肩1`, `鞋子2`, `腰带3`, `上衣4`, `绑腿5`, `戒指6`, `项链7`, `手镯8`,| 242 | | equip |object|装备的属性 参数的值与`状态`的powerUp相同| 243 | | intensify |Number|强化等级| 244 | | intPowerUp |Object|强化带来的增益,武器为`$atk`,其余防具为`$dmgDown`(百分比减伤)| 245 | | pile |Boolean| 是否可以叠加| 246 | | price |Number|物品的价值| 247 | |use|object|可使用物品的效果 `defaultTime :冷却时间`, `restrict 前置条件列表,接受函数为单个条件,this指向使用者`,`effect 造成的效果列表,接受函数为单个事件,this指向使用者`| 248 | *怎么样?是不是发现自己还是不会创建一个属于自己的装备~ 没关系,是我表达的不好~* 249 | 250 | ## 文档未完待续...有空在更新 251 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastarder/vue-endless-h5-game/f0a46f988f502c1df753e4dab5bbaaf8c9e2f80f/favicon.ico -------------------------------------------------------------------------------- /game.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastarder/vue-endless-h5-game/f0a46f988f502c1df753e4dab5bbaaf8c9e2f80f/game.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 34 | 35 | 36 | 40 |
41 |
游 戏 加 载 中 ...
42 |
43 | 46 | 49 | 52 | 关于作者 53 | 游戏Github 54 |
55 | 56 |
57 |
58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello", 3 | "description": "A Vue.js project", 4 | "author": "bastarder <85257684@qq.com>", 5 | "private": true, 6 | "scripts": { 7 | "dev": "webpack-dev-server --open --inline --hot --host 0.0.0.0", 8 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.15.3", 12 | "babel-polyfill": "^6.23.0", 13 | "normalize.css": "^6.0.0", 14 | "vue": "^2.0.1", 15 | "vue-router": "^2.0.0", 16 | "vuex": "^2.0.0" 17 | }, 18 | "devDependencies": { 19 | "babel-core": "^6.0.0", 20 | "babel-loader": "^6.0.0", 21 | "babel-preset-es2015": "^6.0.0", 22 | "cross-env": "^3.0.0", 23 | "css-loader": "^0.25.0", 24 | "file-loader": "^0.9.0", 25 | "less": "^2.7.2", 26 | "less-loader": "^2.2.3", 27 | "style-loader": "^0.13.1", 28 | "vue-loader": "^9.4.0", 29 | "webpack": "2.1.0-beta.22", 30 | "webpack-dev-server": "2.1.0-beta.9", 31 | "lodash": "4.17.4" 32 | } 33 | } -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const login = function(){ 4 | return axios.post('login/signin',{ 5 | username: 'aaa', 6 | password: 'aaa' 7 | }); 8 | } 9 | 10 | export { 11 | login 12 | }; -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | // public css 2 | require("./css/animate.css"); 3 | require("./css/main.css"); 4 | require("./css/map-dialog-modal.css"); 5 | require("normalize.css"); 6 | 7 | // require('babel-polyfill'); 8 | 9 | // public js 10 | import public_function from './js/public-function.js'; 11 | 12 | import Vue from 'vue' 13 | import Vuex from 'vuex' 14 | import router from './router' 15 | import filter from './filter' 16 | import store from './store' 17 | // import axios from 'axios' 18 | 19 | // components 20 | import App from './components/App.vue' 21 | import GamePackage from './components/game-package.vue' 22 | import GameHouse from './components/game-house.vue' 23 | import GameProgress from './components/game-progress.vue' 24 | import GameSkillItem from './components/game-skill-item.vue' 25 | import GameStateItem from './components/game-state-item.vue' 26 | import ComponentItem from './components/component-item.vue' 27 | 28 | Vue.component('App', App) 29 | Vue.component('game-package', GamePackage) 30 | Vue.component('game-house', GameHouse) 31 | Vue.component('game-progress', GameProgress) 32 | Vue.component('game-skill-item', GameSkillItem) 33 | Vue.component('game-state-item', GameStateItem) 34 | Vue.component('component-item', ComponentItem) 35 | 36 | Vue.config.errorHandler = function (err, vm) { 37 | console.warn(err,vm); 38 | router.replace('/'); 39 | // router.replace('/login'); 40 | } 41 | 42 | // Object.assign(axios.defaults,{ 43 | // baseURL : 'http://127.0.0.1:8000', 44 | // }) 45 | 46 | const app = new Vue({ 47 | store, 48 | router, 49 | template: `` 50 | }).$mount('#app') 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/assets/font/mainfont.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastarder/vue-endless-h5-game/f0a46f988f502c1df753e4dab5bbaaf8c9e2f80f/src/assets/font/mainfont.otf -------------------------------------------------------------------------------- /src/assets/hero-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastarder/vue-endless-h5-game/f0a46f988f502c1df753e4dab5bbaaf8c9e2f80f/src/assets/hero-1.png -------------------------------------------------------------------------------- /src/assets/menu-fight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastarder/vue-endless-h5-game/f0a46f988f502c1df753e4dab5bbaaf8c9e2f80f/src/assets/menu-fight.png -------------------------------------------------------------------------------- /src/assets/menu-forge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastarder/vue-endless-h5-game/f0a46f988f502c1df753e4dab5bbaaf8c9e2f80f/src/assets/menu-forge.png -------------------------------------------------------------------------------- /src/assets/menu-setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastarder/vue-endless-h5-game/f0a46f988f502c1df753e4dab5bbaaf8c9e2f80f/src/assets/menu-setting.png -------------------------------------------------------------------------------- /src/assets/menu-shop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastarder/vue-endless-h5-game/f0a46f988f502c1df753e4dab5bbaaf8c9e2f80f/src/assets/menu-shop.png -------------------------------------------------------------------------------- /src/components/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 43 | 44 | 52 | 53 | 77 | -------------------------------------------------------------------------------- /src/components/component-item.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 62 | 63 | 128 | -------------------------------------------------------------------------------- /src/components/game-config.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 97 | 98 | 124 | -------------------------------------------------------------------------------- /src/components/game-fight-drop-list.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 60 | 61 | -------------------------------------------------------------------------------- /src/components/game-fight-event-log.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | 40 | -------------------------------------------------------------------------------- /src/components/game-fight-unit-info.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 49 | 50 | 96 | -------------------------------------------------------------------------------- /src/components/game-fight.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 174 | 175 | 205 | -------------------------------------------------------------------------------- /src/components/game-home-info.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 65 | 66 | 208 | -------------------------------------------------------------------------------- /src/components/game-home-menu.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 32 | 33 | 83 | -------------------------------------------------------------------------------- /src/components/game-home.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 35 | 36 | 56 | -------------------------------------------------------------------------------- /src/components/game-hot-key-item.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/game-house.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 43 | 44 | 70 | -------------------------------------------------------------------------------- /src/components/game-login.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 79 | 80 | 153 | -------------------------------------------------------------------------------- /src/components/game-map-active.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 111 | 112 | 208 | -------------------------------------------------------------------------------- /src/components/game-map-block.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 68 | 69 | -------------------------------------------------------------------------------- /src/components/game-map.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 47 | 48 | 115 | -------------------------------------------------------------------------------- /src/components/game-package.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 57 | 58 | 104 | -------------------------------------------------------------------------------- /src/components/game-progress.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/game-range-select.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 76 | 77 | -------------------------------------------------------------------------------- /src/components/game-shop.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | 27 | -------------------------------------------------------------------------------- /src/components/game-skill-item.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/game-smithy-blueprint.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 94 | 95 | 147 | -------------------------------------------------------------------------------- /src/components/game-smithy-intensify.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 68 | 69 | 147 | -------------------------------------------------------------------------------- /src/components/game-smithy.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 29 | 30 | 45 | -------------------------------------------------------------------------------- /src/components/game-state-item.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/game-switch-button.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | -------------------------------------------------------------------------------- /src/css/item-tool-tip.css: -------------------------------------------------------------------------------- 1 | .item-tool-tip-pover .equip{ 2 | color:#e7d0d0; 3 | } 4 | 5 | .item-tool-tip-pover .attr-name.skills{ 6 | color: yellow; 7 | } 8 | 9 | .item-tool-tip-pover .attr-name.status{ 10 | color: green; 11 | } 12 | 13 | .item-tool-tip-pover .attr-name.down, 14 | .item-tool-tip-pover .attr-data.down, 15 | .item-tool-tip-pover .dsc{ 16 | color: gray; 17 | } 18 | 19 | .item-tool-tip-pover{ 20 | background: rgba(12, 4, 37, 0.8); 21 | border-radius: 4px; 22 | padding: 6px; 23 | color: white; 24 | text-shadow: black 1px 1px; 25 | font-size: 10px; 26 | z-index: 99; 27 | } -------------------------------------------------------------------------------- /src/css/main.css: -------------------------------------------------------------------------------- 1 | *{ 2 | box-sizing: border-box; 3 | user-select: none; 4 | list-style: none; 5 | -ms-box-sizing: border-box; /* IE 9 */ 6 | -moz-box-sizing: border-box; /* Firefox */ 7 | -webkit-box-sizing: border-box; /* Safari 和 Chrome */ 8 | -o-box-sizing: border-box; /* Opera */ 9 | } 10 | 11 | ul{ 12 | margin:0px; 13 | padding: 0px; 14 | } 15 | 16 | *::-webkit-scrollbar{ 17 | display: none; 18 | } 19 | 20 | @font-face { 21 | font-family: 'mainfont'; 22 | src: url('../assets/font/mainfont.otf'); 23 | font-weight: normal; 24 | font-style: normal; 25 | } 26 | 27 | body{ 28 | font-family: mainfont; 29 | padding: 0px; 30 | margin: 0px; 31 | font-weight: 200; 32 | -ms-overflow-style: none; 33 | -moz-overflow-style: none; 34 | -webkit-overflow-style: none; 35 | -o-overflow-style: none; 36 | } 37 | 38 | .btn{ 39 | display: inline-block; 40 | text-align: center; 41 | padding: 2px 4px; 42 | background: none; 43 | color: #1997c6; 44 | border: 1px solid #1997c6; 45 | border-radius: 2px; 46 | cursor: pointer; 47 | text-decoration: none; 48 | outline: none; 49 | transition:all 0.3s ease-in-out; 50 | } 51 | .btn:hover{ 52 | background: #1997c6; 53 | color: white; 54 | transition:all 0.3s ease-in-out; 55 | } 56 | 57 | .btn.disabled, .btn.disabled:hover{ 58 | color: gray; 59 | border-color: gray; 60 | background: none; 61 | cursor: no-drop; 62 | } 63 | 64 | .btn.red{ 65 | color: red; 66 | border-color: red; 67 | } 68 | 69 | .btn.red:hover{ 70 | background: red; 71 | color: white; 72 | } 73 | 74 | .m-t-10{ 75 | margin-top: 10px; 76 | } 77 | 78 | .m-b-10{ 79 | margin-bottom: 10px; 80 | } 81 | 82 | .m-b-4{ 83 | margin-bottom: 4px; 84 | } 85 | 86 | /* 87 | .m-l-10{ 88 | margin-bottom: 10px; 89 | } 90 | 91 | .m-r-10{ 92 | margin-bottom: 10px; 93 | }*/ 94 | 95 | .text-c{ 96 | text-align: center; 97 | } 98 | 99 | .text-l{ 100 | text-align: left; 101 | } 102 | 103 | .text-r{ 104 | text-align: right; 105 | } 106 | 107 | .f-r{ 108 | float: right; 109 | } 110 | 111 | .radius-4{ 112 | border-radius: 4px; 113 | } 114 | 115 | .radius-2{ 116 | border-radius: 2px; 117 | } 118 | 119 | .back-color{ 120 | background: #fff6cb; 121 | } 122 | 123 | .font-min{ 124 | font-size: 12px; 125 | } 126 | 127 | .p-abs{ 128 | position: absolute; 129 | } 130 | 131 | .p-rel{ 132 | position: relative; 133 | } 134 | 135 | .d-ib{ 136 | display: inline-block; 137 | } 138 | 139 | .d-b{ 140 | display: block; 141 | } 142 | 143 | .color-red{ 144 | color: #d44950; 145 | } 146 | 147 | .color-green{ 148 | color: #1bc98e; 149 | } 150 | 151 | .color-yellow{ 152 | color: #e4d836; 153 | } 154 | 155 | .color-purple{ 156 | color: #9f86ff; 157 | } 158 | 159 | .color-darkblue{ 160 | color: #1997c6; 161 | } 162 | 163 | .bg-red{ 164 | background: #d44950; 165 | } 166 | 167 | .bg-green{ 168 | background: #1bc98e; 169 | } 170 | 171 | .bg-yellow{ 172 | background: #e4d836; 173 | } 174 | 175 | .bg-purple{ 176 | background: #9f86ff; 177 | } 178 | 179 | .bg-darkblue{ 180 | background: #1997c6; 181 | } 182 | 183 | .aboutMe{ 184 | display: block; 185 | position: fixed; 186 | top: 0; 187 | padding: 0; 188 | } 189 | 190 | .aboutMe a{ 191 | display: inline-block; 192 | background: black; 193 | cursor: pointer; 194 | color: white; 195 | text-decoration: none; 196 | width: 100px; 197 | height: 40px; 198 | line-height: 40px; 199 | text-align: center; 200 | margin-bottom: 4px; 201 | } 202 | 203 | .game-main-loading{ 204 | display: none; 205 | } -------------------------------------------------------------------------------- /src/css/map-dialog-modal.css: -------------------------------------------------------------------------------- 1 | .Map-Dialog-modal{ 2 | background: white; 3 | border-radius: 2px; 4 | box-shadow: 0px 0px 4px rgba(0,0,0,0.2); 5 | border: 1px solid #eee; 6 | padding: 10px; 7 | font-size: 14px; 8 | letter-spacing: 1px; 9 | line-height: 20px; 10 | } 11 | 12 | .Map-Dialog-modal .close{ 13 | display: inline-block; 14 | transform: rotate(45deg); 15 | font-size: 26px; 16 | height: 20px; 17 | float: right; 18 | cursor: pointer; 19 | } 20 | 21 | .Map-Dialog-modal .close:hover{ 22 | color: gray; 23 | } 24 | 25 | .Map-Dialog-modal .msg{ 26 | padding: 4px 20px 4px 6px; 27 | background: #fcf8e3; 28 | } 29 | 30 | .Map-Dialog-modal .change{ 31 | background: #f2dede; 32 | padding: 4px 6px; 33 | } 34 | 35 | .Map-Dialog-modal .change .num{ 36 | color: #337ab7; 37 | } 38 | 39 | .Map-Dialog-modal .change .name{ 40 | color: #aa6708; 41 | } 42 | 43 | .Map-Dialog-modal .event{ 44 | position: absolute; 45 | top: 164px; 46 | width: 278px; 47 | } 48 | 49 | .Map-Dialog-modal .action{ 50 | border: none; 51 | background: #d9edf7; 52 | padding: 6px 8px; 53 | cursor: pointer; 54 | margin-left: 4px; 55 | float: right; 56 | } 57 | 58 | .Map-Dialog-modal .action:hover{ 59 | box-shadow: 0px 0px 2px; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/data/blueprint-data.js: -------------------------------------------------------------------------------- 1 | const BLUEPRINT_TABLE = [ 2 | { 3 | id : 4000001, 4 | name : '测试蓝图', 5 | need : [ 6 | [3000001, 22], 7 | [3000002, 1], 8 | [ 9 | function(item){ 10 | return item && item.id === 3000001; 11 | }, 12 | function(){ 13 | return 5; 14 | }, 15 | '未知的物品', 16 | ] 17 | ], 18 | synthetics : [ 19 | 3000003, 20 | [ 21 | 3000004, 22 | function(){ 23 | return item 24 | } 25 | ] 26 | ] 27 | }, 28 | { 29 | id : 4000002, 30 | name : '狂战士之斧', 31 | need : [ 32 | [3000001, 2], 33 | [3000002, 1], 34 | ], 35 | synthetics : [ 36 | 30000013, 37 | ] 38 | } 39 | ] 40 | 41 | export default BLUEPRINT_TABLE; -------------------------------------------------------------------------------- /src/data/constant.js: -------------------------------------------------------------------------------- 1 | const CONSTANT = { 2 | MAP_BLOCK_TYPE:{ 3 | STICK : '2', // 障碍 4 | ROAD : '0', // 可行走 5 | PATH : '4', // 路径 6 | HERO : '1', // 英雄 7 | END : '3' // 寻路终点 8 | }, 9 | EQUIP_ID :[ 10 | "武器", "护肩", "鞋子", "腰带", "上衣", "绑腿", "戒指", "项链", "手镯", 11 | ], 12 | UNIT_ATTR_NAME:{ 13 | $hp : '当前生命值', 14 | $mp : '当前魔法值', 15 | $maxHp : '生命', 16 | $maxMp : '魔法', 17 | $atk : '攻击力', 18 | $def : '防御', 19 | $str : '力量', 20 | $dex : '敏捷', 21 | $con : '体质', 22 | $int : '智力', 23 | $critical : '暴击', 24 | $dodge : '闪避', 25 | $coolTimePer : '冷却缩短', 26 | $skills : '技能', 27 | $status : '状态', 28 | $dmgDown : '伤害减免', 29 | $critiDmg : '暴击伤害', 30 | }, 31 | ITEM_LEVEL: ['ghostwhite','springgreen','skyblue','violet','orange'], 32 | } 33 | 34 | export default CONSTANT -------------------------------------------------------------------------------- /src/data/event-data.js: -------------------------------------------------------------------------------- 1 | const DIALOG_DATA = [ 2 | { 3 | id : 7000001, 4 | type: 'MapDialog', 5 | data: [ 6 | { 7 | msg: '你好, 我需要以下材料,给我,我就给你你想要的~', 8 | buttons : [ 9 | "[我没有]{1,1}", 10 | "#[我有的]{3,4,2,1}" 11 | ] , 12 | need : [[3000001,1],[3000002,2]], 13 | get : [[3000003,1]] 14 | }, 15 | '没诚意滚蛋!', 16 | '合作愉快~', 17 | '你的背包好像没有我要的东西~', 18 | '你的背包好像放不下了,下次再来吧!' 19 | ] 20 | } 21 | ] 22 | 23 | export { 24 | DIALOG_DATA 25 | }; -------------------------------------------------------------------------------- /src/data/hero-data.js: -------------------------------------------------------------------------------- 1 | 2 | const EXP_TABLE = [ 3 | 1, // 1 4 | 10, // 2 5 | 45, // 3 6 | 120, // 4 7 | 300, // 5 8 | 450, // 6 9 | ]; 10 | 11 | 12 | export { 13 | EXP_TABLE 14 | }; -------------------------------------------------------------------------------- /src/data/item-data.js: -------------------------------------------------------------------------------- 1 | const ITEM_TYPE = { 2 | 3 | } 4 | 5 | let Material = [ 6 | { 7 | id: 3000001, 8 | name: '野草', 9 | pile : true, 10 | price : 10, 11 | use : { 12 | defaultTime : 1000, 13 | restrict :[ 14 | function(){ 15 | return this.$hp > 500; 16 | } 17 | ], 18 | effect :[ 19 | function(){ 20 | this.changeHp(30); 21 | } 22 | ] 23 | }, 24 | label : [ 25 | '材料' 26 | ], 27 | dsc : '很常见的东西,或许能用来做一些东西' 28 | }, 29 | { 30 | id: 3000002, 31 | name: '浆果', 32 | pile : true, 33 | use : function(){ 34 | return { 35 | defautTime : 1000, 36 | restrict : [], 37 | effect :[] 38 | } 39 | }, 40 | label : [ 41 | '食物','材料' 42 | ], 43 | dsc : '能卖钱,能吃,数量很多' 44 | }, 45 | ]; 46 | 47 | let Equipment = [ 48 | { 49 | id: 3000003, 50 | level: 1, 51 | name: '铁剑', 52 | equipType : 0, 53 | label: [ 54 | '武器' 55 | ], 56 | equip : { 57 | $atk: 5, 58 | $coolTimePer: 50, 59 | $skills : [1000004], 60 | $status : [2000001], 61 | $maxHp : [-10,1], 62 | }, 63 | dsc : '最基础的武器.' 64 | }, 65 | { 66 | id: 3000004, 67 | name: '破旧的护肩', 68 | level: 1, 69 | equipType : 1, 70 | label: [ 71 | '护肩' 72 | ], 73 | equip : { 74 | $def: 2, 75 | $atk: 5, 76 | $skills : [1000002], 77 | $maxHp : 5, 78 | $maxMp : 5, 79 | }, 80 | dsc : '用破布做成的护肩,只能挡风' 81 | }, 82 | { 83 | id: 3000005, 84 | name: '破旧的马靴', 85 | level: 1, 86 | equipType : 2, 87 | label: [ 88 | '鞋子' 89 | ], 90 | equip : { 91 | $def: 2, 92 | $atk: 1, 93 | $maxHp : 5, 94 | $maxMp : 5 95 | }, 96 | dsc : '像是垃圾堆捡来的' 97 | }, 98 | { 99 | id: 3000006, 100 | name: '破旧的腰带', 101 | level: 1, 102 | equipType : 3, 103 | label: [ 104 | '腰带' 105 | ], 106 | equip : { 107 | $def: 2, 108 | $maxHp : 5, 109 | $maxMp : 5, 110 | }, 111 | dsc : '随处可见的腰带' 112 | }, 113 | { 114 | id: 3000007, 115 | name: '破旧的上衣', 116 | level: 1, 117 | equipType : 4, 118 | label: [ 119 | '上衣' 120 | ], 121 | equip : { 122 | $def: 2, 123 | $maxHp : 5, 124 | $maxMp : 5, 125 | }, 126 | dsc : '用破布做成的上衣,只能挡风' 127 | }, 128 | { 129 | id: 3000008, 130 | name: '破旧的绑腿', 131 | level: 1, 132 | equipType : 5, 133 | label: [ 134 | '绑腿' 135 | ], 136 | equip : { 137 | $def: 2, 138 | $maxHp : 5, 139 | $maxMp : 5, 140 | }, 141 | dsc : '用破布做成的护肩,只能挡风' 142 | }, 143 | { 144 | id: 3000009, 145 | name: '草戒', 146 | level: 1, 147 | equipType : 6, 148 | label: [ 149 | '戒指' 150 | ], 151 | equip : { 152 | $def: 2, 153 | $atk: 5, 154 | $maxHp : 5, 155 | $maxMp : 5, 156 | }, 157 | dsc : '用野草编制的戒指' 158 | }, 159 | { 160 | id: 30000010, 161 | name: '石头挂坠', 162 | equipType : 7, 163 | level: 1, 164 | label: [ 165 | '项链' 166 | ], 167 | equip : { 168 | $def: 2, 169 | $atk: 5, 170 | $maxHp : 5, 171 | $maxMp : 5, 172 | }, 173 | dsc : '或许这也是一种信仰吧!' 174 | }, 175 | { 176 | id: 30000011, 177 | name: '野草手镯', 178 | equipType : 8, 179 | level: 1, 180 | label: [ 181 | '手镯' 182 | ], 183 | equip : { 184 | $def: 2, 185 | $atk: 5, 186 | $maxHp : 5, 187 | $maxMp : 5, 188 | }, 189 | dsc : '用野草编制的手镯' 190 | }, 191 | { 192 | id: 30000012, 193 | name: '精致的铁剑', 194 | level: 1, 195 | grade: 1, 196 | equipType : 0, 197 | label: [ 198 | '武器' 199 | ], 200 | equip : { 201 | $def: 2, 202 | $atk: 15, 203 | $maxHp : 5, 204 | $maxMp : 5, 205 | }, 206 | dsc : '用野草编制的手镯' 207 | }, 208 | { 209 | id: 30000013, 210 | name: '无尽之刃', 211 | grade: 4, 212 | level: 20, 213 | equipType : 0, 214 | label: [ 215 | '武器' 216 | ], 217 | equip : { 218 | $con: 100, 219 | $atk: 70, 220 | $critical : 20, 221 | $status : [2000003], 222 | }, 223 | dsc : '流传在世间,创世神的武器' 224 | }, 225 | { 226 | id: 30000014, 227 | name: '反伤护甲', 228 | grade: 3, 229 | level: 20, 230 | equipType : 4, 231 | label: [ 232 | '上衣' 233 | ], 234 | equip : { 235 | $def: 25, 236 | $maxHp : 100, 237 | $maxMp : 25, 238 | $status : [2000004, 2000002], 239 | }, 240 | dsc : '哇!这么刺怎么穿上去的!' 241 | }, 242 | ] 243 | 244 | const ITEM_TABLE = _.concat(Material,Equipment); 245 | 246 | export { 247 | ITEM_TABLE 248 | }; -------------------------------------------------------------------------------- /src/data/map-data.js: -------------------------------------------------------------------------------- 1 | const MAP_TABLE = [ 2 | { 3 | id: 8000001, 4 | name: '村庄', 5 | logo: '', 6 | dsc: '一个几乎毫无危险的地方.', 7 | mapInitOption: { 8 | row : 20, 9 | col : 20, 10 | lines : 10, // 分支量; 11 | inflex : 0.5 // 曲折度; 12 | }, 13 | monsterList: [ 14 | 5000001, 5000002 15 | ], 16 | eventList: [ 17 | 7000001 18 | ], // 特殊事件触发点; 19 | rule: { // 生成规则; 20 | "5000001" : 4, 21 | "5000002" : 5, 22 | "7000001" : 1, 23 | } 24 | }, 25 | { 26 | id: 8000002, 27 | name: '森林', 28 | logo: '', 29 | dsc: '这里可能有野兽出没...', 30 | eventList: [], 31 | monsterList: [], 32 | itemList: [ 33 | 34 | ], 35 | rule: { 36 | 37 | } 38 | 39 | }, 40 | { 41 | id: 8000003, 42 | name: '青木镇', 43 | logo: '', 44 | dsc: '繁华的小镇,却影藏着危机...', 45 | eventList: [], 46 | monsterList: [], 47 | itemList: [ 48 | 49 | ], 50 | rule: { 51 | 52 | } 53 | 54 | }, 55 | { 56 | id: 8000004, 57 | name: '镇外', 58 | logo: '', 59 | dsc: '发狂的人类?...', 60 | eventList: [], 61 | monsterList: [], 62 | itemList: [ 63 | 64 | ], 65 | rule: { 66 | 67 | } 68 | 69 | }, 70 | { 71 | id: 8000005, 72 | name: '青木林', 73 | logo: '', 74 | dsc: '发狂的人类?...', 75 | eventList: [], 76 | monsterList: [], 77 | itemList: [ 78 | 79 | ], 80 | rule: { 81 | 82 | } 83 | 84 | }, 85 | { 86 | id: 8000006, 87 | name: '火焰洞穴', 88 | logo: '', 89 | dsc: '发狂的人类?...', 90 | eventList: [], 91 | monsterList: [], 92 | itemList: [ 93 | 94 | ], 95 | rule: { 96 | 97 | } 98 | 99 | }, 100 | { 101 | id: 8000007, 102 | name: '冰霜洞穴', 103 | logo: '', 104 | dsc: '发狂的人类?...', 105 | eventList: [], 106 | monsterList: [], 107 | itemList: [ 108 | 109 | ], 110 | rule: { 111 | 112 | } 113 | 114 | }, 115 | { 116 | id: 8000008, 117 | name: '祭祀台', 118 | logo: '', 119 | dsc: '发狂的人类?...', 120 | eventList: [], 121 | monsterList: [], 122 | itemList: [ 123 | 124 | ], 125 | rule: { 126 | 127 | } 128 | 129 | }, 130 | { 131 | id: 8000009, 132 | name: '骷髅大厅', 133 | logo: '', 134 | dsc: '发狂的人类?...', 135 | eventList: [], 136 | monsterList: [], 137 | itemList: [ 138 | 139 | ], 140 | rule: { 141 | 142 | } 143 | 144 | }, 145 | { 146 | id: 80000010, 147 | name: '骷髅王', 148 | logo: '', 149 | dsc: '发狂的人类?...', 150 | eventList: [], 151 | monsterList: [], 152 | itemList: [ 153 | 154 | ], 155 | rule: { 156 | 157 | } 158 | 159 | }, 160 | { 161 | id: 80000011, 162 | name: '洞穴出口', 163 | logo: '', 164 | dsc: '发狂的人类?...', 165 | eventList: [], 166 | monsterList: [], 167 | itemList: [ 168 | 169 | ], 170 | rule: { 171 | 172 | } 173 | 174 | }, 175 | { 176 | id: 80000012, 177 | name: '黑色小溪', 178 | logo: '', 179 | dsc: '发狂的人类?...', 180 | eventList: [], 181 | monsterList: [], 182 | itemList: [ 183 | 184 | ], 185 | rule: { 186 | 187 | } 188 | 189 | }, 190 | ]; 191 | 192 | 193 | export default MAP_TABLE; -------------------------------------------------------------------------------- /src/data/monster-data.js: -------------------------------------------------------------------------------- 1 | const MONSTER = 'Monster'; 2 | 3 | const MONSTER_DATA = [ 4 | { 5 | $level : 1, 6 | $type : MONSTER, 7 | $showName : '小野猪', 8 | id : 5000001, 9 | $maxHp : 100, 10 | $maxMp : 0, 11 | $atk : 40, 12 | $def : 0, 13 | $status : [2000002, 2000004], 14 | $dropList : [ 15 | // 物品ID, 数量范围, 几率 16 | [3000001, [1,3], 1], 17 | [3000002, [1,3], 0.5], 18 | [3000003, 1, 1], 19 | ['gold',[1, 2], 1], 20 | ['exp', 1, 1], 21 | ['gem',1, 1] 22 | ] 23 | }, 24 | { 25 | $level : 2, 26 | $type : MONSTER, 27 | $showName : '小牛', 28 | id : 5000002, 29 | $maxHp : 150, 30 | $maxMp : 0, 31 | $atk : 5, 32 | $def : 0, 33 | $status : [2000002], 34 | // $status : [2000002], 35 | // $skills : [1000001] 36 | $dropList : [ 37 | 38 | [3000001, [1,3], 1], 39 | [3000002, [1,3], 0.5], 40 | [3000003, 1, 0.1], 41 | ['gold',[5, 10], 1], 42 | ['exp', 5, 1], 43 | ] 44 | } 45 | ] 46 | 47 | export default MONSTER_DATA; -------------------------------------------------------------------------------- /src/data/skill-data.js: -------------------------------------------------------------------------------- 1 | import { GetRange, GetRandom } from '../js/public-random-range'; 2 | 3 | const SKILL_TABLE = [ 4 | { 5 | id: 1000001, 6 | name: '斩', 7 | dsc : '简简单单的一击', 8 | label : ['普攻','伤害'], 9 | defaultTime : 1000, 10 | eventList: [ 11 | `[11]enemy@beAttack@attacker.$r.$atk` 12 | ] 13 | }, 14 | { 15 | id: 1000002, 16 | name: '魔', 17 | dsc : '造成双倍伤害,并恢复等同于伤害的血量,有一定几率使对方进入中毒状态.', 18 | label : ['吸血','加倍'], 19 | defaultTime : 3000, 20 | currentCoolTime : 3000, 21 | coolTime : 0, 22 | restrict : [ 23 | // "[attacker]{$mp} >= {60}", 24 | // "[attacker]{$hp} <= {250}", 25 | // "[attacker]{$skills} nothas {1000003,1000001}", 26 | // "[attacker]{$status} has {2000001}", 27 | // "[skill]{coolTime} > {0}", 28 | function(skill, attacker, enemy){ 29 | return true; 30 | } 31 | ], 32 | eventList: [ 33 | `[11] 34 | enemy@beAttack@ attacker.$r.$atk * 2; 35 | `,{ 36 | width: 11, 37 | eventStr : function(action, attacker, enemy){ 38 | if(GetRandom(100)){ 39 | action.change('enemy_changeState',[ 40 | { id: 2000001, state: "ADD" } 41 | ]) 42 | } 43 | } 44 | },{ 45 | width: 91, 46 | eventStr : function(action, attacker, enemy){ 47 | console.log(action) 48 | action.change('attacker_beCure', action.enemy_beAttack) 49 | } 50 | }, 51 | ] 52 | // eventList: [ 53 | // `[11]attacker@changeState@[{ id: 2000001, state: "REMOVE" }]` 54 | // ] 55 | }, 56 | { 57 | id: 1000003, 58 | name: '圣光术', 59 | dsc : '恢复大量生命!', 60 | label : ['恢复'], 61 | defaultTime : 5000, 62 | restrict : [ 63 | "[attacker]{$mp} >= {105}", 64 | ], 65 | eventList: [ 66 | `[11] 67 | attacker@changeMp@-15; 68 | attacker@beCure@ attacker.$r.$maxHp * 0.5 69 | ` 70 | ] 71 | }, 72 | { 73 | id: 1000004, 74 | name: '净', 75 | dsc : '净化负面状态!', 76 | label : ['测试2','伤害2'], 77 | defaultTime : 10000, 78 | eventList: [ 79 | `[11]attacker@changeState@[{ id: 2000001, state: "REMOVE" }]` 80 | ] 81 | // eventList: [ 82 | // `[11] 83 | // enemy@beAttack@ attacker.$r.$atk * 2; 84 | // attacker@beAttack@ attacker.$r.$atk * 0.3 85 | // ` 86 | // ] 87 | }, 88 | { 89 | id: 1000005, 90 | name: '致死', 91 | logo: '盾', 92 | dsc : '造成大量伤害,有很大的几率一击必杀!', 93 | label : ['测试2','伤害2'], 94 | defaultTime : 10000, 95 | eventList: [ 96 | `[11] 97 | action@{Math.random() > 0.98} 98 | # 99 | enemy@beAttack@9999999 100 | `, 101 | ] 102 | }, 103 | // { 104 | // id: 1000006, 105 | // name: '测试精简数据', 106 | // logo: '毒', 107 | // dsc : '简简单单的一击', 108 | // label : ['普攻','伤害'], 109 | // defaultTime : 1000, 110 | // currentCoolTime : 1000, 111 | // coolTime : 0, 112 | // eventList : [ 113 | // `[1]enemy@changeHp@attacker.$atk`, 114 | // `[2]attacker@changeHp@2`, 115 | // `[3] 116 | // action@{action.state.isCritical === true}; 117 | // attacker@{attacker.$hp > (attacker.$hp * 0.5)} 118 | // # 119 | // enemy@changeState@[{ id: 2000001, state: "ADD" }]; 120 | // enemy@changeHp@attacker.$atk 121 | // ` 122 | // , 123 | // `[4]action@{action.state.isCritical = true}`, 124 | // ] 125 | // } 126 | ]; 127 | 128 | export default SKILL_TABLE -------------------------------------------------------------------------------- /src/data/state-data.js: -------------------------------------------------------------------------------- 1 | // type: 1 作为攻击方时被提取; 2 | // type: 2 作为被攻击方时被提取; 3 | // type: 3 在判断所受伤害的时候被提取; 4 | 5 | const STATE_TABLE = [ 6 | { 7 | id: 2000001, 8 | name: '中毒', 9 | type: '1', 10 | logo: '毒', 11 | color: 'black', 12 | dsc : '你中毒了,每回合将会减少HP!', 13 | label : ['状态'], 14 | stateEvent : function(hero) { 15 | var self = this; 16 | var duration = 5; 17 | var per = 1; 18 | var current = 1; 19 | self.stateEventTimer = setInterval(function(){ 20 | hero.changeHp(-10); 21 | current +=1; 22 | if(current > 5){ 23 | clearInterval(self.stateEventTimer); 24 | hero.removeList('$status',self); 25 | } 26 | }, per * 1000); 27 | this.actived = true; 28 | } 29 | }, 30 | { 31 | id: 2000002, 32 | name: '坚盾', 33 | type: '3', 34 | logo: '盾', 35 | color: 'red', 36 | dsc : '坚守之盾,免疫50%伤害', 37 | label : ['测试','状态'], 38 | powerUp : { 39 | $dmgDown : [50, 1], 40 | }, 41 | }, 42 | { 43 | id: 2000003, 44 | name: '暴击伤害提升(50%)', 45 | type: '1', 46 | logo: '爆', 47 | color: 'red', 48 | dsc : '暴击伤害提升(50%)', 49 | label : ['状态'], 50 | eventList: [ 51 | `[11] 52 | action@{action.state.isCritical === true} 53 | # 54 | action@{action.set('atk_per', 50)} 55 | ` 56 | ] 57 | }, 58 | { 59 | id: 2000004, 60 | name: '反伤(30%)', 61 | type: '2', 62 | logo: '反', 63 | color: 'red', 64 | dsc : '反弹30%伤害', 65 | label : ['测试','状态'], 66 | eventList: [ 67 | `[91]attacker@beAttack@ action.enemy_beAttack * 0.3 68 | ` 69 | ] 70 | }, 71 | ] 72 | 73 | export default STATE_TABLE -------------------------------------------------------------------------------- /src/directive/drop-item.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import CONSTANT from '../data/constant' 3 | import PGET from '../js/public-static-get' 4 | import store from '../store'; 5 | import dragDrop from '../js/drag-drop'; 6 | import moveClass from '../js/different-item-move-class' 7 | 8 | export default function (el, binding){ 9 | 10 | let { hero, position } = binding.value; 11 | 12 | let event = { 13 | dragend (event){ 14 | event.dataTransfer.clearData("text"); 15 | return false; 16 | }, 17 | dragover (event){ 18 | event.preventDefault(); 19 | return true; 20 | }, 21 | dragstart (event){ 22 | event.dataTransfer.setData("text", position); 23 | try { 24 | event.dataTransfer.setDragImage(!~event.target.className.indexOf("component-item") ? event.target.parentNode : event.target, 20, 20); 25 | } catch (error) { 26 | // pass 27 | } 28 | let itemPover = document.querySelector('.item-tool-tip-pover'); 29 | itemPover && itemPover.parentNode.removeChild(itemPover); 30 | }, 31 | drop (event){ 32 | event.preventDefault(); 33 | 34 | dragDrop( 35 | event.dataTransfer.getData("text"), 36 | position, 37 | hero 38 | ); 39 | 40 | hero.updateAttribute(); 41 | 42 | store.commit('UPDATE'); 43 | 44 | } 45 | } 46 | 47 | // 如果是格子是空的则禁用拖动; 48 | { 49 | let block = new moveClass(position).get(); 50 | 51 | if(block){ 52 | el.setAttribute('draggable','true'); 53 | }else{ 54 | el.removeAttribute('draggable'); 55 | } 56 | block = null; 57 | } 58 | 59 | // 创建事件,并销毁已销毁的事件; 60 | for(let key in event){ 61 | let value = event[key], 62 | keyNameInElement = `${key}_EVENT_FUNCTION_ITEM_BLOCK`; 63 | el.removeEventListener(key, el[keyNameInElement]); 64 | el.addEventListener(key, value); 65 | el[keyNameInElement] = value; 66 | } 67 | 68 | }; 69 | -------------------------------------------------------------------------------- /src/directive/item-tool-tip.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import CONSTANT from '../data/constant' 3 | import PGET from '../js/public-static-get' 4 | 5 | require('../css/item-tool-tip.css'); 6 | 7 | export default function(el, binding){ 8 | let keyName = CONSTANT.UNIT_ATTR_NAME, 9 | itemLevel = CONSTANT.ITEM_LEVEL, 10 | tipClassName = '.item-tool-tip-pover', 11 | item = binding.value; 12 | 13 | let event = { 14 | mouseenter : function(e){ 15 | 16 | event.mouseleave(); 17 | 18 | let tip = document.createElement('div'); 19 | 20 | let {right, top} = e.target.getBoundingClientRect(); 21 | 22 | Object.assign(tip.style, { 23 | position : 'absolute', 24 | left : `${right}px`, 25 | top : `${top}px` 26 | }) 27 | 28 | Object.assign(tip, { 29 | className : tipClassName.slice(1), 30 | innerHTML : ` 31 |
32 | {{this.item.name}} 33 | + {{item.intensify}} 34 |
35 |
36 |
37 | {{v[0]}} 38 | {{v[1]}} 39 |
40 |
41 |
42 | 强化攻击力 + {{item.intPowerUp.$atk}} 43 | 无视伤害 + {{item.intPowerUp.$dmgDown[0] * 100 + '%'}} 44 |
45 |
{{item.dsc}}
46 | ` 47 | }) 48 | 49 | document.body.appendChild(tip); 50 | 51 | new Vue({ 52 | created(){ 53 | this.item = item; 54 | this.itemColor = { 55 | color : itemLevel[this.item.grade || 0] 56 | } 57 | this.attr = _.map(this.item.equip, (v,k) => { 58 | let record = {v}, i = v; 59 | 60 | if(k === '$status' || k === '$skills'){ 61 | return ['附加' + keyName[k], _.map(v, id => Vue.filter('itemKey')(id, 'name')).join(','), false, k.slice(1)]; 62 | } 63 | 64 | if(Object.prototype.toString.call(v) === '[object Array]' && v[1] === 1 || v[1] === 3){ 65 | record.v = v[0] + '%'; 66 | i = v[0]; 67 | } 68 | 69 | if(i > 0){ 70 | record.v = "+" + record.v; 71 | }else{ 72 | record.down = true; 73 | } 74 | 75 | return [keyName[k], record.v, record.down, k.slice(1)]; 76 | }).sort(v => { 77 | return (v[3] === 'status' || v[3] === 'skills') ? 1 : 0 78 | }) 79 | } 80 | }).$mount(tipClassName); 81 | 82 | }, 83 | mouseleave : function(){ 84 | let old = document.querySelector(tipClassName); 85 | // 移除已经存在的tip; 86 | if(old){ 87 | old.parentNode.removeChild(old); 88 | } 89 | } 90 | } 91 | 92 | for(let key in event){ 93 | 94 | let value = event[key], 95 | keyNameInElement = `${key}_EVENT_FUNCTION_ITEM_TOOL_TIP`; 96 | 97 | el.removeEventListener(key, el[keyNameInElement]); 98 | 99 | if(item){ 100 | el.addEventListener(key, value); 101 | el[keyNameInElement] = value; 102 | } 103 | 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/filter.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import PGET from './js/public-static-get'; 3 | import CONSTANT from './data/constant'; 4 | 5 | Vue.filter('itemKey', function (id, key) { 6 | let data = PGET(id); 7 | return data[key] || data; 8 | }) 9 | 10 | Vue.filter('heroAttrKey', function (key) { 11 | return CONSTANT.UNIT_ATTR_NAME[key] 12 | }) 13 | -------------------------------------------------------------------------------- /src/js/astar.js: -------------------------------------------------------------------------------- 1 | const Astar = function Astar(map, start, end) { 2 | function block(x, y) { 3 | this.x = x; 4 | this.y = y; 5 | this.parent = null; 6 | this.G = 0; 7 | this.H = null; 8 | this.getF = function() { 9 | return this.G + this.H; 10 | }; 11 | } 12 | this.map = _.cloneDeep(map); 13 | this.init = function() { 14 | var opt = { 15 | startBlock : start, 16 | endBlock : end, 17 | stickList : _.filter(_.flattenDeep(this.map.mapData) ,{ block_type: '2'}), 18 | openList : [], 19 | closeList : [], 20 | isInList : function(block, type) { 21 | let index = _.findIndex(this[type],{ 22 | x:block.x, 23 | y:block.y 24 | }) 25 | return ~index && { index } 26 | } 27 | } 28 | _.assign(this.map, opt) 29 | 30 | this.map.openList.push(this.map.startBlock); 31 | 32 | var line = this.step(); 33 | if (!line.find) { 34 | console.info('无法生存路径!'); 35 | return []; 36 | } 37 | line = line.endBlock; 38 | let path = []; 39 | while (line.parent.parent) { 40 | line = line.parent; 41 | path.push(line); 42 | } 43 | path.reverse().push(end); 44 | return path; 45 | } 46 | this.step = function() { 47 | this.map.openList = this.map.openList.sort(function(a, b) { 48 | return a.getF() - b.getF(); 49 | }) 50 | var currentBlock = this.map.openList.shift(); 51 | if (!currentBlock) { 52 | return { 53 | find: false 54 | } 55 | }; 56 | this.map.closeList.push(currentBlock); 57 | var around = this.around(currentBlock); 58 | for (var i = 0; i < around.length; i++) { 59 | var _block = around[i]; 60 | var index = this.map.isInList(_block, 'openList'); 61 | _block.parent = currentBlock; 62 | _block.H = this.countH(_block); 63 | _block.G = this.countG(_block) + (currentBlock.G || 0); 64 | if (!index) { 65 | if (_block.x === this.map.endBlock.x && _block.y === this.map.endBlock.y) { 66 | return { 67 | find: true, 68 | endBlock: _block 69 | } 70 | } 71 | this.map.openList.push(_block); 72 | continue; 73 | } 74 | if ((currentBlock.G + this.countG(_block)) < this.map.openList[index.index].G) { 75 | this.map.openList[index.index].parent = currentBlock; 76 | } 77 | }; 78 | return this.step(); 79 | }; 80 | this.around = function(currentBlock) { 81 | var list = []; 82 | for (var i = -1; i <= 1; i++) { 83 | for (var j = -1; j <= 1; j++) { 84 | if (i === 0 && j === 0) { 85 | continue; 86 | }; 87 | if (i !== 0 && j !== 0) { 88 | continue; 89 | } 90 | var x = currentBlock.x + i; 91 | var y = currentBlock.y + j; 92 | if (x >= this.map.row || y >= this.map.col || x < 0 || y < 0) { 93 | continue; 94 | } 95 | var record = new block(x, y); 96 | if (this.map.isInList(record, 'closeList')) { 97 | continue; 98 | } 99 | if (this.map.isInList(record, 'stickList')) { 100 | continue; 101 | } 102 | list.push(record) 103 | } 104 | } 105 | return list; 106 | } 107 | this.countH = function(block) { 108 | var x = Math.abs(block.x - this.map.endBlock.x); 109 | var y = Math.abs(block.y - this.map.endBlock.y); 110 | return (x + y) * 10; 111 | } 112 | this.countG = function(block) { 113 | if (block.x !== block.parent.x && block.y !== block.parent.y) { 114 | return 14; 115 | } else { 116 | return 10; 117 | } 118 | } 119 | return this.init(); 120 | } 121 | 122 | export default Astar -------------------------------------------------------------------------------- /src/js/audio.js: -------------------------------------------------------------------------------- 1 | import store from '../store'; 2 | 3 | // let dist = { 4 | // 'backgroundMusic': require('static/audio/bgm.wav'), 5 | // 'fight-attack' : require('static/audio/fight-attack.wav'), 6 | // } 7 | 8 | let dist = {}; 9 | 10 | let AudioList = []; 11 | window.AudioList = AudioList; 12 | 13 | let GameAudio = function (opt){ 14 | this.$AudioList = AudioList; 15 | this.src = opt.src || dist[opt.key]; 16 | this.id = this.src + Date.now(); 17 | this.opt = Object.assign({ 18 | autoplay : true, 19 | loop : false, 20 | },opt); 21 | // this.start(); 22 | } 23 | 24 | GameAudio.prototype = { 25 | start : function(){ 26 | let opt = this.opt; 27 | if(this.$el){ 28 | this.stop(); 29 | } 30 | this.$el = document.createElement('audio'); 31 | this.$el.src = this.src; 32 | this.$el.volume = 0.2; 33 | (opt.autoplay) && (this.$el.autoplay = "autoplay"); 34 | if(opt.loop){ 35 | this.$el.loop = "loop"; 36 | }else{ 37 | this.$el.addEventListener('ended',() => this.stop(), false); 38 | } 39 | document.body.appendChild(this.$el); 40 | this.$AudioList.push(this.$el); 41 | }, 42 | pause : function(){ 43 | this.$el.pause(); 44 | }, 45 | stop : function(){ 46 | this.$AudioList.splice(this.$AudioList.findIndex(a => a.id = this.id), 1); 47 | document.body.removeChild(this.$el); 48 | this.$el = null; 49 | } 50 | } 51 | 52 | export default GameAudio; 53 | 54 | -------------------------------------------------------------------------------- /src/js/blueprint.js: -------------------------------------------------------------------------------- 1 | import store from '../store' 2 | import PGET from '../js/public-static-get'; 3 | 4 | const BluePrint = function(opt){ 5 | let hero = store.state.HeroStore.hero; 6 | let result = { 7 | success : false, 8 | msg : '' 9 | } 10 | 11 | if(!hero.isEnoughInPackage(opt.need)){ 12 | return Object.assign(result, { 13 | success: false, 14 | msg : '材料不足!' 15 | }) 16 | } 17 | 18 | let synthetics = opt.synthetics || []; 19 | 20 | synthetics = synthetics.map(v => { 21 | let item; 22 | if(({}).toString.call(v) === "[object Array]"){ 23 | item = PGET(v[0]); 24 | console.log('array:',v[0]); 25 | v[1](item); 26 | }else{ 27 | item = PGET(v) 28 | } 29 | return [item, item.num || 1]; 30 | }) 31 | 32 | if(hero.getItem(synthetics).length === 0){ 33 | hero.getItem(synthetics, true); 34 | hero.costItem(opt.need); 35 | }else{ 36 | return Object.assign(result, { 37 | success: false, 38 | msg : '背包不足!', 39 | }) 40 | } 41 | 42 | return Object.assign(result, { 43 | success: true, 44 | msg : '锻造成功!', 45 | synthetics, 46 | }) 47 | 48 | } 49 | 50 | 51 | export default BluePrint -------------------------------------------------------------------------------- /src/js/cool-time-event.js: -------------------------------------------------------------------------------- 1 | import store from '../store'; 2 | 3 | const coolTimeEvent = function(currentCoolTime){ 4 | 5 | let hero = store.state.HeroStore.hero; 6 | 7 | currentCoolTime = Math.max(1000, (1 - hero.$r.$coolTimePer / 100) * (currentCoolTime || this.defaultTime)); 8 | 9 | this.coolTimeTimer && clearInterval(this.coolTimeTimer); 10 | 11 | this.coolTime = currentCoolTime; 12 | 13 | this.currentCoolTime = currentCoolTime; 14 | 15 | this.coolTimeTimer = setInterval( () => { 16 | this.coolTime -= 30; 17 | if(this.coolTime < 0){ 18 | this.coolTime = 0; 19 | clearInterval(this.coolTimeTimer); 20 | } 21 | }, 30); 22 | 23 | } 24 | 25 | export default coolTimeEvent; 26 | -------------------------------------------------------------------------------- /src/js/create-hero.js: -------------------------------------------------------------------------------- 1 | import { EXP_TABLE } from "../data/hero-data"; 2 | 3 | const CreateHero = function(option = {}){ 4 | 5 | let opt = _.cloneDeep(option); 6 | 7 | opt.$exp = opt.$exp || 0; // 当前经验 8 | 9 | opt.$level = opt.$level || 1; 10 | 11 | opt.$maxExp = opt.$maxExp || EXP_TABLE[opt.$level - 1]; // 升级经验 12 | 13 | opt.$package = opt.$package || new Array(40); 14 | 15 | opt.$houseList = opt.$houseList || new Array(40); 16 | 17 | opt.$equipments = opt.$equipments || [0,0,0,0,0,0,0,0,0]; 18 | 19 | opt.$resource = opt.$resource || { 20 | gold : 50, 21 | gem : 0, 22 | }; 23 | 24 | opt.$flashCopy = { 25 | $status: null, 26 | $skills: null, 27 | }; 28 | 29 | opt.$attrGrow = { 30 | maxHp : 10, 31 | maxMp : 10, 32 | atk : 5, 33 | def : 2, 34 | str : 1, // 力量 35 | dex : 2, // 敏捷 36 | con : 3, // 体质 37 | int : 4 // 智力 38 | } 39 | 40 | _.assign(this, opt); 41 | 42 | }; 43 | 44 | export default CreateHero; -------------------------------------------------------------------------------- /src/js/create-monster.js: -------------------------------------------------------------------------------- 1 | import PublicStaticGet from './public-static-get' 2 | 3 | const CreateMonster = function(opt = {}){ 4 | 5 | let option = _.cloneDeep(opt); 6 | 7 | // 精英 8 | if(!option.$elite){ 9 | let rndNum = Math.floor(Math.random() * 100) + 1; 10 | if(rndNum <= 90){ 11 | option.$elite = 1; 12 | }else if(rndNum <= 95){ 13 | option.$elite = 1.3 14 | }else if(rndNum <= 98){ 15 | option.$elite = 1.6 16 | }else if(rndNum <= 100){ 17 | option.$elite = 2 18 | } 19 | } 20 | // 名称前缀 21 | if(!option.$nickname){ 22 | if(option.$elite <=1){ 23 | option.$nickname = "普通的" 24 | }else if(option.$elite <=1.3){ 25 | option.$nickname = "威猛的" 26 | }else if(option.$elite <=1.6){ 27 | option.$nickname = "发怒的" 28 | }else if(option.$elite <=2){ 29 | option.$nickname = "变异的" 30 | }else{ 31 | option.$nickname = "???" 32 | } 33 | } 34 | 35 | // 处理增幅; 36 | let amplification = ['$maxHp','$mapMp','$good','$gem','$exp','$atk','$def','$str','$dex','$con','$int'] 37 | _.each(amplification,key => { 38 | option[key] && (option[key] = Math.ceil(option[key] * option.$elite)); 39 | }); 40 | 41 | // 处理初始属性; 42 | option.$hp = option.$maxHp; 43 | option.$mp = option.$maxMp; 44 | 45 | // 对象化 状态,技能; 46 | !option.$skills && (option.$skills = [1000001]) 47 | option.$skills = _.map(option.$skills,function(id){ 48 | return PublicStaticGet(id); 49 | }); 50 | 51 | option.$status = _.map(option.$status,function(id){ 52 | return PublicStaticGet(id); 53 | }); 54 | 55 | // 删除非必要属性; 56 | delete option.$elite; 57 | delete this.$resource; 58 | delete this.$attrGrow; 59 | delete this.$package; 60 | 61 | // 合并数据; 62 | _.assign(this,option); 63 | 64 | // 删除不必要属性; 65 | 66 | 67 | } 68 | 69 | export default CreateMonster -------------------------------------------------------------------------------- /src/js/different-item-move-class.js: -------------------------------------------------------------------------------- 1 | import store from '../store'; 2 | 3 | const eventList = { 4 | $intensify : { 5 | get: function(){ 6 | return store.state.SmithyStore.intensifyItem; 7 | }, 8 | set: function(obj){ 9 | store.commit('ChangeIntensifyItem', obj); 10 | } 11 | }, 12 | $default : { 13 | get: function(){ 14 | let hero = store.state.HeroStore.hero; 15 | return hero && hero[this.position] && hero[this.position][this.index]; 16 | }, 17 | set: function(obj){ 18 | let hero = store.state.HeroStore.hero; 19 | hero[this.position][this.index] = obj; 20 | } 21 | } 22 | } 23 | 24 | const moveClass = function(option){ 25 | 26 | let opt = option.split('|'); 27 | 28 | this.position = opt[0]; 29 | 30 | this.index = Number(opt[1]); 31 | 32 | this.get = function(){ 33 | throw new Error('移动物品品种类, item方法必须指定!'); 34 | } 35 | 36 | this.set = function(){ 37 | throw new Error('移动物品品种类, set方法必须指定!'); 38 | } 39 | 40 | Object.assign(this, eventList[this.position] || eventList['$default']) 41 | 42 | } 43 | 44 | export default moveClass; -------------------------------------------------------------------------------- /src/js/drag-drop.js: -------------------------------------------------------------------------------- 1 | import moveClass from '../js/different-item-move-class' 2 | 3 | const noting = function(from, to){ 4 | if(from.position === to.position && from.index === to.index){ 5 | return true; 6 | } 7 | } 8 | 9 | const destory = function(from, to){ 10 | if(to.position === '$destory'){ 11 | from.set(); 12 | return true; 13 | } 14 | } 15 | 16 | const equip = function(from, to, hero){ 17 | if(to.position === '$equipments'){ 18 | hero.equip(from.get(), from.index, from.position); 19 | return true; 20 | } 21 | } 22 | 23 | const demount = function(from, to, hero){ 24 | if(from.position === '$equipments'){ 25 | if(to.get() && to.get().equipType === from.index){ 26 | hero.equip(to.get(), to.index, to.position); 27 | return true; 28 | } 29 | } 30 | } 31 | 32 | const merge = function(from, to){ 33 | if(from.get() && to.get() && (from.get().id === to.get().id) && from.get().pile && to.get().pile){ 34 | to.get().num += from.get().num; 35 | from.set(); 36 | return true; 37 | } 38 | } 39 | 40 | const change = function(from, to){ 41 | let T = from.get(); 42 | from.set(to.get()); 43 | to.set(T); 44 | return true; 45 | } 46 | 47 | const initDrop = function(from, to, hero){ 48 | 49 | from = new moveClass(from); 50 | 51 | to = new moveClass(to); 52 | 53 | for(let event of [noting, destory, equip, demount, merge, change]){ 54 | 55 | let dropResult = event(from, to, hero); 56 | 57 | if(dropResult){ 58 | 59 | console.info('[Move Event] From',from,'To',to); 60 | 61 | return dropResult; 62 | 63 | } 64 | 65 | } 66 | 67 | } 68 | 69 | export default initDrop; -------------------------------------------------------------------------------- /src/js/dungeon-creater.js: -------------------------------------------------------------------------------- 1 | const DungeonCreater = function(opt) { 2 | // Dict 3 | this.$BLOCK_STICK = '2'; // 障碍 4 | this.$BLOCK_ROAD = '0'; // 可行走 5 | this.$BLOCK_PATH = '4'; // 路径 6 | this.$BLOCK_HERO = '1'; // 英雄 7 | this.$BLOCK_END = '3'; // 寻路终点 8 | 9 | // Create 10 | this.row = opt.row || 20; 11 | this.col = opt.col || 20; 12 | this.lines = opt.lines || 15; 13 | this.inflex = opt.inflex || 0.5; 14 | 15 | this.init = function() { 16 | var result = []; 17 | for (var i = 0; i < this.row; i++) { 18 | var arr = [] 19 | for (var j = 0; j < this.col; j++) { 20 | arr.push({ 21 | x: i, 22 | y: j, 23 | block_type: this.$BLOCK_STICK 24 | }); 25 | } 26 | result.push(arr); 27 | } 28 | this.mapData = result; 29 | var times = 0; 30 | while (true) { 31 | var start = getRandomPosition(this.row, this.col); 32 | var end = getRandomPosition(this.row, this.col); 33 | if (start.x - end.x > (this.row * 0.5) && start.y - end.y > (this.col * 0.5)) { 34 | createLine.call(this, start, end, true); 35 | break; 36 | } 37 | } 38 | for (var i = 0; i < this.lines - 1;) { 39 | createLine.call(this, getRandomPosition(this.row, this.col), getRandomPosition(this.row, this.col)) && i++; 40 | if (times++ > 100) { 41 | break; 42 | } 43 | } 44 | return this.mapData; 45 | }; 46 | 47 | function createLine(start, end, first) { 48 | // 生成线路: 采用从A - B的走法,中间随机曲折; _x,_y用于记录当前走到得节点位置,默认为起点; 49 | var _x = start.x; 50 | var _y = start.y; 51 | // 计算2点之间的距离; 52 | var distance_x = Math.abs(start.x - end.x); 53 | var distance_y = Math.abs(start.y - end.y); 54 | // 剩余的移动量;用于确保能够到达目标地点; 55 | var moveLeft = { 56 | x: distance_x, 57 | y: distance_y 58 | } 59 | // 用于存放需要被点亮的点的位置(最终的路径); 60 | var lines = []; 61 | // 循环生成曲折线路,直到走到目标地点; 62 | while (_x !== end.x && _y !== end.y) { 63 | // 进行X轴移动; 64 | var move_x = getRandom(0, distance_x, this.inflex, moveLeft.x); 65 | moveLeft.x -= move_x; 66 | for (var i = 0; i < move_x; i++) { 67 | if (start.x > end.x) { 68 | lines.push([_x - i, _y]); 69 | } else { 70 | lines.push([_x + i, _y]); 71 | } 72 | } 73 | if (start.x > end.x) { 74 | _x -= move_x; 75 | } else { 76 | _x += move_x; 77 | } 78 | // 进行Y轴移动; 79 | if (moveLeft.x === 0) { 80 | // 如果X轴已经走到同一线,则这一步Y轴需要直接走到终点; 81 | var move_y = moveLeft.y; 82 | } else { 83 | var move_y = getRandom(0, distance_y, this.inflex, moveLeft.y); 84 | } 85 | moveLeft.y -= move_y; 86 | for (var j = 0; j < move_y; j++) { 87 | if (start.y > end.y) { 88 | lines.push([_x, _y - j]); 89 | } else { 90 | lines.push([_x, _y + j]); 91 | } 92 | } 93 | if (start.y > end.y) { 94 | _y -= move_y; 95 | } else { 96 | _y += move_y; 97 | } 98 | } 99 | // 判断是否是第一条路径; 100 | // 如果非第一条路径,则需要确认此条路径经过了已有的路径,否则此条路径生成无效,将返回false; 101 | if (!first) { 102 | var isIn = false; 103 | lines.forEach(function(pos) { 104 | this.mapData[pos[0]][pos[1]].block_type === this.$BLOCK_ROAD && (isIn = true); 105 | }, this); 106 | if (!isIn) { 107 | return false; 108 | } 109 | } 110 | // 生成路径成功,将其更新至地图数据; 111 | lines.forEach(function(pos) { 112 | this.mapData[pos[0]][pos[1]].block_type = this.$BLOCK_ROAD; 113 | }, this) 114 | return true; 115 | } 116 | 117 | function getRandom(start, end, per, max) { 118 | var num = Number((Math.random() * (end - start) + start).toFixed(0)); 119 | if (per) { 120 | num = Number((num * per).toFixed(0)); 121 | if (max && (num > max)) { 122 | num = max; 123 | } 124 | } 125 | return num; 126 | } 127 | 128 | function getRandomPosition(x, y) { 129 | return { 130 | x: getRandom(0, x - 1), 131 | y: getRandom(0, y - 1), 132 | } 133 | } 134 | 135 | this.init(); 136 | } 137 | 138 | export default DungeonCreater -------------------------------------------------------------------------------- /src/js/event-class.js: -------------------------------------------------------------------------------- 1 | import Unit from '../js/unit-class'; 2 | import PGET from '../js/public-static-get'; 3 | import store from '../store'; 4 | import Vue from 'vue'; 5 | 6 | const MapDialogElementClassName = '.Map-Dialog-modal'; 7 | const ShadowViewClassName = '.shadow-view'; 8 | 9 | const MapDialog = function(event, callback){ 10 | 11 | let modal = document.createElement('div'); 12 | let shadowView = document.createElement('div'); 13 | let view = document.querySelector('#router-view'); 14 | let opt = { 15 | height : 300, 16 | width : 200, 17 | animated : 'animated zoomIn', 18 | backForce : 0.2, 19 | } 20 | 21 | shadowView.className = ShadowViewClassName.slice(1); 22 | 23 | // 背景层 创建; 24 | Object.assign(shadowView.style, { 25 | position : 'absolute', 26 | background : `rgba(0,0,0,${opt.backForce})`, 27 | width : `100%`, 28 | height : `100%`, 29 | left : `0px`, 30 | top : `0px`, 31 | zIndex : '5' 32 | }) 33 | 34 | // 模态框 创建; 35 | Object.assign(modal.style, { 36 | position : 'absolute', 37 | width : `${opt.height}px`, 38 | height : `${opt.width}px`, 39 | left : `${(view.offsetWidth - opt.width)/2}px`, 40 | top : `${(view.offsetHeight - opt.height)/2}px`, 41 | zIndex : '6' 42 | }) 43 | 44 | Object.assign(modal, { 45 | className : [ opt.animated, MapDialogElementClassName.slice(1) ].join(' '), 46 | innerHTML : ` 47 |
+
48 |
49 | {{this.record.msg}} 50 |
51 |
52 | 你愿意使用 53 | 56 | 来交换 57 | 60 | 吗? 61 |
62 |
63 | 66 | 67 |
68 | ` 69 | }) 70 | 71 | view.appendChild(modal); 72 | view.appendChild(shadowView); 73 | 74 | new Vue({ 75 | store, 76 | created(){ 77 | this.event = event; 78 | this.$i = 0; 79 | this.isEnd = false; 80 | this.next(); 81 | }, 82 | methods: { 83 | next(){ 84 | if(this.$isEnd){ 85 | this.close(); 86 | return ; 87 | } 88 | this.record = transformEventObj(this.event.data[this.$i]); 89 | this.isEnd = (this.$i++ === (this.event.data.length-1)); 90 | this.$forceUpdate(); 91 | }, 92 | action(e){ 93 | e.call(this); 94 | }, 95 | close(){ 96 | closeModal(); 97 | } 98 | } 99 | }).$mount(MapDialogElementClassName) 100 | 101 | function closeModal(){ 102 | let modal = document.querySelector(MapDialogElementClassName), 103 | shadow = document.querySelector(ShadowViewClassName); 104 | modal && modal.parentNode.removeChild(modal); 105 | shadow && shadow.parentNode.removeChild(shadow); 106 | callback && callback(); 107 | } 108 | 109 | function transformEventObj(opt){ 110 | let record = _.cloneDeep(opt); 111 | if(typeof record === 'string'){ 112 | record = { 113 | msg: record 114 | } 115 | } 116 | // "['我没有']{1,1}|['我有的!']{2,1}" 117 | let buttons = _.map(record.buttons,function(str){ 118 | if(typeof str === 'object'){ 119 | return str; 120 | } 121 | let strc = str; 122 | str = str.match(/\[([^]+)\]\{([^]+)\}/) 123 | let btn = {}; 124 | btn.title = str[1]; 125 | if(!str[2]){ 126 | return btn; 127 | } 128 | if(strc[0] !== '#'){ 129 | btn.action = function(){ 130 | let [i, isEnd] = str[2].split(','); 131 | this.$i = Number(i); 132 | this.next(); 133 | if(Number(isEnd)){ 134 | this.isEnd = true; 135 | } 136 | } 137 | }else{ 138 | let i = str[2].split(','); 139 | let isEnd = i.unshift(); 140 | btn.action = function(){ 141 | let need = opt.need || []; 142 | let get = opt.get || []; 143 | let unit = this.$store.state.HeroStore.hero; 144 | let enough = unit.isEnoughInPackage(need); 145 | if(!enough){ 146 | this.$i = i[0] 147 | }else{ 148 | let left = unit.getItem(get); 149 | if(left.length){ 150 | this.$i = i[1] 151 | }else{ 152 | unit.getItem(get, true); 153 | unit.costItem(need); 154 | this.$i = i[2] 155 | } 156 | } 157 | this.next(); 158 | if(Number(isEnd)){ 159 | this.isEnd = true; 160 | } 161 | } 162 | } 163 | return btn; 164 | }) 165 | record.buttons = buttons; 166 | 167 | return record; 168 | } 169 | 170 | } 171 | 172 | const MapFight = function(opt){ 173 | 174 | let data = { 175 | monsters: [ new Unit(opt) ] 176 | }; 177 | 178 | store.state.FightStore.monsters = data.monsters; 179 | 180 | // 跳转到 战斗场景; 181 | location.href = "#/fight" 182 | 183 | } 184 | 185 | export { 186 | MapDialog, 187 | MapFight 188 | }; 189 | 190 | -------------------------------------------------------------------------------- /src/js/fight-action-class.js: -------------------------------------------------------------------------------- 1 | const actionClass = function(attacker, enemy){ 2 | 3 | this.state = { 4 | attacker : attacker, 5 | enemy : enemy, 6 | isCritical : false, 7 | isMiss : false, 8 | isMust : false, 9 | holyDmge : 0, 10 | holyDmga : 0, 11 | cure : 0, 12 | cure_per : 0, 13 | atk : 0, 14 | atk_per : 0, 15 | } 16 | 17 | }; 18 | 19 | actionClass.prototype.CountFinal = function(){ 20 | let s = this.state; 21 | // this.attacker_beAttack this.enemy_beAttack; 22 | if(this.attacker_beAttack){ 23 | this.attacker_beAttack = Math.floor( 24 | this.attacker_beAttack * (1 + s.atk_per / 100) + s.atk 25 | ); 26 | } 27 | if(this.enemy_beAttack){ 28 | this.enemy_beAttack = Math.floor( 29 | this.enemy_beAttack * (1 + s.atk_per / 100) + s.atk 30 | ); 31 | if(s.isCritical){ 32 | this.enemy_beAttack = Math.floor( 33 | this.enemy_beAttack * s.attacker.$critiDmg 34 | ); 35 | } 36 | } 37 | if(this.attacker_beCure){ 38 | this.attacker_beCure = Math.floor( 39 | this.attacker_beCure * (1 + s.cure_per / 100) + s.cure 40 | ); 41 | } 42 | if(this.enemy_beCure){ 43 | this.enemy_beCure = Math.floor( 44 | this.enemy_beCure * (1 + s.cure_per / 100) + s.cure 45 | ); 46 | } 47 | } 48 | 49 | actionClass.prototype.init = function(){ 50 | let s = this.state; 51 | let [v,per] = s.attacker.$r.$dmgDown; 52 | let atk = ((this.attacker_beAttack || 0) - v - s.attacker.$r.$def) * (1 - per / 100); 53 | atk = atk > 0 ? atk : 0; 54 | console.log(this) 55 | this.attacker_changeHp = (this.attacker_beCure || 0) - atk - s.holyDmga; 56 | 57 | [v,per] = s.enemy.$r.$dmgDown; 58 | atk = ((this.enemy_beAttack || 0) - v - s.enemy.$r.$def) * (1 - per / 100); 59 | atk = atk > 0 ? atk : 0; 60 | 61 | this.enemy_changeHp = (this.enemy_beCure || 0) - atk - s.holyDmge; 62 | this.state.isCritical && console.info('暴击!'); 63 | this.state.isMiss && console.info('闪避!'); 64 | this.state.isMust && console.info('必中!'); 65 | delete this.attacker_beCure 66 | delete this.attacker_beAttack 67 | delete this.enemy_beCure 68 | delete this.enemy_beAttack 69 | // delete this.state 70 | } 71 | 72 | actionClass.prototype.change = function(key, value, cover) { 73 | if(this[key]){ 74 | if(cover){ 75 | this[key] = value; 76 | }else{ 77 | if(this[key] instanceof Array){ 78 | this[key] = this[key].concat(value); 79 | }else{ 80 | this[key] += value; 81 | } 82 | } 83 | }else{ 84 | this[key] = value; 85 | } 86 | } 87 | 88 | actionClass.prototype.set = function(key, value, index){ 89 | if(index === undefined){ 90 | this.state[key] += value 91 | }else{ 92 | this.state[key][index] += value 93 | } 94 | } 95 | 96 | export default actionClass; -------------------------------------------------------------------------------- /src/js/fight.js: -------------------------------------------------------------------------------- 1 | import coolTimeEvent from './cool-time-event' 2 | import actionClass from './fight-action-class' 3 | import SkillAvailable from './skill-available' 4 | import { GetRange, GetRandom } from './public-random-range'; 5 | import GameAudio from './audio' 6 | import store from '../store'; 7 | 8 | const Fight = (attacker, enemy, skill) => { 9 | 10 | 11 | // 判断是否可以行动; 12 | if(!SkillAvailable(skill, attacker, enemy)){ 13 | store.commit('FightEventLogPush',`未满足施法条件!`); 14 | return ; 15 | } 16 | 17 | // 触发冷却; 18 | coolTimeEvent.call(skill); 19 | 20 | // 开始计算打击事件; 21 | let event = _.concat( 22 | _.filter(attacker.$status, { type: '1' }), 23 | _.filter(enemy.$status, { type: '2' }), 24 | [ skill ] 25 | ); 26 | 27 | // 提取事件; 28 | var eventList = []; 29 | 30 | // 拼接父技能; 31 | _.each(event, skill => { 32 | _.each(skill.eventList, item => { 33 | if(item.width){ 34 | eventList.push(item); 35 | return; 36 | } 37 | eventList.push({ 38 | father : skill, 39 | eventStr : item, 40 | weight : Number(item.match(/\[(\d+)\]/)[1]) 41 | }) 42 | }) 43 | }); 44 | 45 | // 排序; 46 | eventList = _.sortBy(eventList, ['weight']); 47 | 48 | // 初始化行动对象; 49 | var action = new actionClass(attacker, enemy); 50 | 51 | let opt = { 52 | attacker, enemy, action 53 | }; 54 | 55 | // 插入规则中间缓冲 56 | eventList.push( 57 | // action 层 58 | { 59 | width: 10, 60 | father : skill, 61 | eventStr : function(action, attacker, enemy){ 62 | action.state.isCritical = GetRandom(attacker.$r.$critical / 100); 63 | action.state.isMiss = GetRandom(enemy.$r.$dodge / 100); 64 | } 65 | }, 66 | // CountFinal 层 , 需要最终伤害为基准的事件之前; 67 | { 68 | width: 90, 69 | father : skill, 70 | eventStr : function(action, attacker, enemy){ 71 | action.CountFinal(); 72 | } 73 | }, 74 | // 最终层 , 永远在事件列队的最后; 75 | { 76 | width: 9999, 77 | father : skill, 78 | eventStr : function(action, attacker, enemy){ 79 | action.init(); 80 | } 81 | } 82 | ) 83 | 84 | // 计算最终行动对象; 85 | _.each(eventList, item => { 86 | let father = item.father; 87 | var eventStr,ruleStr; 88 | if(typeof item.eventStr === 'function'){ 89 | item.eventStr.apply(father, [action, attacker, enemy]); 90 | return; 91 | } 92 | var str = item.eventStr.replace(/[\r\n\s]/g,"").replace(/\[(\d+)\]/,"").split('#'); 93 | if(str.length > 1){ 94 | ruleStr = str[0]; 95 | eventStr = str[1]; 96 | }else{ 97 | eventStr = str[0]; 98 | } 99 | 100 | if(ruleStr){ 101 | ruleStr = ruleStr.split(';'); 102 | for(let i = 0; i{ 112 | let e = estr.match(/(\w+)@(\w+)@([^]+)/); 113 | if(!e){ 114 | e = estr.match(/action@\{([^]+)\}/); 115 | if(!e){ 116 | console.warn('不能识别的技能效果码!', item); 117 | return 118 | }; 119 | funcStr += `${e[1]};`; 120 | }else{ 121 | let [, target,func,parm] = e; 122 | funcStr += `action.change('${target + '_' + func}',${parm.replace('@',',')});`; 123 | } 124 | }) 125 | 126 | new Function('action','attacker','enemy', funcStr ).apply(father, [action, attacker, enemy]); 127 | 128 | }) 129 | 130 | let attackerLogName = attacker.$type === 'Hero' ? '你' : attacker.$showName; 131 | let enemyLogName = enemy.$type === 'Hero' ? '你' : enemy.$showName; 132 | let skillLogName = `${skill.name}`; 133 | 134 | attackerLogName = `${attackerLogName}`; 135 | enemyLogName = `${enemyLogName}`; 136 | 137 | 138 | let msg = `${attackerLogName} 释放了 ${skillLogName}`; 139 | 140 | if(!(action.state.isMiss && !action.state.isMust)){ 141 | if(action.attacker_changeHp){ 142 | let word = action.attacker_changeHp > 0 ? '恢复': '减少'; 143 | let v = `${Math.abs(action.attacker_changeHp)}` 144 | msg +=`,${attackerLogName}${word}了${v}点HP`; 145 | } 146 | 147 | if(action.enemy_changeHp){ 148 | let v = `${-action.enemy_changeHp}` 149 | msg +=`,对${enemyLogName}造成${v}点伤害`; 150 | } 151 | 152 | if(action.state.isCritical){ 153 | msg = `[暴击]` + msg; 154 | } 155 | } 156 | 157 | store.commit('FightEventLogPush',msg); 158 | 159 | // 判断闪避 160 | if(action.state.isMiss && !action.state.isMust){ 161 | store.commit('FightEventLogPush',`[Miss]${enemyLogName} 闪避了 ${attackerLogName} 的攻击!`); 162 | return ; 163 | } 164 | 165 | new GameAudio({ 166 | key : 'fight-attack' 167 | }); 168 | 169 | // 获取双方变更行动对象; 170 | var actionList = { 171 | attacker: {}, 172 | enemy: {} 173 | }; 174 | 175 | for(var item in action){ 176 | item = item.match(/^([\w]+)_([\w]+)$/); 177 | if(item){ 178 | actionList[item[1]][item[2]] = action[item[0]]; 179 | } 180 | } 181 | 182 | // ======== 处理 ======== 183 | 184 | // attacker 185 | for(var item in actionList.attacker){ 186 | if(attacker[item]){ 187 | attacker[item](actionList.attacker[item]); 188 | }else{ 189 | console.warn('战斗中发现未知事件:' , item , actionList.attacker[item]); 190 | } 191 | } 192 | 193 | // enemy 194 | for(var item in actionList.enemy){ 195 | if(attacker[item]){ 196 | enemy[item] && enemy[item](actionList.enemy[item]); 197 | }else{ 198 | console.warn('战斗中发现未知事件:' , item , actionList.enemy[item]); 199 | } 200 | } 201 | 202 | // 全局冷却 1秒; 203 | _.each(attacker.$skills, skill => { 204 | if(skill.coolTime < 300){ 205 | coolTimeEvent.call(skill, 1000) 206 | } 207 | }) 208 | 209 | // 返回是否存活; 目前无任何实际用途; 210 | return attacker.$alive && enemy.$alive; 211 | 212 | } 213 | 214 | export default Fight; -------------------------------------------------------------------------------- /src/js/hero/update-attribute.js: -------------------------------------------------------------------------------- 1 | export default function updateAttribute(){ 2 | 3 | // 记录当前血量,魔量百分比; 4 | let hp_per = Math.min(this.$hp / (this.$r.$maxHp || this.$maxHp), 1); 5 | let mp_per = Math.min(this.$mp / (this.$r.$maxMp || this.$maxMp), 1); 6 | 7 | let promote = { 8 | // 基础值 基础百分 高级值 高级百分 9 | // ((默认 + 基础值) * (1 + 基础百分) + 高级值) * (1 + 高级百分) 10 | $maxHp : [0,0,0,0], // 血量最大值 11 | $maxMp : [0,0,0,0], // 魔法最大值 12 | $atk : [0,0,0,0], // 攻击 13 | $def : [0,0,0,0], // 防御 14 | $str : [0,0,0,0], // 力量 15 | $dex : [0,0,0,0], // 敏捷 16 | $con : [0,0,0,0], // 体质 17 | $int : [0,0,0,0], // 智力 18 | $critical : [0,0,0,0], // 暴击几率 90 => 暴击率90% 19 | $dodge : [0,0,0,0], // 闪避几率 90 => 闪避率90% 20 | $coolTimePer : [0,0,0,0], // 冷却时间减少 10 => 冷却时间减少10% 21 | $critiDmg : [0,0,0,0], // 暴击伤害倍数 1.5 => 1.5倍 22 | $dmgDown : [0,0], // 伤害减少 [5,10], 免伤 5 + 10% 23 | } 24 | 25 | let data = (this.$equipments || []).concat(this.$status || []); 26 | 27 | let action = { 28 | $default : function(key,v){ 29 | this.$r[key] = Math.floor(((this[key] + v[0]) * (1 + v[1] / 100) + v[2]) * (1 + v[3] / 100)); 30 | }, 31 | $dmgDown : function(key,v){ 32 | this.$r[key] = v; 33 | } 34 | } 35 | 36 | // 计算基础属性 37 | for(let item of data){ 38 | 39 | if(!item){ 40 | continue; 41 | } 42 | 43 | let opt = [item.equip, item.powerUp, item.intPowerUp]; 44 | 45 | for(let obj of opt){ 46 | if(!obj){ 47 | continue; 48 | } 49 | for(let key in obj){ 50 | let v = obj[key]; 51 | 52 | if(!promote[key]){ 53 | continue; 54 | } 55 | 56 | let index = 0,up = v; 57 | 58 | if(v instanceof Array){ 59 | index = v[1]; 60 | up = v[0]; 61 | } 62 | 63 | promote[key][index] += up; 64 | } 65 | } 66 | 67 | }; 68 | 69 | for(let key in promote){ 70 | action[action[key] ? key : '$default'].apply(this, [key, promote[key]]); 71 | } 72 | 73 | // 计算属性对属性的影响; 74 | 75 | action = [ 76 | function $str(){ 77 | this.$r.$atk += (this.$r.$str * (this.$r.$atk * 0.004)); 78 | }, 79 | function $int(){ 80 | this.$r.$maxMp += (this.$r.$int * (this.$r.$maxMp * 0.004)); 81 | }, 82 | function $con(){ 83 | this.$r.$maxHp += (this.$r.$con * (this.$r.$maxHp * 0.004)); 84 | }, 85 | function $dex(){ 86 | this.$r.$critical += (this.$r.$critical * 0.005) 87 | this.$r.$dodge += (this.$r.$critical * 0.001) 88 | } 89 | ] 90 | 91 | for(let act of action){ 92 | act.call(this); 93 | } 94 | 95 | this.$hp = Math.floor(hp_per * this.$r.$maxHp) || 0; 96 | this.$mp = Math.floor(mp_per * this.$r.$maxMp) || 0; 97 | 98 | } -------------------------------------------------------------------------------- /src/js/intensify.js: -------------------------------------------------------------------------------- 1 | import store from '../store'; 2 | import { GetRange, GetRandom } from './public-random-range'; 3 | import GameAudio from './audio' 4 | 5 | let audio = function(){ 6 | let data = { 7 | // success : require('static/audio/intensify-success.ogg'), 8 | // fail : require('static/audio/intensify-fail.ogg'), 9 | // zero : require('static/audio/intensify-zero.ogg'), 10 | // broken : require('static/audio/intensify-broken.ogg') 11 | } 12 | return function(key){ 13 | new GameAudio({ 14 | src : data[key] 15 | }); 16 | } 17 | }(); 18 | 19 | const excuteSuccess = { 20 | type : function(){ 21 | if(!this.equip){ 22 | return { 23 | success: false, 24 | msg : '此物品无法被强化!' 25 | }; 26 | } 27 | }, 28 | good : function(hero){ 29 | let grade = [1, 1.1, 1.2, 1.5, 2], // 品级系数; 30 | base = 100, 31 | // 计算花费 32 | cost = (() => { 33 | let g = grade[this.grade || 0]; 34 | let l = (this.level || 1) * base; 35 | let i = this.intensify || 0; 36 | return (l + l * i) * g; 37 | })(); 38 | if(!hero.cost(cost)){ 39 | return { 40 | success: false, 41 | msg: '金币不足!' 42 | } 43 | } 44 | }, 45 | } 46 | 47 | const isSuccess = function(item){ 48 | let probability = [1, 1, 1, 0.95, 0.9, 0.8, 0.75, 0.62, 0.54, 0.41, 0.33, 0.28, 0.2, 0.17, 0.13, 0.1, 0.04, 0.03, 0.01, 0.01] 49 | let intensify = item.intensify || 0; 50 | return GetRandom(probability[intensify]); 51 | } 52 | 53 | const failureHandle = function(item){ 54 | let intensify = item.intensify || 0; 55 | if(intensify < 3){ 56 | // noop 57 | audio('fail'); 58 | }else if(intensify < 8){ 59 | // 退级 1-3之间; 60 | audio('fail'); 61 | intensify -= GetRange([1,2]); 62 | item.intensify = Math.min(intensify, 3); 63 | }else if(intensify < 10){ 64 | item.intensify = 0; 65 | audio('zero'); 66 | }else{ 67 | // 摧毁 68 | audio('broken'); 69 | store.commit('ChangeIntensifyItem', null); 70 | } 71 | return item; 72 | } 73 | 74 | const excutePowerUp = function(item){ 75 | let excute = { 76 | weapon : function(item){ 77 | // 强化(武器) 78 | // '白','绿','蓝','紫','橙装' 79 | let coeffA = [8, 8, 10, 11, 12, 13][item.grade || 0]; 80 | let coeffB = [0.4, 0.7, 1, 1.25, 1.4][item.grade || 0]; 81 | let coefficient = [1, 2, 3, 4, 6, 8, 10, 12, 14, 17, 33, 50, 67, 108, 150, 192, 267, 360, 417, 500][item.intensify - 1]; 82 | let positionCoeff = [1, 1, 1, 1, 1, 1, 1, 1, 1][item.equipType]; 83 | let level = item.level || 1; 84 | item.intPowerUp = { 85 | $atk : Math.floor([level + coeffA/ 8 ] * coefficient * coeffB * positionCoeff) 86 | } 87 | // 伤害计算公式: [level + coeffA/ 8 ] * coefficient * coeffB * positionCoeff 88 | }, 89 | armor : function(item){ 90 | // 强化(防具) 91 | let coeffA = [1, 1.75, 2.5, 3.25, 3.75][item.grade || 0]; 92 | let coefficient = [1, 2, 3, 5, 7, 9, 12, 14, 16, 20, 40, 60, 80, 130, 180, 230, 320, 410, 500][item.intensify - 1] 93 | let positionCoeff = [1, 1, 1, 1, 1, 1, 1, 1, 1][item.equipType]; 94 | let base = 0.04 / 100; 95 | item.intPowerUp = { 96 | $dmgDown : [+(base * coefficient * coeffA * positionCoeff).toFixed(2), 1] 97 | } 98 | } 99 | }; 100 | return item.equipType === 0 ? excute.weapon(item) : excute.armor(item); 101 | } 102 | 103 | const Intensify = function(item){ 104 | 105 | let result = { 106 | success: true, 107 | msg : '', 108 | } 109 | 110 | // 计算是否符合强化要求; 111 | let validate = { 112 | type : [], 113 | good : [store.state.HeroStore.hero], 114 | }; 115 | 116 | for(let key in validate){ 117 | let re = excuteSuccess[key].apply(item, validate[key]); 118 | if(re){ 119 | audio('fail'); 120 | return re; 121 | } 122 | } 123 | 124 | // 计算是否强化成功 125 | if(!isSuccess(item)){ 126 | failureHandle(item); 127 | Object.assign(result, { 128 | success: false, 129 | msg : "强化失败!" 130 | }) 131 | }else{ 132 | audio('success'); 133 | item.intensify ? (item.intensify++) : (item.intensify = 1); 134 | Object.assign(result, { 135 | success: true, 136 | msg : "强化成功!" 137 | }) 138 | } 139 | 140 | // 重新计算强化数据 141 | excutePowerUp(item); 142 | 143 | return result; 144 | } 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | export default Intensify; -------------------------------------------------------------------------------- /src/js/map-hero-move.js: -------------------------------------------------------------------------------- 1 | import CONSTANT from '../data/constant'; 2 | import { MapDialog, MapFight } from '../js/event-class'; 3 | 4 | const HeroMoveEvent = function(map, $VueScope){ 5 | let key_up = 38, 6 | key_down = 40, 7 | key_left = 37, 8 | key_right = 39, 9 | move_delay = 300, 10 | can_move = true, 11 | block_type = CONSTANT.MAP_BLOCK_TYPE; 12 | 13 | this.start = function(){ 14 | this.keyUpFunc = event => { 15 | if(!event.keyCode){ 16 | return ; 17 | } 18 | this.autoMoveTimer && clearInterval(this.autoMoveTimer); 19 | this.move(event.keyCode); 20 | }; 21 | document.addEventListener('keyup', this.keyUpFunc); 22 | }; 23 | 24 | this.stop = function(){ 25 | document.removeEventListener('keyup', this.keyUpFunc) 26 | clearInterval(this.autoMoveTimer); 27 | }; 28 | 29 | this.move = function(direction){ 30 | if(!can_move){ 31 | return ; 32 | } 33 | 34 | can_move = false; 35 | 36 | setTimeout(()=>{ 37 | can_move = true; 38 | },move_delay) 39 | 40 | let next_block, 41 | x = map.hero.x, 42 | y = map.hero.y 43 | 44 | switch(direction){ 45 | case key_up : 46 | x--; 47 | break; 48 | case key_down : 49 | x++; 50 | break; 51 | case key_left : 52 | y--; 53 | break; 54 | case key_right : 55 | y++; 56 | break; 57 | } 58 | 59 | try{ 60 | next_block = map.$data.mapData[x][y]; 61 | }catch(e){ 62 | /*Pass*/ 63 | } 64 | 65 | if(!next_block || next_block.block_type != block_type['ROAD']){ 66 | return false; 67 | } 68 | 69 | // 将当前格子设置为 Road; 70 | map.hero.block_type = block_type['ROAD']; 71 | // 将目标格子标识为 Hero; 72 | next_block.block_type = block_type['HERO']; 73 | 74 | // 更新视图; 75 | $VueScope.$forceUpdate(); 76 | 77 | let {event_type, event} = next_block.event || {}; 78 | 79 | // 战斗事件不应该被保留; 80 | if(event_type === 'Monster'){ 81 | delete next_block.event; 82 | } 83 | 84 | // 设置新的英雄位置; 85 | map.hero = next_block; 86 | 87 | //判断 初始化 执行事件 88 | switch(event_type){ 89 | case 'Monster': 90 | MapFight(event) 91 | break; 92 | case 'MapDialog': 93 | MapDialog(event, () => this.start()) 94 | break; 95 | } 96 | 97 | } 98 | 99 | this.autoMoveTimer = null; 100 | 101 | this.autoMove = function(path){ 102 | this.autoMoveTimer && clearInterval(this.autoMoveTimer); 103 | let _path = _.cloneDeep(path); 104 | this.autoMoveTimer = setInterval(()=>{ 105 | 106 | let next = _path.splice(0,1)[0], 107 | x = map.hero.x, 108 | y = map.hero.y, 109 | direction; 110 | 111 | if(!next){ 112 | this.autoMoveTimer && clearInterval(this.autoMoveTimer); 113 | return ; 114 | } 115 | 116 | switch(true){ 117 | case next.x < x: 118 | direction = key_up 119 | break; 120 | case next.x > x: 121 | direction = key_down 122 | break; 123 | case next.y < y: 124 | direction = key_left 125 | break; 126 | case next.y > y: 127 | direction = key_right 128 | break; 129 | } 130 | 131 | this.move(direction); 132 | 133 | if(_path.length < 1){ 134 | clearInterval(this.autoMoveTimer); 135 | }; 136 | 137 | }, move_delay + 100); 138 | } 139 | 140 | this.start(); 141 | 142 | return this; 143 | } 144 | 145 | export default HeroMoveEvent; -------------------------------------------------------------------------------- /src/js/map-init.js: -------------------------------------------------------------------------------- 1 | import DungeonCreater from '../js/dungeon-creater'; 2 | import CONSTANT from '../data/constant'; 3 | import PGET from '../js/public-static-get'; 4 | 5 | let blockType = CONSTANT.MAP_BLOCK_TYPE; 6 | 7 | const Map = function(o){ 8 | 9 | let opt = this.$opt = _.cloneDeep(o), 10 | getBlankRandomBlock = size => { 11 | return _.sampleSize( 12 | _.filter( _.flattenDeep(this.$data.mapData), item => { 13 | if (item.block_type != blockType['ROAD'] || item.event){ 14 | return false; 15 | } 16 | return true; 17 | }), 18 | size 19 | ); 20 | } 21 | 22 | this.$data = opt.mapData || new DungeonCreater(opt.mapInitOption || {}); 23 | 24 | this.hero = (() => { 25 | let hero = getBlankRandomBlock(1)[0]; 26 | hero.block_type = blockType['HERO']; 27 | return hero; 28 | })(); 29 | 30 | 31 | for(let id of (opt.monsterList || []).concat(opt.eventList || [])){ 32 | 33 | let event = PGET(id); 34 | 35 | if(!id){ 36 | console.warn('No Such Map Event Object:', id, opt); 37 | continue; 38 | } 39 | 40 | for(let block of getBlankRandomBlock(opt.rule[String(id)])){ 41 | block.event = { 42 | event_type: event.type || event.$type || 'unknown', 43 | event, 44 | }; 45 | } 46 | 47 | } 48 | 49 | 50 | // monsterList: [ 51 | // 5000001, 5000002 52 | // ], 53 | // eventList: [ 54 | // 7000001 55 | // ], // 特殊事件触发点; 56 | // rule: { // 生成规则; 57 | // "5000001" : 4, 58 | // "5000003" : 5, 59 | // "7000001" : 1, 60 | // } 61 | } 62 | 63 | 64 | export default Map; 65 | -------------------------------------------------------------------------------- /src/js/monster-ai.js: -------------------------------------------------------------------------------- 1 | import Fight from './fight'; 2 | import SkillAvailable from './skill-available'; 3 | 4 | const MonsterAI = function(hero, monster){ 5 | 6 | this.monster = monster; 7 | 8 | this.hero = hero; 9 | 10 | this.startTimer = null; 11 | 12 | this.start = function(){ 13 | let delay = 1100; 14 | this.startTimer = setInterval(() => { 15 | let skill = this.monster.EventAI && this.monster.EventAI() || this.normalAi(); 16 | skill && Fight(this.monster, this.hero, skill) && console.info('AI自动释放:' , skill.name); 17 | },delay) 18 | }; 19 | 20 | this.end = function(){ 21 | this.startTimer && clearInterval(this.startTimer); 22 | }; 23 | 24 | this.normalAi = function (){ 25 | let skills = this.monster.$skills; 26 | for(let i = 0; i< skills.length; i++){ 27 | if(SkillAvailable(skills[i], this.monster, this.hero)){ 28 | return skills[i]; 29 | } 30 | }; 31 | return false; 32 | } 33 | 34 | this.start(); 35 | 36 | } 37 | 38 | export default MonsterAI; -------------------------------------------------------------------------------- /src/js/public-function.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | window._ = { 4 | cloneDeep : require('lodash/cloneDeep'), 5 | each : require('lodash/each'), 6 | concat : require('lodash/concat'), 7 | map : require('lodash/map'), 8 | range : require('lodash/range'), 9 | sortBy : require('lodash/sortBy'), 10 | sampleSize : require('lodash/sampleSize'), 11 | filter : require('lodash/filter'), 12 | find : require('lodash/find'), 13 | findIndex : require('lodash/findIndex'), 14 | flattenDeep : require('lodash/flattenDeep'), 15 | random : require('lodash/random'), 16 | every : require('lodash/every'), 17 | assign : Object.assign, 18 | shuffle : require('lodash/shuffle'), 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/js/public-random-range.js: -------------------------------------------------------------------------------- 1 | const GetRange = function(opt){ 2 | if (({}).toString.call(opt) === "[object Array]"){ 3 | return _.random.apply(null,opt); 4 | } 5 | return opt || 0; 6 | } 7 | 8 | const GetRandom = function(opt){ 9 | return Math.random() < opt; 10 | } 11 | 12 | export { 13 | GetRange, 14 | GetRandom 15 | }; -------------------------------------------------------------------------------- /src/js/public-static-get.js: -------------------------------------------------------------------------------- 1 | import { DIALOG_DATA } from '../data/event-data' 2 | import { EXP_TABLE } from '../data/hero-data' 3 | import { ITEM_TABLE } from '../data/item-data' 4 | import MAP_TABLE from '../data/map-data' 5 | import SKILL_TABLE from '../data/skill-data' 6 | import STATE_TABLE from '../data/state-data' 7 | import MONSTER_DATA from '../data/monster-data' 8 | import BLUEPRINT_TABLE from '../data/blueprint-data' 9 | 10 | const Data = { 11 | '0' : EXP_TABLE, 12 | '1' : SKILL_TABLE, 13 | '2' : STATE_TABLE, 14 | '3' : ITEM_TABLE, 15 | '4' : BLUEPRINT_TABLE, 16 | '5' : MONSTER_DATA, 17 | // '6' : MISSION_TABLE, 18 | '7' : DIALOG_DATA, 19 | '8' : MAP_TABLE, 20 | // '9' : ACHIEVEMENT_TABLE, 21 | } 22 | 23 | const PublicStaticGet = function(key){ 24 | let Head = key.toString()[0]; 25 | let record = Data[Head]; 26 | let result = _.cloneDeep(_.find(record, { 27 | id: key 28 | })) || key; 29 | 30 | if(result.defaultTime){ 31 | result.coolTime = 0; 32 | result.currentCoolTime = result.defaultTime; 33 | } 34 | 35 | return result; 36 | } 37 | 38 | export default PublicStaticGet; -------------------------------------------------------------------------------- /src/js/release-skill.js: -------------------------------------------------------------------------------- 1 | import coolTimeEvent from './cool-time-event' 2 | import Fight from '../js/fight' 3 | import store from '../store' 4 | 5 | const SkillEvent = function(hero, monster){ 6 | this.hero = hero; 7 | this.monster = monster; 8 | this.keyDownFunc = null; 9 | this.start(); 10 | } 11 | 12 | SkillEvent.prototype = { 13 | start (){ 14 | this.end(); 15 | this.keyDownFunc = event => { 16 | let index; 17 | // 技能监听; 18 | let skillHotKey = store.state.ConfigStore.skillHotKey; 19 | index = skillHotKey.indexOf(event.keyCode); 20 | if(~index){ 21 | let skill = this.hero.$skills[index]; 22 | skill && Fight(this.hero, this.monster, skill); 23 | } 24 | // 物品监听; 25 | let itemHotKey = store.state.ConfigStore.itemHotKey; 26 | index = itemHotKey.indexOf(event.keyCode); 27 | if(~index){ 28 | console.log('Use: $package',index); 29 | this.hero.use({ 30 | position: '$package', 31 | index 32 | }); 33 | } 34 | }; 35 | document.addEventListener('keydown', this.keyDownFunc); 36 | }, 37 | end (){ 38 | document.removeEventListener('keydown',this.keyDownFunc); 39 | } 40 | } 41 | 42 | export default SkillEvent; 43 | 44 | -------------------------------------------------------------------------------- /src/js/save-load.js: -------------------------------------------------------------------------------- 1 | import Unit from '../js/unit-class' 2 | import MAP_TABLE from '../data/map-data' 3 | import MONSTER_DATA from '../data/monster-data'; 4 | import SKILL_TABLE from "../data/skill-data"; 5 | import STATE_TABLE from "../data/state-data"; 6 | import {ITEM_TABLE} from '../data/item-data'; 7 | import PGET from '../js/public-static-get'; 8 | import store from '../store'; 9 | import Vue from 'vue'; 10 | 11 | let SaveKey = 'ENDLESS_QWHIDHIASDYQWD'; 12 | 13 | const SaveGame = function(){ 14 | // hero 15 | // config 16 | // store.state.HeroStore.hero = hero; 17 | 18 | let hero = _.cloneDeep(store.state.HeroStore.hero); 19 | let config = _.cloneDeep(store.state.ConfigStore); 20 | 21 | hero.__proto__ = {}; 22 | 23 | delete hero.$r; 24 | delete hero.$flashCopy; 25 | delete hero.$attrGrow; 26 | 27 | // 处理特殊物品,技能,状态; 28 | // for(let key of ['$status','$skills','$package','$houseList','$equipments']){ 29 | // if(!hero[key]) continue; 30 | // hero[key] = hero[key].map(v => { 31 | // return pack(v); 32 | // }) 33 | // } 34 | 35 | let saveString = JSON.stringify({ 36 | hero, 37 | config, 38 | }); 39 | 40 | localStorage.setItem(SaveKey,saveString); 41 | 42 | } 43 | 44 | function pack(v){ 45 | 46 | if(!v){ 47 | return 0; 48 | } 49 | if(v.equip && v.intensify){ 50 | return { 51 | id: v.id, 52 | intensify : v.intensify, 53 | intPowerUp : v.intPowerUp, 54 | }; 55 | } 56 | return v.id 57 | } 58 | 59 | function unpack(v){ 60 | if(!v){ 61 | return undefined; 62 | } 63 | if(typeof v === 'number'){ 64 | return PGET(v) 65 | }else{ 66 | return Object.assign(PGET(v.id), v); 67 | } 68 | } 69 | 70 | const LoadGame = function(){ 71 | 72 | let saveString = localStorage.getItem(SaveKey); 73 | 74 | // console.log(saveString); 75 | saveString = ''; 76 | 77 | if(saveString){ 78 | let {hero, config} = _.cloneDeep(JSON.parse(saveString)); 79 | 80 | for(let key of ['$status','$skills','$package','$houseList','$equipments']){ 81 | if(!hero[key]) continue; 82 | hero[key] = hero[key].map(v => { 83 | // console.log(unpack(v)); 84 | return unpack(v); 85 | }) 86 | } 87 | 88 | Vue.set(store.state.HeroStore,'hero', new Unit(hero)); 89 | store.commit('ConfigUpdate', config); 90 | }else{ 91 | // 生成默认测试英雄 92 | let test1 = PGET(3000001); 93 | test1.num = 10; 94 | let test2 = PGET(3000002); 95 | test2.num = 5; 96 | 97 | var hero = new Unit( 98 | { 99 | $showName : 'Bastarder', 100 | $type : 'Hero', 101 | $skills : [PGET(1000001),PGET(1000002),PGET(1000003),PGET(1000004)], 102 | $status : [], 103 | $resource : { 104 | gold: 999999999, 105 | gem : 899999 106 | }, 107 | $blueprint: [ 108 | 4000001, 4000002 109 | ], 110 | $package : [test1,test2].concat(_.cloneDeep(ITEM_TABLE).slice(2)).concat(new Array(26)) 111 | } 112 | ); 113 | Vue.set(store.state.HeroStore,'hero', hero); 114 | } 115 | 116 | SaveGame(); 117 | 118 | } 119 | 120 | 121 | export { 122 | SaveGame, 123 | LoadGame 124 | } -------------------------------------------------------------------------------- /src/js/skill-available.js: -------------------------------------------------------------------------------- 1 | import store from '../store'; 2 | 3 | const SkillAvailable = function(skill, attacker, enemy){ 4 | 5 | /** 6 | * 全局条件 7 | */ 8 | 9 | // 冷却; 10 | if(skill.coolTime > 0){ 11 | store.commit('FightEventLogPush',`技能冷却中!`); 12 | return false; 13 | } 14 | 15 | // 人物状态,是否可行动 等等 暂留; 16 | 17 | /** 18 | * 附加条件 19 | */ 20 | 21 | if(!skill.restrict){ 22 | return true; 23 | } 24 | 25 | let _length = _.filter(skill.restrict, function(rule){ 26 | 27 | // 如果是高级规则,则直接判断返回值; 28 | if(typeof rule === "function"){ 29 | return rule(skill, attacker, enemy); 30 | } 31 | 32 | // 简易规则,计算结果; 33 | let _rule = rule.replace(/\s/g,'').match(/^\[([\w$]+)\]\{([\w$]+)\}([\w\W]+)\{([\w\W]+)\}/); 34 | 35 | if(!_rule){ 36 | return false; 37 | } 38 | 39 | let [ , type, key, condition, targetValue] = _rule; 40 | 41 | var originObject = ''; 42 | 43 | // 判断对象; 44 | switch(type){ 45 | case 'attacker': 46 | originObject = attacker; 47 | break; 48 | case 'skill' : 49 | originObject = skill; 50 | break; 51 | case 'enemy': 52 | originObject = enemy; 53 | break; 54 | } 55 | 56 | originObject = originObject[key]; 57 | 58 | switch(condition){ 59 | case 'has': 60 | targetValue = targetValue.split(','); 61 | for(let i = 0; i' : 76 | return originObject > parseInt(targetValue); 77 | case '<' : 78 | return originObject < parseInt(targetValue); 79 | case '>=' : 80 | return originObject >= parseInt(targetValue); 81 | case '<=' : 82 | return originObject <= parseInt(targetValue); 83 | } 84 | // "[attacker]{$mp} > {100}", 85 | // "[attacker]{$hp} < {300}", 86 | // "[skill]{coolTime} > {0}", 87 | // "[attacker]{$skills} has {}", 88 | // "[attacker]{$status} nothas {}", 89 | }).length; 90 | 91 | return skill.restrict.length === _length; 92 | } 93 | 94 | export default SkillAvailable; -------------------------------------------------------------------------------- /src/js/unit-class.js: -------------------------------------------------------------------------------- 1 | import { EXP_TABLE } from "../data/hero-data"; 2 | import SKILL_TABLE from "../data/skill-data"; 3 | import STATE_TABLE from "../data/state-data"; 4 | import { ITEM_TABLE } from "../data/item-data"; 5 | import store from '../store'; 6 | import CreateMonster from './create-monster'; 7 | import CreateHero from './create-hero'; 8 | import { GetRange, GetRandom } from './public-random-range'; 9 | import PGET from '../js/public-static-get'; 10 | import coolTimeEvent from './cool-time-event'; 11 | import Vue from 'vue'; 12 | 13 | // prototype 14 | import updateAttribute from './hero/update-attribute' 15 | 16 | 17 | 18 | function Unit(obj = {}){ 19 | this.id = 1000 + (Math.random()* 1000).toFixed(0) // 编号 20 | this.$type = 'Hero'; // 单位类型 21 | this.$showName = 'unit' // 展示名称 22 | this.$level = 0; // 等级 23 | this.$alive = true; 24 | this.$status = []; 25 | this.$skills = []; // 技能列表 26 | 27 | // 属性方面 28 | this.$hp = 600; // 当前生命值 29 | this.$mp = 400; // 当前魔法值 30 | this.$maxHp = 600; // 生命最大值 31 | this.$maxMp = 400; // 魔法最大值 32 | this.$atk = 10; // 攻击 33 | this.$def = 0; // 防御 34 | this.$str = 10; // 力量 35 | this.$dex = 10; // 敏捷 36 | this.$con = 10; // 体质 37 | this.$int = 10; // 智力 38 | this.$critical = 3; // 暴击几率 39 | this.$dodge = 5; // 闪避几率 40 | this.$coolTimePer = 0; // 冷却缩短 41 | this.$critiDmg = 1.5; // 暴击伤害倍数; 42 | this.$dmgDown = [0,0]; // 伤害减免; 43 | this.$r = {}; 44 | 45 | switch(obj.$type){ 46 | case 'Monster' : 47 | CreateMonster.call(this, obj); 48 | break; 49 | case 'Hero' : 50 | CreateHero.call(this, obj); 51 | break; 52 | } 53 | 54 | this.updateAttribute(); 55 | } 56 | 57 | Unit.prototype = { 58 | startFight, 59 | endFight, 60 | updateAttribute, 61 | changeMp, 62 | changeHp, 63 | startFight, 64 | endFight, 65 | updateAttribute, 66 | changeMp, 67 | changeHp, 68 | getExp, 69 | getList, 70 | removeList, 71 | changeState, 72 | reset, 73 | dieDrop, 74 | itemSort, 75 | getItem, 76 | equip, 77 | demount, 78 | isEnoughInPackage, 79 | use, 80 | cost, 81 | costItem, 82 | } 83 | 84 | /* --------------- */ 85 | 86 | function startFight(){ 87 | 88 | for(let key in this.$flashCopy){ 89 | this.$flashCopy[key] = _.cloneDeep(this[key]); 90 | } 91 | 92 | for(let state of (this.$status || [])){ 93 | state.stateEvent && state.stateEvent(this); 94 | } 95 | 96 | this.updateAttribute(); 97 | } 98 | 99 | function endFight(){ 100 | 101 | for(let state of (this.$status || [])){ 102 | state.stateEventTimer && clearInterval(state.stateEventTimer); 103 | } 104 | 105 | Object.assign(this, this.$flashCopy); 106 | 107 | !this.$alive && this.reset(); 108 | 109 | this.updateAttribute(); 110 | } 111 | 112 | function changeMp(value) { 113 | 114 | let v = parseInt(value); 115 | 116 | if(!v){ 117 | return false; 118 | } 119 | 120 | let mp = Math.min(this.$mp + v, this.$r.$maxMp); 121 | 122 | this.$mp = mp < 0 ? 0 : mp; 123 | 124 | return true; 125 | } 126 | 127 | function changeHp(value) { 128 | 129 | let v = parseInt(value); 130 | 131 | if(!v || this.$hp <= 0){ 132 | return false; 133 | } 134 | 135 | let hp = Math.min(this.$hp + value, this.$r.$maxHp); // 更新Hp的值; 136 | 137 | if(hp <= 0){ 138 | this.$hp = 0; 139 | this.$alive = false; 140 | }else{ 141 | this.$hp = hp; 142 | } 143 | 144 | return true; 145 | } 146 | 147 | function getExp(value) { 148 | 149 | let ExpTable = EXP_TABLE, 150 | v = parseInt(value); 151 | 152 | if(!v || !this.$maxExp){ 153 | return false; 154 | } 155 | 156 | let exp = this.$exp + v; 157 | 158 | if(exp >= this.$maxExp){ 159 | 160 | this.$exp = parseInt(exp % this.$maxExp); 161 | 162 | this.$level += parseInt(exp / this.$maxExp); 163 | 164 | this.$maxExp = ExpTable[this.$level - 1] || 0; 165 | 166 | for(var key in this.$attrGrow){ 167 | 168 | this[key] += this.$attrGrow[key]; 169 | 170 | } 171 | 172 | this.updateAttribute(); 173 | 174 | }else{ 175 | 176 | this.$exp = exp; 177 | 178 | } 179 | 180 | return true; 181 | 182 | } 183 | 184 | function getList(key, opt, isIndex){ 185 | let list = this[key]; 186 | 187 | if(!list){ 188 | return false; 189 | } 190 | 191 | let condition; 192 | 193 | if(typeof opt === 'number'){ 194 | condition = i => i && (i.id === opt); 195 | } 196 | 197 | if(typeof opt === 'object'){ 198 | condition = i => i && (i.id === opt.id); 199 | } 200 | 201 | if(typeof opt === 'function'){ 202 | condition = opt; 203 | } 204 | 205 | if(!condition){ 206 | return false; 207 | } 208 | 209 | return isIndex ? list.findIndex(condition) : list.find(condition); 210 | } 211 | 212 | function removeList(key, opt) { 213 | let index, 214 | list = this[key]; 215 | 216 | if(typeof opt === 'object'){ 217 | index = list.findIndex(i => i.id === opt.id); 218 | }else{ 219 | index = opt; 220 | } 221 | 222 | if(!~index || !list){ 223 | return false; 224 | } 225 | 226 | if(key === '$status'){ 227 | list[index].stateEventTimer && clearInterval(list[index].stateEventTimer); 228 | } 229 | 230 | let remove = list.splice(index,1); 231 | 232 | this.updateAttribute(); 233 | 234 | return remove; 235 | } 236 | 237 | function changeState(changeList) { 238 | 239 | if(!changeList || !changeList.length){ 240 | return ; 241 | } 242 | 243 | changeList.forEach(item => { 244 | let id = item.id; 245 | let state = this.getList('$status', {id}); 246 | switch (item.state){ 247 | case 'ADD': 248 | 249 | if(state){ 250 | break; 251 | } 252 | 253 | state = Object.assign( PGET(id), item.action || {} ); 254 | 255 | state.stateEvent && state.stateEvent(this); 256 | 257 | this.$status.push(state); 258 | 259 | break; 260 | case 'REMOVE': 261 | 262 | this.removeList('$status', {id}); 263 | 264 | break; 265 | case 'CHANGE': 266 | 267 | if(state){ 268 | 269 | state = Object.assign( state, item.action ); 270 | 271 | } 272 | 273 | break; 274 | } 275 | }) 276 | 277 | this.updateAttribute(); 278 | 279 | } 280 | 281 | function reset(){ 282 | 283 | this.$alive = true; 284 | 285 | this.$hp = this.$maxHp; 286 | 287 | this.$mp = this.$mp; 288 | 289 | } 290 | 291 | function dieDrop(){ 292 | // 数据范例 293 | // $dropList : [ 294 | // // 物品ID, 数量范围, 几率 295 | // [3000001, [3, 10], 1], 296 | // [3000002, 5, 0.3], 297 | // [3000003, 1, 0.5], 298 | // ['gold',[1, 80], 1], 299 | // ['exp', 1, 1] 300 | // ] 301 | let data = this.$dropList || [], 302 | consequence = []; 303 | 304 | data.forEach(item => { 305 | 306 | let num = GetRange(item[1]), 307 | odds = GetRandom(item[2]); 308 | 309 | if(!num || !odds || !item[0] || !item[1] || !item[2]){ 310 | return ; 311 | } 312 | 313 | consequence.push([ 314 | item[0], num 315 | ]) 316 | 317 | }); 318 | 319 | return consequence; 320 | } 321 | 322 | function itemSort(type){ 323 | let list = this[type]; 324 | 325 | if(!list || !list.length){ 326 | return false; 327 | } 328 | 329 | list.sort( 330 | (a, b) => ((a.id || Infinity) - (b.id || Infinity)) 331 | ); 332 | 333 | store.commit('UPDATE'); 334 | 335 | return true; 336 | } 337 | 338 | function getItem(data, force, type = '$package'){ 339 | 340 | let container = force ? this[type] : _.cloneDeep(this[type]), 341 | surplus = []; 342 | 343 | if(!container || !data || !data.length){ 344 | console.warn('[unit.getItem Error]:', data, force, t, this); 345 | return surplus; 346 | } 347 | 348 | data.forEach(i => { 349 | let item, 350 | num = i[1]; 351 | 352 | if(typeof i[0] === 'object'){ 353 | item = i[0]; 354 | }else{ 355 | item = PGET(i[0]); 356 | } 357 | 358 | switch(item){ 359 | case "gold": 360 | force && (this.$resource.gold += num); 361 | return; 362 | case 'gem': 363 | force && (this.$resource.gem += num); 364 | return; 365 | case 'exp': 366 | force && (this.getExp(num)); 367 | return; 368 | } 369 | 370 | let itemInPackage = this.getList(type, { id: item.id }), 371 | nextBlankPlace = container.findIndex(item => !item); 372 | 373 | item.pile && (item.num = num); 374 | 375 | // 可堆叠 376 | if(itemInPackage && item.pile){ 377 | // 存在 378 | itemInPackage.num = (itemInPackage.num || 0) + num; 379 | }else{ 380 | // 不存在 381 | if(~nextBlankPlace){ 382 | // 有空位 383 | container[nextBlankPlace] = item; 384 | }else{ 385 | // 没空位 386 | surplus.push(i); 387 | } 388 | } 389 | 390 | }) 391 | 392 | store.commit('UPDATE'); 393 | 394 | return surplus; 395 | } 396 | 397 | function equip(item, index, type = '$package'){ 398 | //0武器 1护肩 2鞋子 3腰带 4上衣 5绑腿 6戒指 7护腕 8项链 399 | let container = this[type], 400 | $equipments = this.$equipments, 401 | equip = item.equip; 402 | 403 | if(!equip || !container || !$equipments){ 404 | return false; 405 | } 406 | 407 | // 删除包裹中的装备, 如果已有装备, 卸载装备; 408 | container[index] = undefined; 409 | 410 | if($equipments[item.equipType]){ 411 | this.demount(item.equipType, index, type); 412 | } 413 | 414 | $equipments[item.equipType] = item; 415 | 416 | // 更新附加状态; 417 | let status = equip.$status || []; 418 | 419 | status.forEach(id => { 420 | this.changeState({ 421 | id, 422 | state : 'ADD', 423 | action : { notShow: true } 424 | }) 425 | }) 426 | 427 | this.updateAttribute(); 428 | 429 | store.commit('UPDATE'); 430 | 431 | return true; 432 | } 433 | 434 | function demount(equipType, index, type = '$package'){ 435 | let container = this[type], 436 | equipItem = this.$equipments[equipType], 437 | $equipments = this.$equipments; 438 | 439 | $equipments[equipType] = 0; 440 | 441 | if(!equipItem){ 442 | return ; 443 | } 444 | 445 | index = index || container.findIndex(i => !i); 446 | 447 | if(~index){ 448 | container[index] = equipItem; 449 | }else{ 450 | $equipments[equipType] = equipItem; 451 | return false; 452 | } 453 | 454 | for(let key in (equipItem.equip || {})){ 455 | if(~['$skills', '$status'].indexOf(key)){ 456 | equipItem.equip[key].forEach( id => this.removeList(key, {id}) ); 457 | } 458 | } 459 | 460 | this.updateAttribute(); 461 | 462 | store.commit('UPDATE'); 463 | 464 | } 465 | 466 | function costItem(list, type = '$package'){ 467 | let container = this[type]; 468 | 469 | for(let opt of list){ 470 | let index = this.getList(type, opt[0], true); 471 | 472 | let num = (()=>{ 473 | if(typeof opt[1] === 'function'){ 474 | return opt[1].call(this); 475 | } 476 | return opt[1]; 477 | })(); 478 | 479 | container[index].num -= num; 480 | 481 | if(!container[index].num){ 482 | container[index] = undefined; 483 | } 484 | } 485 | 486 | } 487 | 488 | function isEnoughInPackage(list, type = '$package'){ 489 | // [ 490 | // [200001, 5] 491 | // [function(item){ return item.id === 200001}, function(item){ return 10; }, '测试物品1','10'] 492 | // ] 493 | let container = this[type]; 494 | 495 | if(!container || !list || !list.length ){ 496 | return false; 497 | } 498 | 499 | for(let opt of list){ 500 | let item; 501 | 502 | item = this.getList(type, opt[0]); 503 | 504 | let num = (()=>{ 505 | if(typeof opt[1] === 'function'){ 506 | return opt[1].call(this); 507 | } 508 | return opt[1]; 509 | })(); 510 | 511 | if(!item || (item.num || 1) < num){ 512 | return false; 513 | } 514 | } 515 | 516 | return true; 517 | } 518 | 519 | function use(option){ 520 | let container = this[option.position || '$package'], 521 | item = container[option.index]; 522 | 523 | // 暂时不支持战斗时更换装备,所以只判断消耗品; 524 | if(!item || !item.use){ 525 | return false; 526 | } 527 | 528 | let use = typeof item.use === 'function' ? item.use.call(this) : item.use; 529 | 530 | if(use.defaultTime){ 531 | if(!item.hasOwnProperty('coolTime')){ 532 | Vue.set(item, 'defaultTime', use.defaultTime); 533 | Vue.set(item, 'coolTime', 0); 534 | Vue.set(item, 'currentCoolTime', use.defaultTime); 535 | } 536 | }else{ 537 | Vue.set(item, 'coolTime', 0); 538 | } 539 | 540 | if(item.coolTime > 0){ 541 | console.log('冷却中!'); 542 | return false; 543 | } 544 | 545 | if(!item.num){ 546 | console.log('物品数量不足!'); 547 | return false; 548 | } 549 | 550 | for(let restrict of (use.restrict || [])){ 551 | if(!restrict.call(this)){ 552 | return false; 553 | } 554 | } 555 | 556 | for(let effect of (use.effect || [])){ 557 | effect.call(this); 558 | } 559 | 560 | if(--item.num < 1){ 561 | container[option.index] = undefined; 562 | }else{ 563 | coolTimeEvent.call(item); 564 | } 565 | 566 | } 567 | 568 | function cost(num, type = "gold"){ 569 | 570 | let costNum = Math.ceil(Number(num)) || 0, 571 | container = this.$resource; 572 | 573 | if(!costNum || costNum > container[type]){ 574 | return false; 575 | }else{ 576 | container[type] -= costNum; 577 | return true; 578 | } 579 | 580 | } 581 | 582 | export default Unit; -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from "vue-router"; 3 | 4 | import GameHome from './components/game-home.vue' 5 | import GameFight from './components/game-fight.vue' 6 | import GameMap from './components/game-map.vue' 7 | import GameMapActive from './components/game-map-active.vue' 8 | import GameLogin from './components/game-login.vue' 9 | import GameConfig from './components/game-config.vue' 10 | import GameSmithy from './components/game-smithy.vue' 11 | import GameShop from './components/game-shop.vue' 12 | 13 | Vue.use(VueRouter) 14 | 15 | const routes = [ 16 | { 17 | path: '/login', 18 | name: 'login', 19 | component: GameLogin 20 | },{ 21 | path: '/', 22 | name: 'home', 23 | component: GameHome 24 | },{ 25 | path: '/fight', 26 | name: 'fight', 27 | component: GameFight 28 | },{ 29 | path: '/map', 30 | name: 'map', 31 | component: GameMap 32 | },{ 33 | path: '/map-active', 34 | name: 'mapActive', 35 | component: GameMapActive 36 | },{ 37 | path: '/config', 38 | name: 'config', 39 | component: GameConfig 40 | },{ 41 | path: '/smithy', 42 | name: 'smithy', 43 | component: GameSmithy 44 | },{ 45 | path: '/shop', 46 | name: 'shop', 47 | component: GameShop 48 | } 49 | ] 50 | 51 | const router = new VueRouter({ 52 | routes , 53 | }) 54 | 55 | router.beforeEach((to, from, next) => { 56 | // if(from.path === to.path){ 57 | // location.reload(); 58 | // } 59 | next(); 60 | }); 61 | 62 | export default router; 63 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import FightStore from './store/fight-store'; 5 | import HeroStore from './store/hero-store'; 6 | import MapStore from './store/map-store'; 7 | import ConfigStore from './store/config-store'; 8 | import SmithyStore from './store/smithy-store'; 9 | 10 | Vue.use(Vuex) 11 | 12 | const store = new Vuex.Store({ 13 | modules: { 14 | FightStore, 15 | HeroStore, 16 | MapStore, 17 | ConfigStore, 18 | SmithyStore, 19 | }, 20 | state: { 21 | UPDATE: 1, 22 | }, 23 | mutations: { 24 | UPDATE(state){ 25 | Vue.set(state,'UPDATE', Math.random() + Date.now()); 26 | }, 27 | } 28 | }) 29 | 30 | export default store; 31 | -------------------------------------------------------------------------------- /src/store/config-store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const state = { 4 | volumeEffectMusic : 0.5, 5 | volumeBackgroundMusic : 0.5, 6 | skillAvailableFightTip : true, 7 | skillHotKey : [81, 87, 69, 82], 8 | itemHotKey : [49, 50, 51, 52], 9 | } 10 | 11 | const mutations = { 12 | ConfigUpdate (state, option){ 13 | 14 | // 修改音量 15 | for(let el of window.AudioList){ 16 | el.volume = option.volumeBackgroundMusic; 17 | } 18 | 19 | Object.assign(state, option); 20 | 21 | } 22 | } 23 | 24 | export default { 25 | state, 26 | mutations 27 | } -------------------------------------------------------------------------------- /src/store/fight-store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Unit from '../js/unit-class' 3 | import MONSTER_DATA from '../data/monster-data'; 4 | 5 | const state = { 6 | eventLogs : [], 7 | monsters : [ 8 | new Unit(_.cloneDeep(MONSTER_DATA[0])), 9 | new Unit(_.cloneDeep(MONSTER_DATA[1])) 10 | ] 11 | } 12 | 13 | // mutations 14 | const mutations = { 15 | // FightEvent; 16 | FightEventLogPush(state, log){ 17 | if(!log){ 18 | return ; 19 | } 20 | let now = new Date().toString().slice(16, 16 + 8); 21 | state.eventLogs.push( 22 | `[${now}] ${log}.` 23 | ); 24 | }, 25 | FightEventLogClear(state){ 26 | Vue.set(state, 'eventLogs', []); 27 | } 28 | } 29 | 30 | export default { 31 | state, 32 | mutations 33 | } -------------------------------------------------------------------------------- /src/store/hero-store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | const state = { 5 | hero : null, 6 | } 7 | 8 | const mutations = { 9 | 10 | } 11 | 12 | export default { 13 | state, 14 | mutations 15 | } -------------------------------------------------------------------------------- /src/store/map-store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import MAP_TABLE from '../data/map-data' 3 | 4 | const state = { 5 | mapList: _.cloneDeep(MAP_TABLE), 6 | map: null, 7 | } 8 | 9 | const mutations = { 10 | 11 | } 12 | 13 | export default { 14 | state, 15 | mutations 16 | } -------------------------------------------------------------------------------- /src/store/smithy-store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const state = { 4 | intensifyItem : 0, 5 | } 6 | 7 | const mutations = { 8 | ChangeIntensifyItem (state, newItem){ 9 | Vue.set(state, 'intensifyItem', newItem); 10 | } 11 | } 12 | 13 | export default { 14 | state, 15 | mutations 16 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/app.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: 'dist/', 9 | filename: 'build.js' 10 | }, 11 | resolveLoader: { 12 | root: path.join(__dirname, 'node_modules'), 13 | }, 14 | resolve: { 15 | alias:{ 16 | vue: 'vue/dist/vue.js', 17 | static : '../assets' 18 | } 19 | }, 20 | module: { 21 | loaders: [ 22 | { 23 | test: /\.css$/, 24 | loader: 'style!css' 25 | }, 26 | { 27 | test: /\.vue$/, 28 | loader: 'vue' 29 | }, 30 | { 31 | test: /\.js$/, 32 | loader: 'babel', 33 | exclude: /node_modules/ 34 | }, 35 | { 36 | test: /\.(png|jpg|gif|svg|wav|ogg|wma|otf)$/, 37 | loader: 'file', 38 | query: { 39 | name: '[name].[ext]?[hash]' 40 | } 41 | } 42 | ] 43 | }, 44 | devServer: { 45 | historyApiFallback: false, 46 | noInfo: true 47 | }, 48 | devtool: '#eval-source-map' 49 | } 50 | 51 | if (process.env.NODE_ENV === 'production') { 52 | module.exports.devtool = '#source-map' 53 | module.exports.plugins = (module.exports.plugins || []).concat([ 54 | new webpack.DefinePlugin({ 55 | 'process.env': { 56 | NODE_ENV: '"production"' 57 | } 58 | }), 59 | new webpack.optimize.UglifyJsPlugin({ 60 | compress: { 61 | warnings: false 62 | } 63 | }) 64 | ]) 65 | } 66 | --------------------------------------------------------------------------------