merge from master

This commit is contained in:
echo 2018-01-09 21:13:05 +08:00
commit 4db9583e41
43 changed files with 3250 additions and 1302 deletions

View File

@ -21,6 +21,7 @@ HTML5 canvas制作的魔塔样板支持全平台游戏
│ └─ *.png # 对应的某个具体的图片素材
├── /libs/ # JS源代码目录
│ ├─ /floors/ # 剧本文件,记录了每个地图的数据和事件
│ ├─ /thirdparty/ # 游戏所用到的第三方库文件
│ ├─ core.js # 系统核心文件
│ ├─ data.js # 记录了勇士的初始化信息、各个全局变量和全局Flag值
│ ├─ enemys.js # 记录了怪物的信息,包括怪物的数据和特殊属性、伤害计算公式、临界值计算等。
@ -44,7 +45,21 @@ HTML5 canvas制作的魔塔样板支持全平台游戏
## 更新说明
### 2017.12.21
### 2018.1.1 V1.3
* [x] 支持全键盘操作。
* [x] 支持将某个图片作为某层的背景素材。
* [x] 便捷PS工具支持更改图片色相。
* [x] 支持经验升级(进阶/境界塔)。
* [x] 打败怪物可以进行加点(加点塔)。
* [x] 增加阻击、N连击等属性在怪物手册有属性显示。
* [x] 支持九宫格领域和大范围领域。
* [x] 增加负伤。
* [x] 支持各种BGM的播放。
* [x] 支持不同层使用不同的地面素材支持多个Autotile同时存在。
* [x] 许多细节进行了优化一些已知的Bug进行了修复。
### 2017.12.21 V1.2
* [x] 新增本地HTTP服务器。
* [x] 新增:可视化地图编辑工具。
@ -57,7 +72,7 @@ HTML5 canvas制作的魔塔样板支持全平台游戏
* [x] 新增更多的默认素材现在对于大多数地图风格无需P图直接替换即可。
* [x] 添加部分自定义事件部分细节优化一些已知的Bug进行了修复。
### 2017.12.16
### 2017.12.16 V1.1
* [x] 新增:战斗过程显示,可以在设置中关闭
* [x] 新增勇士支持48*32大图的行走图
@ -67,7 +82,7 @@ HTML5 canvas制作的魔塔样板支持全平台游戏
* [x] 增添Web的Markdown文档移除原本的doc和pdf文档。
* [x] 修复若干Bug。
### 2017.12.9
### 2017.12.9 V1.0
* [x] 发布初版HTML5魔塔样板

View File

@ -24,73 +24,23 @@ body{
width: 435px;
height: 630px;
}
.nav {
width: 100%;
height: 40px;
margin-top: 5px;
box-sizing: border-box;
}
.nav a{
display: block;
float: left;
line-height: 30px;
font-size: 14px;
text-align: center;
color: #424242;
margin: 0 10px;
padding: 0 3px ;
list-style: none;
box-sizing: border-box;
cursor: pointer;
/* border-bottom: 3px solid #009688; */
}
.is-selected {
border-bottom: 3px solid #009688;
}
#left .tabs{
position: absolute;
top: 50px;
left: 0;
width: 435px;
height: 535px;
}
#left .card{
width: 100%;
height: 535px;
position: absolute;
left: 0;
top: 10px;
border-top: 1px solid #ccc;
}
#arrEditor{
z-index: 8;
}
.card .lineTitle {
position: absolute;
top: -12px;
left: 20px;
padding: 0 5px;
font-size: 14px;
color: #03A9F4;
background-color: #F5F5F5;
z-index: 10;
}
#editArea{
position: absolute;
width: 100%;
height: 70%;
left: 0;
top: 0;
/* padding: 10px 5px; */
box-sizing: border-box;
margin-top: 15px;
}
#pout{
position: absolute;
left: 22px;
top: 37px;
display: block;
width: 410px;
height: 300px;
height: 100%;
box-sizing: border-box;
margin-left: 22px;
margin-top: 23px;
line-height: 20px;
font-size: 12.3px;
font-family: 'Lucida Console', Monaco, monospace;
@ -118,26 +68,6 @@ body{
margin-right: 20px;
margin-top: 5px;
}
.files {
width: 100%;
height: 120px;
/* padding: 10px; */
margin-top: 15px;
}
.files .input{
display: block;
max-width: 150px;
height: 20px;
padding: 6px 12px;
font-size: 14px;
margin-top: 10px;
color: #555;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
}
/* ==================================== */
#mid{
position: absolute;
left: 448px;
@ -175,13 +105,32 @@ body{
font-size: 15px;
line-height: 14px;
}
.files {
width: 50%;
height: 120px;
/* padding: 10px; */
margin-top: 15px;
}
.input{
display: block;
max-width: 150px;
height: 20px;
padding: 6px 12px;
font-size: 14px;
margin-top: 10px;
color: #555;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
}
#bgSelect{
width: 50%;
height: 100px;
margin-top: 10px;
}
#bgSelect span{
display: block;
/* display: block; */
font-size: 14px;
line-height: 30px;
}
@ -198,7 +147,7 @@ body{
min-width: 50px;
padding: 0 5px;
display: inline-block;
margin-top: 5px;
font-size: 14px;
font-weight: 400;
/* text-transform: uppercase; */
@ -218,8 +167,6 @@ body{
background-color: #009688;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
}
/* =========================== */
#right{
position: absolute;
left: 900px;

View File

@ -187,7 +187,7 @@ var tip = new Vue({
isClearBlock: false,
geneMapSuccess: false,
timer: null,
msgs: [ //分别编号1,2,3,4,5,6,7,8;奇数警告,偶数成功
msgs: [ //分别编号1,2,3,4,5,6,7,8,9,10;奇数警告,偶数成功
"当前未选择任何图块,请先在右边选择要画的图块!",
"生成地图成功!可点击复制按钮复制地图数组到剪切板",
"生成失败! 地图中有未定义的图块,建议先用其他有效图块覆盖或点击清除地图!",
@ -195,7 +195,9 @@ var tip = new Vue({
"复制失败!",
"复制成功!可直接粘贴到楼层文件的地图数组中。",
"复制失败!当前还没有数据",
"修改成功!可点击复制按钮复制地图数组到剪切板"
"修改成功!可点击复制按钮复制地图数组到剪切板",
"选择背景图片失败!文件名格式错误或图片不存在!",
"更新背景图片成功!",
],
mapMsg: '',
whichShow: 0,
@ -255,60 +257,31 @@ var bgSelect = new Vue({
el: '#bgSelect',
data: {
bgs: {},
selectedBg: 'ground'
selectedBg: 'ground',
imgname: ''
},
watch:{
selectedBg: function(){
editor.bgY = this.bgs.indexOf(this.selectedBg);
editor.drawMapBg();
}
}
})
var editFile4map = new Vue({
el: '#editFile4map',
data: {
selected: 'untitle',
options:[],
filelist: [],
},
watch: {
filelist: function(val){
if(val){
var oval = val.length ? JSON.parse(JSON.stringify(val)) : [];
// for(var i=0; i<oval.length; i++)
// this.$set("options", i, oval[i]);
}
},
}
})
var modSeletNav = new Vue({
el: '#modSeletNav',
data: {
currTabnum: 1,
tabs:[
{num: 1, title: "地图数组"},
{num: 2, title: "对象属性"},
{num: 3, title: "选点事件"},
{num: 4, title: "楼层事件"},
]
},
watch: {
currTabnum(val){
this.$emit('on-change', val)
this.$emit('input', val)
}
},
methods: {
selectTab(num){
this.currTabnum = num;
tabSelection.showTab = num;
updatebg: function(){
tip.whichShow = 0;
var regx = /\S+\.(png|bmp|jpg|jpeg|gif)$/i;
if(regx.test(this.imgname)){
var url = 'images/'+this.imgname;
editor.loadImg(url).then(function(img){
editor.drawMapBg(img);
tip.whichShow = 10;
}).catch(function(err){
console.log(err);
tip.whichShow = 9;
});
}else{
tip.whichShow = 9;
}
}
}
})
var tabSelection = new Vue({
el: '#tabSelection',
data: {
showTab: 1,
},
})

View File

@ -1,35 +1,73 @@
# 附录:API列表
?> 上次更新时间:* {docsify-updated} *
所有系统支持的API都列在了这里。所有可能被用到的API都在前面用\*标记。
可以在chrome浏览器的控制台中`ctrl+shift+I`找到Console中直接进行调用以查看效果。
!> **`main.js`游戏入口所有其他JS文件都是被此文件加载。**
``` js
main.init // 初始化
main.loaderJs // 动态加载所有核心JS文件
main,loaderFloors // 动态加载所有楼层(剧本)
main.loadMod // 加载某一个JS文件
main.loadFloor // 加载某一个楼层
main.setMainTipsText // 加载过程提示
window.onresize // 窗口大小变化时
main.dom.body.onkeydown // 在界面上按下某按键时
main.dom.body.onkeydown // 在界面上放开某按键时
main.dom.body.onselectstart // 开始选择时
main.dom.data.onmousedown // 鼠标按下时
main.dom.data.onmousemove // 鼠标移动时
main.dom.data.onmouseup // 鼠标放开时
main.dom.data.onmousewheel // 鼠标滑轮滚动时
main.dom.data.ontouchstart // 手指在触摸屏开始触摸时
main.dom.data.ontouchmove // 手指在触摸屏上移动时
main.dom.data.ontouchend // 手指离开触摸屏时
main.statusBar.image.book.onclick // 点击状态栏中的怪物手册时
main.statusBar.image.fly.onclick // 点击状态栏中的楼层传送器时
main.statusBar.image.toolbox.onclick // 点击状态栏中的工具箱时
main.statusBar.image.shop.onclick // 点击状态栏中的快捷商店时
main.statusBar.image.save.onclick // 点击状态栏中的存档按钮时
main.statusBar.image.load.onclick // 点击状态栏中的读档按钮时
main.statusBar.image.settings.onclick // 点击状态栏中的系统菜单时
main.dom.playGame.onclick // 点击“开始游戏”时
main.dom.loadGame.onclick // 点击“载入游戏”时
main.dom.aboutGame.onclick // 点击“关于本塔”时
main.dom.easyLevel.onclick // 点击“简单难度”时
main.dom.normalLevel.onclick // 点击“普通难度”时
main.dom.hardLevel.onclick // 点击“困难难度”时
```
!> **`core.js`:系统核心文件。所有核心逻辑处理都在此文件完成。**
``` js
* core.status.floorId // 获得当前层floorId
* core.status.thisMap // 获得当前层的地图信息
* core.status.maps // 获得所有楼层的地图信息
* core.floors // 获得所有楼层的剧本
// ------ 初始化部分 ------
core.init // 初始化
core.showStartAnimate // 显示开始界面
core.hideStartAnimate // 隐藏开始界面
core.showStartAnimate // 显示游戏开始界面
core.hideStartAnimate // 隐藏游戏开始界面
core.setStartProgressVal // 设置加载进度条进度
core.setStartLoadTipText // 设置加载进度条提示文字
core.loader // 加载图片和音频
core.loadImage // 加载图片
core.loadSound // 加载音频
core.loadSoundItem // 加载某一个音频
core.loadMusic // 加载音频
core.isPlaying // 游戏是否已经开始
core.clearStatus // 清除游戏状态和数据
core.resetStatus // 重置游戏状态和初始数据
core.startGame // 具体开始游戏
core.startGame // 开始游戏
* core.restart // 重新开始游戏;此函数将回到标题页面
// ------ 键盘、鼠标事件 ------
core.onKeyDown // 按下某个键时
core.onKeyUp // 放开某个键时
core.pressKey // 按住某个键不动时
core.pressKey // 按住某个键时
core.keyDown // 根据按下键的code来执行一系列操作
core.keyUp // 根据放开键的code来执行一系列操作
core.ondown // 点击(触摸)事件按下时
@ -37,14 +75,14 @@ core.onmove // 当在触摸屏上滑动时
core.onup // 当点击(触摸)事件放开时
core.getClickLoc // 获得点击事件相对左上角的坐标0到12之间
core.onclick // 具体点击屏幕上(x,y)点时,执行的操作
core.onmousewheel // 滑动鼠标滚轮时的操作(楼层传送时可用滚轮切换楼层)
core.onmousewheel // 滑动鼠标滚轮时的操作
// ------ 自动寻路代码相关 ------
core.clearAutomaticRouteNode // 清除自动寻路路线
core.stopAutomaticRoute // 停止自动寻路操作
core.continueAutomaticRoute // 继续剩下的自动寻路操作
core.clearContinueAutomaticRoute // 清剩下的自动寻路列表
core.setAutomaticRoute // 设置一个自动寻路
core.clearContinueAutomaticRoute // 清剩下的自动寻路列表
core.setAutomaticRoute // 设置自动寻路路线
core.automaticRoute // 自动寻路算法,找寻最优路径
core.fillPosWithPoint // 显示离散的寻路点
core.clearStepPostfix // 清除已经寻路过的部分
@ -54,13 +92,15 @@ core.stopAutoHeroMove // 停止勇士的自动行走
core.setAutoHeroMove // 设置勇士的自动行走路线
core.autoHeroMove // 让勇士开始自动行走
core.setHeroMoveInterval // 设置行走的效果动画
core.setHeroMoveTriggerInterval // 设置勇士行走过程中对途经事件的触发检测
* core.turnHero(direction) // 设置勇士的方向转向如果指定了direction则会面向该方向否则执行一个转向操作。
core.setHeroMoveTriggerInterval // 设置勇士行走过程中对事件的触发检测
* core.turnHero(direction) // 设置勇士的方向(转向)
core.canMoveHero // 勇士能否前往某方向
core.moveHero // 让勇士开始移动
core.eventMoveHero // 使用事件让勇士移动。这个函数将不会触发任何事件。
core.moveOneStep // 每移动一格后执行的事件。中毒时在这里进行扣血判断。
core.waitHeroToStop(callback) // 停止勇士的一切行动等待勇士行动结束后再执行callback回调函数。
core.stopHero // 停止勇士的移动状态。
core.drawHero // 在hero层绘制勇士。
core.drawHero // 绘制勇士。
* core.setHeroLoc(name, value) // 设置勇士的位置。name为”direction”,”x”,”y”
* core.getHeroLoc(name) // 获得勇士的位置。
* core.nextX // 获得勇士面对位置的x坐标
@ -71,42 +111,48 @@ core.drawHero // 在hero层绘制勇士。
* core.battle(id, x, y, force, callback) // 进行战斗force表示是否强制战斗
core.afterBattle // 战斗完毕
core.trigger(x,y) // 触发x,y点的事件
* core.changeFloor(floorId, stair, heroLoc, time, callback) // 楼层切换floorId为目标楼层Idstair可指定为上/下楼梯time动画时间
core.mapChangeAnimate // 实际切换的动画效果
core.clearMap // 清除地图显示
* core.changeFloor(floorId, stair, heroLoc, time, callback) // 楼层切换floorId为目标楼层Idstair可指定为上/下楼梯time动画时间
core.mapChangeAnimate // 地图切换动画效果
core.clearMap // 清除地图
core.fillText // 在某个canvas上绘制一段文字
core.fillRect // 在某个canvas上绘制一个矩形
core.strokeRect // 在某个canvas上绘制一个矩形的边框
core.drawLine // 在某个canvas上绘制一条线
core.setFont // 设置某个canvas的文字字体
core.setLineWidth // 设置某个canvas的线宽度
core.saveCanvas // 保存某个canvas状态
core.loadCanvas // 读取某个canvas状态
core.loadCanvas // 加载某个canvas状态
core.setStrokeStyle // 设置某个canvas边框属性
core.setAlpha // 设置某个canvas的alpha值
core.setOpacity // 设置某个canvas的透明度
core.setFillStyle // 设置某个canvas的绘制属性如颜色等
* core.drawMap(mapId, callback) // 绘制某张地图。mapId为地图Id绘制完毕将执行callback回调函数。
core.drawAutotile // 绘制Autotile
* core.noPassExists(x,y) // 某个点是否不可通行
core.noPass // 某个点是否在区域内且不可通行
* core.npcExists(x,y) // 某个点是否存在NPC
* core.terrainExists(x,y) // 某个点是否存在指定的地形
* core.terrainExists(x,y) // 某个点是否存在指定的地形
* core.stairExists(x,y) // 某个点是否存在楼梯
* core.nearStair // 当前位置是否在楼梯边
* core.enemyExists(x,y) // 某个点是否存在怪物
* core.enemyExists(x,y) // 某个点是否存在(指定的)怪物
* core.getBlock(x, y, floorId, needEnable) // 获得某个点的block。floorId指定目标楼层needEnable如果为false则即使该点的事件处于禁用状态也将被返回否则只有事件启用的点才被返回
core.moveBlock // 显示移动某块的动画,达到{“type”:”move”}的效果
core.animateBlock // 显示/隐藏某个块时的动画效果
core.addBlock // 将某个块从禁用变成启用状态
core.showBlock // 将某个块从禁用变成启用状态
core.removeBlock // 将某个块从启用变成禁用状态
core.removeBlockById // 根据block的索引删除该块
core.removeBlockByIds // 一次性删除多个block
core.addGlobalAnimate // 添加一个全局动画
core.removeGlobalAnimate // 删除一个或所有全局动画
core.setGlobalAnimate // 设置全局动画的显示效果
core.syncGlobalAnimate // 同步所有的全局动画效果
core.setBoxAnimate // 显示UI层某个box的动画如怪物手册中怪物的动画
core.drawBoxAnimate // 绘制UI层的box动画
core.setFg // 色调渐变
* core.updateFg // 更新全地图的显伤
core.updateCheckBlock // 更新领域、夹击、阻击的伤害地图
core.checkBlock // 检查并执行领域、夹击、阻击事件
core.snipe // 阻击事件(动画效果)
core.setFg // 更改画面色调
* core.updateFg // 更新全地图显伤
* core.itemCount // 获得某个物品的个数
* core.hasItem // 是否存在某个物品
* core.setItem // 设置某个物品的个数
@ -114,23 +160,23 @@ core.setFg // 色调渐变
* core.useItem // 使用某个物品直接调用items.js中的useItem函数。
* core.canUseItem // 能否使用某个物品。直接调用items.js中的canUseItem函数。
* core.addItem // 增加某个物品的个数
* core.getItem // 获得某个物品时的事件
core.getNextItem // 获得面前的物品(轻按)
* core.getItem // 获得某个物品
* core.drawTip // 左上角绘制一段提示
* core.drawText // 地图中间绘制一段文字
// ------ 系统机制 ------
core.replaceText // 将文字中的${和}(表达式)进行替换
core.calValue // 计算表达式的值
core.splitText // 字符串自动换行的分割
core.doEffect // 执行一个表达式的effect操作
core.splitLines // 字符串自动换行的分割
core.unshift // 向某个数组前插入另一个数组或元素
core.setLocalStorage // 设置本地存储
core.getLocalStorage // 获得本地存储
core.removeLocalStorage // 移除本地存储
core.clone // 复制一个对象
core.clone // 深拷贝一个对象
core.formatDate // 格式化时间为字符串
core.setTwoDigits // 两位数显示
core.win // 获胜将直接调用events.js中的win函数
core.lose // 失败将直接调用events.js中的lose函数
core.debug // 进入Debug模式攻防血和钥匙都调成很高的数值
core.checkStatus // 判断当前能否进入某个事件
core.openBook // 点击怪物手册时的打开操作
@ -144,6 +190,7 @@ core.saveData // 存档到本地
core.loadData // 从本地读档
* core.setStatus // 设置勇士属性
* core.getStatus // 获得勇士属性
core.getLvName // 获得某个等级的名称
* core.setFlag // 设置某个自定义变量或flag
* core.getFlag // 获得某个自定义变量或flag
* core.hasFlag // 是否存在某个自定义变量或flag且值为true
@ -151,17 +198,16 @@ core.insertAction // 往当前事件列表之前插入一系列事件
* core.lockControl // 锁定状态栏,常常用于事件处理
* core.unlockControl // 解锁状态栏
* core.isset // 判断某对象是否不为undefined也不会null
* core.playSound // 播放音频
* core.playBgm // 播放背景音乐
core.changeSoundStatus // 切换声音状态
core.enableSound // 启用音效
core.disableSound // 禁用音效
* core.pauseBgm // 暂停背景音乐的播放
* core.resumeBgm // 恢复背景音乐的播放
* core.playSound // 播放音频
core.show // 动画显示某对象
core.hide // 动画使某对象消失
core.clearStatusBar // 清空状态栏
core.updateStatusBar // 更新状态栏
core.resize // 屏幕分辨率改变后重新自适应
core.resetSize // 屏幕分辨率改变后重新自适应
core.domRenderer // 渲染DOM
// ------ core.js 结束 ------
```
@ -171,58 +217,111 @@ core.resetSize // 屏幕分辨率改变后重新自适应
!> **`enemys.js` 定义了怪物信息。**
``` js
* core.enemys.getSpecialText // 获得特殊属性的文字
core.enemys.init // 初始化
* core.enemys.getEnemys // 获得一个或所有怪物数据
* core.enemys.hasSpecial // 判断是否含有某特殊属性
* core.enemys.getSpecialText // 获得所有特殊属性的名称
* core.enemys.getSpecialHint // 获得每个特殊属性的说明
* core.enemys.getDamage // 获得某个怪物的伤害
* core.enemys.getExtraDamage // 获得某个怪物的额外伤害(吸血)
* core.enemys.getCritical // 计算某个怪物的临界值
* core.enemys.getCriticalDamage // 计算某个怪物的临界减伤
* core.enemys.getDefDamage // 计算某个怪物的1防减伤
* core.enemys.calDamage // 实际的伤害计算公式
core.enemys.getCurrentEnemys // 获得当前层剩下的的怪物列表
* core.enemys.getExtraDamage // 获得某个怪物的额外伤害
* core.enemys.getCritical // 临界值计算
* core.enemys.getCriticalDamage // 临界减伤计算
* core.enemys.getDefDamage // 1防减伤计算
* core.enemys.calDamage // 具体的伤害计算公式
core.enemys.getCurrentEnemys // 获得当前层的怪物列表
```
!> **`events.js` 定义了各个事件的处理流程。**
``` js
* core.events.startGame // 开始游戏
* core.events.win // 获胜
* core.events.lose // 失败
core.events.checkBlock // 检查领域、夹击事件
core.events.afterChangeFloor // 楼层切换结束时的事件
core.events.init // 初始化
core.events.getEvents // 获得一个或所有系统事件类型
core.events.startGame // 游戏开始事件
* core.events.setInitData // 不同难度分别设置初始属性
* core.events.win // 游戏获胜事件
* core.events.lose // 游戏失败事件
core.events.afterChangeFloor // 转换楼层结束的事件
core.events.doEvents // 开始执行一系列自定义事件
core.events.doAction // 执行当前自定义事件列表中的下一个事件
core.events.insertAction // 往当前自定义事件列表前插入若干个事件
core.events.insertAction // 往当前事件列表之前添加一个或多个事件
core.events.openShop // 打开一个全局商店
core.events.disableQuickShop // 禁用一个快捷商店
core.events.disableQuickShop // 禁用一个全局商店
* core.events.canUseQuickShop // 当前能否使用快捷商店
* core.events.checkLvUp // 检查升级事件
* core.events.useItem // 尝试使用道具
core.events.addPoint // 加点事件
core.events.afterBattle // 战斗结束后触发的事件
core.events.afterOpenDoor // 开一个门后触发的事件
core.events.passNet // 经过一个路障
core.events.beforeSaveData // 即将存档前可以执行的操作
core.events.afterLoadData // 读档后,载入事件前可以执行的操作
core.events.changeLight // 改变亮灯(感叹号)的事件
* core.events.afterChangeLight // 改变亮灯之后,可以触发的事件
* core.events.afterUseBomb // 使用炸弹/圣锤后的事件
* core.events.beforeSaveData // 即将存档前可以执行的操作
* core.events.afterLoadData // 读档事件后,载入事件前,可以执行的操作
// ------ 界面上的点击事件 ------
core.events.clickAction // 自定义事件处理时,对用户点击的处理
core.events.clickBook // 怪物手册打开时,对用户点击的处理
core.events.clickFly // 楼层传送器打开时,对用户点击的处理
core.events.clickShop // 全局商店打开时,对用户点击的处理
core.events.clickQuickShop // 快捷商店选项打开时
core.events.clickToolbox // 工具栏打开时
core.events.clickSL // 存/读档界面打开时
core.events.clickSettings // 设置页面打开时
// ------ 点击事件和键盘事件的处理 ------
core.events.keyDownCtrl // 按下Ctrl键时快捷跳过对话
core.events.clickConfirmBox // 确认框界面时的点击操作
core.events.keyUpConfirmBox // 确认框界面时,放开某个键的操作
core.events.clickAction // 自定义事件时的点击操作
core.events.keyDownAction // 自定义事件时,按下某个键的操作
core.events.keyUpAction // 自定义事件时,放开某个键的操作
core.events.clickBook // 怪物手册界面的点击操作
core.events.keyDownBook // 怪物手册界面时,按下某个键的操作
core.events.keyUpBook // 怪物手册界面时,放开某个键的操作
core.events.clickBookDetail // 怪物手册属性显示界面时的点击操作
core.events.clickFly // 楼层传送器界面时的点击操作
core.events.keyDownFly // 楼层传送器界面时,按下某个键的操作
core.events.keyUpFly // 楼层传送器界面时,放开某个键的操作
core.events.clickShop // 商店界面时的点击操作
core.events.keyDownShop // 商店界面时,按下某个键的操作
core.events.keyUpShop // 商店界面时,放开某个键的操作
core.events.clickQuickShop // 快捷商店界面时的点击操作
core.events.keyDownQuickShop // 快捷商店界面时,按下某个键的操作
core.events.keyUpQuickShop // 快捷商店界面时,放开某个键的操作
core.events.clickToolbox // 工具栏界面时的点击操作
core.events.clickToolboxIndex // 选择工具栏界面中某个Index后的操作
core.events.keyDownToolbox // 工具栏界面时,按下某个键的操作
core.events.keyUpToolbox // 工具栏界面时,放开某个键的操作
core.events.clickSL // 存读档界面时的点击操作
core.events.keyDownSL // 存读档界面时,按下某个键的操作
core.events.keyUpSL // 存读档界面时,放开某个键的操作
core.events.clickSwitchs // 系统设置界面时的点击操作
core.events.keyDownSwitchs // 系统设置界面时,按下某个键的操作
core.events.keyUpSwitchs // 系统设置界面时,放开某个键的操作
core.events.clickSettings // 系统菜单栏界面时的点击事件
core.events.keyDownSettings // 系统菜单栏界面时,按下某个键的操作
core.events.keyUpSettings // 系统菜单栏界面时,放开某个键的操作
core.events.clickSyncSave // 同步存档界面时的点击操作
core.events.keyDownSyncSave // 同步存档界面时,按下某个键的操作
core.events.keyUpSyncSave // 同步存档界面时,放开某个键的操作
core.events.clickAbout // “关于”界面时的点击操作
```
!> `maps.js` 定义了地图,以及每个数字所代表的意义。
!> `icons.js` 定义了素材ID和它在图片上的索引的对应关系。
!> `items.js` 定义了每个道具的名称,以及使用效果。
``` js
core.items.init // 初始化
core.items.getItems // 获得所有道具
core.items.getItemEffect // “即捡即用类”道具的使用效果
core.items.getItemEffectTip // “即捡即用类”道具的文字提示
* core.items.useItem // 使用道具
* core.items.cauUseItem // 当前能否使用道具
```
!> `maps.js` 定义了数字-ID的对应关系。
``` js
core.maps.loadFloor // 加载某个楼层(从剧本或存档中)
core.maps.getBlock // 将数字替换成实际的内容
core.maps.getBlock // 数字和ID的对应关系
core.maps.addEvent // 向该楼层添加剧本的自定义事件
core.maps.addChangeFloor // 向该楼层添加剧本的楼层转换事件
core.maps.initMaps // 初始化所有地图
core.maps.save // 将当前地图重新变成数字,以便于存档
core.maps.load // 将存档中的地图信息重新读取出来
core.maps.getMapArray // 将当前地图重新变成二维数组形式
```
!> `ui.js` 定义了各种界面的绘制。
@ -232,16 +331,19 @@ core.ui.closePanel // 结束一切事件和绘制关闭UI窗口返回游
core.ui.drawTextBox // 绘制一个对话框
core.ui.drawChoices // 绘制一个选项界面
core.ui.drawConfirmBox // 绘制一个确认/取消的警告页面
core.ui.drawSwitchs // 绘制系统设置界面
core.ui.drawSettings // 绘制系统菜单栏
core.ui.drawQuickShop // 绘制快捷商店选择栏
core.ui.drawBattleAnimate // 绘制战斗过程
core.ui.drawWaiting // 绘制一个“请稍后”页
core.ui.drawSyncSave // 绘制存档同步选项
core.ui.drawBattleAnimate // 绘制战斗动画
core.ui.drawWaiting // 绘制等待界
core.ui.drawSyncSave // 绘制存档同步界面
core.ui.drawPagination // 绘制分页
core.ui.drawEnemyBook // 绘制怪物手册
core.ui.drawBookDetail // 绘制怪物属性的详细信息
core.ui.drawFly // 绘制楼层传送器
core.ui.drawToolbox // 绘制道具栏
core.ui.drawSLPanel // 绘制存档/读档界面
core.ui.drawThumbnail // 绘制一个缩略图
core.ui.drawAbout // 绘制“关于”界面
core.ui.drawHelp // 绘制帮助界面
```

View File

@ -1,5 +1,7 @@
# 元件说明
?> 上次更新时间:* {docsify-updated} *
在本章中,将对样板里的各个元件进行说明。各个元件主要包括道具、门、怪物、楼梯等等。
请打开样板0层 `sample0.js` 进行参照对比。
@ -30,7 +32,7 @@
## 怪物
本塔支持的怪物列表参见`enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。如不知道怪物素材长啥样的请打开`enemys.png`对比查看。
本塔支持的怪物列表参见`enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。如不知道怪物素材长啥样的请打开`enemys.png`对比查看。
如有自己的怪物素材需求请参见[自定义素材](personalization#自定义素材)的内容。
怪物可以有特殊属性,每个怪物可以有多个自定义属性。
@ -40,14 +42,15 @@
``` js
enemys.prototype.getSpecialText = function (enemyId) {
if (enemyId == undefined) return "";
var special = this.enemys[enemyId].special;
var enemy = this.enemys[enemyId];
var special = enemy.special;
var text = [];
if (this.hasSpecial(special, 1)) text.push("先攻");
if (this.hasSpecial(special, 2)) text.push("魔攻");
if (this.hasSpecial(special, 3)) text.push("坚固");
if (this.hasSpecial(special, 4)) text.push("2连击");
if (this.hasSpecial(special, 5)) text.push("3连击");
if (this.hasSpecial(special, 6)) text.push("4连击");
if (this.hasSpecial(special, 6)) text.push((enemy.n||4)+"连击");
if (this.hasSpecial(special, 7)) text.push("破甲");
if (this.hasSpecial(special, 8)) text.push("反击");
if (this.hasSpecial(special, 9)) text.push("净化");
@ -59,22 +62,36 @@ enemys.prototype.getSpecialText = function (enemyId) {
if (this.hasSpecial(special, 15)) text.push("领域");
if (this.hasSpecial(special, 16)) text.push("夹击");
if (this.hasSpecial(special, 17)) text.push("仇恨");
if (this.hasSpecial(special, 18)) text.push("阻击");
if (this.hasSpecial(special, 19)) text.push("自爆");
if (this.hasSpecial(special, 20)) text.push("无敌");
return text.join(" ");
}
```
如果需要双属性则采用100x+y的写法。例如 103 则视为同时拥有1和3的属性即先攻且坚固。同理1314为衰弱诅咒双属性。
多属性可采用数组的写法,比如`'special': [1,3]`视为同时拥有先攻和坚固属性;`'special': [5,10,14,18]`视为拥有3连击、魔防、诅咒、阻击四个属性。
如果需要三属性则采用10000x+100y+z的写法。例如71116视为同时拥有7,11和16的属性即破甲、吸血、夹击
本塔支持战斗动画,在`data.js`中存在三个全局选项:`canOpenBattleAnimate`, `showBattleAnimateConfirm`, `battleAnimate`
四个乃至更多的属性以此类推。
- `canOpenBattleAnimate`代表是否允许用户开启战斗动画。如果你添加了一些自定义属性且不想修改战斗界面的UI则可以将其关闭。
- `showBattleAnimateConfirm`代表是否在游戏开始时给用户提供开启动画的选项。对于一些偏向于萌新的塔,可以开启此项。
- `battleAnimate`代表是否默认开启战斗动画。此项会被用户存储的设置给覆盖。
- 如果`canOpenBattleAnimate`为false则后面两个也强制为false。
怪物的伤害计算在下面的`calDamage`函数中,如有自己需求的伤害计算公式请修改该函数的代码。
怪物可以负伤,在`data.js`的全局变量`enableNegativeDamage`中指定。
下面的`getSpecialHint`函数则给定了每个特殊属性的详细描述。这个描述将在怪物手册中看到。
**打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](event#加点事件)。**
如果`data.js`中的enableExperience为false即不启用经验的话怪物手册里将不显示怪物的经验值打败怪物也不获得任何经验。
拿到幸运金币后,打怪获得的金币将翻倍。
N连击怪物的special是6且我们可以为它定义n代表实际连击数。参见样板中剑王的写法。
![N连击](./img/nattack.png)
吸血怪需要在怪物后添加value代表吸血的比例。
![怪物吸血](./img/blood.png)
@ -89,15 +106,21 @@ enemys.prototype.getSpecialText = function (enemyId) {
领域怪需要在怪物后添加value代表领域伤害的数值。如果勇士生命值扣减到0则直接死亡触发lose事件。
![怪物领域](./img/domainenemy.png)
领域是十字伤害还是九宫格伤害由data.js中的全局变量`zoneSquare`设定。你也可以对该怪物自行进行设定。
请注意如果吸血和领域同时存在则value会冲突。**因此请勿将吸血和领域放置在同一个怪物身上。**
`range`选项可选代表该领域怪的范围不写则默认为1。
本塔暂不支持阻击、激光、自爆、退化等属性。
![怪物领域](./img/zone.png)
阻击怪同样需要在怪物后添加value代表领域伤害的数值。如果勇士生命值扣减到0则直接死亡触发lose事件。
!> 阻击怪后退的地点不能有任何事件存在,即使是已经被禁用的自定义事件!
请注意如果吸血、领域、阻击中任何两个同时存在则value会冲突。**因此请勿将吸血、领域或阻击放置在同一个怪物身上。**
如有额外需求,可参见[自定义怪物属性](personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。
### 路障、楼梯、传送门
## 路障,楼梯,传送门
血网的伤害数值、中毒后每步伤害数值、衰弱时暂时攻防下降的数值,都在 `data.js` 的values内定义。
@ -123,6 +146,68 @@ floorId指定的是目标楼层的唯一标识符ID
![楼层转换穿透](./img/floorset.png)
## 背景音乐
本塔支持BGM和SE的播放。
要播放音乐和音效你需要将对应的文件放在sounds目录下然后在main.js中进行定义
``` js
this.bgms = [ // 在此存放所有的bgm和文件名一致。第一项为默认播放项
// 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好
'058-Slow01.mid', 'bgm.mp3', 'qianjin.mid', 'star.mid'
];
this.sounds = [ // 在此存放所有的SE和文件名一致
// 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好
'floor.mp3', 'attack.ogg', 'door.ogg', 'item.ogg'
]
```
!> 音频名不能使用中文,不能带空格或特殊字符。
目前BGM支持主流的音乐格式如mp3, ogg, mid格式等。SE则不支持mid格式的播放。
!> mid格式是通过数学方法模拟出来的音乐效果质量可能会和实际效果差距较大。
定义完毕后,我们可以调用`playBgm`/`playSound`事件来播放对应的音乐/音效,有关事件的详细介绍请参见[事件](event)。
**另外,考虑到用户的流量问题,将遵循如下规则:**
- **如果用户当前使用的电脑则默认开启音乐效果并播放默认BGM**
- **如果用户当前使用的手机且处于Wifi状态则默认开启音乐效果并播放默认BGM**
- **其他情况,将默认关闭音乐效果,只有在用户在菜单栏中点击“音乐开关”后才会播放音乐**
!> iOS平台以及部分浏览器不支持获得当前网络状态此时即使在使用Wifi也必须要用户点击“音乐开关”才能播放音乐。
## 操作说明
本塔主要支持鼠标(触摸屏)操作和键盘操作。
鼠标(触摸屏)操作说明如下:
- **点状态栏中图标:** 进行对应的操作
- **点任意块:** 寻路并移动
- **点任意块并拖动:** 指定寻路路线
- **单击勇士:** 转向
- **双击勇士:** 轻按(仅在轻按开关打开时有效)
键盘操作快捷键如下:
- **[CTRL]** 跳过对话
- **[X]** 打开/关闭怪物手册
- **[G]** 打开/关闭楼层传送器
- **[S/D]** 打开/关闭存/读档页面
- **[K]** 打开/关闭快捷商店选择列表
- **[T]** 打开/关闭工具栏
- **[ESC]** 打开/关闭系统菜单
- **[H]** 打开帮助页面
- **[SPACE]** 轻按(仅在轻按开关打开时有效)
- **[1]** 快捷使用破墙镐
- **[2]** 快捷使用炸弹/圣锤
- **[3]** 快捷使用中心对称飞行器
以上快捷键也能在游戏菜单中的操作说明中看到。
&nbsp;
&nbsp;
@ -134,4 +219,3 @@ floorId指定的是目标楼层的唯一标识符ID
==========================================================================================
[继续阅读下一章:事件](event)

View File

@ -1,10 +1,12 @@
# 事件
?> 上次更新时间:* {docsify-updated} *
本章内将对样板所支持的事件进行介绍。
## 事件的机制
本塔所有的事件都是依靠触发`trigger`完成的。例如,勇士碰到一个门可以触发一个事件`openDoor`,勇士碰到怪物可以触发一个事件`battle`,勇士碰到一个(上面定义的)楼层传送点可以触发一个事件`changeFloor`,勇士穿过路障可以触发一个事件`passNet`包括勇士到达一个指定的`checkBlock`也可以触发一个检查领域、夹击的事件。上面说的这些事件都是系统本身自带的即类似于RMXP中的公共事件。
本塔所有的事件都是依靠触发`trigger`完成的。例如,勇士碰到一个门可以触发一个事件`openDoor`,勇士碰到怪物可以触发一个事件`battle`,勇士碰到一个(上面定义的)楼层传送点可以触发一个事件`changeFloor`,勇士穿过路障可以触发一个事件`passNet`等等。上面说的这些事件都是系统本身自带的即类似于RMXP中的公共事件。
上述这些默认的事件已经存在处理机制,不需要我们操心。我们真正所需要关心的,其实只是一个自定义的事件。
@ -29,6 +31,7 @@
"x,y": {
"trigger": "action", // 触发的trigger, action代表自定义事件
"enable": true, // 该事件初始状态下是否处于启用状态
"noPass": true, // 该点是否不可通行。true代表不可通行false代表可通行。
"data": [ // 实际执行的事件列表
// 事件1
// 事件2
@ -53,6 +56,7 @@
"x,y": {
// 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略
"enable": true, // 该事件初始状态下是否处于启用状态
"noPass": true, // 该点是否不可通行。true代表不可通行false代表可通行。
"data": [ // 实际执行的事件列表
// 事件1
// 事件2
@ -71,6 +75,28 @@
"x,y": {
// 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略
// 该事件初始状态下是启用状态,则可以省略"enable": true如果是禁用状态则必须加上"enable": false
"noPass": true, // 该点是否不可通行。true代表不可通行false代表可通行。
"data": [ // 实际执行的事件列表
// 事件1
// 事件2
// ...
]
}
}
```
`"noPass"`为该点是否可通行的标记。`true`代表该点不可通行,`false`代表该点可通行。
对于目前所有的素材,都存在默认的是否可通行状态。如果你在该点指定`noPass`,则原本的可通行状态会被覆盖。
因此,除非你想覆盖默认的可通行选项(比如将一个空地设为不可通行),否则该项可以忽略。
``` js
"events": { // 该楼的所有可能事件列表
"x,y": {
// 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略
// 该事件初始状态下是启用状态,则可以省略"enable": true如果是禁用状态则必须加上"enable": false
// 除非你想覆盖系统默认的可通行状态,否则"noPass"项可以忽略
"data": [ // 实际执行的事件列表
// 事件1
// 事件2
@ -86,7 +112,7 @@
``` js
"events": { // 该楼的所有可能事件列表
// 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号
// 如果大括号里只有"data"项(没有"action", "enable"或"noPass"),则可以省略到只剩下中括号
"x,y": [ // 实际执行的事件列表
// 事件1
// 事件2
@ -97,7 +123,7 @@
这种简写方式可以极大方便地造塔者进行造塔。
!> **请注意:如果该点初始的`enable`为`false`,或者该点本身有系统默认事件且需要覆盖(`trigger`),则必须采用上面那种大括号写的方式来定义。**
!> **请注意:如果该点初始的`enable`为`false`,或者该点本身有系统默认事件且需要覆盖(`trigger`或者你想覆盖该点的默认通行状态,则必须采用上面那种大括号写的方式来定义。**
&nbsp;
@ -397,7 +423,9 @@ revisit常常使用在一些商人之类的地方当用户购买物品后不
如果强制战斗失败则会立刻生命归0并死亡调用lose函数接下来的事件不会再被执行。
强制战斗没有指定loc的选项因此战斗后需要调用hide使怪物消失如果有必要。强制战斗不会触发任何afterBattle里的事件。
打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](#加点事件)。
强制战斗没有指定loc的选项因此战斗后需要调用hide使怪物消失如果有必要
### openDoor: 开门
@ -416,8 +444,6 @@ loc指定门的坐标floorId指定门所在的楼层ID。如果是当前层
如果loc所在的点既不是门也不是墙壁则忽略本事件。
openDoor不会触发任何afterOpenDoor里的事件。
### changeFloor: 楼层切换
在事件中也可以对楼层进行切换。一个比较典型的例子就是TSW中勇士在三楼的陷阱被扔到了二楼就是一个楼层切换事件。
@ -553,6 +579,26 @@ move完毕后移动的NPC/怪物一定会消失只不过可以通过immediate
不过值得注意的是,用这种方式移动勇士的过程中将无视一切地形,无视一切事件,中毒状态也不会扣血。
### playBgm: 播放背景音乐
使用playBgm可以播放一个背景音乐。
使用方法:`{"type": "playBgm", "name": "bgm.mp3"}`
值得注意的是额外添加进文件的背景音乐需在main.js中this.bgms里加载它。
目前支持mp3/ogg/wav/mid等多种格式的音乐播放。
有关BGM播放的详细说明参见[背景音乐](element#背景音乐)
### pauseBgm: 暂停背景音乐
使用`{"type": "pauseBgm"}`可以暂停背景音乐的播放。
### resumeBgm: 恢复背景音乐
使用`{"type": "resumeBgm"}`可以恢复背景音乐的播放。
### playSound: 播放音效
使用playSound可以立刻播放一个音效。
@ -561,10 +607,6 @@ move完毕后移动的NPC/怪物一定会消失只不过可以通过immediate
值得注意的是如果是额外添加进文件的音效则需在main.js中this.sounds里加载它。
!> 自定义添加的音效名请勿包含`-`,否则将无法正常使用!
由于考虑到用户流量问题每个额外音效最好不超过20KB。
### win: 获得胜利
`{"type": "win", "reason": "xxx"}` 将会直接调用events.js中的win函数并将reason作为参数传入。
@ -756,6 +798,43 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
// ……
```
## 加点事件
打败怪物后可以进行加点。
如果要对某个怪物进行加点操作,则首先需要修改该怪物的点数值,即在怪物定义的后面添加`point`,代表怪物本身的加点数值。
``` js
... 'def': 0, 'money': 1, 'experience': 1, 'special': 0, 'point': 1}, // 在怪物后面添加point代表怪物的加点数
```
然后在`events.js`文件中找到`addPoint`函数。它将返回一个choices事件。修改此函数为我们需要的加点项即可。
``` js
////// 加点 //////
events.prototype.addPoint = function (enemy) {
var point = enemy.point; // 获得该怪物的point
if (!core.isset(point) || point<=0) return [];
// 加点返回一个choices事件
return [
{"type": "choices",
"choices": [ // 提供三个选项:对于每一点,攻击+1/防御+2/生命+200
{"text": "攻击+"+(1*point), "action": [
{"type": "setValue", "name": "status:atk", "value": "status:atk+"+(1*point)}
]},
{"text": "防御+"+(2*point), "action": [
{"type": "setValue", "name": "status:def", "value": "status:def+"+(2*point)}
]},
{"text": "生命+"+(200*point), "action": [
{"type": "setValue", "name": "status:hp", "value": "status:hp+"+(200*point)}
]},
]
}
];
}
```
## 全局商店
我们可以采用上面的choices方式来给出一个商店。这样的商店确实可以有效地进行操作但是却是"非全局"的换句话说只有在碰到NPC的时候才能触发商店事件。
@ -776,7 +855,6 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
// 上面的例子是50层商店的计算公式。你也可以写任意其他的计算公式只要以times作为参数即可。
// 例如: "need": "25" 就是恒定需要25金币的商店 "need": "20+2*times" 就是第一次访问要20金币以后每次递增2金币的商店。
// 如果是对于每个选项有不同的计算公式,写 "need": "-1" 即可。可参见下面的经验商店。
"text": "勇敢的武士啊,给我${need}金币就可以:", // 显示的文字,需手动加换行符。可以使用${need}表示上面的need值。
"choices": [ // 商店的选项
{"text": "生命+800", "effect": "status:hp+=800"},
@ -784,10 +862,12 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
{"text": "攻击+4", "effect": "status:atk+=4"},
{"text": "防御+4", "effect": "status:def+=4"},
{"text": "魔防+10", "effect": "status:mdef+=10"}
// effect只能对status和item进行操作不能修改flag值。且其中间只能用+=符号(也就是只能增加某个属性或道具)
// effect只能对status和item进行操作不能修改flag值。
// 必须是X+=Y的形式其中Y可以是一个表达式以status:xxx或item:xxx为参数
// 其他effect样例
// "item:yellowKey+=1" 黄钥匙+1
// "item:pickaxe+=3" 破墙镐+3
// "status:hp+=2*(status:atk+status:def)" 将生命提升攻防和的数值的两倍
]
},
"expShop1": { // 商店唯一ID
@ -800,13 +880,13 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
"choices": [
// 在choices中写need可以针对每个选项都有不同的需求。
// 这里的need同样可以以times作为参数比如 "need": "100+20*times"
{"text": "等级+1", "need": "100", "effect": "status:hp+=1000;status:atk+=7;status:def+=7"},
{"text": "等级+1", "need": "100", "effect": "status:lv+=1;status:hp+=1000;status:atk+=7;status:def+=7"},
// 多个effect直接以分号分开即可。如上面的意思是生命+1000攻击+7防御+7。
{"text": "攻击+5", "need": "30", "effect": "status:atk+=5"},
{"text": "防御+5", "need": "30", "effect": "status:def+=5"},
]
}
}
},
},
```
全局商店全部定义在`data.js`中的shops一项里。
@ -829,7 +909,7 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
请注意,快捷商店默认是不可被使用的。直到至少调用一次自定义事件中的 `{"type": "openShop"}` 打开商店后,才能真正在快捷栏中被使用。
``` java
``` js
"1,0": [ // 金币商店
// 打开商店前,你也可以添加自己的剧情
// 例如通过if来事件来判断是不是第一次访问商店是的则显示一段文字类似宿命的华音那样
@ -887,37 +967,90 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
当且仅当勇士第一次到达某层时,将会触发此事件。可以利用此事件来显示一些剧情,或再让它调用 `{"type": "trigger"}` 来继续调用其他的事件。
## 战前剧情
有时候光战后事件`afterBattle`是不够的我们可能还需要战前剧情例如Boss战之前和Boss进行一段对话。
要使用战前剧情,首先你需要覆盖该店的系统默认事件,改成你自己的自定义事件,然后在战前剧情后调用`{"type": "battle"}`强制战斗。
值得注意的是,使用这种自定义事件来覆盖系统的默认战斗事件时,可以增加`displayDamage`代表该点是否需要显伤。此项可省略,默认为有显伤。
``` js
"x,y": { // (x,y)为该怪物坐标
"trigger": "action", // 覆盖该点本身默认事件,变成自定义事件
"displayDamage": true, // 覆盖后,该点是否有显伤;此项可忽略,默认为有。
"data": [ // 该点的自定义事件列表
// ... 战前剧情
{"type": "battle", "id": "xxx"}, // 强制战斗
// ... 战后剧情;请注意上面的强制战斗不会使怪物消失,如有需要请调动{"type": "hide"}
]
}
```
## 经验升级(进阶/境界塔)
本塔也支持经验升级,即用户杀怪获得经验后,可以到达某些数值自动进阶,全面提升属性。
要经验升级,你需要先在`data.js`中的全局变量中启用。你需要将`enableExperience`启用经验,且`enableLevelUp`启用进阶。同时你也可以将`enableLv`置为true以在状态栏中显示当前等级境界
同时,你还需要在`data.js`中的`levelUp`来定义每一个进阶所需要的生命值,以及进阶时的效果。
``` js
"levelUp": [ // 经验升级所需要的数值,是一个数组
{}, // 第一项为初始等级可以简单留空也可以写name
// 每一个里面可以含有三个参数 need, name, effect
// need为所需要的经验数值是一个正整数。请确保need所需的依次递增
// name为该等级的名称也可以省略代表使用系统默认值本项将显示在状态栏中
// effect为本次升级所执行的操作可由若干项组成由分号分开
// 其中每一项写法和上面的商店完全相同同样必须是X+=Y的形式Y是一个表达式同样可以使用status:xxx或item:xxx代表勇士的某项数值/道具个数
{"need": 20, "name": "第二级", "effect": "status:hp+=2*(status:atk+status:def);status:atk+=10;status:def+=10"}, // 先将生命提升攻防和的2倍再将攻击+10防御+10
// effect也允许写一个function代表本次升级将会执行的操作比如可以显示一段提示文字或者触发一个事件
{"need": 40, "effect": function () {
core.drawText("恭喜升级!");
core.status.hero.hp *= 2;
core.status.hero.atk += 100;
core.status.hero.def += 100;
}},
// 依次往下写需要的数值即可
]
```
`levelUp`是一个数组里面分别定义了每个等级的信息。里面每一项是一个object主要有三个参数`need`, `name`, `effect`
- `need` 该等级所需要的经验值是一个正整数。请确保数组中的need依次递增。
- `name` 该等级的名称,比如“佣兵下级”等。该项可以忽略,以使用系统默认的等级。该项将显示在状态栏中。
- `effect` 为本次等级执行的操作。它有两种写法:字符串,或函数。
- 如果`effect`为字符串则和上面的全局商店的写法完全相同。可由分号分开每一项为X+=Y的形式X为你要修改的勇士属性/道具个数Y为一个表达式。
- 如果`effect`为函数,则也允许写一个`function`,来代表本次升级将会执行的操作。
## 开始,难度分歧,获胜与失败
游戏开始时将调用`events.js`中的startGame函数。
游戏开始时将调用`events.js`中的`startGame`函数。
它将显示`data.js`中的startText内容可以修改成自己的并正式开始游戏。
它将显示`data.js`中的startText内容可以修改成自己的提供战斗动画开启选择设置初始福利并正式开始游戏。
我们可以修改`setInitData`函数来对于不同难度分别设置初始属性。
其参数hard为以下三个字符串之一`"Easy"`, `"Normal"`, `"Hard"`,分别对应三个难度。针对不同的难度,我们可以设置一些难度分歧。
``` js
////// 游戏开始事件 //////
events.prototype.startGame = function (hard) {
if (core.status.isStarting) return;
core.status.isStarting = true;
core.hideStartAnimate(function() {
core.drawText(core.clone(core.firstData.startText), function() {
core.startGame(hard);
if (hard=='Easy') { // 简单难度
core.setFlag('hard', 1); // 可以用flag:hard来获得当前难度
// 可以在此设置一些初始福利,比如设置初始生命值可以调用:
// core.setStatus("hp", 10000);
}
if (hard=='Normal') { // 普通难度
core.setFlag('hard', 2); // 可以用flag:hard来获得当前难度
}
if (hard=='Hard') { // 困难难度
core.setFlag('hard', 3); // 可以用flag:hard来获得当前难度
}
});
})
////// 不同难度分别设置初始属性 //////
events.prototype.setInitData = function (hard) {
if (hard=='Easy') { // 简单难度
core.setFlag('hard', 1); // 可以用flag:hard来获得当前难度
// 可以在此设置一些初始福利,比如设置初始生命值可以调用:
// core.setStatus("hp", 10000);
// 赠送一把黄钥匙可以调用
// core.setItem("yellowKey", 1);
}
if (hard=='Normal') { // 普通难度
core.setFlag('hard', 2); // 可以用flag:hard来获得当前难度
}
if (hard=='Hard') { // 困难难度
core.setFlag('hard', 3); // 可以用flag:hard来获得当前难度
}
}
```
@ -928,9 +1061,8 @@ events.prototype.startGame = function (hard) {
当获胜`{"type": "win"}`事件发生时,将调用`events.js`中的win事件。其显示一段恭喜文字并重新开始游戏。
``` js
////// 游戏结束事件 //////
////// 游戏获胜事件 //////
events.prototype.win = function(reason) {
// 获胜
core.waitHeroToStop(function() {
core.removeGlobalAnimate(0,0,true);
core.clearMap('all'); // 清空全地图
@ -948,8 +1080,8 @@ events.prototype.win = function(reason) {
当失败(`{"type": "lose"}`,或者被怪强制战斗打死、被领域怪扣血死、中毒导致扣血死,路障导致扣血死等等)事件发生时,将调用`events.js`中的`lose`事件。其直接显示一段文字,并重新开始游戏。
``` js
////// 游戏失败事件 //////
events.prototype.lose = function(reason) {
// 失败
core.waitHeroToStop(function() {
core.drawText([
"\t[结局1]你死了。\n如题。"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/img/nattack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/img/zone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -2,10 +2,11 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<title>HTML5魔塔样板</title>
<link rel="shortcut icon" type="image/png" href="./img/logo.png">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link href="https://cdn.bootcss.com/docsify/4.5.5/themes/vue.css" rel="stylesheet">
</head>
@ -15,11 +16,11 @@
window.$docsify = {
homepage: 'index.md',
loadSidebar: true,
name: 'H5魔塔样板',
name: 'HTML5魔塔样板',
repo: 'https://github.com/ckcz123/mota-js',
// basepath: '../docs/',
// Search Support
// Search Support
// search: {
// maxAge: 43200000, // 过期时间,单位毫秒,默认一天
// paths: 'auto',
@ -37,10 +38,12 @@
loadSidebar: '_sidebar.md',
subMaxLevel: 2,
autoHeader: true,
auto2top: true,
mergeNavbar: true,
formatUpdated: '{YYYY}-{MM}-{DD} {HH}:{mm}:{ss}',
}
//离线模式
if (typeof navigator.serviceWorker !== 'undefined') {
navigator.serviceWorker.register('serviceWorker.js')
navigator.serviceWorker.register('serviceWorker.js')
}
</script>
<script src="https://cdn.bootcss.com/docsify/4.5.5/docsify.min.js"></script>

View File

@ -1,5 +1,7 @@
# HTML5 魔塔样板说明文档
?> 上次更新时间:* {docsify-updated} *
众所周知魔塔的趋势是向移动端发展贴吧中也常常能见到“求手机魔塔”的帖子。然而现有的工具中NekoRPG有着比较大的局限性游戏感较差更是完全没法在iOS上运行。而一些APP的魔塔虽然可用但是必须要下载安装对于Android和iOS还必须开发不同的版本非常麻烦。
但是现在我们有了HTML5。 HTML5的画布canvas以及它被Android/iOS内置浏览器所支持的特性可以让我们做出真正意义上的全平台覆盖的魔塔。
@ -11,7 +13,7 @@
继续查看文档的详细介绍让你学会如何使用这一个样板来制作属于自己的HTML5魔塔。
视频教程地址http://www.bilibili.com/video/av17608025/ ,配合本教程观看效果更佳~
视频教程地址:[http://www.bilibili.com/video/av17608025/](http://www.bilibili.com/video/av17608025/) ,配合本教程观看效果更佳~
==========================================================================================

View File

@ -1,12 +1,14 @@
# 个性化
?> 上次更新时间:* {docsify-updated} *
有时候只靠样板本身可能是不够的。我们需要一些个性化、自定义的素材,道具效果,怪物属性,等等。
## 自定义素材
所有素材的图片都在`images`目录下。
- `animates.png` 为所有动画效果。主要是星空熔岩,开门,毒网,传送门之类的效果。为四帧。
- `autotile.png` 为Autotile块。全塔只支持一种Autotile其ID为20。
- `autotile.png` 为Autotile块。
- `enemys.png` 为所有怪物的图片。其对应的数字从上至下依次是会从201开始计算绿色史莱姆为201小蝙蝠为205依次类推。请注意动画效果为两帧一般是原始四帧中的1和3。四帧中12相同34相同因此只取1和3即可
- `heros.png` 为勇士行走图。
- `items.png` 为所有道具的图标。
@ -17,10 +19,45 @@
### 使用预定义的素材
在images目录的“默认素材”下给定了若干预定义的自定义素材。包括野外(草地),星空,木板等等都已经被预先给定。
在images目录的“默认素材”下给定了若干预定义的自定义素材。
如果你需要某个素材已经存在则可以直接将其覆盖images目录下的同名文件就能看到效果。
### 使用自己的图片作为某层楼的背景素材
由于HTML5功能素材有限导致了对很多比较复杂的素材比如房子内等无法有着较好的绘图方式。
为了解决这个问题,我们允许用户自己放置一张图片作为某一层的背景素材。
要启用这个功能,我们首先需要在`main.js`中将可能的图片进行加载。
``` js
this.pngs = [ // 在此存放所有可能的背景图片背景图片最好是416*416像素其他分辨率会被强制缩放成416*416
// 建议对于较大的图片,在网上使用在线的“图片压缩工具”来进行压缩,以节省流量
"bg.png", // "yewai.png",
];
```
!> 背景素材只支持png格式且会被强制缩放到416*416。
!> 请使用网上的一些[在线图片压缩工具](http://www.asqql.com/gifzip/)对png图片进行压缩以节省流量。一张500KB的png图片可以被压缩到20-30KB显示效果不会有太大差异。
之后,我们可以在每层剧本的`"png": "xxx"`里来定义该层的默认背景图片素材。
``` js
"png": "bg.png", // 背景图你可以选择一张png图片来作为背景素材。
```
你的图片背景素材将会覆盖原来本身的背景层。
**如果你需要让某些点不可通行(比如你建了个房子,墙壁和家具等位置不让通行),则需在`events`中指定`{"noPass": false}`,参见[自定义事件](event#自定义事件)的写法。
``` js
"events": {
"x,y": {"noPass": true} // (x,y)点不可通行
}
```
### 使用便捷PS工具生成素材
如果我们有更多的素材要求我们可以使用“便捷PS工具”进行处理。
@ -29,6 +66,8 @@
我们可以打开有需求改变的素材和我们需要被替换的素材然后简单的Ctrl+C和Ctrl+V操作即可。
便捷PS工具同样支持图片色相的修改和RMXP几乎完全相同。
用这种方式,我们能极快地替换或素材,包括需要新增的怪物。
### 添加素材到游戏
@ -37,63 +76,73 @@
这是因为,该素材没有被定义,无法被游戏所识别。
#### 素材的机制
本塔所有的素材都拥有三个属性:**ID****索引****数字**。
- **ID** 为该素材的唯一标识符任何两个素材的ID都不能相同。
- **索引** 为该素材的在对应图片上的图标索引,即该素材是图片上的第几个。
- **数字** 为该素材的对应数字,以方便地图的生成和存储。
**`ID-索引` 对应关系定义在icons.js文件中。该文件将唯一确定一个ID在图片上所在的位置。**
**`ID-数字` 对应关系定义在maps.js文件的getBlock函数中。该函数将唯一确定一个ID对应的数字是多少。**
如果需要添加一个素材到游戏,则必须为其分配一个唯一标识符,并同时修改`icons.js`和`maps.js`两个文件。
#### 新添加自定义地形(路面、墙壁等)
如果你在terrains.png中新增了一行
1. 指定一个唯一的英文ID不能和terrains中现有的重复。
2. 进入icons.js在terrains分类下进行添加对应图标在图片上的位置即index
3. 指定一个数字在maps.js的getBlock下类似进行添加。
``` js
if (id == 13) tmp.event = {'cls': 'animates', 'id': 'weakNet', 'noPass': false, 'trigger': 'passNet'}; // 衰网
if (id == 14) tmp.event = {'cls': 'animates', 'id': 'curseNet', 'noPass': false, 'trigger': 'passNet'}; // 咒网
if (id == 15) tmp.event = {'cls': 'animates', 'id': 'water', 'noPass': true}; // 水
// 可以在此处类似添加数字-ID对应关系但不能和任何已有的数字重复
// autotile: 20
if (id == 20) tmp.event = {'cls': 'autotile', 'id': 'autotile', 'noPass': true}; // autotile
```
1. 指定一个唯一的英文ID不能和现有的重复。
2. 进入icons.js在terrains分类下进行添加索引对应图标在图片上的位置即index
**如果你无须在游戏内使用本地形,而仅仅是将其作为“背景图”使用,则操作如下:**
3. 修改对应楼层的剧本文件的`defaultGround`项改成新的ID。
**如果你要在游戏内使用本地形,则操作如下:**
3. 指定一个数字在maps.js的getBlock函数下类似进行添加。
#### 新添加Autotile
如果你需要新增一个Autotile
1. 将新的Autotile图片复制到images目录下。
2. 进入icons.js在autotile分类下进行添加该文件的名称索引简单的写0。
3. 指定一个数字在maps.js的getBlock函数下类似进行添加。
!> Autotile的ID和文件名完全相同且其ID/文件名不能含有中文、空格或特殊字符。
#### 新添加道具
如果你需要新增一个未被定义的道具:
1. 指定一个唯一的英文ID不能和items中现有的重复。
2. 进入icons.js在items分类下进行添加对应图标在图片上的位置即index
1. 指定一个唯一的英文ID不能和现有的重复。
2. 进入icons.js在items分类下进行添加索引对应图标在图片上的位置即index
3. 指定一个数字在maps.js的getBlock下类似进行添加。
4. 在items.js中仿照其他道具来添加道具的信息。
``` js
if (id == 63) tmp.event = {'cls': 'items', 'id': 'moneyPocket'} // 金钱袋
if (id == 64) tmp.event = {'cls': 'items', 'id': 'shoes'} // 绿鞋
if (id == 65) tmp.event = {'cls': 'items', 'id': 'hammer'} // 圣锤
// 可以在这里添加自己的数字-ID对应关系但不能和任何已有的数字重复
```
有关如何自行实现一个道具的效果,参见[自定义道具效果](#自定义道具效果)。
#### 新添加怪物
如果我们需要新添加怪物请在enemys.png中新增一行然后复制粘贴上四帧怪物图的**1和3帧**。
你可以通过便捷PS工具的“更改色相”来将红头怪变成橙头怪等。
然后执行如下操作:
1. 指定一个唯一的英文ID不能和enemys中现有的重复。
2. 进入icons.js在enemys分类下进行添加对应图标在图片上的位置即index
2. 进入icons.js在enemys分类下进行添加索引对应图标在图片上的位置即index
3. 在maps.js的getBlock下继续进行添加。请注意其ID为200开始的顺序即如果新增一行为261依次类推
4. 在enemys.js中仿照其他怪物来添加怪物的信息。
``` js
if (id == 258) tmp.event = {'cls': 'enemys', 'id': 'octopus'};
if (id == 259) tmp.event = {'cls': 'enemys', 'id': 'fairy'};
if (id == 260) tmp.event = {'cls': 'enemys', 'id': 'greenKnight'};
// 在此依次添加,数字要求是递增的
```
有关如何自行实现一个怪物的特殊属性或伤害计算公式,参见[怪物的特殊属性](#怪物的特殊属性)。
#### 新添加NPC
类似同上给NPC指定ID在icons.js中指定ID-index关系在maps.js中指定ID-数字的关系,即可。
1. 指定一个唯一的英文ID不能和现有的重复。
2. 进入icons.js在npcs分类下进行添加索引对应图标在图片上的位置即index
3. 指定一个数字在maps.js的getBlock下类似进行添加。
### 地图生成器使用自定义素材
@ -176,18 +225,24 @@ enemys.prototype.getExtraDamage = function (monster) {
}
// ... 下略
```
3. 免疫领域、夹击效果:在`core.js`中找到updateCheckBlock函数并编辑成如果有神圣盾标记则直接返回
3. 免疫领域、夹击、阻击效果:在`core.js`中找到checkBlock函数并编辑成如果有神圣盾标记则将伤害变成0
``` js
// 更新领域、显伤点
core.prototype.updateCheckBlock = function() {
if (!core.isset(core.status.thisMap)) return;
if (!core.isset(core.status.checkBlockMap)) core.updateCheckBlockMap();
core.status.checkBlock = [];
if (core.hasItem('shield5')) return; // 如拥有神圣盾则直接返回
for (var x=0;x<13;x++) {
for (var y=0;y<13;y++) {
// 计算(x,y)点伤害
var damage = 0;
// 检查领域、夹击、阻击事件
core.prototype.checkBlock = function () {
var x=core.getHeroLoc('x'), y=core.getHeroLoc('y');
var damage = core.status.checkBlock.damage[13*x+y];
if (damage>0) {
if (core.hasFlag("shield5")) damage = 0; // 如果存在神圣盾则将伤害变成0
core.status.hero.hp -= damage;
// 检查阻击事件
var snipe = [];
var scan = {
'up': {'x': 0, 'y': -1},
'left': {'x': -1, 'y': 0},
'down': {'x': 0, 'y': 1},
'right': {'x': 1, 'y': 0}
}
// ... 下略
```
4. 如果有更高的需求,例如想让吸血效果变成一半(如异空间),则还是在上面这些地方进行对应的修改即可。
@ -196,42 +251,15 @@ core.prototype.updateCheckBlock = function() {
如果你对现有的怪物不满意,想自行添加怪物属性(例如让怪物拥有双属性乃至更多属性),也是可以的。具体参见`enemys.js`文件。
你需自己指定一个special数字修改getSpecialText函数。
你需自己指定一个special数字修改getSpecialText函数属性名和getSpecialHint函数属性提示文字
如果要修改伤害计算公式请修改下面的calDamage函数。请注意如果无法战斗该函数必须返回`999999999`。
因此无敌属性可以这样设置:
先给无敌属性指定一个数字例如18在getSpecialText中定义
``` js
// ... 上略
if (this.hasSpecial(special, 15)) text.push("领域");
if (this.hasSpecial(special, 16)) text.push("夹击");
if (this.hasSpecial(special, 17)) text.push("仇恨");
if (this.hasSpecial(special, 18)) text.push("无敌"); // 添加无敌的显示
return text.join(" ");
}
```
然后修改calDamage如果无敌属性且勇士没有拥有十字架则立刻返回无穷大。
``` js
enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special) {
if (this.hasSpecial(mon_special, 18) && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架
return 999999999; // 返回无限大
// 魔攻
if (this.hasSpecial(mon_special, 2)) hero_def = 0;
// ... 下略
```
对于吸血怪的额外伤害计算在getExtraDamage中。
对于毒衰弱怪物的战斗后结算在`events.js`中的afterBattle函数中。
对于领域、夹击怪物的检查在`events.js`中的checkBlock函数中。
对于领域、夹击、阻击怪物的检查在`events.js`中的checkBlock函数中。
`getCritical`, `getCriticalDamage`和`getDefDamage`三个函数依次计算的是该怪物的临界值、临界减伤和1防减伤。也可以适当进行修改。

View File

@ -1,31 +1,31 @@
const RUNTIME = 'docsify'
const HOSTNAME_WHITELIST = [
self.location.hostname,
'fonts.gstatic.com',
'fonts.googleapis.com',
'unpkg.com'
self.location.hostname,
'fonts.gstatic.com',
'fonts.googleapis.com',
'cdn.bootcss.com'
]
// The Util Function to hack URLs of intercepted requests
const getFixedUrl = (req) => {
var now = Date.now()
var url = new URL(req.url)
var now = Date.now()
var url = new URL(req.url)
// 1. fixed http URL
// Just keep syncing with location.protocol
// fetch(httpURL) belongs to active mixed content.
// And fetch(httpRequest) is not supported yet.
url.protocol = self.location.protocol
// 1. fixed http URL
// Just keep syncing with location.protocol
// fetch(httpURL) belongs to active mixed content.
// And fetch(httpRequest) is not supported yet.
url.protocol = self.location.protocol
// 2. add query for caching-busting.
// Github Pages served with Cache-Control: max-age=600
// max-age on mutable content is error-prone, with SW life of bugs can even extend.
// Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
// Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
if (url.hostname === self.location.hostname) {
url.search += (url.search ? '&' : '?') + 'cache-bust=' + now
}
return url.href
// 2. add query for caching-busting.
// Github Pages served with Cache-Control: max-age=600
// max-age on mutable content is error-prone, with SW life of bugs can even extend.
// Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
// Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
if (url.hostname === self.location.hostname) {
url.search += (url.search ? '&' : '?') + 'cache-bust=' + now
}
return url.href
}
/**
@ -35,7 +35,7 @@ const getFixedUrl = (req) => {
* waitUntil(): activating ====> activated
*/
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim())
event.waitUntil(self.clients.claim())
})
/**
@ -45,8 +45,8 @@ self.addEventListener('activate', event => {
* void respondWith(Promise<Response> r)
*/
self.addEventListener('fetch', event => {
// Skip some of cross-origin requests, like those for Google Analytics.
if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
// Skip some of cross-origin requests, like those for Google Analytics.
if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
// Stale-while-revalidate
// similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale
// Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1
@ -60,16 +60,16 @@ self.addEventListener('fetch', event => {
// If theres nothing in cache, wait for the fetch.
// If neither yields a response, return offline pages.
event.respondWith(
Promise.race([fetched.catch(_ => cached), cached])
.then(resp => resp || fetched)
.catch(_ => { /* eat any errors */ })
)
Promise.race([fetched.catch(_ => cached), cached])
.then(resp => resp || fetched)
.catch(_ => { /* eat any errors */ })
)
// Update the cache with the version we fetched (only for ok status)
event.waitUntil(
Promise.all([fetchedCopy, caches.open(RUNTIME)])
.then(([response, cache]) => response.ok && cache.put(event.request, response))
Promise.all([fetchedCopy, caches.open(RUNTIME)])
.then(([response, cache]) => response.ok && cache.put(event.request, response))
.catch(_ => { /* eat any errors */ })
)
}
})
)
}
})

View File

@ -1,5 +1,7 @@
# 快速上手
?> 上次更新时间:* {docsify-updated} *
在这一节中,将详细介绍做一部塔的流程。现在,让我们来做一部单层塔!
## 前置需求
@ -8,8 +10,8 @@
- Windows 8以上操作系统Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“启动服务.exe”即可
- 任一款现代浏览器。强烈推荐Chrome。
- 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如WebStormVSCode或者至少也要Sublime Text。
[VSCode下载地址](https://code.visualstudio.com/),群里的群文件中也有,强烈推荐之。)
- 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如WebStormVSCode或者至少也要Sublime Text。
- [VSCode下载地址](https://code.visualstudio.com/),群里的群文件中也有,强烈推荐之。)
只要满足了上述条件,你就可以开始做自己的塔啦!
@ -36,7 +38,23 @@
然后将楼层名改为MT1floorId改名为MT1title可以改成任意内容将在切换楼层时进行显示比如可以改成“1层小塔”
具体样板文件的每个要素都有详细的注释。我们最终的任务其实是,将每个楼层的剧本(地图&事件)给写完即可。
具体样板文件的每个要素如下:
- **`floorId`** 楼层唯一标识符;必须和文件名,以及 `main.floors.xxx` 完全一致
- **`title`** 楼层中文名,将在切换楼层时进行显示
- **`canFlyTo`** 当前楼层可否被楼传器飞到。如果该层不能飞到,则也在该层也不允许使用楼传器。
- **`canUseQuickShop`** 当前楼层可否使用快捷商店。
- **`defaultGround`** 该层的背景(地面)素材。需要是在`icon.js`里`terrains`中定义的一个ID如`ground`, `grass2`等等。
- **`color`** 该层的画面色调。本项可选如果不写则色调为默认值无色调否则是一个RGBA数组比如`[255,0,0,0.3]`等。
- **`bgm`** 到达该层后默认播放的BGM。本项可忽略。
- **`map`** 本层地图需要是13x13数组建议使用地图生成器或者可视化地图编辑器制作。
- **`firstArrive`** 第一次到该楼层触发的事件
- **`events`** 该楼的所有可能事件列表
- **`changeFloor`** 楼层转换事件该事件不能和上面的events有冲突同位置点否则会被覆盖
- **`afterBattle`** 战斗后可能触发的事件列表
- **`afterGetItem`** 获得道具后可能触发的事件列表
- **`afterOpenDoor`** 开完门后可能触发的事件列表
我们最终的任务其实是,将每个楼层的剧本(地图&事件)给写完即可。
换句话说,只需要简单的复制操作,我们就可以新建一个剧本了。

View File

@ -7,52 +7,24 @@
<body>
<div class="main">
<div id="left">
<div class="nav" id="modSeletNav" v-cloak>
<input v-model="currTabnum" type="hidden">
<a v-for="tab in tabs" :key="tab.num"
:class="{ 'is-selected': currTabnum == tab.num }"
@click="selectTab(tab.num)" >
<span v-text="tab.title"></span>
</a>
</div>
<div class="tabs" id="tabSelection" v-cloak>
<div id="arrEditor" class="card" v-show="showTab==1">
<div class="lineTitle">地图数组编辑区</div>
<div id="editArea" v-cloak>
<table class="col" id='arrColMark'></table>
<table class="row" id='arrRowMark'></table>
<textarea cols="10" rows="10" id="pout" v-model="mapArr"></textarea>
<p class="warnText" v-if="error">{{ errors[error-1] }}</p>
</div>
<div id="editTip" v-cloak>
<!-- <input class='btn' type="button" value="复制地图" v-on:click="copyMap"/> -->
</div>
<div class="files" id="editFile4map" v-cloak >
<div class="lineTitle">文件操作区</div>
<select v-model="selected">
<option disabled value="">请选择要编辑的文件</option>
<option v-for="option in options" v-bind:value="option">
{{ option }}
</option>
</select>
<span>当前文件: {{ selected }}</span>
<input class='btn' type="button" value="保存" onclick="readUTF8file(pin.value)" />
<input class='btn' type="button" value="另存为" onclick="writeUTF8file(pin.value,pout.innerText)" />
<input class='input' id='pin' placeholder="请输入文件名"/>
<div id="printOut"></div>
</div>
<div id="arrEditor">
<table class="col" id='arrColMark'></table>
<table class="row" id='arrRowMark'></table>
<div id="editArea" v-cloak>
<textarea cols="10" rows="10" id="pout" v-model="mapArr"></textarea>
<p class="warnText" v-if="error">{{ errors[error-1] }}</p>
</div>
<div id="objDataEditor" class="card" v-show="showTab==2">
<div class="lineTitle">对象属性编辑区</div>
</div>
<div id="eventEditor" class="card" v-show="showTab==3">
<div class="lineTitle">地图选点事件编辑区</div>
</div>
<div id="floorEditor" class="card" v-show="showTab==4">
<div class="lineTitle">楼层事件编辑区</div>
<div id="editTip" v-cloak>
<input class='btn' type="button" value="复制地图" v-on:click="copyMap"/>
</div>
</div>
<div id="objDataEditor">
</div>
<div id="eventEditor">
</div>
</div>
<div id="mid">
<table class="col" id='mapColMark'></table>
@ -80,14 +52,19 @@
</div>
<input class='btn' id='clear' type="button" value="清除地图" v-on:click="clearMap"/>
<input class='btn' type="button" value="导出地图" id="exportM" v-on:click="exportMap"/>
<div id="bgSelect" v-cloak>
<span>当前地板: </span>
<select v-model="selectedBg">
<option disabled value="">请选择地板</option>
<option v-for="bg in bgs" v-bind:value="bg">
{{ bg }}
</option>
</select>
<span>当前地板: {{ selectedBg }}</span>
<div class="selectpng">
<input class='input' id='pin' v-model="imgname" placeholder="请输入自定义背景文件名"/>
<input class='btn' type="button" value="确定" v-on:click="updatebg"/>
</div>
</div>
</div>
</div>
@ -129,14 +106,9 @@
<script src='_server/editor_file.js'></script>
<script src='_server/vm.js'></script>
<script>
printf = function(str,weak) {
var prefix='<span class="result">',postfix='</span>';
if (weak){prefix='<span class="weakresult">';}
if (typeof(str)==="undefined")str='';
printOut.innerHTML=prefix+String(str)+postfix;
}
//所有全局量
__all__=['Vue','fs','printf','editor','main','core','fullX','fullY'];
__all__=['Vue','fs','printf','editor','main','core'];
__id__=['printOut','arrRowMark','mapRowMark','data','bg','dataSelection'];
__Vue__=['exportM','editArea','editTip','clear','tip','selectBox'];
@ -235,7 +207,7 @@ editor.prototype.loadAllImgs = function(icons){
return Promise.all(p);
}
editor.prototype.idsInit = function(maps, icons){
editor.ids = [];
editor.ids = [0];
editor.indexs = [];
var MAX_NUM = 400;
var getInfoById = function(id){
@ -334,7 +306,7 @@ editor.prototype.mapInit = function(){
}
}
}
editor.prototype.drawMapBg = function(){
editor.prototype.drawMapBg = function(img){
var bgc = bg.getContext('2d');
for (var ii = 0; ii < 13; ii++)
@ -342,6 +314,9 @@ editor.prototype.drawMapBg = function(){
bgc.clearRect(ii*32, jj*32, 32, 32);
bgc.drawImage(editor.material.images['terrains'], 0, 32*(editor.bgY||0), 32, 32, ii*32, jj*32, 32, 32);
}
if(img){
bgc.drawImage(img, 0, 0, 416, 416);
}
}
editor.prototype.updateMap = function(){
@ -367,6 +342,7 @@ editor.prototype.updateMap = function(){
ctx.drawImage(editor.material.images[tileInfo.images], 0, tileInfo.y*32, 32, 32, x*32, y*32, 32, 32);
}
// autotile的相关处理
var indexArrs = [ //16种组合的图块索引数组; // 将autotile分割成48块16*16的小块; 数组索引即对应各个小块
// +----+----+----+----+----+----+
[10, 9, 4, 3 ], //0 bin:0000 | 1 | 2 | 3 | 4 | 5 | 6 |
@ -634,7 +610,7 @@ editor.prototype.listen = function() {
// console.log(pos,editor.material.images[pos.images].height)
dataSelection.style.left = pos.x*32 +'px';
dataSelection.style.top = pos.y*32 +'px';
if(pos.x==0&&pos.y==0){
// editor.info={idnum:0, id:'empty','images':'清除块', 'y':0};
editor.info=0;

BIN
images/lv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 B

BIN
images/yewai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -47,10 +47,14 @@
<p id='floorNameLabel'></p>
</div>
<div id='statusBar' class="clearfix">
<div class="status">
<div class="status" id="floorCol">
<img src='images/floor.png' id="img-floor">
<p class='statusLabel' id='floor'></p>
</div>
<div class="status" id="lvCol">
<img src='images/lv.png' id="img-lv">
<p class='statusLabel' id='lv'></p>
</div>
<div class="status">
<img src='images/hp.png' id="img-hp">
<p class='statusLabel' id='hp'></p>
@ -67,7 +71,7 @@
<img src='images/mdef.png' id="img-mdef">
<p class='statusLabel' id='mdef'></p>
</div>
<div class="status">
<div class="status" id="moneyCol">
<img src='images/money.png' id="img-money">
<p class='statusLabel' id='money'></p>
</div>
@ -75,12 +79,16 @@
<img src='images/experience.png' id="img-experience">
<p class='statusLabel' id='experience'></p>
</div>
<div class="status" id="upCol">
<img src='images/up.png' id="img-up">
<p class='statusLabel' id='up'></p>
</div>
<div class="status">
<span class='statusLabel' id='yellowKey' style="color:#FFCCAA"></span>
<span class='statusLabel' id='blueKey' style="color:#AAAADD"></span>
<span class='statusLabel' id='redKey' style="color:#FF8888"></span>
</div>
<div class="status">
<div class="status" id="debuffCol">
<span class='statusLabel' id='poison' style="color: #AFFCA8;"></span>
<span class='statusLabel' id='weak' style="color: #FECCD0;"></span>
<span class='statusLabel' id='curse' style="color: #C2F4E7;"></span>
@ -96,13 +104,15 @@
<img src="images/settings.png" class="tools" id='img-settings'>
<p class="statusLabel tools" id="hard"></p>
</div>
<div id="curtain"></div>
<canvas class='gameCanvas' id='bg' width='416' height='416'></canvas>
<canvas class='gameCanvas' id='event' width='416' height='416'></canvas>
<canvas class='gameCanvas' id='hero' width='416' height='416'></canvas>
<canvas class='gameCanvas' id='fg' width='416' height='416'></canvas>
<canvas class='gameCanvas' id='hero' width='416' height='416'></canvas>
<canvas class='gameCanvas' id='ui' width='416' height='416'></canvas>
<canvas class='gameCanvas' id='data' width='416' height='416'>此浏览器不支持HTML5</canvas>
</div>
<script id='mainScript' src='main.js'></script>
<script src='libs/thirdparty/mid.js'></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -10,12 +10,13 @@ data.prototype.init = function() {
"floorId": "sample0", // 初始楼层ID
"hero": { // 勇士初始数据
"name": "阳光", // 勇士名;可以改成喜欢的
'lv': 1, // 初始等级,该项必须为正整数
"hp": 1000, // 初始生命值
"atk": 100, // 初始攻击
"def": 100, // 初始防御
"mdef": 100, // 初始魔防
"money": 100, // 初始金币
"experience": 1000, // 初始经验
"experience": 0, // 初始经验
"items": { // 初始道具个数
"keys": {
"yellowKey": 0,
@ -57,10 +58,11 @@ data.prototype.init = function() {
{"text": "防御+4", "effect": "status:def+=4"},
{"text": "魔防+10", "effect": "status:mdef+=10"}
// effect只能对status和item进行操作不能修改flag值。
// 中间只能用+=符号(也就是只能增加某个属性或道具)
// 必须是X+=Y的形式其中Y可以是一个表达式以status:xxx或item:xxx为参数
// 其他effect样例
// "item:yellowKey+=1" 黄钥匙+1
// "item:pickaxe+=3" 破墙镐+3
// "status:hp+=2*(status:atk+status:def)" 将生命提升攻防和的数值的两倍
]
},
"expShop1": { // 商店唯一ID
@ -73,13 +75,33 @@ data.prototype.init = function() {
"choices": [
// 在choices中写need可以针对每个选项都有不同的需求。
// 这里的need同样可以以times作为参数比如 "need": "100+20*times"
{"text": "等级+1", "need": "100", "effect": "status:hp+=1000;status:atk+=7;status:def+=7"},
{"text": "等级+1", "need": "100", "effect": "status:lv+=1;status:hp+=1000;status:atk+=7;status:def+=7"},
// 多个effect直接以分号分开即可。如上面的意思是生命+1000攻击+7防御+7。
{"text": "攻击+5", "need": "30", "effect": "status:atk+=5"},
{"text": "防御+5", "need": "30", "effect": "status:def+=5"},
]
},
},
"levelUp": [ // 经验升级所需要的数值,是一个数组
{}, // 第一项为初始等级可以简单留空也可以写name
// 每一个里面可以含有三个参数 need, name, effect
// need为所需要的经验数值是一个正整数。请确保need所需的依次递增
// name为该等级的名称也可以省略代表使用系统默认值本项将显示在状态栏中
// effect为本次升级所执行的操作可由若干项组成由分号分开
// 其中每一项写法和上面的商店完全相同同样必须是X+=Y的形式Y是一个表达式同样可以使用status:xxx或item:xxx代表勇士的某项数值/道具个数
{"need": 20, "name": "第二级", "effect": "status:hp+=2*(status:atk+status:def);status:atk+=10;status:def+=10"}, // 先将生命提升攻防和的2倍再将攻击+10防御+10
// effect也允许写一个function代表本次升级将会执行的操作
{"need": 40, "effect": function () {
core.drawText("恭喜升级!");
core.status.hero.hp *= 2;
core.status.hero.atk += 100;
core.status.hero.def += 100;
}},
// 依次往下写需要的数值即可
]
}
// 各种数值;一些数值可以在这里设置
this.values = {
@ -117,17 +139,26 @@ data.prototype.init = function() {
}
// 系统FLAG在游戏运行中中请不要修改它。
this.flags = {
/****** 角色状态相关 ******/
"enableMDef": true, // 是否涉及勇士的魔防值如果此项为false则状态栏不会显示勇士的魔防值
"enableExperience": true, // 是否涉及经验值如果此项为false则状态栏和怪物手册均将不会显示经验值
/****** 状态栏相关 ******/
"enableFloor": true, // 是否在状态栏显示当前楼层
"enableLv": true, // 是否在状态栏显示当前等级
"enableMDef": true, // 是否在状态栏及战斗界面显示魔防(护盾)
"enableMoney": true, // 是否在状态栏、怪物手册及战斗界面显示金币
"enableExperience": true, // 是否在状态栏、怪物手册及战斗界面显示经验
"enableLevelUp": true, // 是否允许等级提升进阶如果上面enableExperience为false则此项恒视为false
"enableDebuff": true, // 是否涉及毒衰咒如果此项为false则不会在状态栏中显示毒衰咒的debuff
////// 上述的几个开关将直接影响状态栏的显示效果 //////
/****** 道具相关 ******/
"flyNearStair": true, // 是否需要在楼梯边使用传送器
"pickaxeFourDirections": true, // 使用破墙镐是否四个方向都破坏如果false则只破坏面前的墙壁
"bombFourDirections": true, // 使用炸弹是否四个方向都会炸如果false则只炸面前的怪物即和圣锤等价
"bigKeyIsBox": false, // 如果此项为true则视为钥匙盒红黄蓝钥匙+1若为false则视为大黄门钥匙
/****** 怪物相关 ******/
"enableNegativeDamage": true, // 是否支持负伤害(回血)
"zoneSquare": false, // 领域类型。如果此项为true则为九宫格伤害为false则为十字伤害
/****** 系统相关 ******/
"startDirectly": false, // 点击“开始游戏”后是否立刻开始游戏而不显示难度选择界面
"canOpenBattleAnimate": true, // 是否允许用户开启战斗过程如果此项为false则下面两项均强制视为false
"showBattleAnimateConfirm": true, // 是否在游戏开始时提供“是否开启战斗动画”的选项
"battleAnimate": true, // 是否默认显示战斗动画;用户可以手动在菜单栏中开关
"displayEnemyDamage": true, // 是否地图怪物显伤;用户可以手动在菜单栏中开关

View File

@ -2,13 +2,14 @@ function enemys() {
}
////// 初始化 //////
enemys.prototype.init = function () {
// 怪物属性初始化定义:
this.enemys = {
'greenSlime': {'name': '绿头怪', 'hp': 100, 'atk': 120, 'def': 0, 'money': 1, 'experience': 0, 'special': 0},
'greenSlime': {'name': '绿头怪', 'hp': 100, 'atk': 120, 'def': 0, 'money': 1, 'experience': 1, 'special': [1,5,7,8]},
'redSlime': {'name': '红头怪', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'blackSlime': {'name': '青头怪', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'slimelord': {'name': '怪王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': 9},
'slimelord': {'name': '怪王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': [1,9]},
'bat': {'name': '小蝙蝠', 'hp': 100, 'atk': 120, 'def': 0, 'money': 2, 'experience': 0, 'special': 1},
'bigBat': {'name': '大蝙蝠', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'redBat': {'name': '红蝙蝠', 'hp': 100, 'atk': 120, 'def': 0, 'money': 5, 'experience': 0, 'special': 4},
@ -21,10 +22,10 @@ enemys.prototype.init = function () {
'zombieKnight': {'name': '兽人武士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'rock': {'name': '石头人', 'hp': 100, 'atk': 120, 'def': 0, 'money': 4, 'experience': 0, 'special': 3},
'slimeMan': {'name': '影子战士', 'hp': 100, 'atk': 0, 'def': 0, 'money': 11, 'experience': 0, 'special': 10}, // 模仿怪的攻防设为0就好
'bluePriest': {'name': '初级法师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 3, 'experience': 0, 'special': 2},
'bluePriest': {'name': '初级法师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 3, 'experience': 0, 'special': 2, 'point': 1}, // 'point'可以在打败怪物后进行加点,详见文档说明。
'redPriest': {'name': '高级法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'brownWizard': {'name': '初级巫师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 16, 'experience': 0, 'special': 15, 'value': 100}, // 领域怪需要加value表示领域伤害的数值
'redWizard': {'name': '高级巫师', 'hp': 1000, 'atk': 1200, 'def': 0, 'money': 160, 'experience': 0, 'special': 15, 'value': 200},
'brownWizard': {'name': '初级巫师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 16, 'experience': 0, 'special': 15, 'value': 100, 'zoneSquare': true}, // 领域怪需要加value表示领域伤害的数值zoneSquare代表是否九宫格伤害
'redWizard': {'name': '高级巫师', 'hp': 1000, 'atk': 1200, 'def': 0, 'money': 160, 'experience': 0, 'special': 15, 'value': 200, 'range': 2}, // range可选代表领域伤害的范围不加默认为1
'yellowGuard': {'name': '初级卫兵', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': 0},
'blueGuard': {'name': '中级卫兵', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'redGuard': {'name': '高级卫兵', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
@ -41,7 +42,7 @@ enemys.prototype.init = function () {
'poisonSkeleton': {'name': '紫骷髅', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'poisonBat': {'name': '紫蝙蝠', 'hp': 100, 'atk': 120, 'def': 0, 'money': 14, 'experience': 0, 'special': 13},
'steelRock': {'name': '铁面人', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'skeletonPriest': {'name': '骷髅法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'skeletonPriest': {'name': '骷髅法师', 'hp': 100, 'atk': 100, 'def': 0, 'money': 0, 'experience': 0, 'special': 18, 'value': 20},
'skeletonKing': {'name': '骷髅王', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'skeletonWizard': {'name': '骷髅巫师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'redSkeletonCaption': {'name': '骷髅武士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'special': 0},
@ -58,7 +59,7 @@ enemys.prototype.init = function () {
'badPrincess': {'name': '痛苦魔女', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'badFairy': {'name': '黑暗仙子', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'grayPriest': {'name': '中级法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'redSwordsman': {'name': '剑王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 7, 'experience': 0, 'special': 6},
'redSwordsman': {'name': '剑王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 7, 'experience': 0, 'special': 6, 'n': 8}, // 多连击需要在后面指定n代表是几连击
'whiteGhost': {'name': '水银战士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'poisonZombie': {'name': '绿兽人', 'hp': 100, 'atk': 120, 'def': 0, 'money': 13, 'experience': 0, 'special': 12},
'magicDragon': {'name': '魔龙', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
@ -68,27 +69,31 @@ enemys.prototype.init = function () {
}
}
////// 获得一个或所有怪物数据 //////
enemys.prototype.getEnemys = function (enemyId) {
if (enemyId == undefined) {
if (!core.isset(enemyId)) {
return this.enemys;
}
return this.enemys[enemyId];
}
////// 判断是否含有某特殊属性 //////
enemys.prototype.hasSpecial = function (special, test) {
return special!=0 && (special%100 == test || this.hasSpecial(parseInt(special/100), test));
return (special instanceof Array)?special.indexOf(test)>=0:(special!=0&&(special%100==test||this.hasSpecial(parseInt(special/100), test)));
}
////// 获得所有特殊属性的名称 //////
enemys.prototype.getSpecialText = function (enemyId) {
if (enemyId == undefined) return "";
var special = this.enemys[enemyId].special;
var enemy = this.enemys[enemyId];
var special = enemy.special;
var text = [];
if (this.hasSpecial(special, 1)) text.push("先攻");
if (this.hasSpecial(special, 2)) text.push("魔攻");
if (this.hasSpecial(special, 3)) text.push("坚固");
if (this.hasSpecial(special, 4)) text.push("2连击");
if (this.hasSpecial(special, 5)) text.push("3连击");
if (this.hasSpecial(special, 6)) text.push("4连击");
if (this.hasSpecial(special, 6)) text.push((enemy.n||4)+"连击");
if (this.hasSpecial(special, 7)) text.push("破甲");
if (this.hasSpecial(special, 8)) text.push("反击");
if (this.hasSpecial(special, 9)) text.push("净化");
@ -100,18 +105,63 @@ enemys.prototype.getSpecialText = function (enemyId) {
if (this.hasSpecial(special, 15)) text.push("领域");
if (this.hasSpecial(special, 16)) text.push("夹击");
if (this.hasSpecial(special, 17)) text.push("仇恨");
return text.join(" ");
if (this.hasSpecial(special, 18)) text.push("阻击");
if (this.hasSpecial(special, 19)) text.push("自爆");
if (this.hasSpecial(special, 20)) text.push("无敌");
return text;
}
////// 获得每个特殊属性的说明 //////
enemys.prototype.getSpecialHint = function (enemy, special) {
if (!core.isset(special)) {
var hints = [];
for (var i=1;i<100;i++) {
if (this.hasSpecial(enemy.special, i)) {
var hint=this.getSpecialHint(enemy, i);
if (hint!='')
hints.push(hint);
}
}
return hints;
}
switch (special) {
case 1: return "先攻:怪物首先攻击";
case 2: return "魔攻:怪物无视勇士的防御";
case 3: return "坚固勇士每回合最多只能对怪物造成1点伤害";
case 4: return "2连击怪物每回合攻击2次";
case 5: return "3连击怪物每回合攻击3次";
case 6: return (enemy.n||4)+"连击: 怪物每回合攻击"+(enemy.n||4)+"次";
case 7: return "破甲:战斗前,怪物附加角色防御的"+parseInt(100*core.values.breakArmor)+"%作为伤害";
case 8: return "反击:战斗时,怪物每回合附加角色攻击的"+parseInt(100*core.values.counterAttack)+"%作为伤害,无视角色防御";
case 9: return "净化:战斗前,怪物附加勇士魔防的"+core.values.purify+"倍作为伤害";
case 10: return "模仿:怪物的攻防和勇士攻防相等";
case 11: return "吸血:战斗前,怪物首先吸取角色的"+parseInt(100*enemy.value)+"%生命作为伤害";
case 12: return "中毒:战斗后,勇士陷入中毒状态,每一步损失生命"+core.values.poisonDamage+"点";
case 13: return "衰弱:战斗后,勇士陷入衰弱状态,攻防暂时下降"+core.values.weakValue+"点";
case 14: return "诅咒:战斗后,勇士陷入诅咒状态,战斗无法获得金币和经验";
case 15: return "领域:经过怪物周围"+(enemy.range||1)+"格时自动减生命"+enemy.value+"点";
case 16: return "夹击:经过两只相同的怪物中间,勇士生命值变成一半";
case 17: return "仇恨:战斗前,怪物附加之前积累的仇恨值作为伤害;战斗后,释放一半的仇恨值。(每杀死一个怪物获得"+core.values.hatred+"点仇恨值)";
case 18: return "阻击:经过怪物的十字领域时自动减生命"+enemy.value+"点,同时怪物后退一格";
case 19: return "自爆战斗后勇士的生命值变成1";
case 20: return "无敌:勇士无法打败怪物,除非拥有十字架";
default: break;
}
return ""
}
////// 获得某个怪物的伤害 //////
enemys.prototype.getDamage = function (monsterId) {
var monster = core.material.enemys[monsterId];
var hero_atk = core.status.hero.atk, hero_def = core.status.hero.def, hero_mdef = core.status.hero.mdef;
var mon_hp = monster.hp, mon_atk = monster.atk, mon_def = monster.def, mon_special = monster.special;
var damage = this.calDamage(hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special);
var damage = this.calDamage(hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special, monster.n);
if (damage == 999999999) return damage;
return damage + this.getExtraDamage(monster);
}
////// 获得某个怪物的额外伤害 //////
enemys.prototype.getExtraDamage = function (monster) {
var extra_damage = 0;
if (this.hasSpecial(monster.special, 11)) { // 吸血
@ -125,17 +175,17 @@ enemys.prototype.getExtraDamage = function (monster) {
return extra_damage;
}
// 临界值计算
////// 临界值计算 //////
enemys.prototype.getCritical = function (monsterId) {
var monster = core.material.enemys[monsterId];
if (this.hasSpecial(monster.special, 3) || this.hasSpecial(monster.special, 10)) return "???";
var last = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef,
monster.hp, monster.atk, monster.def, monster.special);
if (last == 0) return 0;
monster.hp, monster.atk, monster.def, monster.special, monster.n);
if (last <= 0) return 0;
for (var i = core.status.hero.atk + 1; i <= monster.hp + monster.def; i++) {
var damage = this.calDamage(i, core.status.hero.def, core.status.hero.mdef,
monster.hp, monster.atk, monster.def, monster.special);
monster.hp, monster.atk, monster.def, monster.special, monster.n);
if (damage < last)
return i - core.status.hero.atk;
last = damage;
@ -143,40 +193,46 @@ enemys.prototype.getCritical = function (monsterId) {
return 0;
}
// 临界减伤计算
////// 临界减伤计算 //////
enemys.prototype.getCriticalDamage = function (monsterId) {
var c = this.getCritical(monsterId);
if (c == '???') return '???';
if (c == 0) return 0;
if (c <= 0) return 0;
var monster = core.material.enemys[monsterId];
// if (c<=0) return 0;
var last = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef,
monster.hp, monster.atk, monster.def, monster.special);
monster.hp, monster.atk, monster.def, monster.special, monster.n);
if (last == 999999999) return '???';
return last - this.calDamage(core.status.hero.atk + c, core.status.hero.def, core.status.hero.mdef,
monster.hp, monster.atk, monster.def, monster.special);
monster.hp, monster.atk, monster.def, monster.special, monster.n);
}
// 1防减伤计算
////// 1防减伤计算 //////
enemys.prototype.getDefDamage = function (monsterId) {
var monster = core.material.enemys[monsterId];
return this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef,
monster.hp, monster.atk, monster.def, monster.special) -
this.calDamage(core.status.hero.atk, core.status.hero.def + 1, core.status.hero.mdef,
monster.hp, monster.atk, monster.def, monster.special)
var nowDamage = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef,
monster.hp, monster.atk, monster.def, monster.special, monster.n);
var nextDamage = this.calDamage(core.status.hero.atk, core.status.hero.def + 1, core.status.hero.mdef,
monster.hp, monster.atk, monster.def, monster.special, monster.n);
if (nowDamage == 999999999 || nextDamage == 999999999) return "???";
return nowDamage - nextDamage;
}
enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special) {
// 魔攻
if (this.hasSpecial(mon_special,2)) hero_def = 0;
// 坚固
if (this.hasSpecial(mon_special,3) && mon_def < hero_atk - 1) mon_def = hero_atk - 1;
////// 具体的伤害计算公式 //////
enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special, n) {
if (this.hasSpecial(mon_special, 20) && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架
return 999999999; // 返回无限大
// 模仿
if (this.hasSpecial(mon_special,10)) {
mon_atk = hero_atk;
mon_def = hero_def;
}
// 魔攻
if (this.hasSpecial(mon_special,2)) hero_def = 0;
// 坚固
if (this.hasSpecial(mon_special,3) && mon_def < hero_atk - 1) mon_def = hero_atk - 1;
if (hero_atk <= mon_def) return 999999999; // 不可战斗时请直接返回999999999
var per_damage = mon_atk - hero_def;
@ -185,7 +241,7 @@ enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mo
if (this.hasSpecial(mon_special, 4)) per_damage *= 2;
if (this.hasSpecial(mon_special, 5)) per_damage *= 3;
if (this.hasSpecial(mon_special, 6)) per_damage *= 4;
if (this.hasSpecial(mon_special, 6)) per_damage *= (n||4);
var counterDamage = 0;
// 反击
@ -202,14 +258,10 @@ enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mo
var ans = damage + turn * per_damage + (turn + 1) * counterDamage;
ans -= hero_mdef;
// 魔防回血
// return ans;
// 魔防不回血
return ans <= 0 ? 0 : ans;
return core.flags.enableNegativeDamage?ans:Math.max(0, ans);
}
// 获得当前楼层的怪物列表
////// 获得当前楼层的怪物列表 //////
enemys.prototype.getCurrentEnemys = function () {
var enemys = [];
var used = {};
@ -229,6 +281,10 @@ enemys.prototype.getCurrentEnemys = function () {
mon_def=core.status.hero.def;
}
var specialText = core.enemys.getSpecialText(monsterId);
if (specialText.length>=3) specialText = "多属性...";
else specialText = specialText.join(" ");
enemys.push({
'id': monsterId,
'name': monster.name,
@ -237,7 +293,7 @@ enemys.prototype.getCurrentEnemys = function () {
'def': mon_def,
'money': monster.money,
'experience': monster.experience,
'special': core.enemys.getSpecialText(monsterId),
'special': specialText,
'damage': this.getDamage(monsterId),
'critical': this.getCritical(monsterId),
'criticalDamage': this.getCriticalDamage(monsterId),

View File

@ -2,6 +2,7 @@ function events() {
}
////// 初始化 //////
events.prototype.init = function () {
this.events = {
'battle': function (data, core, callback) {
@ -20,12 +21,11 @@ events.prototype.init = function () {
callback();
},
'changeFloor': function (data, core, callback) {
var heroLoc = null;
if (core.isset(data.event.data.loc)) {
var heroLoc = {};
if (core.isset(data.event.data.loc))
heroLoc = {'x': data.event.data.loc[0], 'y': data.event.data.loc[1]};
if (core.isset(data.event.data.direction))
heroLoc.direction = data.event.data.direction;
}
if (core.isset(data.event.data.direction))
heroLoc.direction = data.event.data.direction;
core.changeFloor(data.event.data.floorId, data.event.data.stair,
heroLoc, data.event.data.time, callback);
},
@ -46,9 +46,9 @@ events.prototype.init = function () {
}
}
// 初始化
////// 获得一个或所有系统事件类型 //////
events.prototype.getEvents = function (eventName) {
if (eventName == undefined) {
if (!core.isset(eventName)) {
return this.events;
}
return this.events[eventName];
@ -88,12 +88,14 @@ events.prototype.startGame = function (hard) {
})
}
////// 简单难度设置初始福利 //////
////// 不同难度分别设置初始属性 //////
events.prototype.setInitData = function (hard) {
if (hard=='Easy') { // 简单难度
core.setFlag('hard', 1); // 可以用flag:hard来获得当前难度
// 可以在此设置一些初始福利,比如设置初始生命值可以调用:
// core.setStatus("hp", 10000);
// 赠送一把黄钥匙可以调用
// core.setItem("yellowKey", 1);
}
if (hard=='Normal') { // 普通难度
core.setFlag('hard', 2); // 可以用flag:hard来获得当前难度
@ -103,9 +105,8 @@ events.prototype.setInitData = function (hard) {
}
}
////// 游戏结束事件 //////
////// 游戏获胜事件 //////
events.prototype.win = function(reason) {
// 获胜
core.waitHeroToStop(function() {
core.removeGlobalAnimate(0,0,true);
core.clearMap('all'); // 清空全地图
@ -117,8 +118,8 @@ events.prototype.win = function(reason) {
});
}
////// 游戏失败事件 //////
events.prototype.lose = function(reason) {
// 失败
core.waitHeroToStop(function() {
core.drawText([
"\t[结局1]你死了。\n如题。"
@ -136,7 +137,7 @@ events.prototype.afterChangeFloor = function (floorId) {
}
}
////// 实际事件的处理 //////
////// 开始执行一系列自定义事件 //////
events.prototype.doEvents = function (list, x, y, callback) {
// 停止勇士
core.waitHeroToStop(function() {
@ -152,6 +153,7 @@ events.prototype.doEvents = function (list, x, y, callback) {
});
}
////// 执行当前自定义事件列表中的下一个事件 //////
events.prototype.doAction = function() {
// 清空boxAnimate和UI层
clearInterval(core.interval.boxAnimate);
@ -162,7 +164,7 @@ events.prototype.doAction = function() {
if (core.status.event.data.list.length==0) {
if (core.isset(core.status.event.data.callback))
core.status.event.data.callback();
core.ui.closePanel(false);
core.ui.closePanel();
return;
}
@ -191,12 +193,12 @@ events.prototype.doAction = function() {
case "show": // 显示
if (core.isset(data.time) && data.time>0 && (!core.isset(data.floorId) || data.floorId==core.status.floorId)) {
core.animateBlock(data.loc[0],data.loc[1],'show', data.time, function () {
core.addBlock(data.loc[0],data.loc[1],data.floorId);
core.showBlock(data.loc[0],data.loc[1],data.floorId);
core.events.doAction();
});
}
else {
core.addBlock(data.loc[0],data.loc[1],data.floorId)
core.showBlock(data.loc[0],data.loc[1],data.floorId)
this.doAction();
}
break;
@ -286,11 +288,6 @@ events.prototype.doAction = function() {
block = block.block;
if (core.isset(block.event) && block.event.trigger=='action') {
// 触发
/*
core.status.event = {'id': 'action', 'data': {
'list': core.clone(block.event.data), 'x': block.x, 'y': block.y, 'callback': core.status.event.data.callback
}}
*/
core.status.event.data.list = core.clone(block.event.data);
core.status.event.data.x=block.x;
core.status.event.data.y=block.y;
@ -299,11 +296,21 @@ events.prototype.doAction = function() {
this.doAction();
break;
case "playSound":
var name=data.name.split(".");
if (name.length==2)
core.playSound(name[0],name[1]);
core.playSound(data.name);
this.doAction();
break;
case "playBgm":
core.playBgm(data.name);
this.doAction();
break
case "pauseBgm":
core.pauseBgm();
this.doAction();
break
case "resumeBgm":
core.resumeBgm();
this.doAction();
break
case "setValue":
try {
var value=core.calValue(data.value);
@ -330,7 +337,6 @@ events.prototype.doAction = function() {
core.status.hero.hp=0;
core.updateStatusBar();
core.events.lose('damage');
}
else {
core.updateStatusBar();
@ -354,8 +360,14 @@ events.prototype.doAction = function() {
core.events.lose(data.reason);
break;
case "function":
if (core.isset(data["function"]))
data["function"]();
var func = data["function"];
if (core.isset(func)) {
if ((typeof func == "string") && func.indexOf("function")==0) {
eval('('+func+')()');
}
else if (func instanceof Function)
func();
}
this.doAction();
break;
case "update":
@ -390,10 +402,15 @@ events.prototype.doAction = function() {
////// 往当前事件列表之前添加一个或多个事件 //////
events.prototype.insertAction = function (action) {
core.unshift(core.status.event.data.list, action)
if (core.status.event.id == null) {
this.doEvents(action);
}
else {
core.unshift(core.status.event.data.list, action)
}
}
////// 打开商店 //////
////// 打开一个全局商店 //////
events.prototype.openShop = function(shopId, needVisited) {
var shop = core.status.shops[shopId];
shop.times = shop.times || 0;
@ -435,35 +452,11 @@ events.prototype.openShop = function(shopId, needVisited) {
core.ui.drawChoices(content, choices);
}
////// 禁用一个全局商店 //////
events.prototype.disableQuickShop = function (shopId) {
core.status.shops[shopId].visited = false;
}
////// 降低难度 //////
events.prototype.decreaseHard = function() {
core.drawTip("本塔不支持降低难度!");
/*
if (core.status.hard == 0) {
core.drawTip("当前已是难度0不能再降低难度了");
return;
}
var add = 100, x=core.status.hard;
while (x<10) {
x++; add*=2;
}
core.ui.drawConfirmBox("本次操作可生命+" + add + ",确定吗?", function () {
core.status.hero.hp += add;
core.status.hard--;
core.updateStatusBar();
core.ui.closePanel();
core.drawTip("降低难度成功,生命+" + add);
}, function () {
core.ui.drawSettings(false);
});
*/
}
////// 能否使用快捷商店 //////
events.prototype.canUseQuickShop = function(shopIndex) {
if (core.isset(core.floors[core.status.floorId].canUseQuickShop) && !core.isset(core.floors[core.status.floorId].canUseQuickShop))
@ -472,9 +465,36 @@ events.prototype.canUseQuickShop = function(shopIndex) {
return null;
}
////// 检查升级事件 //////
events.prototype.checkLvUp = function () {
if (!core.flags.enableLevelUp || core.status.hero.lv>=core.firstData.levelUp.length) return;
// 计算下一个所需要的数值
var need=core.firstData.levelUp[core.status.hero.lv].need;
if (!core.isset(need)) return;
if (core.status.hero.experience>=need) {
// 升级
core.status.hero.lv++;
var effect = core.firstData.levelUp[core.status.hero.lv-1].effect;
if (typeof effect == "string") {
if (effect.indexOf("function")==0) {
eval("("+effect+")()");
}
else {
effect.split(";").forEach(function (t) {
core.doEffect(t);
});
}
}
else if (effect instanceof Function) {
effect();
}
this.checkLvUp();
}
}
////// 尝试使用道具 //////
events.prototype.useItem = function(itemId) {
core.ui.closePanel(false);
core.ui.closePanel();
if (itemId=='book') {
core.openBook(false);
@ -497,7 +517,30 @@ events.prototype.useItem = function(itemId) {
else core.drawTip("当前无法使用"+core.material.items[itemId].name);
}
/****** 打完怪物 ******/
////// 加点事件 //////
events.prototype.addPoint = function (enemy) {
var point = enemy.point;
if (!core.isset(point) || point<=0) return [];
// 加点返回一个choices事件
return [
{"type": "choices",
"choices": [
{"text": "攻击+"+(1*point), "action": [
{"type": "setValue", "name": "status:atk", "value": "status:atk+"+(1*point)}
]},
{"text": "防御+"+(2*point), "action": [
{"type": "setValue", "name": "status:def", "value": "status:def+"+(2*point)}
]},
{"text": "生命+"+(200*point), "action": [
{"type": "setValue", "name": "status:hp", "value": "status:hp+"+(200*point)}
]},
]
}
];
}
////// 战斗结束后触发的事件 //////
events.prototype.afterBattle = function(enemyId,x,y,callback) {
// 毒衰咒的处理
@ -520,50 +563,66 @@ events.prototype.afterBattle = function(enemyId,x,y,callback) {
if (core.enemys.hasSpecial(special, 17)) {
core.setFlag('hatred', parseInt(core.getFlag('hatred', 0)/2));
}
// 自爆
if (core.enemys.hasSpecial(special, 19)) {
core.status.hero.hp = 1;
}
// 增加仇恨值
core.setFlag('hatred', core.getFlag('hatred',0)+core.values.hatred);
core.updateStatusBar();
// 如果已有事件正在处理中
if (core.status.lockControl) {
if (core.isset(callback)) callback();
return;
// 事件的处理
var todo = [];
// 如果不为阻击,且该点存在,且有事件
if (!core.enemys.hasSpecial(special, 18) && core.isset(x) && core.isset(y)) {
var event = core.floors[core.status.floorId].afterBattle[x+","+y];
if (core.isset(event)) {
// 插入事件
core.unshift(todo, event);
}
}
// 如果有加点
var point = core.material.enemys[enemyId].point;
if (core.isset(point) && point>0) {
core.unshift(todo, core.events.addPoint(core.material.enemys[enemyId]));
}
// 检查处理后的事件。
var event = core.floors[core.status.floorId].afterBattle[x+","+y];
if (core.isset(event)) {
core.events.doEvents(event, x, y, callback);
// 如果事件不为空,将其插入
if (todo.length>0) {
this.insertAction(todo);
}
//继续行走
else {
// 如果已有事件正在处理中
if (core.status.event.id == null) {
core.continueAutomaticRoute();
if (core.isset(callback)) callback();
}
if (core.isset(callback)) callback();
}
/****** 开完门 ******/
////// 开一个门后触发的事件 //////
events.prototype.afterOpenDoor = function(doorId,x,y,callback) {
// 如果已有事件正在处理中
if (core.status.lockControl) {
if (core.isset(callback)) callback();
return;
var todo = [];
if (core.isset(x) && core.isset(y)) {
var event = core.floors[core.status.floorId].afterOpenDoor[x+","+y];
if (core.isset(event)) {
core.unshift(todo, event);
}
}
// 检查处理后的事件。
var event = core.floors[core.status.floorId].afterOpenDoor[x+","+y];
if (core.isset(event)) {
core.events.doEvents(event, x, y, callback);
if (todo.length>0) {
this.insertAction(todo);
}
//继续行走
else {
if (core.status.event.id == null) {
core.continueAutomaticRoute();
if (core.isset(callback)) callback();
}
if (core.isset(callback)) callback();
}
/****** 经过路障 ******/
////// 经过一个路障 //////
events.prototype.passNet = function (data) {
// 有鞋子
if (core.hasItem('shoes')) return;
@ -594,6 +653,7 @@ events.prototype.passNet = function (data) {
core.updateStatusBar();
}
////// 改变亮灯(感叹号)的事件 //////
events.prototype.changeLight = function(x, y) {
var block = core.getBlock(x, y);
if (block==null) return;
@ -610,26 +670,33 @@ events.prototype.changeLight = function(x, y) {
this.afterChangeLight(x,y);
}
// 改变灯后的事件
////// 改变,可以触发的事件 //////
events.prototype.afterChangeLight = function(x,y) {
}
// 存档事件前一刻的处理
////// 使用炸弹/圣锤后的事件 //////
events.prototype.afterUseBomb = function () {
}
////// 即将存档前可以执行的操作 //////
events.prototype.beforeSaveData = function(data) {
}
// 读档事件后,载入事件前,对数据的处理
////// 读档事件后,载入事件前,可以执行的操作 //////
events.prototype.afterLoadData = function(data) {
}
/******************************************/
/*********** 界面上的点击事件 ***************/
/******************************************/
/****************************************/
/********** 点击事件、键盘事件 ************/
/****************************************/
////// 按下Ctrl键时快捷跳过对话 //////
events.prototype.keyDownCtrl = function () {
if (core.status.event.id=='text') {
core.drawText();
@ -641,6 +708,15 @@ events.prototype.keyDownCtrl = function () {
}
}
////// 点击确认框时 //////
events.prototype.clickConfirmBox = function (x,y) {
if ((x == 4 || x == 5) && y == 7 && core.isset(core.status.event.data.yes))
core.status.event.data.yes();
if ((x == 7 || x == 8) && y == 7 && core.isset(core.status.event.data.no))
core.status.event.data.no();
}
////// 键盘操作确认框时 //////
events.prototype.keyUpConfirmBox = function (keycode) {
if (keycode==37) {
core.status.event.selection=0;
@ -663,14 +739,8 @@ events.prototype.keyUpConfirmBox = function (keycode) {
}
}
}
events.prototype.clickConfirmBox = function (x,y) {
if ((x == 4 || x == 5) && y == 7 && core.isset(core.status.event.data.yes))
core.status.event.data.yes();
if ((x == 7 || x == 8) && y == 7 && core.isset(core.status.event.data.no))
core.status.event.data.no();
}
// 正在处理事件时的点击操作...
////// 自定义事件时的点击操作 //////
events.prototype.clickAction = function (x,y) {
if (core.status.event.data.type=='text') {
@ -693,6 +763,7 @@ events.prototype.clickAction = function (x,y) {
}
}
////// 自定义事件时,按下某个键的操作 //////
events.prototype.keyDownAction = function (keycode) {
if (core.status.event.data.type=='choices') {
var data = core.status.event.data.current;
@ -712,6 +783,7 @@ events.prototype.keyDownAction = function (keycode) {
}
}
////// 自定义事件时,放开某个键的操作 //////
events.prototype.keyUpAction = function (keycode) {
if (core.status.event.data.type=='text' && (keycode==13 || keycode==32 || keycode==67)) {
this.doAction();
@ -729,37 +801,67 @@ events.prototype.keyUpAction = function (keycode) {
}
}
// 怪物手册
////// 怪物手册界面的点击操作 //////
events.prototype.clickBook = function(x,y) {
// 上一页
if ((x == 3 || x == 4) && y == 12) {
core.ui.drawEnemyBook(core.status.event.data - 1);
core.ui.drawBook(core.status.event.data - 6);
return;
}
// 下一页
if ((x == 8 || x == 9) && y == 12) {
core.ui.drawEnemyBook(core.status.event.data + 1);
core.ui.drawBook(core.status.event.data + 6);
return;
}
// 返回
if (x>=10 && x<=12 && y==12) {
core.ui.closePanel(true);
core.ui.closePanel();
return;
}
// 怪物信息
var data = core.status.event.data;
if (core.isset(data) && y<12) {
var page=parseInt(data/6);
var index=6*page+parseInt(y/2);
core.ui.drawBook(index);
core.ui.drawBookDetail(index);
}
return;
}
////// 怪物手册界面时,按下某个键的操作 //////
events.prototype.keyDownBook = function (keycode) {
if (keycode==37 || keycode==38) core.ui.drawEnemyBook(core.status.event.data - 1);
else if (keycode==39 || keycode==40) core.ui.drawEnemyBook(core.status.event.data + 1);
if (keycode==37) core.ui.drawBook(core.status.event.data-6);
if (keycode==38) core.ui.drawBook(core.status.event.data-1);
if (keycode==39) core.ui.drawBook(core.status.event.data+6);
if (keycode==40) core.ui.drawBook(core.status.event.data+1);
if (keycode==33) core.ui.drawBook(core.status.event.data-6);
if (keycode==34) core.ui.drawBook(core.status.event.data+6);
return;
}
////// 怪物手册界面时,放开某个键的操作 //////
events.prototype.keyUpBook = function (keycode) {
if (keycode==27 || keycode==88) {
core.ui.closePanel(true);
core.ui.closePanel();
return;
}
if (keycode==13 || keycode==32 || keycode==67) {
var data=core.status.event.data;
if (core.isset(data)) {
this.clickBook(6, 2*(data%6));
}
return;
}
}
// 飞行器
////// 怪物手册属性显示界面时的点击操作 //////
events.prototype.clickBookDetail = function () {
core.clearMap('data', 0, 0, 416, 416);
core.status.event.id = 'book';
}
////// 楼层传送器界面时的点击操作 //////
events.prototype.clickFly = function(x,y) {
if ((x==10 || x==11) && y==9) core.ui.drawFly(core.status.event.data-1);
if ((x==10 || x==11) && y==5) core.ui.drawFly(core.status.event.data+1);
@ -774,12 +876,14 @@ events.prototype.clickFly = function(x,y) {
return;
}
////// 楼层传送器界面时,按下某个键的操作 //////
events.prototype.keyDownFly = function (keycode) {
if (keycode==37 || keycode==38) core.ui.drawFly(core.status.event.data+1);
else if (keycode==39 || keycode==40) core.ui.drawFly(core.status.event.data-1);
return;
}
////// 楼层传送器界面时,放开某个键的操作 //////
events.prototype.keyUpFly = function (keycode) {
if (keycode==71 || keycode==27 || keycode==88)
core.ui.closePanel();
@ -788,7 +892,7 @@ events.prototype.keyUpFly = function (keycode) {
return;
}
// 商店
////// 商店界面时的点击操作 //////
events.prototype.clickShop = function(x,y) {
var shop = core.status.event.data.shop;
var choices = shop.choices;
@ -818,12 +922,7 @@ events.prototype.clickShop = function(x,y) {
// 更新属性
choice.effect.split(";").forEach(function (t) {
if (t.indexOf("status:")==0) {
eval(t.replace("status:", "core.status.hero."));
}
else if (t.indexOf("item:")==0) {
eval(t.replace("item:", "core.getItem('").replace("+=", "', ")+")");
}
core.doEffect(t);
});
core.updateStatusBar();
shop.times++;
@ -840,6 +939,7 @@ events.prototype.clickShop = function(x,y) {
}
}
////// 商店界面时,按下某个键的操作 //////
events.prototype.keyDownShop = function (keycode) {
var shop = core.status.event.data.shop;
var choices = shop.choices;
@ -855,6 +955,7 @@ events.prototype.keyDownShop = function (keycode) {
}
}
////// 商店界面时,放开某个键的操作 //////
events.prototype.keyUpShop = function (keycode) {
if (keycode==27 || keycode==88) {
if (core.status.event.data.fromList) {
@ -875,7 +976,7 @@ events.prototype.keyUpShop = function (keycode) {
return;
}
// 快捷商店
////// 快捷商店界面时的点击操作 //////
events.prototype.clickQuickShop = function(x, y) {
var shopList = core.status.shops, keys = Object.keys(shopList);
if (x >= 5 && x <= 7) {
@ -896,6 +997,7 @@ events.prototype.clickQuickShop = function(x, y) {
}
}
////// 快捷商店界面时,按下某个键的操作 //////
events.prototype.keyDownQuickShop = function (keycode) {
var shopList = core.status.shops, keys = Object.keys(shopList);
if (keycode==38) {
@ -910,6 +1012,7 @@ events.prototype.keyDownQuickShop = function (keycode) {
}
}
////// 快捷商店界面时,放开某个键的操作 //////
events.prototype.keyUpQuickShop = function (keycode) {
if (keycode==27 || keycode==75 || keycode==88) {
core.ui.closePanel();
@ -923,11 +1026,11 @@ events.prototype.keyUpQuickShop = function (keycode) {
return;
}
// 工具栏
////// 工具栏界面时的点击操作 //////
events.prototype.clickToolbox = function(x,y) {
// 返回
if (x>=10 && x<=12 && y==12) {
core.ui.closePanel(false);
core.ui.closePanel();
return;
}
var index=0;
@ -937,6 +1040,7 @@ events.prototype.clickToolbox = function(x,y) {
this.clickToolboxIndex(index);
}
////// 选择工具栏界面中某个Index后的操作 //////
events.prototype.clickToolboxIndex = function(index) {
var items = null;
var ii=index;
@ -957,6 +1061,7 @@ events.prototype.clickToolboxIndex = function(index) {
}
}
////// 工具栏界面时,按下某个键的操作 //////
events.prototype.keyDownToolbox = function (keycode) {
if (!core.isset(core.status.event.data)) return;
@ -1020,6 +1125,7 @@ events.prototype.keyDownToolbox = function (keycode) {
}
}
////// 工具栏界面时,放开某个键的操作 //////
events.prototype.keyUpToolbox = function (keycode) {
if (keycode==84 || keycode==27 || keycode==88) {
core.ui.closePanel();
@ -1033,7 +1139,7 @@ events.prototype.keyUpToolbox = function (keycode) {
}
}
// 存读档
////// 存读档界面时的点击操作 //////
events.prototype.clickSL = function(x,y) {
// 上一页
if ((x == 3 || x == 4) && y == 12) {
@ -1045,7 +1151,7 @@ events.prototype.clickSL = function(x,y) {
}
// 返回
if (x>=10 && x<=12 && y==12) {
core.ui.closePanel(false);
core.ui.closePanel();
if (!core.isPlaying()) {
core.showStartAnimate();
}
@ -1066,6 +1172,7 @@ events.prototype.clickSL = function(x,y) {
}
}
////// 存读档界面时,按下某个键的操作 //////
events.prototype.keyDownSL = function(keycode) {
if (keycode==37) { // left
core.ui.drawSLPanel(core.status.event.data - 1);
@ -1093,6 +1200,7 @@ events.prototype.keyDownSL = function(keycode) {
}
}
////// 存读档界面时,放开某个键的操作 //////
events.prototype.keyUpSL = function (keycode) {
if (keycode==27 || keycode==88 || (core.status.event.id == 'save' && keycode==83) || (core.status.event.id == 'load' && keycode==68)) {
core.ui.closePanel();
@ -1107,41 +1215,53 @@ events.prototype.keyUpSL = function (keycode) {
}
}
////// 系统设置界面时的点击操作 //////
events.prototype.clickSwitchs = function (x,y) {
if (x<5 || x>7) return;
var choices = [
"背景音乐", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单"
"背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单"
];
var topIndex = 6 - parseInt((choices.length - 1) / 2);
if (y>=topIndex && y<topIndex+choices.length) {
var selection = y-topIndex;
switch (selection) {
case 0:
if (core.musicStatus.isIOS) {
core.drawTip("iOS设备不支持播放音乐");
return;
}
core.changeSoundStatus();
core.musicStatus.bgmStatus = !core.musicStatus.bgmStatus;
if (core.musicStatus.bgmStatus)
core.resumeBgm();
else
core.pauseBgm();
core.setLocalStorage('bgmStatus', core.musicStatus.bgmStatus);
core.ui.drawSwitchs();
break;
case 1:
core.flags.battleAnimate=!core.flags.battleAnimate;
core.setLocalStorage('battleAnimate', core.flags.battleAnimate);
core.musicStatus.soundStatus = !core.musicStatus.soundStatus;
core.setLocalStorage('soundStatus', core.musicStatus.soundStatus);
core.ui.drawSwitchs();
break;
case 2:
if (!core.flags.canOpenBattleAnimate) {
core.drawTip("本塔不能开启战斗动画!");
}
else {
core.flags.battleAnimate=!core.flags.battleAnimate;
core.setLocalStorage('battleAnimate', core.flags.battleAnimate);
core.ui.drawSwitchs();
}
break;
case 3:
core.flags.displayEnemyDamage=!core.flags.displayEnemyDamage;
core.updateFg();
core.setLocalStorage('enemyDamage', core.flags.displayEnemyDamage);
core.ui.drawSwitchs();
break;
case 3:
case 4:
core.flags.displayExtraDamage=!core.flags.displayExtraDamage;
core.updateFg();
core.setLocalStorage('extraDamage', core.flags.displayExtraDamage);
core.ui.drawSwitchs();
break;
case 4:
case 5:
core.status.event.selection=0;
core.ui.drawSettings(false);
break;
@ -1149,9 +1269,10 @@ events.prototype.clickSwitchs = function (x,y) {
}
}
////// 系统设置界面时,按下某个键的操作 //////
events.prototype.keyDownSwitchs = function (keycode) {
var choices = [
"背景音乐", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单"
"背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单"
];
if (keycode==38) {
core.status.event.selection--;
@ -1165,6 +1286,7 @@ events.prototype.keyDownSwitchs = function (keycode) {
}
}
////// 系统设置界面时,放开某个键的操作 //////
events.prototype.keyUpSwitchs = function (keycode) {
if (keycode==27 || keycode==88) {
core.status.event.selection=0;
@ -1172,7 +1294,7 @@ events.prototype.keyUpSwitchs = function (keycode) {
return;
}
var choices = [
"背景音乐", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单"
"背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单"
];
if (keycode==13 || keycode==32 || keycode==67) {
var topIndex = 6 - parseInt((choices.length - 1) / 2);
@ -1181,7 +1303,7 @@ events.prototype.keyUpSwitchs = function (keycode) {
}
// 菜单栏
////// 系统菜单栏界面时的点击事件 //////
events.prototype.clickSettings = function (x,y) {
if (x<5 || x>7) return;
var choices = [
@ -1228,6 +1350,7 @@ events.prototype.clickSettings = function (x,y) {
return;
}
////// 系统菜单栏界面时,按下某个键的操作 //////
events.prototype.keyDownSettings = function (keycode) {
var choices = [
"系统设置", "快捷商店", "同步存档", "重新开始", "操作帮助", "关于本塔", "返回游戏"
@ -1244,6 +1367,7 @@ events.prototype.keyDownSettings = function (keycode) {
}
}
////// 系统菜单栏界面时,放开某个键的操作 //////
events.prototype.keyUpSettings = function (keycode) {
if (keycode==27 || keycode==88) {
core.ui.closePanel();
@ -1258,6 +1382,7 @@ events.prototype.keyUpSettings = function (keycode) {
}
}
////// 同步存档界面时的点击操作 //////
events.prototype.clickSyncSave = function (x,y) {
if (x<5 || x>7) return;
var choices = [
@ -1293,6 +1418,7 @@ events.prototype.clickSyncSave = function (x,y) {
return;
}
////// 同步存档界面时,按下某个键的操作 //////
events.prototype.keyDownSyncSave = function (keycode) {
var choices = [
"同步存档到服务器", "从服务器加载存档", "清空本地存档", "返回主菜单"
@ -1309,6 +1435,7 @@ events.prototype.keyDownSyncSave = function (keycode) {
}
}
////// 同步存档界面时,放开某个键的操作 //////
events.prototype.keyUpSyncSave = function (keycode) {
if (keycode==27 || keycode==88) {
core.status.event.selection=2;
@ -1324,11 +1451,11 @@ events.prototype.keyUpSyncSave = function (keycode) {
}
}
////// “关于”界面时的点击操作 //////
events.prototype.clickAbout = function () {
if (core.isPlaying())
core.ui.closePanel(false);
core.ui.closePanel();
else
core.showStartAnimate();
}
/*********** 点击事件 END ***************/

View File

@ -4,10 +4,13 @@
main.floors.MT0 = {
"floorId": "MT0", // 楼层唯一标识符,需要和名字完全一致
"title": "主塔 0 层", // 楼层中文名
"name": 0, // 显示在状态栏中的层数
"name": "0", // 显示在状态栏中的层数
"canFlyTo": true, // 该楼能否被楼传器飞到(不能的话在该楼也不允许使用楼传器)
"canUseQuickShop": true, // 该层是否允许使用快捷商店
"defaultGround": "ground", // 默认地面的图块IDterrains中
// "png": "bg.png", // 背景图你可以选择一张png图片来作为背景素材。详细用法请参见文档“自定义素材”中的说明。
// "color": [0,0,0,0.3], // 该层的默认画面色调。本项可不写代表无色调如果写需要是一个RGBA数组。
// "bgm": "bgm.mp3", // 到达该层后默认播放的BGM。本项可忽略。
"map": [ // 地图数据需要是13x13建议使用地图生成器来生成
],

View File

@ -4,10 +4,13 @@
main.floors.sample0 = {
"floorId": "sample0", // 楼层唯一标识符,需要和名字完全一致
"title": "样板 0 层", // 楼层中文名
"name": 0, // 显示在状态栏中的层数
"name": "0", // 显示在状态栏中的层数
"canFlyTo": true, // 该楼能否被楼传器飞到(不能的话在该楼也不允许使用楼传器)
"canUseQuickShop": true, // 该层是否允许使用快捷商店
"defaultGround": "ground", // 默认地面的图块IDterrains中
// "png": "bg.png", // 背景图你可以选择一张png图片来作为背景素材。详细用法请参见文档“自定义素材”中的说明。
// "color": [0,0,0,0.3] // 该层的默认画面色调。本项可不写代表无色调如果写需要是一个RGBA数组。
"bgm": "bgm.mp3", // 到达该层后默认播放的BGM。本项可忽略。
"map": [ // 地图数据需要是13x13建议使用地图生成器来生成
[0, 0, 220, 0, 0, 20, 87, 3, 65, 64, 44, 43, 42],
[0, 246, 0, 246, 0, 20, 0, 3, 58, 59, 60, 61, 41],
@ -16,9 +19,9 @@ main.floors.sample0 = {
[216, 247, 256, 235, 248, 6, 0, 3, 49, 50, 51, 52, 38],
[6, 6, 125, 6, 6, 6, 0, 1, 45, 46, 47, 48, 37],
[224, 254, 212, 232, 204, 5, 0, 1, 31, 32, 34, 33, 36],
[201, 205, 217, 215, 207, 5, 50, 1, 27, 28, 29, 30, 35],
[5, 5, 125, 5, 5, 5, 50, 1, 21, 22, 23, 24, 25],
[0, 0, 0, 0, 0, 0, 45, 1, 1, 1, 121, 1, 1],
[201, 205, 217, 215, 207, 5, 0, 1, 27, 28, 29, 30, 35],
[5, 5, 125, 5, 5, 5, 0, 1, 21, 22, 23, 24, 25],
[0, 0, 237, 0, 0, 0, 45, 1, 1, 1, 121, 1, 1],
[4, 4, 126, 4, 4, 4, 0, 0, 0, 0, 0, 85, 124],
[87, 11, 12, 13, 14, 4, 4, 2, 2, 2, 122, 2, 2],
[88, 89, 90, 91, 92, 93, 94, 2, 81, 82, 83, 84, 86],
@ -51,7 +54,7 @@ main.floors.sample0 = {
{"type": "hide", "time": 500}
],
"2,8": [ // 守着第一批怪物的老人
"\t[老人,magician]这些都是各种各样的怪物所有怪物的数据都在enemys.js中设置。\n\n每个怪物最多只能有一个特殊属性。",
"\t[老人,magician]这些都是各种各样的怪物所有怪物的数据都在enemys.js中设置。",
"\t[老人,magician]这批怪物分别为普通、先攻、魔攻、坚固、2连击、3连击、4连击、破甲、反击、净化。",
"\t[老人,magician]打败怪物后可触发 afterBattle 事件。\n\n有关事件的各种信息在下一层会有更为详细的说明。",
{"type": "hide", "time": 500}
@ -102,7 +105,6 @@ main.floors.sample0 = {
"炸弹是只能炸面前的怪物还是四个方向的怪物由data.js中的系统Flag所决定。\n如只能炸前方怪物则和上面的圣锤等价。\n不能被炸的怪物在enemys中可以定义可参见样板里黑衣魔王和黑暗大法师的写法。",
],
"10,4": ["“上楼”和“下楼”的目标层由 main.js 的 floorIds顺序所决定。"],
"10,3": ["十字架目前未被定义,可能需要自行实现功能。\n有关如何实现一个道具功能参见doc文档。"],
"9,2": ["该道具默认是大黄门钥匙,如需改为钥匙盒直接修改 data.js 中的系统Flag即可。"],
"10,2": ["屠龙匕首目前未被定义,可能需要自行实现功能。\n有关如何实现一个道具功能参见doc文档。"],
},

View File

@ -4,24 +4,27 @@
main.floors.sample1 = {
"floorId": "sample1", // 楼层唯一标识符,需要和名字完全一致
"title": "样板 1 层", // 楼层中文名
"name": 1, // 显示在状态栏中的层数
"name": "1", // 显示在状态栏中的层数
"canFlyTo": true, // 该楼能否被楼传器飞到(不能的话在该楼也不允许使用楼传器)
"canUseQuickShop": true, // 该层是否允许使用快捷商店
"defaultGround": "grass", // 默认地面的图块IDterrains中
"png": "bg.png", // 背景图你可以选择一张png图片来作为背景素材。详细用法请参见文档“自定义素材”中的说明。
// "color": [0,0,0,0.3] // 该层的默认画面色调。本项可不写代表无色调如果写需要是一个RGBA数组。
// "bgm": "bgm.mp3", // 到达该层后默认播放的BGM。本项可忽略。
"map": [ // 地图数据需要是13x13建议使用地图生成器来生成
[7, 131, 8, 2, 9, 130, 10, 2, 166, 165, 132, 165, 166],
[0, 0, 0, 0, 0, 0, 0, 2, 165, 164, 0, 162, 165],
[2, 2, 2, 2, 121, 2, 2, 2, 0, 0, 229, 0, 0],
[43, 33, 44, 1, 0, 0, 0, 2, 165, 161, 0, 163, 165],
[21, 22, 21, 1, 0, 0, 0, 2, 166, 165, 0, 165, 166],
[1, 245, 1, 1, 0, 87, 0, 2, 2, 2, 85, 2, 2],
[0, 246, 0, 1, 0, 0, 0, 2, 2, 221, 0, 221, 2],
[246, 0, 246, 1, 0, 0, 0, 121, 85, 0, 0, 0, 2],
[1, 246, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2],
[7, 131, 8, 152, 9, 130, 10, 152, 166, 165, 132, 165, 166],
[0, 0, 0, 0, 0, 0, 0, 152, 165, 164, 0, 162, 165],
[152, 152, 152, 152, 121, 152, 152, 152, 0, 0, 229, 0, 0],
[43, 33, 44, 151, 0, 0, 0, 152, 165, 161, 0, 163, 165],
[21, 22, 21, 151, 0, 0, 0, 152, 166, 165, 0, 165, 166],
[151, 245, 151, 151, 0, 87, 0, 152, 152, 152, 85, 153, 153],
[0, 246, 0, 151, 0, 0, 0, 152, 152, 221, 0, 221, 153],
[246, 0, 246, 151, 0, 0, 0, 121, 85, 0, 0, 0, 153],
[151, 246, 151, 151, 0, 153, 153, 153, 153, 153, 153, 153, 153],
[0, 0, 0, 0, 0, 0, 0, 164, 0, 0, 163, 0, 0],
[1, 1, 1, 1, 0, 3, 0, 0, 0, 162, 0, 161, 0],
[1, 0, 123, 1, 0, 3, 124, 0, 121, 0, 122, 0, 126],
[1, 0, 0, 1, 88, 3, 86, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 0, 20, 0, 0, 0, 162, 0, 161, 0],
[1, 0, 123, 1, 0, 20, 124, 0, 121, 0, 122, 0, 126],
[1, 0, 0, 1, 88, 20, 86, 0, 0, 0, 0, 0, 0],
],
"firstArrive": [ // 第一次到该楼层触发的事件
@ -31,8 +34,9 @@ main.floors.sample1 = {
"4,10": [ // 走到中间时的提示
"\t[样板提示]本层楼将会对各类事件进行介绍。",
"左边是一个仿50层的陷阱做法上方是商店、快捷商店的使用方法右上是一个典型的杀怪开门的例子右下是各类可能的NPC事件。",
"本样板目前支持的事件列表大致有:\ntext: 显示一段文字(比如你现在正在看到的)\ntip: 左上角显示提示\nshow: 使一个事件有效(可见、可被交互)\nhide: 使一个事件失效(不可见、不可被交互)\ntrigger: 触发另一个地点的事件\nbattle: 强制和某怪物战斗\nopenDoor: 无需钥匙开门(例如机关门、暗墙)\nopenShop: 打开一个全局商店\ndisableShop: 禁用一个全局商店\nchangeFloor: 传送勇士到某层某位置\nchangePos: 传送勇士到当层某位置;转向\nsetFg: 更改画面色调",
"move: 移动事件效果\nmoveHero: 移动勇士效果\nplaySound: 播放某个音频\nif: 条件判断\nchoices: 提供选项\nsetValue: 设置勇士属性道具,或某个变量/flag\nupdate: 更新状态栏和地图显伤\nwin: 获得胜利(游戏通关)\nlose: 游戏失败\nsleep: 等待多少毫秒\nexit: 立刻结束当前事件\nrevisit: 立刻结束事件并重新触发\nfunction: 自定义JS脚本\n更多支持的事件还在编写中欢迎您宝贵的意见。",
"本样板目前支持的事件列表大致有:\ntext: 显示一段文字(比如你现在正在看到的)\ntip: 左上角显示提示\nshow: 使一个事件有效(可见、可被交互)\nhide: 使一个事件失效(不可见、不可被交互)\ntrigger: 触发另一个地点的事件\nbattle: 强制和某怪物战斗\nopenDoor: 无需钥匙开门(例如机关门、暗墙)",
"openShop: 打开一个全局商店\ndisableShop: 禁用一个全局商店\nchangeFloor: 传送勇士到某层某位置\nchangePos: 传送勇士到当层某位置;转向\nsetFg: 更改画面色调\nmove: 移动事件效果\nmoveHero: 移动勇士效果\nplayBgm: 播放某个背景音乐\npauseBgm: 暂停背景音乐\nresumeBgm: 恢复背景音乐的播放\nplaySound: 播放某个音频",
"if: 条件判断\nchoices: 提供选项\nsetValue: 设置勇士属性道具,或某个变量/flag\nupdate: 更新状态栏和地图显伤\nwin: 获得胜利(游戏通关)\nlose: 游戏失败\nsleep: 等待多少毫秒\nexit: 立刻结束当前事件\nrevisit: 立刻结束事件并重新触发\nfunction: 自定义JS脚本\n\n更多支持的事件还在编写中欢迎您宝贵的意见。",
"有关各事件的样例可参见本层一些NPC的写法。\n所有事件样例本层都有介绍。\n\n一个自定义事件处理完后需要调用{\"type\": \"hide\"}该事件才不会再次出现。",
{"type": "hide"}
],
@ -273,7 +277,7 @@ main.floors.sample1 = {
},
"changeFloor": { // 楼层转换事件该事件不能和上面的events有冲突同位置点否则会被覆盖
"4,12": {"floorId": "sample0", "loc": [6,0]}, // 由于楼下有多个上楼梯,所以需指定位置而不是简单地写"stair": "upFloor"
"5,5": {"floorId": "sample2", "stair": "downFloor"}
"5,5": {"floorId": "sample2", "stair": "downFloor", "direction": "up"}
},
"afterBattle": { // 战斗后可能触发的事件列表
"9,6": [ // 初级卫兵1

View File

@ -4,10 +4,13 @@
main.floors.sample2 = {
"floorId": "sample2", // 楼层唯一标识符,需要和名字完全一致
"title": "主塔 40 层", // 楼层中文名
"name": 40, // 显示在状态栏中的层数
"name": "40", // 显示在状态栏中的层数
"canFlyTo": false, // 该楼能否被楼传器飞到(不能的话在该楼也不允许使用楼传器)
"canUseQuickShop": true, // 该层是否允许使用快捷商店
"defaultGround": "soil", // 默认地面的图块IDterrains中
"defaultGround": "snowGround", // 默认地面的图块IDterrains中
// "png": "bg.png", // 背景图你可以选择一张png图片来作为背景素材。详细用法请参见文档“自定义素材”中的说明。
"color": [255,0,0,0.3], // 该层的默认画面色调。本项可不写代表无色调如果写需要是一个RGBA数组。
"bgm": "qianjin.mid", // 到达该层后默认播放的BGM。本项可忽略。
"map": [ // 地图数据需要是13x13建议使用地图生成器来生成
[5, 5, 5, 5, 5, 5, 87, 5, 5, 5, 5, 5, 5],
[5, 4, 4, 4, 4, 1, 0, 1, 4, 4, 4, 4, 5],
@ -199,10 +202,26 @@ main.floors.sample2 = {
{"type": "trigger", "loc": [6,6]} // 立刻触发妖精事件
]
},
"4,3": {"trigger":"action","enable":false}, // 四个角的大法师添加trigger:action可避免该点出现显伤
"8,3": {"trigger":"action","enable":false}, // 四个角的大法师
"4,6": {"trigger":"action","enable":false}, // 四个角的大法师
"8,6": {"trigger":"action","enable":false}, // 四个角的大法师
"4,3": { // 四个角的大法师,
"trigger": "action",
"displayDamage": false,
"enable":false
},
"8,3": { // 四个角的大法师,
"trigger": "action",
"displayDamage": false,
"enable":false
},
"4,6": { // 四个角的大法师,
"trigger": "action",
"displayDamage": false,
"enable":false
},
"8,6": { // 四个角的大法师,
"trigger": "action",
"displayDamage": false,
"enable":false
},
"6,6": { // 妖精
"enable":false, // 初始时禁用状态

View File

@ -2,6 +2,7 @@ function items() {
}
////// 初始化 //////
items.prototype.init = function () {
this.items = {
// 钥匙
@ -36,7 +37,7 @@ items.prototype.init = function () {
'fly': {'cls': 'constants', 'name': '楼层传送器', 'text': '可以自由往来去过的楼层'},
'coin': {'cls': 'constants', 'name': '幸运金币', 'text': '持有时打败怪物可得双倍金币'},
'snow': {'cls': 'constants', 'name': '冰冻徽章', 'text': '可以将四周的熔岩变成平地'},
'cross': {'cls': 'constants', 'name': '十字架', 'text': '该道具尚未被定义'},
'cross': {'cls': 'constants', 'name': '十字架', 'text': '持有后无视怪物的无敌属性'},
'knife': {'cls': 'constants', 'name': '屠龙匕首', 'text': '该道具尚未被定义'},
'shoes': {'cls': 'constants', 'name': '绿鞋', 'text': '持有时无视负面地形'},
@ -59,7 +60,7 @@ items.prototype.init = function () {
}
}
// 初始化道具
////// 获得所有道具 //////
items.prototype.getItems = function () {
// 大黄门钥匙?钥匙盒?
if (core.flags.bigKeyIsBox)
@ -74,7 +75,7 @@ items.prototype.getItems = function () {
main.instance.items = new items();
////// “即捡即用类”道具的使用效果 //////
items.prototype.getItemEffect = function(itemId, itemNum) {
var itemCls = core.material.items[itemId].cls;
// 消耗品
@ -115,6 +116,7 @@ items.prototype.getItemEffect = function(itemId, itemNum) {
}
}
////// “即捡即用类”道具的文字提示 //////
items.prototype.getItemEffectTip = function(itemId) {
if (itemId === 'redJewel') return ",攻击+"+core.values.redJewel;
if (itemId === 'blueJewel') return ",防御+"+core.values.blueJewel;
@ -140,14 +142,12 @@ items.prototype.getItemEffectTip = function(itemId) {
return "";
}
////// 使用道具 //////
items.prototype.useItem = function (itemId) {
// 使用道具
if (!this.canUseItem(itemId)) return;
var itemCls = core.material.items[itemId].cls;
if (itemId=='book') core.ui.drawEnemyBook(1);
if (itemId=='book') core.ui.drawBook(0);
if (itemId=='fly') core.ui.drawFly(core.status.hero.flyRange.indexOf(core.status.floorId));
if (itemId == 'earthquake' || itemId == 'bomb' || itemId == 'pickaxe' || itemId=='icePickaxe'
|| itemId == 'snow' || itemId == 'hammer' || itemId=='bigKey') {
@ -157,6 +157,9 @@ items.prototype.useItem = function (itemId) {
core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop');
core.updateFg();
core.drawTip(core.material.items[itemId].name + "使用成功");
if (itemId == 'bomb' || itemId == 'hammer')
core.events.afterUseBomb();
});
}
if (itemId == 'centerFly') {
@ -182,9 +185,11 @@ items.prototype.useItem = function (itemId) {
if (itemId == 'curseWine') core.setFlag('curse', false);
if (itemId == 'superWine') {
core.setFlag('poison', false);
core.setFlag('weak', false);
core.status.hero.atk += core.values.weakValue;
core.status.hero.def += core.values.weakValue;
if (core.hasFlag('weak')) {
core.setFlag('weak', false);
core.status.hero.atk += core.values.weakValue;
core.status.hero.def += core.values.weakValue;
}
core.setFlag('curse', false);
}
core.updateStatusBar();
@ -195,12 +200,11 @@ items.prototype.useItem = function (itemId) {
delete core.status.hero.items[itemCls][itemId];
}
////// 当前能否使用道具 //////
items.prototype.canUseItem = function (itemId) {
// 没有道具
if (!core.hasItem(itemId)) return false;
var itemCls = core.material.items[itemId].cls;
if (itemId == 'book') return true;
if (itemId == 'fly') return core.status.hero.flyRange.indexOf(core.status.floorId)>=0;
if (itemId == 'pickaxe') {

View File

@ -1,6 +1,7 @@
function maps() {}
maps.prototype.init = function() {}
////// 加载某个楼层(从剧本或存档中) //////
maps.prototype.loadFloor = function (floorId, map) {
var floor = core.floors[floorId];
var content = {};
@ -13,7 +14,7 @@ maps.prototype.loadFloor = function (floorId, map) {
for (var i = 0; i < 13; i++) {
for (var j = 0; j < 13; j++) {
var block = this.getBlock(j, i, map[i][j]);
if (block.event != undefined) {
if (core.isset(block.event)) {
if (block.event.cls == 'enemys' && block.event.trigger==undefined) {
block.event.trigger = 'battle';
}
@ -34,7 +35,7 @@ maps.prototype.loadFloor = function (floorId, map) {
}
}
}
this.addEvent(block,j,i,floor.events[j+","+i],floor.defaultGround || "ground")
this.addEvent(block,j,i,floor.events[j+","+i])
this.addChangeFloor(block,j,i,floor.changeFloor[j+","+i]);
if (core.isset(block.event)) blocks.push(block);
}
@ -44,6 +45,7 @@ maps.prototype.loadFloor = function (floorId, map) {
return content;
}
////// 数字和ID的对应关系 //////
maps.prototype.getBlock = function (x, y, id) {
var enable=null;
id = ""+id;
@ -61,6 +63,8 @@ maps.prototype.getBlock = function (x, y, id) {
var tmp = {'x': x, 'y': y, 'id': id};
if (enable!=null) tmp.enable = enable;
////////////////////////// 地形部分 //////////////////////////
// 0-20 地形
if (id == 1) tmp.event = {'cls': 'terrains', 'id': 'yellowWall'}; // 黄墙
if (id == 2) tmp.event = {'cls': 'terrains', 'id': 'whiteWall'}; // 白墙
@ -77,15 +81,17 @@ maps.prototype.getBlock = function (x, y, id) {
if (id == 13) tmp.event = {'cls': 'animates', 'id': 'weakNet', 'noPass': false, 'trigger': 'passNet'}; // 衰网
if (id == 14) tmp.event = {'cls': 'animates', 'id': 'curseNet', 'noPass': false, 'trigger': 'passNet'}; // 咒网
if (id == 15) tmp.event = {'cls': 'animates', 'id': 'water', 'noPass': true}; // 水
// 在这里添加更多地形
// 如果空位不足可以从180以后开始继续放只要不和现有的数字冲突即可
// Autotile
if (id == 20) tmp.event = {'cls': 'autotile', 'id': 'autotile', 'noPass': true}; // autotile
// 更多的autotile从151到160,只要不和现有的数字冲突即可
// 更多的autotile从151到160,只要不和现有的数字冲突即可
if (id == 151) tmp.event = {'cls': 'autotile', 'id': 'autotile1', 'noPass': true};
if (id == 152) tmp.event = {'cls': 'autotile', 'id': 'autotile2', 'noPass': true};
if (id == 153) tmp.event = {'cls': 'autotile', 'id': 'autotile3', 'noPass': true};
////////////////////////// 物品部分 //////////////////////////
// 21-80 物品
if (id == 21) tmp.event = {'cls': 'items', 'id': 'yellowKey'}; // 黄钥匙
@ -134,6 +140,9 @@ maps.prototype.getBlock = function (x, y, id) {
if (id == 64) tmp.event = {'cls': 'items', 'id': 'shoes'} // 绿鞋
if (id == 65) tmp.event = {'cls': 'items', 'id': 'hammer'} // 圣锤
////////////////////////// 门、楼梯、传送点部分 //////////////////////////
// 81-100 门
if (id == 81) tmp.event = {'cls': 'terrains', 'id': 'yellowDoor', 'trigger': 'openDoor'}; // 黄门
if (id == 82) tmp.event = {'cls': 'terrains', 'id': 'blueDoor', 'trigger': 'openDoor'}; // 蓝门
@ -151,6 +160,8 @@ maps.prototype.getBlock = function (x, y, id) {
if (id == 94) tmp.event = {'cls': 'animates', 'id': 'rightPortal', 'noPass': false}; // 右箭头
////////////////////////// NPC部分 //////////////////////////
// 121-150 NPC
if (id == 121) tmp.event = {'cls': 'npcs', 'id': 'man'};
if (id == 122) tmp.event = {'cls': 'npcs', 'id': 'woman'};
@ -165,6 +176,8 @@ maps.prototype.getBlock = function (x, y, id) {
if (id == 131) tmp.event = {'cls': 'npcs', 'id': 'blueShop'};
if (id == 132) tmp.event = {'cls': 'npcs', 'id': 'princess'};
////////////////////////// 其他部分 //////////////////////////
// 161-200 其他(单向箭头、灯、箱子等等)
if (id == 161) tmp.event = {'cls': 'terrains', 'id': 'arrowUp', 'noPass': false}; // 单向上箭头
if (id == 162) tmp.event = {'cls': 'terrains', 'id': 'arrowDown', 'noPass': false}; // 单向下箭头
@ -173,6 +186,9 @@ maps.prototype.getBlock = function (x, y, id) {
if (id == 165) tmp.event = {'cls': 'terrains', 'id': 'light', 'trigger': 'changeLight', 'noPass': false}; // 灯
if (id == 166) tmp.event = {'cls': 'terrains', 'id': 'darkLight', 'noPass': true}; // 暗灯
////////////////////////// 怪物部分 //////////////////////////
// 201-300 怪物
if (id == 201) tmp.event = {'cls': 'enemys', 'id': 'greenSlime'};
if (id == 202) tmp.event = {'cls': 'enemys', 'id': 'redSlime'};
@ -235,13 +251,17 @@ maps.prototype.getBlock = function (x, y, id) {
if (id == 259) tmp.event = {'cls': 'enemys', 'id': 'darkFairy'};
if (id == 260) tmp.event = {'cls': 'enemys', 'id': 'greenKnight'};
////////////////////////// 待定... //////////////////////////
// 目前ID暂时不要超过400
return tmp;
}
maps.prototype.addEvent = function (block, x, y, event, ground) {
////// 向该楼层添加剧本的自定义事件 //////
maps.prototype.addEvent = function (block, x, y, event) {
if (!core.isset(event)) return;
if (!core.isset(block.event)) { // 本身是空地?
block.event = {'cls': 'terrains', 'id': ground, 'noPass': false};
block.event = {'cls': 'terrains', 'id': 'none', 'noPass': false};
}
// event是字符串或数组
if (typeof event == "string") {
@ -253,6 +273,10 @@ maps.prototype.addEvent = function (block, x, y, event, ground) {
if (!core.isset(event.data))
event.data = [];
// 覆盖noPass
if (core.isset(event.noPass))
block.event.noPass = event.noPass;
// 覆盖enable
if (!core.isset(block.enable) && core.isset(event.enable)) {
block.enable=event.enable;
@ -273,11 +297,13 @@ maps.prototype.addEvent = function (block, x, y, event, ground) {
}
}
////// 向该楼层添加剧本的楼层转换事件 //////
maps.prototype.addChangeFloor = function (block, x, y, event, ground) {
if (!core.isset(event)) return;
this.addEvent(block, x, y, {"trigger": "changeFloor", "data": event}, ground);
}
////// 初始化所有地图 //////
maps.prototype.initMaps = function (floorIds) {
var maps = {};
for (var i=0;i<floorIds.length;i++) {
@ -287,6 +313,7 @@ maps.prototype.initMaps = function (floorIds) {
return maps;
}
////// 将当前地图重新变成数字,以便于存档 //////
maps.prototype.save = function(maps, floorId) {
if (!core.isset(floorId)) {
var map = {};
@ -316,6 +343,7 @@ maps.prototype.save = function(maps, floorId) {
return blocks;
}
////// 将存档中的地图信息重新读取出来 //////
maps.prototype.load = function (data, floorId) {
if (floorId == undefined) {
var map = {};
@ -327,4 +355,30 @@ maps.prototype.load = function (data, floorId) {
return this.loadFloor(floorId, data[floorId]);
}
////// 将当前地图重新变成二维数组形式 //////
maps.prototype.getMapArray = function (maps, floorId){
if (!core.isset(floorId)) {
var map = {};
for (var id in maps) {
map[id] = this.getMapArray(maps, id);
}
return map;
}
var thisFloor = maps[floorId];
var blocks = [];
for (var x=0;x<13;x++) {
blocks[x]=[];
for (var y=0;y<13;y++) {
blocks[x].push(0);
}
}
thisFloor.blocks.forEach(function (block) {
if (!(core.isset(block.enable) && !block.enable))
blocks[block.y][block.x] = block.id;
});
return blocks;
}
main.instance.maps = new maps();

701
libs/thirdparty/mid.js vendored Normal file
View File

@ -0,0 +1,701 @@
var sampleRate = 44100; /* hard-coded in Flash player */
function AudioPlayer(context, generator, loop) {
// Uses Webkit Web Audio API if available
sampleRate = context.sampleRate;
var channelCount = 2;
var bufferSize = 4096*4; // Higher for less gitches, lower for less latency
var node = context.createScriptProcessor(bufferSize, 0, channelCount);
node.onaudioprocess = function(e) { process(e) };
function process(e) {
if (generator.finished) {
if (loop) {
generator.reset();
generator.finished = false;
}
else {
node.disconnect();
return;
}
}
var dataLeft = e.outputBuffer.getChannelData(0);
var dataRight = e.outputBuffer.getChannelData(1);
var generate = generator.generate(bufferSize);
for (var i = 0; i < bufferSize; ++i) {
dataLeft[i] = generate[i*2];
dataRight[i] = generate[i*2+1];
}
}
// start
// node.connect(context.destination);
return {
'play': function () {
node.connect(context.destination);
},
'pause': function() {
node.disconnect();
}
}
}
/*
class to parse the .mid file format
(depends on stream.js)
*/
function MidiFile(data) {
function readChunk(stream) {
var id = stream.read(4);
var length = stream.readInt32();
return {
'id': id,
'length': length,
'data': stream.read(length)
};
}
var lastEventTypeByte;
function readEvent(stream) {
var event = {};
event.deltaTime = stream.readVarInt();
var eventTypeByte = stream.readInt8();
if ((eventTypeByte & 0xf0) == 0xf0) {
/* system / meta event */
if (eventTypeByte == 0xff) {
/* meta event */
event.type = 'meta';
var subtypeByte = stream.readInt8();
var length = stream.readVarInt();
switch(subtypeByte) {
case 0x00:
event.subtype = 'sequenceNumber';
if (length != 2) throw "Expected length for sequenceNumber event is 2, got " + length;
event.number = stream.readInt16();
return event;
case 0x01:
event.subtype = 'text';
event.text = stream.read(length);
return event;
case 0x02:
event.subtype = 'copyrightNotice';
event.text = stream.read(length);
return event;
case 0x03:
event.subtype = 'trackName';
event.text = stream.read(length);
return event;
case 0x04:
event.subtype = 'instrumentName';
event.text = stream.read(length);
return event;
case 0x05:
event.subtype = 'lyrics';
event.text = stream.read(length);
return event;
case 0x06:
event.subtype = 'marker';
event.text = stream.read(length);
return event;
case 0x07:
event.subtype = 'cuePoint';
event.text = stream.read(length);
return event;
case 0x20:
event.subtype = 'midiChannelPrefix';
if (length != 1) throw "Expected length for midiChannelPrefix event is 1, got " + length;
event.channel = stream.readInt8();
return event;
case 0x2f:
event.subtype = 'endOfTrack';
if (length != 0) throw "Expected length for endOfTrack event is 0, got " + length;
return event;
case 0x51:
event.subtype = 'setTempo';
if (length != 3) throw "Expected length for setTempo event is 3, got " + length;
event.microsecondsPerBeat = (
(stream.readInt8() << 16)
+ (stream.readInt8() << 8)
+ stream.readInt8()
)
return event;
case 0x54:
event.subtype = 'smpteOffset';
if (length != 5) throw "Expected length for smpteOffset event is 5, got " + length;
var hourByte = stream.readInt8();
event.frameRate = {
0x00: 24, 0x20: 25, 0x40: 29, 0x60: 30
}[hourByte & 0x60];
event.hour = hourByte & 0x1f;
event.min = stream.readInt8();
event.sec = stream.readInt8();
event.frame = stream.readInt8();
event.subframe = stream.readInt8();
return event;
case 0x58:
event.subtype = 'timeSignature';
if (length != 4) throw "Expected length for timeSignature event is 4, got " + length;
event.numerator = stream.readInt8();
event.denominator = Math.pow(2, stream.readInt8());
event.metronome = stream.readInt8();
event.thirtyseconds = stream.readInt8();
return event;
case 0x59:
event.subtype = 'keySignature';
if (length != 2) throw "Expected length for keySignature event is 2, got " + length;
event.key = stream.readInt8(true);
event.scale = stream.readInt8();
return event;
case 0x7f:
event.subtype = 'sequencerSpecific';
event.data = stream.read(length);
return event;
default:
// console.log("Unrecognised meta event subtype: " + subtypeByte);
event.subtype = 'unknown'
event.data = stream.read(length);
return event;
}
event.data = stream.read(length);
return event;
} else if (eventTypeByte == 0xf0) {
event.type = 'sysEx';
var length = stream.readVarInt();
event.data = stream.read(length);
return event;
} else if (eventTypeByte == 0xf7) {
event.type = 'dividedSysEx';
var length = stream.readVarInt();
event.data = stream.read(length);
return event;
} else {
throw "Unrecognised MIDI event type byte: " + eventTypeByte;
}
} else {
/* channel event */
var param1;
if ((eventTypeByte & 0x80) == 0) {
/* running status - reuse lastEventTypeByte as the event type.
eventTypeByte is actually the first parameter
*/
param1 = eventTypeByte;
eventTypeByte = lastEventTypeByte;
} else {
param1 = stream.readInt8();
lastEventTypeByte = eventTypeByte;
}
var eventType = eventTypeByte >> 4;
event.channel = eventTypeByte & 0x0f;
event.type = 'channel';
switch (eventType) {
case 0x08:
event.subtype = 'noteOff';
event.noteNumber = param1;
event.velocity = stream.readInt8();
return event;
case 0x09:
event.noteNumber = param1;
event.velocity = stream.readInt8();
if (event.velocity == 0) {
event.subtype = 'noteOff';
} else {
event.subtype = 'noteOn';
}
return event;
case 0x0a:
event.subtype = 'noteAftertouch';
event.noteNumber = param1;
event.amount = stream.readInt8();
return event;
case 0x0b:
event.subtype = 'controller';
event.controllerType = param1;
event.value = stream.readInt8();
return event;
case 0x0c:
event.subtype = 'programChange';
event.programNumber = param1;
return event;
case 0x0d:
event.subtype = 'channelAftertouch';
event.amount = param1;
return event;
case 0x0e:
event.subtype = 'pitchBend';
event.value = param1 + (stream.readInt8() << 7);
return event;
default:
throw "Unrecognised MIDI event type: " + eventType
}
}
}
stream = Stream(data);
var headerChunk = readChunk(stream);
if (headerChunk.id != 'MThd' || headerChunk.length != 6) {
throw "Bad .mid file - header not found";
}
var headerStream = Stream(headerChunk.data);
var formatType = headerStream.readInt16();
var trackCount = headerStream.readInt16();
var timeDivision = headerStream.readInt16();
if (timeDivision & 0x8000) {
throw "Expressing time division in SMTPE frames is not supported yet"
} else {
ticksPerBeat = timeDivision;
}
var header = {
'formatType': formatType,
'trackCount': trackCount,
'ticksPerBeat': ticksPerBeat
}
var tracks = [];
for (var i = 0; i < header.trackCount; i++) {
tracks[i] = [];
var trackChunk = readChunk(stream);
if (trackChunk.id != 'MTrk') {
throw "Unexpected chunk - expected MTrk, got "+ trackChunk.id;
}
var trackStream = Stream(trackChunk.data);
while (!trackStream.eof()) {
var event = readEvent(trackStream);
tracks[i].push(event);
//console.log(event);
}
}
return {
'header': header,
'tracks': tracks
}
}
function Replayer(midiFile, synth) {
var trackStates = [];
var beatsPerMinute = 120;
var ticksPerBeat = midiFile.header.ticksPerBeat;
var channelCount = 16;
var channels = [];
var nextEventInfo;
var samplesToNextEvent;
function Channel() {
var generatorsByNote = {};
var currentProgram = PianoProgram;
function noteOn(note, velocity) {
if (generatorsByNote[note] && !generatorsByNote[note].released) {
/* playing same note before releasing the last one. BOO */
generatorsByNote[note].noteOff(); /* TODO: check whether we ought to be passing a velocity in */
}
generator = currentProgram.createNote(note, velocity);
synth.addGenerator(generator);
generatorsByNote[note] = generator;
}
function noteOff(note, velocity) {
if (generatorsByNote[note] && !generatorsByNote[note].released) {
generatorsByNote[note].noteOff(velocity);
}
}
function setProgram(programNumber) {
currentProgram = PROGRAMS[programNumber] || PianoProgram;
}
return {
'noteOn': noteOn,
'noteOff': noteOff,
'setProgram': setProgram
}
}
function getNextEvent() {
var ticksToNextEvent = null;
var nextEventTrack = null;
var nextEventIndex = null;
for (var i = 0; i < trackStates.length; i++) {
if (
trackStates[i].ticksToNextEvent != null
&& (ticksToNextEvent == null || trackStates[i].ticksToNextEvent < ticksToNextEvent)
) {
ticksToNextEvent = trackStates[i].ticksToNextEvent;
nextEventTrack = i;
nextEventIndex = trackStates[i].nextEventIndex;
}
}
if (nextEventTrack != null) {
/* consume event from that track */
var nextEvent = midiFile.tracks[nextEventTrack][nextEventIndex];
if (midiFile.tracks[nextEventTrack][nextEventIndex + 1]) {
trackStates[nextEventTrack].ticksToNextEvent += midiFile.tracks[nextEventTrack][nextEventIndex + 1].deltaTime;
} else {
trackStates[nextEventTrack].ticksToNextEvent = null;
}
trackStates[nextEventTrack].nextEventIndex += 1;
/* advance timings on all tracks by ticksToNextEvent */
for (var i = 0; i < trackStates.length; i++) {
if (trackStates[i].ticksToNextEvent != null) {
trackStates[i].ticksToNextEvent -= ticksToNextEvent
}
}
nextEventInfo = {
'ticksToEvent': ticksToNextEvent,
'event': nextEvent,
'track': nextEventTrack
}
var beatsToNextEvent = ticksToNextEvent / ticksPerBeat;
var secondsToNextEvent = beatsToNextEvent / (beatsPerMinute / 60);
samplesToNextEvent += secondsToNextEvent * synth.sampleRate;
} else {
nextEventInfo = null;
samplesToNextEvent = null;
self.finished = true;
}
}
function generate(samples) {
var data = new Array(samples*2);
var samplesRemaining = samples;
var dataOffset = 0;
while (true) {
if (samplesToNextEvent != null && samplesToNextEvent <= samplesRemaining) {
/* generate samplesToNextEvent samples, process event and repeat */
var samplesToGenerate = Math.ceil(samplesToNextEvent);
if (samplesToGenerate > 0) {
synth.generateIntoBuffer(samplesToGenerate, data, dataOffset);
dataOffset += samplesToGenerate * 2;
samplesRemaining -= samplesToGenerate;
samplesToNextEvent -= samplesToGenerate;
}
handleEvent();
getNextEvent();
} else {
/* generate samples to end of buffer */
if (samplesRemaining > 0) {
synth.generateIntoBuffer(samplesRemaining, data, dataOffset);
samplesToNextEvent -= samplesRemaining;
}
break;
}
}
return data;
}
function handleEvent() {
var event = nextEventInfo.event;
switch (event.type) {
case 'meta':
switch (event.subtype) {
case 'setTempo':
beatsPerMinute = 60000000 / event.microsecondsPerBeat
}
break;
case 'channel':
switch (event.subtype) {
case 'noteOn':
channels[event.channel].noteOn(event.noteNumber, event.velocity);
break;
case 'noteOff':
channels[event.channel].noteOff(event.noteNumber, event.velocity);
break;
case 'programChange':
//console.log('program change to ' + event.programNumber);
channels[event.channel].setProgram(event.programNumber);
break;
}
break;
}
}
function reset() {
for (var i = 0; i < midiFile.tracks.length; i++) {
trackStates[i] = {
'nextEventIndex': 0,
'ticksToNextEvent': (
midiFile.tracks[i].length ?
midiFile.tracks[i][0].deltaTime :
null
)
};
}
for (var i = 0; i < channelCount; i++) {
channels[i] = Channel();
}
samplesToNextEvent = 0;
getNextEvent();
}
reset();
var self = {
'reset': reset,
'generate': generate,
'finished': false
}
return self;
}
/* Wrapper for accessing strings through sequential reads */
function Stream(str) {
var position = 0;
function read(length) {
var result = str.substr(position, length);
position += length;
return result;
}
/* read a big-endian 32-bit integer */
function readInt32() {
var result = (
(str.charCodeAt(position) << 24)
+ (str.charCodeAt(position + 1) << 16)
+ (str.charCodeAt(position + 2) << 8)
+ str.charCodeAt(position + 3));
position += 4;
return result;
}
/* read a big-endian 16-bit integer */
function readInt16() {
var result = (
(str.charCodeAt(position) << 8)
+ str.charCodeAt(position + 1));
position += 2;
return result;
}
/* read an 8-bit integer */
function readInt8(signed) {
var result = str.charCodeAt(position);
if (signed && result > 127) result -= 256;
position += 1;
return result;
}
function eof() {
return position >= str.length;
}
/* read a MIDI-style variable-length integer
(big-endian value in groups of 7 bits,
with top bit set to signify that another byte follows)
*/
function readVarInt() {
var result = 0;
while (true) {
var b = readInt8();
if (b & 0x80) {
result += (b & 0x7f);
result <<= 7;
} else {
/* b is the last byte */
return result + b;
}
}
}
return {
'eof': eof,
'read': read,
'readInt32': readInt32,
'readInt16': readInt16,
'readInt8': readInt8,
'readVarInt': readVarInt
}
}
function SineGenerator(freq) {
var self = {'alive': true};
var period = sampleRate / freq;
var t = 0;
self.generate = function(buf, offset, count) {
for (; count; count--) {
var phase = t / period;
var result = Math.sin(phase * 2 * Math.PI);
buf[offset++] += result;
buf[offset++] += result;
t++;
}
}
return self;
}
function SquareGenerator(freq, phase) {
var self = {'alive': true};
var period = sampleRate / freq;
var t = 0;
self.generate = function(buf, offset, count) {
for (; count; count--) {
var result = ( (t / period) % 1 > phase ? 1 : -1);
buf[offset++] += result;
buf[offset++] += result;
t++;
}
}
return self;
}
function ADSRGenerator(child, attackAmplitude, sustainAmplitude, attackTimeS, decayTimeS, releaseTimeS) {
var self = {'alive': true}
var attackTime = sampleRate * attackTimeS;
var decayTime = sampleRate * (attackTimeS + decayTimeS);
var decayRate = (attackAmplitude - sustainAmplitude) / (decayTime - attackTime);
var releaseTime = null; /* not known yet */
var endTime = null; /* not known yet */
var releaseRate = sustainAmplitude / (sampleRate * releaseTimeS);
var t = 0;
self.noteOff = function() {
if (self.released) return;
releaseTime = t;
self.released = true;
endTime = releaseTime + sampleRate * releaseTimeS;
}
self.generate = function(buf, offset, count) {
if (!self.alive) return;
var input = new Array(count * 2);
for (var i = 0; i < count*2; i++) {
input[i] = 0;
}
child.generate(input, 0, count);
childOffset = 0;
while(count) {
if (releaseTime != null) {
if (t < endTime) {
/* release */
while(count && t < endTime) {
var ampl = sustainAmplitude - releaseRate * (t - releaseTime);
buf[offset++] += input[childOffset++] * ampl;
buf[offset++] += input[childOffset++] * ampl;
t++;
count--;
}
} else {
/* dead */
self.alive = false;
return;
}
} else if (t < attackTime) {
/* attack */
while(count && t < attackTime) {
var ampl = attackAmplitude * t / attackTime;
buf[offset++] += input[childOffset++] * ampl;
buf[offset++] += input[childOffset++] * ampl;
t++;
count--;
}
} else if (t < decayTime) {
/* decay */
while(count && t < decayTime) {
var ampl = attackAmplitude - decayRate * (t - attackTime);
buf[offset++] += input[childOffset++] * ampl;
buf[offset++] += input[childOffset++] * ampl;
t++;
count--;
}
} else {
/* sustain */
while(count) {
buf[offset++] += input[childOffset++] * sustainAmplitude;
buf[offset++] += input[childOffset++] * sustainAmplitude;
t++;
count--;
}
}
}
}
return self;
}
function midiToFrequency(note) {
return 440 * Math.pow(2, (note-69)/12);
}
PianoProgram = {
'attackAmplitude': 0.2,
'sustainAmplitude': 0.1,
'attackTime': 0.02,
'decayTime': 0.3,
'releaseTime': 0.02,
'createNote': function(note, velocity) {
var frequency = midiToFrequency(note);
return ADSRGenerator(
SineGenerator(frequency),
this.attackAmplitude * (velocity / 128), this.sustainAmplitude * (velocity / 128),
this.attackTime, this.decayTime, this.releaseTime
);
}
}
StringProgram = {
'createNote': function(note, velocity) {
var frequency = midiToFrequency(note);
return ADSRGenerator(
SineGenerator(frequency),
0.5 * (velocity / 128), 0.2 * (velocity / 128),
0.4, 0.8, 0.4
);
}
}
PROGRAMS = {
41: StringProgram,
42: StringProgram,
43: StringProgram,
44: StringProgram,
45: StringProgram,
46: StringProgram,
47: StringProgram,
49: StringProgram,
50: StringProgram
};
function Synth(sampleRate) {
var generators = [];
function addGenerator(generator) {
generators.push(generator);
}
function generate(samples) {
var data = new Array(samples*2);
generateIntoBuffer(samples, data, 0);
return data;
}
function generateIntoBuffer(samplesToGenerate, buffer, offset) {
for (var i = offset; i < offset + samplesToGenerate * 2; i++) {
buffer[i] = 0;
}
for (var i = generators.length - 1; i >= 0; i--) {
generators[i].generate(buffer, offset, samplesToGenerate);
if (!generators[i].alive) generators.splice(i, 1);
}
}
return {
'sampleRate': sampleRate,
'addGenerator': addGenerator,
'generate': generate,
'generateIntoBuffer': generateIntoBuffer
}
}

View File

@ -12,17 +12,12 @@ ui.prototype.init = function () {
main.instance.ui = new ui();
/**
* 关闭一切UI窗口
* @param clearData 是否同时清掉data层
*/
ui.prototype.closePanel = function (clearData) {
////// 结束一切事件和绘制关闭UI窗口返回游戏进程 //////
ui.prototype.closePanel = function () {
core.status.boxAnimateObjs = [];
core.setBoxAnimate();
core.clearMap('ui', 0, 0, 416, 416);
core.setAlpha('ui', 1.0);
if (core.isset(clearData) && clearData)
core.clearMap('data', 0, 0, 416, 416);
core.unLockControl();
core.status.event.data = null;
core.status.event.id = null;
@ -30,12 +25,7 @@ ui.prototype.closePanel = function (clearData) {
core.status.event.ui = null;
}
/**
* 绘制对话框
* @param content
* @param id
*/
////// 绘制一个对话框 //////
ui.prototype.drawTextBox = function(content) {
// 获得name, image, icon
@ -137,7 +127,7 @@ ui.prototype.drawTextBox = function(content) {
core.fillText('ui', '<点击任意位置继续>', 270, top+height-13, '#CCCCCC', '13px Verdana');
}
// 绘制选项事件
////// 绘制一个选项界面 //////
ui.prototype.drawChoices = function(content, choices) {
var background = core.canvas.ui.createPattern(core.material.ground, "repeat");
@ -273,12 +263,7 @@ ui.prototype.drawChoices = function(content, choices) {
return;
}
/**
* 绘制确认/取消警告
* @param text
* @param yesCallback
* @param noCallback
*/
////// 绘制一个确认/取消的警告页面 //////
ui.prototype.drawConfirmBox = function (text, yesCallback, noCallback) {
core.lockControl();
@ -327,14 +312,13 @@ ui.prototype.drawConfirmBox = function (text, yesCallback, noCallback) {
}
////// 绘制开关界面 //////
////// 绘制系统设置界面 //////
ui.prototype.drawSwitchs = function() {
// 背景音乐、背景音效、战斗动画、怪物显伤、领域显伤、返回
core.status.event.id = 'switchs';
var choices = [
"背景音乐:"+(core.musicStatus.soundStatus ? "[ON]" : "[OFF]"),
"背景音乐:"+(core.musicStatus.bgmStatus ? "[ON]" : "[OFF]"),
"背景音效:"+(core.musicStatus.soundStatus ? "[ON]" : "[OFF]"),
"战斗动画: " + (core.flags.battleAnimate ? "[ON]" : "[OFF]"),
"怪物显伤: " + (core.flags.displayEnemyDamage ? "[ON]" : "[OFF]"),
"领域显伤: " + (core.flags.displayExtraDamage ? "[ON]" : "[OFF]"),
@ -344,10 +328,7 @@ ui.prototype.drawSwitchs = function() {
}
/**
* 绘制菜单栏
* @param need
*/
////// 绘制系统菜单栏 //////
ui.prototype.drawSettings = function (need) {
if (!core.checkStatus('settings', need))
return;
@ -357,6 +338,7 @@ ui.prototype.drawSettings = function (need) {
]);
}
////// 绘制快捷商店选择栏 //////
ui.prototype.drawQuickShop = function (need) {
if (core.isset(need) && !core.checkStatus('selectShop', need))
return;
@ -373,7 +355,7 @@ ui.prototype.drawQuickShop = function (need) {
this.drawChoices(null, choices);
}
////// 绘制战斗动画 //////
ui.prototype.drawBattleAnimate = function(monsterId, callback) {
// UI层
@ -389,12 +371,12 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
hero_hp -= core.enemys.getExtraDamage(monster);
if (core.enemys.hasSpecial(mon_special, 2)) hero_def=0; // 魔攻
if (core.enemys.hasSpecial(mon_special, 3) && mon_def<hero_atk) mon_def=hero_atk-1; // 坚固
if (core.enemys.hasSpecial(mon_special, 10)) { // 模仿
mon_atk=hero_atk;
mon_def=hero_def;
}
if (core.enemys.hasSpecial(mon_special, 2)) hero_def=0; // 魔攻
if (core.enemys.hasSpecial(mon_special, 3) && mon_def<hero_atk) mon_def=hero_atk-1; // 坚固
// 实际操作
var turn = 0; // 0为勇士攻击
@ -404,7 +386,7 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
var turns = 2;
if (core.enemys.hasSpecial(mon_special, 4)) turns=3;
if (core.enemys.hasSpecial(mon_special, 5)) turns=4;
if (core.enemys.hasSpecial(mon_special, 6)) turns=5;
if (core.enemys.hasSpecial(mon_special, 6)) turns=1+(monster.n||4);
// 初始伤害
@ -422,7 +404,7 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
hero_mdef=0;
}
var specialText = core.enemys.getSpecialText(monsterId);
var specialTexts = core.enemys.getSpecialText(monsterId);
var background = core.canvas.ui.createPattern(core.material.ground, "repeat");
@ -430,7 +412,10 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
var left=10, right=416-2*left;
var lines = core.flags.enableExperience?5:4;
// var lines = core.flags.enableExperience?5:4;
var lines = 3;
if (core.flags.enableMDef || core.flags.enableMoney || core.flags.enableExperience) lines=4;
if (core.flags.enableMoney && core.flags.enableExperience) lines=5;
var lineHeight = 60;
var height = lineHeight * lines + 50;
@ -443,6 +428,8 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
core.setAlpha('ui', 1);
core.strokeRect('ui', left - 1, top - 1, right + 1, bottom + 1, '#FFFFFF', 2);
core.clearMap('data',0,0,416,416);
clearInterval(core.interval.tipAnimate);
core.setAlpha('data', 1);
core.setOpacity('data', 1);
core.status.boxAnimateObjs = [];
@ -460,7 +447,6 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
core.canvas.ui.textAlign='center';
core.fillText('ui', core.status.hero.name, left+margin+boxWidth/2, top+margin+heroHeight+40, '#FFD700', 'bold 22px Verdana');
core.fillText('ui', "怪物", left+right-margin-boxWidth/2, top+margin+32+40);
var specialTexts = specialText.split(" ");
for (var i=0, j=0; i<specialTexts.length;i++) {
if (specialTexts[i]!='') {
core.fillText('ui', specialTexts[i], left+right-margin-boxWidth/2, top+margin+32+44+20*(++j), '#FF6A6A', '15px Verdana');
@ -513,7 +499,7 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
if (core.flags.enableMDef) {
textTop += lineHeight;
core.canvas.ui.textAlign='left';
core.fillText('ui', "魔防", left_start, textTop, '#DDDDDD', '16px Verdana');
core.fillText('ui', "护盾", left_start, textTop, '#DDDDDD', '16px Verdana');
core.drawLine('ui', left_start, textTop + 8, left_end, textTop + 8, '#FFFFFF', 2);
core.canvas.data.textAlign='right';
core.fillText('data', hero_mdef, left_end, textTop+26, '#DDDDDD', 'bold 16px Verdana');
@ -541,12 +527,14 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
core.canvas.ui.textAlign='left';
core.fillText('ui', mon_def, right_start, textTop+26, '#DDDDDD', 'bold 16px Verdana');
textTop += lineHeight;
core.canvas.ui.textAlign='right';
core.fillText('ui', "金币", right_end, textTop, '#DDDDDD', '16px Verdana');
core.drawLine('ui', right_start, textTop + 8, right_end, textTop + 8, '#FFFFFF', 2);
core.canvas.ui.textAlign='left';
core.fillText('ui', mon_money, right_start, textTop+26, '#DDDDDD', 'bold 16px Verdana');
if (core.flags.enableMoney) {
textTop += lineHeight;
core.canvas.ui.textAlign = 'right';
core.fillText('ui', "金币", right_end, textTop, '#DDDDDD', '16px Verdana');
core.drawLine('ui', right_start, textTop + 8, right_end, textTop + 8, '#FFFFFF', 2);
core.canvas.ui.textAlign = 'left';
core.fillText('ui', mon_money, right_start, textTop + 26, '#DDDDDD', 'bold 16px Verdana');
}
if (core.flags.enableExperience) {
textTop += lineHeight;
@ -562,15 +550,9 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
core.canvas.ui.textAlign='right';
core.fillText("ui", "S", right_start-8, 208+15, "#FFFFFF", "italic bold 40px Verdana");
/*
core.drawLine('data', left + right - margin - boxWidth + 6, top+margin+boxWidth-6,
left+right-margin-6, top+margin+6, '#FF0000', 4);
core.drawLine('data', left + margin + 6, top+margin+heroHeight+(boxWidth-32)-6,
left+margin+boxWidth-6, top+margin+6, '#FF0000', 4);
*/
var battleInterval = setInterval(function() {
core.playSound("attack", "ogg");
core.playSound("attack.ogg");
if (turn==0) {
// 勇士攻击
@ -592,8 +574,7 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
// 反击
if (core.enemys.hasSpecial(mon_special, 8)) {
var counterDamage = parseInt(core.values.counterAttack * hero_atk);
hero_mdef -= counterDamage;
hero_mdef -= parseInt(core.values.counterAttack * hero_atk);
if (hero_mdef<0) {
hero_hp+=hero_mdef;
@ -662,10 +643,7 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) {
}, 500);
}
/**
* 绘制请等候...
* @param text
*/
////// 绘制等待界面 //////
ui.prototype.drawWaiting = function(text) {
core.lockControl();
@ -685,9 +663,7 @@ ui.prototype.drawWaiting = function(text) {
}
/**
* 绘制存档同步选项
*/
////// 绘制存档同步界面 //////
ui.prototype.drawSyncSave = function () {
core.status.event.id = 'syncSave';
@ -698,11 +674,7 @@ ui.prototype.drawSyncSave = function () {
}
/**
* 绘制分页
* @param page
* @param totalPage
*/
////// 绘制分页 //////
ui.prototype.drawPagination = function (page, totalPage) {
core.setFont('ui', 'bold 15px Verdana');
@ -724,11 +696,8 @@ ui.prototype.drawPagination = function (page, totalPage) {
}
/**
* 绘制怪物手册
* @param page 页数
*/
ui.prototype.drawEnemyBook = function (page) {
////// 绘制怪物手册 //////
ui.prototype.drawBook = function (index) {
var enemys = core.enemys.getCurrentEnemys();
var background = core.canvas.ui.createPattern(core.material.ground, "repeat");
@ -758,11 +727,12 @@ ui.prototype.drawEnemyBook = function (page) {
return;
}
if (index<0) index=0;
if (index>=enemys.length) index=enemys.length-1;
var perpage = 6;
var page=parseInt(index/perpage)+1;
var totalPage = parseInt((enemys.length - 1) / perpage) + 1;
if (page < 1) page = 1;
if (page > totalPage) page = totalPage;
core.status.event.data = page;
core.status.event.data = index;
var start = (page - 1) * perpage, end = Math.min(page * perpage, enemys.length);
enemys = enemys.slice(start, end);
@ -796,25 +766,34 @@ ui.prototype.drawEnemyBook = function (page) {
core.fillText('ui', enemy.atk, 285, 62 * i + 32, '#DDDDDD', 'bold 13px Verdana');
core.fillText('ui', '防御', 335, 62 * i + 32, '#DDDDDD', '13px Verdana');
core.fillText('ui', enemy.def, 365, 62 * i + 32, '#DDDDDD', 'bold 13px Verdana');
core.fillText('ui', '金币', 165, 62 * i + 50, '#DDDDDD', '13px Verdana');
core.fillText('ui', enemy.money, 195, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana');
var damage_offset = 326;
var expOffset = 165;
if (core.flags.enableMoney) {
core.fillText('ui', '金币', 165, 62 * i + 50, '#DDDDDD', '13px Verdana');
core.fillText('ui', enemy.money, 195, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana');
expOffset = 255;
}
if (core.flags.enableExperience) {
core.canvas.ui.textAlign = "left";
core.fillText('ui', '经验', 255, 62 * i + 50, '#DDDDDD', '13px Verdana');
core.fillText('ui', enemy.experience, 285, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana');
damage_offset = 361;
core.fillText('ui', '经验', expOffset, 62 * i + 50, '#DDDDDD', '13px Verdana');
core.fillText('ui', enemy.experience, expOffset + 30, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana');
}
var damageOffet = 281;
if (core.flags.enableMoney && core.flags.enableExperience)
damageOffet = 361;
else if (core.flags.enableMoney || core.flags.enableExperience)
damageOffet = 326;
core.canvas.ui.textAlign = "center";
var damage = enemy.damage;
var color = '#FFFF00';
if (damage >= core.status.hero.hp) color = '#FF0000';
if (damage == 0) color = '#00FF00';
if (damage <= 0) color = '#00FF00';
if (damage >= 999999999) damage = '无法战斗';
var length = core.canvas.ui.measureText(damage).width;
core.fillText('ui', damage, damage_offset, 62 * i + 50, color, 'bold 13px Verdana');
core.fillText('ui', damage, damageOffet, 62 * i + 50, color, 'bold 13px Verdana');
core.canvas.ui.textAlign = "left";
@ -825,15 +804,79 @@ ui.prototype.drawEnemyBook = function (page) {
core.fillText('ui', '1防', 335, 62 * i + 68, '#DDDDDD', '13px Verdana');
core.fillText('ui', enemy.defDamage, 365, 62 * i + 68, '#DDDDDD', 'bold 13px Verdana');
if (index == start+i) {
core.strokeRect('ui', 10, 62 * i + 13, 416-10*2, 62, '#FFD700');
}
}
core.setBoxAnimate();
this.drawPagination(page, totalPage);
}
/**
* 绘制楼传器
* @param page
*/
////// 绘制怪物属性的详细信息 //////
ui.prototype.drawBookDetail = function (index) {
var enemys = core.enemys.getCurrentEnemys();
if (enemys.length==0) return;
if (index<0) index=0;
if (index>=enemys.length) index=enemys.length-1;
var enemy = enemys[index];
var enemyId=enemy.id;
var hints=core.enemys.getSpecialHint(core.enemys.getEnemys(enemyId));
if (hints.length==0) {
core.drawTip("该怪物无特殊属性!");
return;
}
var content=hints.join("\n");
core.status.event.id = 'book-detail';
clearInterval(core.interval.tipAnimate);
core.clearMap('data', 0, 0, 416, 416);
core.setOpacity('data', 1);
var left=10, right=416-2*left;
var content_left = left + 25;
var validWidth = right-(content_left-left)-13;
var contents = core.splitLines("data", content, validWidth, '16px Verdana');
var height = 416 - 10 - Math.min(416-24*(contents.length+1)-65, 250);
var top = (416-height)/2, bottom = height;
// var left = 97, top = 64, right = 416 - 2 * left, bottom = 416 - 2 * top;
core.setAlpha('data', 0.9);
core.fillRect('data', left, top, right, bottom, '#000000');
core.setAlpha('data', 1);
core.strokeRect('data', left - 1, top - 1, right + 1, bottom + 1, '#FFFFFF', 2);
// 名称
core.canvas.data.textAlign = "left";
core.fillText('data', enemy.name, content_left, top + 30, '#FFD700', 'bold 22px Verdana');
var content_top = top + 57;
for (var i=0;i<contents.length;i++) {
// core.fillText('data', contents[i], content_left, content_top, '#FFFFFF', '16px Verdana');
var text=contents[i];
var index=text.indexOf("");
if (index>=0) {
var x1 = text.substring(0, index+1);
core.fillText('data', x1, content_left, content_top, '#FF6A6A', 'bold 16px Verdana');
var len=core.canvas.data.measureText(x1).width;
core.fillText('data', text.substring(index+1), content_left+len, content_top, '#FFFFFF', '16px Verdana');
}
else {
core.fillText('data', contents[i], content_left, content_top, '#FFFFFF', '16px Verdana');
}
content_top+=24;
}
core.fillText('data', '<点击任意位置继续>', 270, top+height-13, '#CCCCCC', '13px Verdana');
}
////// 绘制楼层传送器 //////
ui.prototype.drawFly = function(page) {
if (page<0) page=0;
@ -859,6 +902,7 @@ ui.prototype.drawFly = function(page) {
this.drawThumbnail(floorId, 'ui', core.status.maps[floorId].blocks, 20, 100, 273);
}
////// 绘制道具栏 //////
ui.prototype.drawToolbox = function(index) {
var tools = Object.keys(core.status.hero.items.tools).sort();
@ -973,7 +1017,7 @@ ui.prototype.drawToolbox = function(index) {
core.fillText('ui', '返回游戏', 370, 403,'#DDDDDD', 'bold 15px Verdana');
}
////// 绘制存档/读档界面 //////
ui.prototype.drawSLPanel = function(index) {
if (!core.isset(index)) index=1;
if (index<=0) index=1;
@ -1027,6 +1071,7 @@ ui.prototype.drawSLPanel = function(index) {
this.drawPagination(page+1, 30);
}
////// 绘制一个缩略图 //////
ui.prototype.drawThumbnail = function(floorId, canvas, blocks, x, y, size, heroLoc) {
core.clearMap(canvas, x, y, size, size);
var groundId = core.floors[floorId].defaultGround || "ground";
@ -1038,23 +1083,30 @@ ui.prototype.drawThumbnail = function(floorId, canvas, blocks, x, y, size, heroL
core.canvas[canvas].drawImage(blockImage, 0, blockIcon * 32, 32, 32, x + i * persize, y + j * persize, persize, persize);
}
}
var autotileMaps = [];
if (core.isset(core.floors[floorId].png)) {
var png = core.floors[floorId].png;
if (core.isset(core.material.images.pngs[png])) {
core.canvas.ui.drawImage(core.material.images.pngs[png], x, y, size, size);
}
}
var mapArray = core.maps.getMapArray(core.status.maps, floorId);
for (var b in blocks) {
var block = blocks[b];
if (core.isset(block.event) && !(core.isset(block.enable) && !block.enable)) {
if (block.event.cls == 'autotile') {
// core.drawAutotile();
autotileMaps[13*block.x + block.y] = true;
continue;
core.drawAutotile(core.canvas.ui, mapArray, block, persize, x, y);
}
else {
var blockIcon = core.material.icons[block.event.cls][block.event.id];
var blockImage = core.material.images[block.event.cls];
core.canvas[canvas].drawImage(blockImage, 0, blockIcon * 32, 32, 32, x + block.x * persize, y + block.y * persize, persize, persize);
if (block.event.id!='none') {
var blockIcon = core.material.icons[block.event.cls][block.event.id];
var blockImage = core.material.images[block.event.cls];
core.canvas[canvas].drawImage(blockImage, 0, blockIcon * 32, 32, 32, x + block.x * persize, y + block.y * persize, persize, persize);
}
}
}
}
core.drawAutotile(floorId, 'ui', autotileMaps, x, y, persize);
if (core.isset(heroLoc)) {
var heroIcon = core.material.icons.hero[heroLoc.direction];
@ -1064,9 +1116,7 @@ ui.prototype.drawThumbnail = function(floorId, canvas, blocks, x, y, size, heroL
}
}
/**
* 绘制"关于"
*/
////// 绘制“关于”界面 //////
ui.prototype.drawAbout = function() {
if (!core.isPlaying()) {
@ -1092,18 +1142,9 @@ ui.prototype.drawAbout = function() {
core.fillText('ui', "作者: 艾之葵", text_start, top + 80, "#FFFFFF", "bold 17px Verdana");
core.fillText('ui', 'HTML5魔塔交流群539113091', text_start, top+112);
// TODO: 写自己的“关于”页面
/*
core.fillText('ui', "原作: ss433_2", text_start, top + 112, "#FFFFFF", "bold 17px Verdana");
core.fillText('ui', "制作工具: WebStorm", text_start, top + 144, "#FFFFFF", "bold 17px Verdana");
core.fillText('ui', "测试平台: Chrome/微信/iOS", text_start, top + 176, "#FFFFFF", "bold 17px Verdana");
core.fillText('ui', '特别鸣谢: ss433_2', text_start, top+208);
var len = core.canvas.ui.measureText('特别鸣谢: ').width;
core.fillText('ui', 'iEcho', text_start+len, top+240);
core.fillText('ui', '打Dota的喵', text_start+len, top+272);
core.fillText('ui', 'HTML5魔塔交流群539113091', text_start, top+304);
*/
}
////// 绘制帮助页面 //////
ui.prototype.drawHelp = function () {
core.drawText([
"\t[键盘快捷键列表]"+

110
main.js
View File

@ -1,4 +1,33 @@
function main() {
//------------------------ 用户修改内容 ------------------------//
this.version = "0.1"; // 游戏版本号如果更改了游戏内容建议修改此version以免造成缓存问题。
this.useCompress = false; // 是否使用压缩文件
// 当你即将发布你的塔时请使用“JS代码压缩工具”将所有js代码进行压缩然后将这里的useCompress改为true。
// 请注意只有useCompress是false时才会读取floors目录下的文件为true时会直接读取libs目录下的floors.min.js文件。
// 如果要进行剧本的修改请务必将其改成false。
this.floorIds = [ // 在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器的顺序和上楼器/下楼器的顺序
"sample0", "sample1", "sample2"
];
this.pngs = [ // 在此存放所有可能的背景图片背景图片最好是416*416像素其他分辨率会被强制缩放成416*416
// 建议对于较大的图片,在网上使用在线的“图片压缩工具”来进行压缩,以节省流量
// 有关使用自定义背景图,请参见文档的“自定义素材”说明
"bg.png", "yewai.png", // 依次向后添加
];
this.bgms = [ // 在此存放所有的bgm和文件名一致。第一项为默认播放项
// 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好
'058-Slow01.mid', 'bgm.mp3', 'qianjin.mid', 'star.mid',
];
this.sounds = [ // 在此存放所有的SE和文件名一致
// 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好
'floor.mp3', 'attack.ogg', 'door.ogg', 'item.ogg',
];
//------------------------ 用户修改内容 END ------------------------//
this.dom = {
'body': document.body,
'gameGroup': document.getElementById('gameGroup'),
@ -20,6 +49,7 @@ function main() {
'toolBar': document.getElementById('toolBar'),
'tools': document.getElementsByClassName('tools'),
'gameCanvas': document.getElementsByClassName('gameCanvas'),
'curtain': document.getElementById('curtain'),
'startButtons': document.getElementById('startButtons'),
'playGame': document.getElementById('playGame'),
'loadGame': document.getElementById('loadGame'),
@ -30,31 +60,33 @@ function main() {
'hardLevel': document.getElementById('hardLevel'),
'data': document.getElementById('data'),
'statusLabels': document.getElementsByClassName('statusLabel'),
'floorCol': document.getElementById('floorCol'),
'lvCol': document.getElementById('lvCol'),
'mdefCol': document.getElementById('mdefCol'),
'moneyCol': document.getElementById('moneyCol'),
'expCol': document.getElementById('expCol'),
'upCol': document.getElementById('upCol'),
'debuffCol': document.getElementById('debuffCol'),
'hard': document.getElementById('hard'),
};
// console.log('加载游戏容器和开始界面dom对象完成 如下');
// console.log(this.dom);
this.loadList = [
'items', 'icons', 'maps', 'enemys', 'events', 'data', 'ui', 'core'
];
// console.log('加载js文件列表加载完成' + this.loadList);
this.images = [
'animates', 'enemys', 'hero', 'items', 'npcs', 'terrains', "autotile"
'animates', 'enemys', 'hero', 'items', 'npcs', 'terrains'
];
this.sounds = {
'mp3': ['bgm-loop', 'floor'],
'ogg': ['attack', 'door', 'item']
}
this.statusBar = {
'image': {
'floor': document.getElementById('img-floor'),
'lv': document.getElementById('img-lv'),
'hp': document.getElementById("img-hp"),
'atk': document.getElementById("img-atk"),
'def': document.getElementById("img-def"),
'mdef': document.getElementById("img-mdef"),
'money': document.getElementById("img-money"),
'experience': document.getElementById("img-experience"),
'up': document.getElementById("img-up"),
'book': document.getElementById("img-book"),
'fly': document.getElementById("img-fly"),
'toolbox': document.getElementById("img-toolbox"),
@ -64,12 +96,14 @@ function main() {
'settings': document.getElementById("img-settings")
},
'floor': document.getElementById('floor'),
'lv': document.getElementById('lv'),
'hp': document.getElementById('hp'),
'atk': document.getElementById('atk'),
'def': document.getElementById("def"),
'mdef': document.getElementById('mdef'),
'money': document.getElementById("money"),
'experience': document.getElementById("experience"),
'up': document.getElementById('up'),
'yellowKey': document.getElementById("yellowKey"),
'blueKey': document.getElementById("blueKey"),
'redKey': document.getElementById("redKey"),
@ -78,21 +112,12 @@ function main() {
'curse': document.getElementById('curse'),
'hard': document.getElementById("hard")
}
this.version = "0.1"; // 游戏版本号如果更改了游戏内容建议修改此version以免造成缓存问题。
this.useCompress = false; // 是否使用压缩文件
// 当你即将发布你的塔时请使用“JS代码压缩工具”将所有js代码进行压缩然后将这里的useCompress改为true。
// 请注意只有useCompress是false时才会读取floors目录下的文件为true时会直接读取libs目录下的floors.min.js文件。
// 如果要进行剧本的修改请务必将其改成false。
this.floorIds = [ // 在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器的顺序和上楼器/下楼器的顺序
"sample0", "sample1", "sample2"
]
this.floors = {}
this.instance = {};
this.canvas = {};
}
////// 初始化 //////
main.prototype.init = function () {
for (var i = 0; i < main.dom.gameCanvas.length; i++) {
main.canvas[main.dom.gameCanvas[i].id] = main.dom.gameCanvas[i].getContext('2d');
@ -106,12 +131,13 @@ main.prototype.init = function () {
coreData[name] = main[name];
}
main.loaderFloors(function() {
main.core.init(main.dom, main.statusBar, main.canvas, main.images, main.sounds, main.floorIds, main.floors, coreData);
main.core.init(main.dom, main.statusBar, main.canvas, main.images, main.pngs, main.bgms, main.sounds, main.floorIds, main.floors, coreData);
main.core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight);
})
});
}
////// 动态加载所有核心JS文件 //////
main.prototype.loaderJs = function (callback) {
var instanceNum = 0;
// 加载js
@ -132,13 +158,14 @@ main.prototype.loaderJs = function (callback) {
}
}
////// 动态加载所有楼层(剧本) //////
main.prototype.loaderFloors = function (callback) {
// 加载js
main.setMainTipsText('正在加载楼层文件...')
if (this.useCompress) { // 读取压缩文件
var script = document.createElement('script');
script.src = 'libs/floors.min.js?' + this.version;
script.src = 'libs/floors.min.js?v=' + this.version;
main.dom.body.appendChild(script);
script.onload = function () {
main.dom.mainTips.style.display = 'none';
@ -158,10 +185,11 @@ main.prototype.loaderFloors = function (callback) {
}
}
////// 加载某一个JS文件 //////
main.prototype.loadMod = function (modName, callback) {
var script = document.createElement('script');
var name = modName;
script.src = 'libs/' + modName + (this.useCompress?".min":"") + '.js?' + this.version;
script.src = 'libs/' + modName + (this.useCompress?".min":"") + '.js?v=' + this.version;
main.dom.body.appendChild(script);
script.onload = function () {
main[name] = main.instance[name];
@ -169,15 +197,17 @@ main.prototype.loadMod = function (modName, callback) {
}
}
////// 加载某一个楼层 //////
main.prototype.loadFloor = function(floorId, callback) {
var script = document.createElement('script');
script.src = 'libs/floors/' + floorId +'.js?' + this.version;
script.src = 'libs/floors/' + floorId +'.js?v=' + this.version;
main.dom.body.appendChild(script);
script.onload = function () {
callback(floorId);
}
}
////// 加载过程提示 //////
main.prototype.setMainTipsText = function (text) {
main.dom.mainTips.innerHTML = text;
}
@ -185,12 +215,14 @@ main.prototype.setMainTipsText = function (text) {
var main = new main();
main.init();
////// 窗口大小变化时 //////
window.onresize = function () {
try {
main.core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight);
}catch (e) {}
}
////// 在界面上按下某按键时 //////
main.dom.body.onkeydown = function(e) {
try {
if (main.core.isPlaying() || main.core.status.lockControl)
@ -198,6 +230,7 @@ main.dom.body.onkeydown = function(e) {
} catch (ee) {}
}
////// 在界面上放开某按键时 //////
main.dom.body.onkeyup = function(e) {
try {
if (main.core.isPlaying() || main.core.status.lockControl)
@ -205,22 +238,12 @@ main.dom.body.onkeyup = function(e) {
} catch (ee) {}
}
////// 开始选择时 //////
main.dom.body.onselectstart = function () {
return false;
}
document.onmousemove = function() {
try {
main.core.loadSound();
}catch (e) {}
}
document.ontouchstart = function() {
try {
main.core.loadSound();
}catch (e) {}
}
////// 鼠标按下时 //////
main.dom.data.onmousedown = function (e) {
try {
e.stopPropagation();
@ -235,6 +258,7 @@ main.dom.data.onmousedown = function (e) {
} catch (ee) {}
}
////// 鼠标移动时 //////
main.dom.data.onmousemove = function (e) {
try {
e.stopPropagation();
@ -245,12 +269,14 @@ main.dom.data.onmousemove = function (e) {
}catch (ee) {}
}
////// 鼠标放开时 //////
main.dom.data.onmouseup = function () {
try {
main.core.onup();
}catch (e) {}
}
////// 鼠标滑轮滚动时 //////
main.dom.data.onmousewheel = function(e) {
try {
if (e.wheelDelta)
@ -260,6 +286,7 @@ main.dom.data.onmousewheel = function(e) {
} catch (ee) {}
}
////// 手指在触摸屏开始触摸时 //////
main.dom.data.ontouchstart = function (e) {
try {
e.preventDefault();
@ -271,6 +298,7 @@ main.dom.data.ontouchstart = function (e) {
}catch (ee) {}
}
////// 手指在触摸屏上移动时 //////
main.dom.data.ontouchmove = function (e) {
try {
e.preventDefault();
@ -281,6 +309,7 @@ main.dom.data.ontouchmove = function (e) {
}catch (ee) {}
}
////// 手指离开触摸屏时 //////
main.dom.data.ontouchend = function () {
try {
main.core.onup();
@ -288,41 +317,49 @@ main.dom.data.ontouchend = function () {
}
}
////// 点击状态栏中的怪物手册时 //////
main.statusBar.image.book.onclick = function () {
if (main.core.isPlaying())
main.core.openBook(true);
}
////// 点击状态栏中的楼层传送器时 //////
main.statusBar.image.fly.onclick = function () {
if (main.core.isPlaying())
main.core.useFly(true);
}
////// 点击状态栏中的工具箱时 //////
main.statusBar.image.toolbox.onclick = function () {
if (main.core.isPlaying())
main.core.openToolbox(true);
}
////// 点击状态栏中的快捷商店时 //////
main.statusBar.image.shop.onclick = function () {
if (main.core.isPlaying())
main.core.ui.drawQuickShop(true);
}
////// 点击状态栏中的存档按钮时 //////
main.statusBar.image.save.onclick = function () {
if (main.core.isPlaying())
main.core.save(true);
}
////// 点击状态栏中的读档按钮时 //////
main.statusBar.image.load.onclick = function () {
if (main.core.isPlaying())
main.core.load(true);
}
////// 点击状态栏中的系统菜单时 //////
main.statusBar.image.settings.onclick = function () {
if (main.core.isPlaying())
main.core.ui.drawSettings(true);
}
////// 点击“开始游戏”时 //////
main.dom.playGame.onclick = function () {
main.dom.startButtons.style.display='none';
@ -334,22 +371,27 @@ main.dom.playGame.onclick = function () {
}
}
////// 点击“载入游戏”时 //////
main.dom.loadGame.onclick = function() {
main.core.load();
}
////// 点击“关于本塔”时 //////
main.dom.aboutGame.onclick = function () {
main.core.ui.drawAbout();
}
////// 点击“简单难度”时 //////
main.dom.easyLevel.onclick = function() {
core.events.startGame('Easy');
}
////// 点击“普通难度”时 //////
main.dom.normalLevel.onclick = function () {
core.events.startGame('Normal');
}
////// 点击“困难难度”时 //////
main.dom.hardLevel.onclick = function () {
core.events.startGame('Hard');
}

BIN
sounds/058-Slow01.mid Normal file

Binary file not shown.

BIN
sounds/qianjin.mid Normal file

Binary file not shown.

BIN
sounds/star.mid Normal file

Binary file not shown.

View File

@ -21,7 +21,7 @@
position: fixed;
top: 10px;
left: 10px;
z-index: 12;
z-index: 13;
}
#startPanel {
@ -32,7 +32,7 @@
left: 0;
background-color: #fff;
overflow: hidden;
z-index: 8;
z-index: 9;
}
#startTop {
@ -42,7 +42,7 @@
top: 0;
left: 0;
background-color: #000;
z-index: 11;
z-index: 12;
}
#startTopProgressBar {
@ -52,7 +52,7 @@
position: absolute;
top: 5%;
background-color: #fff;
z-index: 12;
z-index: 13;
}
#startTopProgress {
@ -67,7 +67,7 @@
position: absolute;
top: 8%;
left: 5%;
z-index: 12;
z-index: 13;
}
#startBackground {
@ -77,12 +77,12 @@
height: 100%;
width: auto;
transform:translate(-50%,-50%);
z-index: 9;
z-index: 10;
}
#startLogo {
position: absolute;
z-index: 9;
z-index: 10;
left: 0;
right: 0;
margin-left: auto;
@ -95,7 +95,7 @@
#startTitle {
position: absolute;
z-index: 10;
z-index: 11;
}
#startButtonGroup {
@ -106,7 +106,7 @@
background-color: #000;
opacity: 0.85;
display: none;
z-index: 9;
z-index: 10;
bottom: 0;
margin-bottom: 7%;
}
@ -142,7 +142,7 @@
display: none;
color: #fff;
background-color: #000;
z-index: 7;
z-index: 8;
}
#logoLabel {
@ -170,7 +170,7 @@
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
background: url(images/ground.png) round;
z-index: 6;
z-index: 7;
display: none;
}
#statusBar .status{
@ -180,8 +180,9 @@
}
.status img{
vertical-align: middle;
width: 1.6em;
height: 1.6em;
width: auto;
height: 100%;
max-height: 1.6em;
}
#statusBar span{
color: white;
@ -198,7 +199,7 @@
#toolBar {
position: absolute;
background: url(images/ground.png) round;
z-index: 5;
z-index: 6;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
@ -232,6 +233,13 @@ span#poison, span#weak, span#curse {
-webkit-box-sizing: border-box;
}
#curtain {
z-index: 5;
position: absolute;
opacity: 0;
background: #000000;
}
#bg {
z-index: 1;
}
@ -240,20 +248,20 @@ span#poison, span#weak, span#curse {
z-index: 2;
}
#hero {
#fg {
z-index: 3;
}
#fg {
#hero {
z-index: 4;
}
#ui {
z-index: 5;
z-index: 6;
}
#data {
z-index: 6;
z-index: 7;
}
.clearfix:before,

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,40 @@
全键盘操作 √
Ctrl快速跳过对话 √
支持不同层使用不同的地面素材 √
直接内嵌了诸多默认的terrains素材
自动定位到上次存/读档位置 √
设置储存 √
HTML5魔塔样板V1.3
支持全键盘操作。
支持将某个图片作为某层的背景素材。
便捷PS工具支持更改图片色相。
支持经验升级(进阶/境界塔)。
打败怪物可以进行加点(加点塔)。
增加阻击、N连击等属性在怪物手册有属性显示。
支持九宫格领域和大范围领域。
增加负伤。
支持各种BGM的播放。
支持不同层使用不同的地面素材支持多个Autotile同时存在。
许多细节进行了优化一些已知的Bug进行了修复。
-----------------------------------------------------------------------
HTML5魔塔样板V1.2
新增本地HTTP服务器。
新增:可视化地图编辑工具。
新增便捷PS工具。
移除了meaning.txt现在“地图生成器”将直接从js文件中读取数字和图块对应关系。
新增对Autotile图块的支持。
新增:怪物支持多种属性;添加仇恨属性。
移除了不再支持的checkBlock现在对于领域和夹击无需再手动指定可能的点了。
新增:单向箭头、感叹号(单次通行)的支持。
新增更多的默认素材现在对于大多数地图风格无需P图直接替换即可。
添加部分自定义事件部分细节优化一些已知的Bug进行了修复。
-----------------------------------------------------------------------
HTML5魔塔样板V1.1
新增:战斗过程显示,可以在设置中关闭
新增勇士支持48*32大图的行走图
新增:更改画面色调
新增:文字显示支持自动换行
部分修改状态栏UI
增添Web的Markdown文档移除原本的doc和pdf文档。
修复若干Bug。