上传文件至 /
This commit is contained in:
parent
3f45b47256
commit
b94e3740b1
57
L1.md
Normal file
57
L1.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# 第一章:伤害计算函数(getDamageInfo)
|
||||||
|
|
||||||
|
要求:读懂脚本编辑 - 伤害计算函数(getDamageInfo)的全部脚本代码(无需理解支援的实现)。
|
||||||
|
|
||||||
|
回答如下几个问题:
|
||||||
|
|
||||||
|
1. 如下几个局部变量分别是什么意义?
|
||||||
|
|
||||||
|
`mon_atk`, `init_damage`, `per_damage`, `hero_per_damage`, `turn`, `damage`
|
||||||
|
|
||||||
|
2. 如何理解下面这几句话?
|
||||||
|
|
||||||
|
`if (core.hasSpecial(mon_special, 2)) per_damage = mon_atk;`
|
||||||
|
|
||||||
|
`var turn = Math.ceil(mon_hp / hero_per_damage);`
|
||||||
|
|
||||||
|
`var damage = init_damage + (turn - 1) * per_damage + turn * counterDamage;`
|
||||||
|
|
||||||
|
3. 负伤在哪里实现的?
|
||||||
|
|
||||||
|
依次实现如下几个怪物属性(仅允许修改`getDamageInfo`函数):
|
||||||
|
|
||||||
|
1. **闪避(编号31):** 受到伤害降低40%。
|
||||||
|
|
||||||
|
2. **穿刺(编号32):** 无视角色70%防御力。
|
||||||
|
|
||||||
|
3. **冰冻(编号33):** 怪物首先冰冻角色3回合,冰冻期间每回合额外造成角色护盾的20%伤害。
|
||||||
|
|
||||||
|
4. **中子束(编号34):** 每回合普攻两次,魔攻一次。
|
||||||
|
|
||||||
|
5. **残暴斩杀(编号35):** 战斗开始时,如果角色血量不大于怪物血量的200%,则直接暴毙。
|
||||||
|
|
||||||
|
6. **窥血为攻(编号36):** 战斗开始时,自身攻击力变为角色当前生命值的10%,向下取整。
|
||||||
|
|
||||||
|
7. **匙之力(编号37):** 角色身上每存在一把黄钥匙,最终伤害提升5%;蓝钥匙视为三把黄钥匙,红钥匙视为九把黄钥匙,线性叠加。
|
||||||
|
|
||||||
|
8. **崩甲(编号38):** 怪物每回合附加勇士战斗开始时的护盾数值的0.1倍作为伤害。
|
||||||
|
|
||||||
|
9. **混乱(编号39):** 战斗中,勇士攻防互换。
|
||||||
|
|
||||||
|
10. **强击(编号40):** 怪物第一回合三倍攻击。
|
||||||
|
|
||||||
|
11. **暗影庇护(编号41):** 处于无敌状态,但每回合损耗自身2%的最大生命值。
|
||||||
|
|
||||||
|
再依次实现如下几个角色技能:
|
||||||
|
|
||||||
|
1. 角色每回合回复`flag:x1`%倍护盾的生命值。(如,`flag:x1`是10时,每回合回复10%护盾值的生命)
|
||||||
|
|
||||||
|
2. 角色每回合造成和受到的伤害均提升`flag:x2`%。(如,`flag:x2`为10时,每回合造成和受到伤害都提升10%)
|
||||||
|
|
||||||
|
3. 角色攻击在战斗时减少`flag:x3`,防御力增加`flag:x3`。
|
||||||
|
|
||||||
|
4. 角色无视怪物的`flag:x4`%的防御力。(如,`flag:x4`是10时,无视怪物的10%防御力)
|
||||||
|
|
||||||
|
5. 角色额外抢攻`flag:x5`回合。
|
||||||
|
|
||||||
|
[点此查看答案](L1_answer)
|
244
L1_answer.md
Normal file
244
L1_answer.md
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
```js
|
||||||
|
function (enemy, hero, x, y, floorId) {
|
||||||
|
// 获得战斗伤害信息(实际伤害计算函数)
|
||||||
|
//
|
||||||
|
// 参数说明:
|
||||||
|
// enemy:该怪物信息
|
||||||
|
// hero:勇士的当前数据;如果对应项不存在则会从core.status.hero中取。
|
||||||
|
// x,y:该怪物的坐标(查看手册和强制战斗时为undefined)
|
||||||
|
// floorId:该怪物所在的楼层
|
||||||
|
// 后面三个参数主要是可以在光环等效果上可以适用
|
||||||
|
floorId = floorId || core.status.floorId;
|
||||||
|
|
||||||
|
var hero_hp = core.getRealStatusOrDefault(hero, 'hp'),
|
||||||
|
hero_atk = core.getRealStatusOrDefault(hero, 'atk'),
|
||||||
|
hero_def = core.getRealStatusOrDefault(hero, 'def'),
|
||||||
|
hero_mdef = core.getRealStatusOrDefault(hero, 'mdef'),
|
||||||
|
origin_hero_hp = core.getStatusOrDefault(hero, 'hp'),
|
||||||
|
origin_hero_atk = core.getStatusOrDefault(hero, 'atk'),
|
||||||
|
origin_hero_def = core.getStatusOrDefault(hero, 'def');
|
||||||
|
|
||||||
|
// 勇士的负属性都按0计算
|
||||||
|
hero_hp = Math.max(0, hero_hp);
|
||||||
|
hero_atk = Math.max(0, hero_atk);
|
||||||
|
hero_def = Math.max(0, hero_def);
|
||||||
|
hero_mdef = Math.max(0, hero_mdef);
|
||||||
|
|
||||||
|
// 怪物的各项数据
|
||||||
|
// 对坚固模仿等处理扔到了脚本编辑-getEnemyInfo之中
|
||||||
|
var enemyInfo = core.enemys.getEnemyInfo(enemy, hero, x, y, floorId);
|
||||||
|
var mon_hp = enemyInfo.hp,
|
||||||
|
mon_atk = enemyInfo.atk,
|
||||||
|
mon_def = enemyInfo.def,
|
||||||
|
mon_special = enemyInfo.special;
|
||||||
|
|
||||||
|
// 混乱(编号39):战斗中,勇士攻防互换。
|
||||||
|
if (core.hasSpecial(mon_special, 39)) {
|
||||||
|
var temp = hero_atk;
|
||||||
|
hero_atk = hero_def;
|
||||||
|
hero_def = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 残暴斩杀(编号35):战斗开始时,如果角色血量不大于怪物血量的200%,则直接暴毙。
|
||||||
|
if (core.hasSpecial(mon_special, 35) && hero_hp < mon_hp * 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窥血为攻(编号36):战斗开始时,自身攻击力变为角色当前生命值的10%,向下取整。
|
||||||
|
if (core.hasSpecial(mon_special, 36)) {
|
||||||
|
mon_atk = Math.floor(hero_hp / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 技能3:角色攻击在战斗时减少`flag:x3`,防御力增加`flag:x3`。
|
||||||
|
if (core.getFlag('skill', 0) == 3) {
|
||||||
|
// 注意这里直接改变的面板攻击力(经过装备增幅后的)而不是基础攻击力。
|
||||||
|
hero_atk -= core.getFlag('x3', 0);
|
||||||
|
hero_def += core.getFlag('x3', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 技能4:角色无视怪物的`flag:x4`%的防御力。(如,`flag:x4`是10时,无视怪物的10%防御力)
|
||||||
|
if (core.getFlag('skill', 0) == 4) {
|
||||||
|
mon_def -= Math.floor(core.getFlag('x4', 0) / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是无敌属性,且勇士未持有十字架
|
||||||
|
if (core.hasSpecial(mon_special, 20) && !core.hasItem("cross"))
|
||||||
|
return null; // 不可战斗
|
||||||
|
|
||||||
|
// 战前造成的额外伤害(可被护盾抵消)
|
||||||
|
var init_damage = 0;
|
||||||
|
|
||||||
|
// 吸血
|
||||||
|
if (core.hasSpecial(mon_special, 11)) {
|
||||||
|
var vampire_damage = hero_hp * enemy.value;
|
||||||
|
|
||||||
|
// 如果有神圣盾免疫吸血等可以在这里写
|
||||||
|
// 也可以用hasItem和hasEquip来判定装备
|
||||||
|
// if (core.hasFlag('shield5')) vampire_damage = 0;
|
||||||
|
|
||||||
|
vampire_damage = Math.floor(vampire_damage) || 0;
|
||||||
|
// 加到自身
|
||||||
|
if (enemy.add) // 如果加到自身
|
||||||
|
mon_hp += vampire_damage;
|
||||||
|
|
||||||
|
init_damage += vampire_damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每回合怪物对勇士造成的战斗伤害
|
||||||
|
var per_damage = mon_atk - hero_def;
|
||||||
|
// 魔攻:战斗伤害就是怪物攻击力
|
||||||
|
if (core.hasSpecial(mon_special, 2)) per_damage = mon_atk;
|
||||||
|
// 穿刺(编号32):无视角色70%防御力。
|
||||||
|
if (core.hasSpecial(mon_special, 32)) per_damage = Math.floor(mon_atk - 0.3 * hero_def);
|
||||||
|
// 战斗伤害不能为负值
|
||||||
|
if (per_damage < 0) per_damage = 0;
|
||||||
|
|
||||||
|
// 中子束(编号34):每回合普攻两次,魔攻一次。
|
||||||
|
if (core.hasSpecial(mon_special, 34)) {
|
||||||
|
per_damage *= 2;
|
||||||
|
per_damage += mon_atk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 崩甲(编号38):怪物每回合附加勇士战斗开始时的护盾数值的0.1倍作为伤害。
|
||||||
|
if (core.hasSpecial(mon_special, 38)) {
|
||||||
|
per_damage += Math.floor(hero_mdef * 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2连击 & 3连击 & N连击
|
||||||
|
if (core.hasSpecial(mon_special, 4)) per_damage *= 2;
|
||||||
|
if (core.hasSpecial(mon_special, 5)) per_damage *= 3;
|
||||||
|
if (core.hasSpecial(mon_special, 6)) per_damage *= (enemy.n || 4);
|
||||||
|
|
||||||
|
// 每回合的反击伤害;反击是按照勇士的攻击次数来计算回合
|
||||||
|
var counterDamage = 0;
|
||||||
|
if (core.hasSpecial(mon_special, 8))
|
||||||
|
counterDamage += Math.floor((enemy.atkValue || core.values.counterAttack) * hero_atk);
|
||||||
|
|
||||||
|
// 先攻
|
||||||
|
if (core.hasSpecial(mon_special, 1)) init_damage += per_damage;
|
||||||
|
|
||||||
|
// 破甲
|
||||||
|
if (core.hasSpecial(mon_special, 7))
|
||||||
|
init_damage += Math.floor((enemy.defValue || core.values.breakArmor) * hero_def);
|
||||||
|
|
||||||
|
// 净化
|
||||||
|
if (core.hasSpecial(mon_special, 9))
|
||||||
|
init_damage += Math.floor((enemy.n || core.values.purify) * hero_mdef);
|
||||||
|
|
||||||
|
// 冰冻(编号33):怪物首先冰冻角色3回合,冰冻期间每回合额外造成角色护盾的20%伤害。
|
||||||
|
if (core.hasSpecial(mon_special, 33)) {
|
||||||
|
init_damage += Math.floor(3 * (per_damage + 0.2 * hero_mdef));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 勇士每回合对怪物造成的伤害
|
||||||
|
var hero_per_damage = Math.max(hero_atk - mon_def, 0);
|
||||||
|
|
||||||
|
// 技能2:角色每回合造成和受到的伤害均提升`flag:x2`%。(如,`flag:x2`为10时,每回合造成和受到伤害都提升10%)
|
||||||
|
if (core.getFlag("skill", 0) == 2) {
|
||||||
|
per_damage += Math.floor(per_damage * core.getFlag('x2', 0) / 100);
|
||||||
|
hero_per_damage += Math.floor(hero_per_damage * core.getFlag('x2', 0) / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 闪避(编号31):受到伤害降低40%。
|
||||||
|
if (core.hasSpecial(mon_special, 31)) {
|
||||||
|
hero_per_damage = Math.floor(hero_per_damage * 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有破防,则不可战斗
|
||||||
|
if (hero_per_damage <= 0) return null;
|
||||||
|
|
||||||
|
// 技能5:角色额外抢攻`flag:x5`回合。
|
||||||
|
if (core.getFlag("skill", 0) == 5) {
|
||||||
|
mon_hp -= core.getFlag('x5', 0) * hero_per_damage;
|
||||||
|
// 判定是否已经秒杀 -> 注意mon_hp不能小于等于0否则回合计算会出问题
|
||||||
|
if (mon_hp <= 0) mon_hp = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暗影庇护(编号41):处于无敌状态,但每回合损耗自身2%的最大生命值。
|
||||||
|
if (core.hasSpecial(mon_special, 41)) {
|
||||||
|
hero_per_damage = Math.floor(mon_hp * 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 强击(编号40):怪物第一回合三倍攻击。
|
||||||
|
// 注意:需要判定角色是否秒杀怪物
|
||||||
|
if (core.hasSpecial(mon_special, 40) && hero_per_damage < mon_hp) {
|
||||||
|
init_damage += 2 * mon_atk;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 勇士的攻击回合数;为怪物生命除以每回合伤害向上取整
|
||||||
|
var turn = Math.ceil(mon_hp / hero_per_damage);
|
||||||
|
|
||||||
|
// ------ 支援 ----- //
|
||||||
|
// 这个递归最好想明白为什么,flag:__extraTurn__是怎么用的
|
||||||
|
var guards = core.getFlag("__guards__" + x + "_" + y, enemyInfo.guards);
|
||||||
|
var guard_before_current_enemy = false; // ------ 支援怪是先打(true)还是后打(false)?
|
||||||
|
turn += core.getFlag("__extraTurn__", 0);
|
||||||
|
if (guards.length > 0) {
|
||||||
|
if (!guard_before_current_enemy) { // --- 先打当前怪物,记录当前回合数
|
||||||
|
core.setFlag("__extraTurn__", turn);
|
||||||
|
}
|
||||||
|
// 获得那些怪物组成小队战斗
|
||||||
|
for (var i = 0; i < guards.length; i++) {
|
||||||
|
var gx = guards[i][0],
|
||||||
|
gy = guards[i][1],
|
||||||
|
gid = guards[i][2];
|
||||||
|
// 递归计算支援怪伤害信息,这里不传x,y保证不会重复调用
|
||||||
|
// 这里的mdef传0,因为护盾应该只会被计算一次
|
||||||
|
var info = core.enemys.getDamageInfo(core.material.enemys[gid], { hp: origin_hero_hp, atk: origin_hero_atk, def: origin_hero_def, mdef: 0 });
|
||||||
|
if (info == null) { // 小队中任何一个怪物不可战斗,直接返回null
|
||||||
|
core.removeFlag("__extraTurn__");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 已经进行的回合数
|
||||||
|
core.setFlag("__extraTurn__", info.turn);
|
||||||
|
init_damage += info.damage;
|
||||||
|
}
|
||||||
|
if (guard_before_current_enemy) { // --- 先打支援怪物,增加当前回合数
|
||||||
|
turn += core.getFlag("__extraTurn__", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core.removeFlag("__extraTurn__");
|
||||||
|
// ------ 支援END ------ //
|
||||||
|
|
||||||
|
// 最终伤害:初始伤害 + 怪物对勇士造成的伤害 + 反击伤害
|
||||||
|
var damage = init_damage + (turn - 1) * per_damage + turn * counterDamage;
|
||||||
|
// 再扣去护盾
|
||||||
|
damage -= hero_mdef;
|
||||||
|
|
||||||
|
// 匙之力(编号37):角色身上每存在一把黄钥匙,最终伤害提升5%;蓝钥匙视为三把黄钥匙,红钥匙视为九把黄钥匙,线性叠加。
|
||||||
|
// 需要判定是否负伤
|
||||||
|
if (core.hasSpecial(mon_special, 37) && damage > 0) {
|
||||||
|
var cnt = core.itemCount("yellowKey") + 3 * core.itemCount("blueKey") + 9 * core.itemCount("redKey");
|
||||||
|
damage += Math.floor(damage * 0.05 * cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否允许负伤
|
||||||
|
if (!core.flags.enableNegativeDamage)
|
||||||
|
damage = Math.max(0, damage);
|
||||||
|
|
||||||
|
// 最后处理仇恨和固伤(因为这两个不能被护盾减伤)
|
||||||
|
if (core.hasSpecial(mon_special, 17)) { // 仇恨
|
||||||
|
damage += core.getFlag('hatred', 0);
|
||||||
|
}
|
||||||
|
if (core.hasSpecial(mon_special, 22)) { // 固伤
|
||||||
|
damage += enemy.damage || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 技能1:角色每回合回复`flag:x1`%倍护盾的生命值。(如,`flag:x1`是10时,每回合回复10%护盾值的生命)
|
||||||
|
if (core.getFlag("skill", 0) == 1) {
|
||||||
|
damage -= Math.floor(core.getFlag('x1', 0) / 100 * hero_mdef * turn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"mon_hp": Math.floor(mon_hp),
|
||||||
|
"mon_atk": Math.floor(mon_atk),
|
||||||
|
"mon_def": Math.floor(mon_def),
|
||||||
|
"init_damage": Math.floor(init_damage),
|
||||||
|
"per_damage": Math.floor(per_damage),
|
||||||
|
"hero_per_damage": Math.floor(hero_per_damage),
|
||||||
|
"turn": Math.floor(turn),
|
||||||
|
"damage": Math.floor(damage)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
102
L2.md
Normal file
102
L2.md
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# 第二章:怪物特殊属性定义(getSpecials),表格配置
|
||||||
|
|
||||||
|
要求:读懂脚本编辑 - 怪物特殊属性定义(getSpecials)的实现。理解如何定义一个新的怪物属性。同时,还需要理解如何给怪物表格增加新项目。
|
||||||
|
|
||||||
|
回答如下问题:
|
||||||
|
|
||||||
|
1. 如何定义一个新的怪物属性?
|
||||||
|
|
||||||
|
2. 如何取得当前怪物的各项数值?
|
||||||
|
|
||||||
|
3. 第五项参数在哪些情况下写1?为什么需要?
|
||||||
|
|
||||||
|
4. 如何给怪物表格定义新项目?如何在伤害计算等地方取用定义的新项目?
|
||||||
|
|
||||||
|
5. 当前,有一部分怪物属性使用了相同的表格项,如阻激夹域都使用了`value`等;这样无法同时给怪物增加吸血和领域两个属性。那应该怎么做才能同时支持这两项?
|
||||||
|
|
||||||
|
6. 理解《咕工智障》的下述怪物特殊属性并回答:
|
||||||
|
- 如果一个该属性怪物设置了`hpValue = 10, range = 2`,那么怪物手册中显示该怪物的特殊属性名和描述是什么?
|
||||||
|
- 如果一个该属性怪物设置了`hpValue = -20, defValue = 30`,那么怪物手册中显示该怪物的特殊属性名和描述是什么?
|
||||||
|
- 如果一个该属性怪物设置了`hpValue = 10, atkValue = 20, defValue = 30, add = true`,那么怪物手册中显示该怪物的特殊属性名和描述是什么?
|
||||||
|
- 如果怪物手册中的描述是`周围方形范围内5格的友军生命提升10%,攻击降低20%,线性叠加`,那么该怪物的哪些项被设置了成了多少?该怪物的特殊属性名是什么?
|
||||||
|
- 如果怪物手册中的描述是`周围十字范围内3格的友军生命提升20%,攻击提升10%,防御提升5%,不可叠加`,那么该怪物的哪些项被设置了成了多少?该怪物的特殊属性名是什么?
|
||||||
|
|
||||||
|
```js
|
||||||
|
[25, function (enemy) {
|
||||||
|
if (enemy.auraName) return enemy.auraName;
|
||||||
|
if (enemy.hpValue && enemy.atkValue && enemy.defValue) return "魔力光环";
|
||||||
|
if (enemy.hpValue) {
|
||||||
|
if (enemy.hpValue > 0) return "活力光环";
|
||||||
|
return "懒散光环";
|
||||||
|
}
|
||||||
|
if (enemy.atkValue) {
|
||||||
|
if (enemy.atkValue > 0) return "狂暴光环";
|
||||||
|
return "静谧光环";
|
||||||
|
}
|
||||||
|
if (enemy.defValue) {
|
||||||
|
if (enemy.defValue > 0) return "坚毅光环";
|
||||||
|
return "软弱光环";
|
||||||
|
}
|
||||||
|
return "光环";
|
||||||
|
}, function (enemy) {
|
||||||
|
var str;
|
||||||
|
if (!enemy.range) {
|
||||||
|
str = "同楼层所有友军";
|
||||||
|
} else {
|
||||||
|
str = "周围" + (enemy.zoneSquare ? "方形" : "十字")
|
||||||
|
+ "范围内\r[yellow]" + (enemy.range || 1) + "\r格的友军";
|
||||||
|
}
|
||||||
|
if (enemy.hpValue) {
|
||||||
|
if (enemy.hpValue > 0) str += "生命提升\r[yellow]" + enemy.hpValue + "%\r,";
|
||||||
|
else str += "生命降低\r[yellow]" + (-enemy.hpValue) + "%\r,";
|
||||||
|
}
|
||||||
|
if (enemy.atkValue) {
|
||||||
|
if (enemy.atkValue > 0) str += "攻击提升\r[yellow]" + enemy.atkValue + "%\r,";
|
||||||
|
else str += "攻击降低\r[yellow]" + (-enemy.atkValue) + "%\r,";
|
||||||
|
}
|
||||||
|
if (enemy.defValue) {
|
||||||
|
if (enemy.defValue > 0) str += "防御提升\r[yellow]" + enemy.defValue + "%\r,";
|
||||||
|
else str += "防御降低\r[yellow]" + (-enemy.defValue) + "%\r,";
|
||||||
|
}
|
||||||
|
str += (enemy.add ? "线性叠加" : "不可叠加");
|
||||||
|
return str;
|
||||||
|
}, "#e6e099", 1],
|
||||||
|
```
|
||||||
|
|
||||||
|
7. 理解《咕工智障》的下述怪物特殊属性并回答:
|
||||||
|
- 吸血数值是如何计算在怪物手册之中的?
|
||||||
|
- 如何根据角色当前拥有的道具或者变量来修改怪物属性描述?
|
||||||
|
- 如果要让简单难度下,怪物吸血值减半,特殊属性定义中应该怎么修改?
|
||||||
|
|
||||||
|
```js
|
||||||
|
[11, "吸血", function (enemy) {
|
||||||
|
var str = "\r[\#e525ff]【红海技能】\r战斗前,首先吸取对方的"
|
||||||
|
+ Math.floor(100 * enemy.value || 0) + "%生命(约"
|
||||||
|
+ Math.floor((enemy.value || 0) * core.getRealStatus('hp')) + "点)作为伤害"
|
||||||
|
+ (enemy.add ? ",并把伤害数值加到自身生命上" : "");
|
||||||
|
if (core.hasItem("I_noVampire")) str += "\r[yellow](对你无效)\r";
|
||||||
|
return str;
|
||||||
|
}, "#ff00d2"],
|
||||||
|
```
|
||||||
|
|
||||||
|
8. 定义同时满足如下条件的特殊属性(编号`28`),并和《咕工智障》的写法进行比较。
|
||||||
|
- 当怪物的 `value28 > 0` 时,特殊属性名为`迅捷光环`
|
||||||
|
- 当怪物的 `value28 <= 0` 时,特殊属性名为`怠惰光环`
|
||||||
|
- 当难度为简单(`flag:hard = 1`)时,描述为`此技能在简单难度下无效!`
|
||||||
|
- 当角色拥有道具 `I123` 时,描述为`此技能已被魔杖抵消!`
|
||||||
|
- 当怪物的 `range > 0` 时,描述存在`周围[方形/十字]范围内[?]格的友军`;其中这`[]`里面内容由怪物的 `zoneSquare` 和 `range` 决定。
|
||||||
|
- 当怪物的 `range = null` 时,描述存在`当前楼层的友军`
|
||||||
|
- 当怪物的 `value28 > 0` 时,描述存在`先攻X回合`,其中X由`value28`决定(如,`value28 = 5`时,描述存在`先攻5回合`)
|
||||||
|
- 当怪物的 `value28 <= 0` 时,描述存在`被先攻X回合`,其中X由`value28`决定(如,`value28 = -5`时,描述存在`被先攻5回合`)
|
||||||
|
|
||||||
|
9. 定义同时满足如下条件的特殊属性(编号`30`),并和《咕工智障》的写法进行比较。
|
||||||
|
- 特殊属性名:匙之力
|
||||||
|
- 当怪物的 `range > 0` 时,描述存在`周围[方形/十字]范围内[?]格内`;其中这`[]`里面内容由怪物的 `zoneSquare` 和 `range` 决定。
|
||||||
|
- 当怪物的 `range = null` 时,描述存在`同楼层内`
|
||||||
|
- 描述存在 `每存在一个黄钥匙,生命增加X点,攻击增加Y点,防御增加Z点`,其中`X, Y, Z`由怪物的`value, atkValue, defValue`决定。如果其中某一项为0或null则不显示对应项。(例如,如果`value = 30, defValue = 20`则只显示`生命增加30点,防御增加20点`)
|
||||||
|
|
||||||
|
10. 将阻击、激光、领域、吸血所使用的特殊属性值(当前均为`value`)分离,并实现一个怪物同时拥有`阻击伤害100点,激光伤害50点,领域伤害200点,且吸血20%`这四个特殊属性。
|
||||||
|
- 需在怪物手册正确显示
|
||||||
|
- 需正确实现对应的效果(需要修改`伤害计算(getDamageInfo)和阻激夹域伤害(updateCheckBlock)`两个函数)
|
||||||
|
|
||||||
|
[点此查看答案](L2_answer)
|
153
L2_answer.md
Normal file
153
L2_answer.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
1. 在怪物特殊属性中增加一行,分别定义特殊属性的数字、名字、描述、颜色,以及是否是地图类技能(如光环)。
|
||||||
|
|
||||||
|
2. 使用`function (enemy) {}`,里面可以取用`enemy.xxx`调用数值;例如`enemy.value`可以调用怪物的value值。
|
||||||
|
|
||||||
|
3. 仅在地图类技能时需要写1。当怪物的该项为1时,意味着该怪物的技能是和地图上其他图块有关的(而不是怪物自身独立的)。例如,先攻魔攻这些技能就是自身独立(和地图上其他图块无关),而光环,匙之力(根据周边钥匙数量提升属性)等这些技能就是与地图上其他怪物有关的。
|
||||||
|
|
||||||
|
4. 点击「配置表格」,找到怪物相关项目,并照葫芦画瓢即可。常见写法如下。
|
||||||
|
|
||||||
|
```js
|
||||||
|
"value": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_docs": "特殊属性数值",
|
||||||
|
"_data": "特殊属性的数值\n如:领域/阻激/激光怪的伤害值;吸血怪的吸血比例;光环怪增加生命的比例"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 可定义不同项目如v1, v2, v3等分别表示伤害值;然后修改特殊描述和实际效果以取用v1, v2, v3等来代替原来的value。参见第10题解答。
|
||||||
|
|
||||||
|
6.
|
||||||
|
- 活力光环;周围十字范围内2格的友军生命提升10%,不可叠加。
|
||||||
|
- 懒散光环;同楼层所有友军生命降低20%,防御提升30%,不可叠加。(注意,不会显示坚毅光环!)
|
||||||
|
- 魔力光环:同楼层所有友军生命提升10%,攻击提升20%,防御提升30%,线性叠加。
|
||||||
|
- range=5, zoneSquare = true, hpValue=10, atkValue=-20, add=true;活力光环。
|
||||||
|
- range=3, hpValue = 20, atkValue = 10, defValue = 5;魔力光环。
|
||||||
|
|
||||||
|
7.
|
||||||
|
- 直接使用`enemy.value`获得当前吸血比例,乘以玩家当前生命值`core.getRealStatus('hp')`获得吸血数值,并取整再显示。
|
||||||
|
- 可以通过额外的if来修改具体的描述。
|
||||||
|
- 可以在简单难度下将value减半以达到效果。
|
||||||
|
|
||||||
|
```js
|
||||||
|
[11, "吸血", function (enemy) {
|
||||||
|
var value = enemy.value || 0; // 怪物吸血比例
|
||||||
|
if (flags.hard == 1) value /= 2; // 简单难度下吸血值减半
|
||||||
|
// 使用局部变量 value 代替 enemy.value
|
||||||
|
var str = "\r[\#e525ff]【红海技能】\r战斗前,首先吸取对方的"
|
||||||
|
+ Math.floor(100 * value) + "%生命(约"
|
||||||
|
+ Math.floor(value * core.getRealStatus('hp')) + "点)作为伤害"
|
||||||
|
+ (enemy.add ? ",并把伤害数值加到自身生命上" : "");
|
||||||
|
if (core.hasItem("I_noVampire")) str += "\r[yellow](对你无效)\r";
|
||||||
|
return str;
|
||||||
|
}, "#ff00d2"],
|
||||||
|
```
|
||||||
|
|
||||||
|
8.
|
||||||
|
```js
|
||||||
|
[28, function (enemy) {
|
||||||
|
if (enemy.value28 > 0) return "迅捷光环";
|
||||||
|
return "怠惰光环";
|
||||||
|
}, function (enemy) {
|
||||||
|
var str;
|
||||||
|
// 直接 return 可以不显示具体技能内容。
|
||||||
|
if (flags.hard == 1) return '此技能在简单难度下无效!';
|
||||||
|
if (core.hasItem('I123')) return '此技能已被魔杖抵消!';
|
||||||
|
if (!enemy.range) {
|
||||||
|
str = "同楼层所有友军";
|
||||||
|
} else {
|
||||||
|
str = "周围" + (enemy.zoneSquare ? "方形" : "十字") + "范围内\r[yellow]" + (enemy.range || 1) + "\r格的友军";
|
||||||
|
}
|
||||||
|
if (enemy.value28 > 0) {
|
||||||
|
str += "先攻\r[yellow]" + (enemy.value28 || 0) + "\r回合。";
|
||||||
|
} else {
|
||||||
|
str += "被先攻\r[yellow]" + (-enemy.value28 || 0) + "\r回合。";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}, "#00dd00", 1],
|
||||||
|
```
|
||||||
|
|
||||||
|
9.
|
||||||
|
```js
|
||||||
|
[30, "匙之力", function (enemy) {
|
||||||
|
var str = "";
|
||||||
|
if (enemy.range) {
|
||||||
|
str += "周围" + (enemy.zoneSquare ? "方形" : "十字") + "范围内\r[yellow]" + enemy.range + "\r格,每存在一把黄钥匙,";
|
||||||
|
}
|
||||||
|
else str += "同楼层每存在一把黄钥匙,";
|
||||||
|
if (enemy.value) str += "生命增加\r[yellow]" + enemy.value + "\r点,";
|
||||||
|
if (enemy.atkValue) str += "攻击增加\r[yellow]" + enemy.atkValue + "\r点,";
|
||||||
|
if (enemy.defValue) str += "防御增加\r[yellow]" + enemy.defValue + "\r点,";
|
||||||
|
str += "线性叠加。1把蓝钥匙=3把黄钥匙,1把红钥匙=10把黄钥匙。";
|
||||||
|
return str;
|
||||||
|
}, "#A0A000", 1],
|
||||||
|
```
|
||||||
|
|
||||||
|
10. 使用配置表格增设如下项目(也可以使用更好的名称如laserValue):
|
||||||
|
```js
|
||||||
|
"v1": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_docs": "阻击伤害"
|
||||||
|
},
|
||||||
|
"v2": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_docs": "激光伤害"
|
||||||
|
},
|
||||||
|
"v3": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_docs": "领域伤害"
|
||||||
|
},
|
||||||
|
"v4": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_docs": "吸血比例"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
然后,特殊描述可以如下修改(分别使用v1, v2, v3, v4代替value):
|
||||||
|
```js
|
||||||
|
[11, "吸血", function (enemy) { return "战斗前,怪物首先吸取角色的" + Math.floor(100 * enemy.v4 || 0) + "%生命(约" + Math.floor((enemy.v4 || 0) * core.getStatus('hp')) + "点)作为伤害" + (enemy.add ? ",并把伤害数值加到自身生命上" : ""); }, "#dd4448"],
|
||||||
|
[15, "领域", function (enemy) { return "经过怪物周围" + (enemy.zoneSquare ? "九宫格" : "十字") + "范围内" + (enemy.range || 1) + "格时自动减生命" + (enemy.v3 || 0) + "点"; }, "#c677dd"],
|
||||||
|
[18, "阻击", function (enemy) { return "经过怪物周围" + (enemy.zoneSquare ? "九宫格" : "十字") + "时自动减生命" + (enemy.v1 || 0) + "点,同时怪物后退一格"; }, "#8888e6"],
|
||||||
|
[24, "激光", function (enemy) { return "经过怪物同行或同列时自动减生命" + (enemy.v2 || 0) + "点"; }, "#dda0dd"],
|
||||||
|
```
|
||||||
|
最后,修改实际效果。
|
||||||
|
```js
|
||||||
|
// 吸血:getDamageInfo(节选)
|
||||||
|
if (core.hasSpecial(mon_special, 11)) {
|
||||||
|
/** !!使用enemy.v4替代enemy.value!! **/
|
||||||
|
var vampire_damage = hero_hp * enemy.v4;
|
||||||
|
|
||||||
|
// 如果有神圣盾免疫吸血等可以在这里写
|
||||||
|
// 也可以用hasItem和hasEquip来判定装备
|
||||||
|
// if (core.hasFlag('shield5')) vampire_damage = 0;
|
||||||
|
|
||||||
|
vampire_damage = Math.floor(vampire_damage) || 0;
|
||||||
|
// 加到自身
|
||||||
|
if (enemy.add) // 如果加到自身
|
||||||
|
mon_hp += vampire_damage;
|
||||||
|
|
||||||
|
init_damage += vampire_damage;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```js
|
||||||
|
// 领域:updateCheckBlock(节选)
|
||||||
|
for (var dx = -range; dx <= range; dx++) {
|
||||||
|
for (var dy = -range; dy <= range; dy++) {
|
||||||
|
if (dx == 0 && dy == 0) continue;
|
||||||
|
var nx = x + dx,
|
||||||
|
ny = y + dy,
|
||||||
|
currloc = nx + "," + ny;
|
||||||
|
if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
|
||||||
|
// 如果是十字领域,则还需要满足 |dx|+|dy|<=range
|
||||||
|
if (!zoneSquare && Math.abs(dx) + Math.abs(dy) > range) continue;
|
||||||
|
/** !!使用enemy.v3替代enemy.value!! **/
|
||||||
|
damage[currloc] = (damage[currloc] || 0) + (enemy.v3 || 0);
|
||||||
|
type[currloc] = type[currloc] || {};
|
||||||
|
type[currloc]["领域伤害"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
激光和阻击同领域实现。
|
38
L3.md
Normal file
38
L3.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# 第三章:怪物真实属性获取(getEnemyInfo)
|
||||||
|
|
||||||
|
要求:读懂脚本编辑 - 怪物真实属性(getEnemyInfo)的实现(无需理解支援)。理解如何动态修改怪物的三维,理解光环效果的实现并知晓如何新增光环效果,理解如何定义新变量并在getDamageInfo中取用。
|
||||||
|
|
||||||
|
回答如下问题:
|
||||||
|
|
||||||
|
1. 为什么要存在`getEnemyInfo`函数以获得怪物真实属性?所有东西都写在`getDamageInfo`里面不好吗?
|
||||||
|
|
||||||
|
2. 实现**仿攻(编号45)**:怪物攻击不低于勇士攻击。
|
||||||
|
|
||||||
|
3. 实现**窥血为攻(编号36)**: 战斗开始时,自身攻击力变为角色当前生命值的10%,向下取整。这种写法比第一章中在`getDamageInfo`中写法效果更好,为什么?
|
||||||
|
|
||||||
|
4. 仅修改`getEnemyInfo`,实现一个怪物`E345`满足:
|
||||||
|
- 简单难度(flag:hard==1)下,无特殊属性。
|
||||||
|
- 普通难度(flag:hard==2)下,拥有先攻属性。
|
||||||
|
- 困难难度(flag:hard==3)下,拥有三连击属性。
|
||||||
|
- 噩梦难度(flag:hard==4)下,同时拥有先攻、魔攻、二连击属性。
|
||||||
|
- 如果玩家拥有道具`I123`,则直接无视此怪物所有属性。
|
||||||
|
|
||||||
|
5. 仅修改`getEnemyInfo`,实现一个道具`I234`:**当玩家持有此道具时,怪物的血攻防均降低10%。**
|
||||||
|
|
||||||
|
6. 仅修改`getEnemyInfo`,实现特殊属性 **「上位压制」(特殊编号46)**:玩家每低于怪物一个等级(通过`core.getStatus('lv')`获得当前玩家等级,通过`enemy.level`获得该怪物的等级),怪物三维提升10%;玩家每高于怪物一个等级,怪物三维降低5%。
|
||||||
|
|
||||||
|
7. 解释光环的缓存原理。为什么光环需要缓存?
|
||||||
|
|
||||||
|
8. `index`是什么意思? `var cache = core.status.checkBlock.cache[index];` 是干什么的?
|
||||||
|
|
||||||
|
9. 在默认光环的实现中,`hp_buff`, `atk_buff`, `def_buff`, `usedEnemyIds` 分别是干什么的?
|
||||||
|
|
||||||
|
10. 在默认光环的实现中,怪物属性的`value`, `atkValue`和`defValue`用于控制怪物的比例增幅。将其效果修改成数值增幅(例如 `value = 50` 表示生命提升50点,而不是50%)。
|
||||||
|
|
||||||
|
11. 局部光环的实现原理是什么?十字和九宫格光环是如何区分的?
|
||||||
|
|
||||||
|
12. 实现 **「协同作战」(特殊属性47)**:同楼层/周围X格(由range和zoneSquare决定),每存在一个该属性的队友(包括自身),生命和攻防对应比例提升(提升比例由怪物value, atkValue, defValue决定,线性叠加)。并在游戏中实际验证效果。(注意这个效果和默认光环的区别)。
|
||||||
|
|
||||||
|
13. 实现 **「迅捷光环」(特殊属性28)**:同楼层/周围X格(由range和zoneSquare决定)的怪物额外先攻X回合(由此怪物的value28决定)。并在游戏中实际验证效果。
|
||||||
|
|
||||||
|
14. 实现 **「吸血光环」(特殊属性48)**:同楼层/周围X格(由range和zoneSquare决定)的怪物拥有「吸血」特殊属性,吸血比例由光环怪的`vampireValue`项决定。并在游戏中实际验证效果。
|
4242
MotaAction.g4
Normal file
4242
MotaAction.g4
Normal file
File diff suppressed because it is too large
Load Diff
10
_sidebar.md
Normal file
10
_sidebar.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
- [快速上手](start)
|
||||||
|
- [元件说明](element)
|
||||||
|
- [事件编辑](event)
|
||||||
|
- [事件指令](instruction)
|
||||||
|
- [个性化](personalization)
|
||||||
|
- [脚本](script)
|
||||||
|
- [修改编辑器](editor)
|
||||||
|
- [UI编辑器](ui-editor)
|
||||||
|
- [附录:API列表](api)
|
145
action.d.ts
vendored
Normal file
145
action.d.ts
vendored
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* 鼠标与触屏操作的函数
|
||||||
|
*/
|
||||||
|
type MotaMouseFunc = (x: number, y: number, px: number, py: number) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按键操作的函数
|
||||||
|
*/
|
||||||
|
type MotaKeyboardFunc = (e: KeyboardEvent) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 没有最乱,只有更乱
|
||||||
|
*/
|
||||||
|
interface RegisteredActionMap {
|
||||||
|
keyDown: (keyCode: number) => boolean;
|
||||||
|
keyDownCtrl: () => boolean;
|
||||||
|
keyUp: (keyCode: number, altKey: boolean, fromReplay: boolean) => boolean;
|
||||||
|
longClick: MotaMouseFunc;
|
||||||
|
onStatusBarClick: (px: number, py: number, vertical: boolean) => boolean;
|
||||||
|
ondown: MotaMouseFunc;
|
||||||
|
onkeyDown: MotaKeyboardFunc;
|
||||||
|
onkeyUp: MotaKeyboardFunc;
|
||||||
|
onmousewheel: (direct: 1 | -1) => boolean;
|
||||||
|
onmove: MotaMouseFunc;
|
||||||
|
onup: MotaMouseFunc;
|
||||||
|
pressKey: (keyCode: number) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionKey = keyof RegisteredActionMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将注册的函数的返回值变成void就变成了actions上的函数...
|
||||||
|
*/
|
||||||
|
type VoidedActionFuncs = {
|
||||||
|
[P in ActionKey]: (...params: Parameters<RegisteredActionMap[P]>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击位置
|
||||||
|
*/
|
||||||
|
interface ClickLoc extends Loc {
|
||||||
|
/**
|
||||||
|
* 格子的大小(这不是32还能是其它的吗??
|
||||||
|
*/
|
||||||
|
size: 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegisteredActionOf<K extends ActionKey> {
|
||||||
|
/**
|
||||||
|
* 交互的类型
|
||||||
|
*/
|
||||||
|
action: K;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交互的唯一标识符
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级,越高越优先执行
|
||||||
|
*/
|
||||||
|
priority: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交互函数
|
||||||
|
*/
|
||||||
|
func: RegisteredActionMap[K];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交互模块
|
||||||
|
*/
|
||||||
|
interface Actions extends VoidedActionFuncs {
|
||||||
|
/**
|
||||||
|
* 横向的最后一个格子的横坐标
|
||||||
|
*/
|
||||||
|
readonly LAST: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格子长度的一半
|
||||||
|
*/
|
||||||
|
readonly _HX_: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格子高度的一半
|
||||||
|
*/
|
||||||
|
readonly _HY_: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有已注册的交互操作
|
||||||
|
*/
|
||||||
|
readonly actions: {
|
||||||
|
[P in ActionKey]: RegisteredActionOf<P>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 此函数将注册一个用户交互行为。
|
||||||
|
* @param action 要注册的交互类型
|
||||||
|
* @param name 自定义名称,可被注销使用
|
||||||
|
* @param func 执行函数,如果func返回true,则不会再继续执行其他的交互函数
|
||||||
|
* @param priority 优先级,优先级高的将会被执行。此项可不填,默认为0
|
||||||
|
*/
|
||||||
|
registerAction<K extends ActionKey>(
|
||||||
|
action: K,
|
||||||
|
name: string,
|
||||||
|
func: RegisteredActionMap[K],
|
||||||
|
priority?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销一个用户交互行为
|
||||||
|
* @param action 要注销的交互类型
|
||||||
|
* @param name 要注销的自定义名称
|
||||||
|
*/
|
||||||
|
unregisterAction(action: ActionKey, name: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行一个用户交互行为
|
||||||
|
*/
|
||||||
|
doRegisteredAction<K extends ActionKey>(
|
||||||
|
action: K,
|
||||||
|
...params: Parameters<RegisteredActionMap[K]>
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个横坐标是否在(_HX_ - 2, _HX_ + 2)范围外
|
||||||
|
* @param x 要判断的横坐标
|
||||||
|
*/
|
||||||
|
_out(x: number): boolean;
|
||||||
|
|
||||||
|
_getNextFlyFloor(delta: number, index: number): number;
|
||||||
|
|
||||||
|
_clickGameInfo_openComments();
|
||||||
|
|
||||||
|
_getClickLoc(
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const actions: new () => Actions;
|
3222
actions.js
Normal file
3222
actions.js
Normal file
File diff suppressed because it is too large
Load Diff
55
blocksdemo.md
Normal file
55
blocksdemo.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
http://127.0.0.1:1055/_docs/#/blocksdemo
|
||||||
|
|
||||||
|
方便测试用
|
||||||
|
|
||||||
|
这种写法不会生成按钮
|
||||||
|
```js
|
||||||
|
'run';
|
||||||
|
return bg.parseList(['行内']);
|
||||||
|
// return bg.parse(['asdasd','df'],'event')+'<br>'+bg.parseList(['asfewg','sty']);
|
||||||
|
```
|
||||||
|
并且是行内的
|
||||||
|
|
||||||
|
这种写法之后的版本会补一个按钮'添加到常用事件', 目前先这样吧
|
||||||
|
|
||||||
|
``` MotaAction.event
|
||||||
|
[
|
||||||
|
'显示文章',
|
||||||
|
'...'
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
``` MotaAction.shop
|
||||||
|
[{
|
||||||
|
"id": "shop1",
|
||||||
|
"text": "\t[贪婪之神,moneyShop]勇敢的武士啊, 给我${20+2*flag:shop1}金币就可以:",
|
||||||
|
"textInList": "1F金币商店",
|
||||||
|
"choices": [
|
||||||
|
{"text": "生命+800", "need": "status:money>=20+2*flag:shop1", "action": [
|
||||||
|
{"type": "setValue", "name": "status:money", "operator": "-=", "value": "20+2*flag:shop1"},
|
||||||
|
{"type": "setValue", "name": "flag:shop1", "operator": "+=", "value": "1"},
|
||||||
|
{"type": "setValue", "name": "status:hp", "operator": "+=", "value": "800"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"id": "itemShop",
|
||||||
|
"item": true,
|
||||||
|
"textInList": "道具商店",
|
||||||
|
"choices": [
|
||||||
|
{"id": "yellowKey", "number": 10, "money": 10}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"id": "keyShop1",
|
||||||
|
"textInList": "回收钥匙商店",
|
||||||
|
"commonEvent": "回收钥匙商店",
|
||||||
|
"args": ""
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|
||||||
|
``` MotaAction.action
|
||||||
|
'显示文章'
|
||||||
|
```
|
||||||
|
|
||||||
|
``` MotaAction
|
||||||
|
['MotaAction.action的anction可以省略','只写MotaAction']
|
||||||
|
```
|
1
config.json
Normal file
1
config.json
Normal file
File diff suppressed because one or more lines are too long
1095
control.d.ts
vendored
Normal file
1095
control.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3693
control.js
Normal file
3693
control.js
Normal file
File diff suppressed because it is too large
Load Diff
548
core.js
Normal file
548
core.js
Normal file
@ -0,0 +1,548 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 start
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @type {CoreMixin}
|
||||||
|
// */
|
||||||
|
// const core = (() => {
|
||||||
|
|
||||||
|
function core () {
|
||||||
|
this._WIDTH_ = 13;
|
||||||
|
this._HEIGHT_ = 13;
|
||||||
|
this._PX_ = this._WIDTH_ * 32;
|
||||||
|
this._PY_ = this._HEIGHT_ * 32;
|
||||||
|
this._HALF_WIDTH_ = Math.floor(this._WIDTH_ / 2);
|
||||||
|
this._HALF_HEIGHT_ = Math.floor(this._HEIGHT_ / 2);
|
||||||
|
|
||||||
|
this.__SIZE__ = main.mode == 'editor' ? 13 : this._HEIGHT_;
|
||||||
|
this.__PIXELS__ = this.__SIZE__ * 32;
|
||||||
|
this.__HALF_SIZE__ = Math.floor(this.__SIZE__ / 2);
|
||||||
|
this.material = {
|
||||||
|
'animates': {},
|
||||||
|
'images': {},
|
||||||
|
'bgms': {},
|
||||||
|
'sounds': {},
|
||||||
|
'items': {},
|
||||||
|
'enemys': {},
|
||||||
|
'icons': {},
|
||||||
|
'ground': null,
|
||||||
|
'grundCanvas': null,
|
||||||
|
'groundPattern': null,
|
||||||
|
'autotileEdges': {},
|
||||||
|
}
|
||||||
|
this.timeout = {
|
||||||
|
'turnHeroTimeout': null,
|
||||||
|
'onDownTimeout': null,
|
||||||
|
'sleepTimeout': null,
|
||||||
|
}
|
||||||
|
this.interval = {
|
||||||
|
'heroMoveInterval': null,
|
||||||
|
'onDownInterval': null,
|
||||||
|
}
|
||||||
|
this.animateFrame = {
|
||||||
|
'totalTime': 0,
|
||||||
|
'totalTimeStart': 0,
|
||||||
|
'globalAnimate': false,
|
||||||
|
'globalTime': 0,
|
||||||
|
'selectorTime': 0,
|
||||||
|
'selectorUp': true,
|
||||||
|
'animateTime': 0,
|
||||||
|
'moveTime': 0,
|
||||||
|
'lastLegTime': 0,
|
||||||
|
'leftLeg': true,
|
||||||
|
'weather': {
|
||||||
|
'time': 0,
|
||||||
|
'type': null,
|
||||||
|
'level': 1,
|
||||||
|
'nodes': [],
|
||||||
|
'data': null,
|
||||||
|
'fog': null,
|
||||||
|
'cloud': null,
|
||||||
|
'sun': null
|
||||||
|
},
|
||||||
|
"tip": null,
|
||||||
|
"asyncId": {},
|
||||||
|
"lastAsyncId": null
|
||||||
|
}
|
||||||
|
this.musicStatus = {
|
||||||
|
'audioContext': null, // WebAudioContext
|
||||||
|
'bgmStatus': false, // 是否播放BGM
|
||||||
|
'soundStatus': true, // 是否播放SE
|
||||||
|
'playingBgm': null, // 正在播放的BGM
|
||||||
|
'pauseTime': 0, // 上次暂停的时间
|
||||||
|
'lastBgm': null, // 上次播放的bgm
|
||||||
|
'gainNode': null,
|
||||||
|
'playingSounds': {}, // 正在播放的SE
|
||||||
|
'userVolume': 1.0, // 用户音量
|
||||||
|
'designVolume': 1.0, //设计音量
|
||||||
|
'bgmSpeed': 100, // 背景音乐速度
|
||||||
|
'bgmUsePitch': null, // 是否同时修改音调
|
||||||
|
'cachedBgms': [], // 缓存BGM内容
|
||||||
|
'cachedBgmCount': 8, // 缓存的bgm数量
|
||||||
|
}
|
||||||
|
this.platform = {
|
||||||
|
'isOnline': true, // 是否http
|
||||||
|
'isPC': true, // 是否是PC
|
||||||
|
'isAndroid': false, // 是否是Android
|
||||||
|
'isIOS': false, // 是否是iOS
|
||||||
|
'string': 'PC',
|
||||||
|
'isWeChat': false, // 是否是微信
|
||||||
|
'isQQ': false, // 是否是QQ
|
||||||
|
'isChrome': false, // 是否是Chrome
|
||||||
|
'supportCopy': false, // 是否支持复制到剪切板
|
||||||
|
|
||||||
|
'fileInput': null, // FileInput
|
||||||
|
'fileReader': null, // 是否支持FileReader
|
||||||
|
'successCallback': null, // 读取成功
|
||||||
|
'errorCallback': null, // 读取失败
|
||||||
|
}
|
||||||
|
// 样式
|
||||||
|
this.domStyle = {
|
||||||
|
scale: 1.0,
|
||||||
|
ratio: 1.0,
|
||||||
|
hdCanvas: ["damage", "ui", "data"],
|
||||||
|
availableScale: [],
|
||||||
|
isVertical: false,
|
||||||
|
showStatusBar: true,
|
||||||
|
toolbarBtn: false,
|
||||||
|
}
|
||||||
|
this.bigmap = {
|
||||||
|
canvas: ["bg", "event", "event2", "fg", "damage"],
|
||||||
|
offsetX: 0, // in pixel
|
||||||
|
offsetY: 0,
|
||||||
|
posX: 0, //
|
||||||
|
posY: 0,
|
||||||
|
width: main.mode == 'editor' ? this.__SIZE__ : this._WIDTH_, // map width and height
|
||||||
|
height: main.mode == 'editor' ? this.__SIZE__ : this._HEIGHT_,
|
||||||
|
v2: false,
|
||||||
|
threshold: 1024,
|
||||||
|
extend: 10,
|
||||||
|
scale: 1.0,
|
||||||
|
tempCanvas: null, // A temp canvas for drawing
|
||||||
|
cacheCanvas: null, // A cache canvas
|
||||||
|
}
|
||||||
|
this.saves = {
|
||||||
|
"saveIndex": null,
|
||||||
|
"ids": {},
|
||||||
|
"autosave": {
|
||||||
|
"data": null,
|
||||||
|
"time": 0,
|
||||||
|
"updated": false,
|
||||||
|
"storage": true, // 是否把自动存档写入文件a
|
||||||
|
"max": 20, // 自动存档最大回退数
|
||||||
|
"now": 0,
|
||||||
|
},
|
||||||
|
"favorite": [],
|
||||||
|
"favoriteName": {},
|
||||||
|
"cache": {}
|
||||||
|
}
|
||||||
|
this.initStatus = {
|
||||||
|
'played': false,
|
||||||
|
'gameOver': false,
|
||||||
|
|
||||||
|
// 勇士属性
|
||||||
|
'hero': {},
|
||||||
|
'heroCenter': { 'px': null, 'py': null },
|
||||||
|
|
||||||
|
// 当前地图
|
||||||
|
'floorId': null,
|
||||||
|
'thisMap': null,
|
||||||
|
'maps': null,
|
||||||
|
'bgmaps': {},
|
||||||
|
'fgmaps': {},
|
||||||
|
'mapBlockObjs': {},
|
||||||
|
'checkBlock': {}, // 每个点的阻激夹域信息
|
||||||
|
'damage': { // 每个点的显伤绘制
|
||||||
|
'posX': 0,
|
||||||
|
'posY': 0,
|
||||||
|
'data': [],
|
||||||
|
'extraData': [],
|
||||||
|
},
|
||||||
|
|
||||||
|
'lockControl': false,
|
||||||
|
|
||||||
|
// 勇士移动状态
|
||||||
|
'heroMoving': 0,
|
||||||
|
'heroStop': true,
|
||||||
|
|
||||||
|
// 自动寻路相关
|
||||||
|
'automaticRoute': {
|
||||||
|
'autoHeroMove': false,
|
||||||
|
'autoStep': 0,
|
||||||
|
'movedStep': 0,
|
||||||
|
'destStep': 0,
|
||||||
|
'destX': null,
|
||||||
|
'destY': null,
|
||||||
|
'offsetX': null,
|
||||||
|
'offsetY': null,
|
||||||
|
'autoStepRoutes': [],
|
||||||
|
'moveStepBeforeStop': [],
|
||||||
|
'lastDirection': null,
|
||||||
|
'cursorX': 0,
|
||||||
|
'cursorY': 0,
|
||||||
|
"moveDirectly": false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 按下键的时间:为了判定双击
|
||||||
|
'downTime': null,
|
||||||
|
'ctrlDown': false,
|
||||||
|
'preview': {
|
||||||
|
'enabled': false,
|
||||||
|
'prepareDragging': false,
|
||||||
|
'dragging': false,
|
||||||
|
'px': 0,
|
||||||
|
'py': 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 路线&回放
|
||||||
|
'route': [],
|
||||||
|
'replay': {
|
||||||
|
'replaying': false,
|
||||||
|
'pausing': false,
|
||||||
|
'animate': false, // 正在某段动画中
|
||||||
|
'failed': false,
|
||||||
|
'toReplay': [],
|
||||||
|
'totalList': [],
|
||||||
|
'speed': 1.0,
|
||||||
|
'steps': 0,
|
||||||
|
'save': [],
|
||||||
|
},
|
||||||
|
// 录像折叠
|
||||||
|
'routeFolding': {},
|
||||||
|
|
||||||
|
// event事件
|
||||||
|
'shops': {},
|
||||||
|
'event': {
|
||||||
|
'id': null,
|
||||||
|
'data': null,
|
||||||
|
'selection': null,
|
||||||
|
'ui': null,
|
||||||
|
'interval': null,
|
||||||
|
},
|
||||||
|
'autoEvents': [],
|
||||||
|
'textAttribute': {
|
||||||
|
'position': "center",
|
||||||
|
"offset": 0,
|
||||||
|
"title": [255, 215, 0, 1],
|
||||||
|
"background": [0, 0, 0, 0.85],
|
||||||
|
"text": [255, 255, 255, 1],
|
||||||
|
"titlefont": 22,
|
||||||
|
"textfont": 16,
|
||||||
|
"bold": false,
|
||||||
|
"time": 0,
|
||||||
|
"letterSpacing": 0,
|
||||||
|
"animateTime": 0,
|
||||||
|
},
|
||||||
|
"globalAttribute": {
|
||||||
|
'equipName': main.equipName || [],
|
||||||
|
"statusLeftBackground": main.styles.statusLeftBackground || "url(project/materials/ground.png) repeat",
|
||||||
|
"statusTopBackground": main.styles.statusTopBackground || "url(project/materials/ground.png) repeat",
|
||||||
|
"toolsBackground": main.styles.toolsBackground || "url(project/materials/ground.png) repeat",
|
||||||
|
"borderColor": main.styles.borderColor || [204, 204, 204, 1],
|
||||||
|
"statusBarColor": main.styles.statusBarColor || [255, 255, 255, 1],
|
||||||
|
"floorChangingStyle": main.styles.floorChangingStyle || "background-color: black; color: white",
|
||||||
|
"selectColor": main.styles.selectColor || [255, 215, 0, 1],
|
||||||
|
"font": main.styles.font || "Verdana"
|
||||||
|
},
|
||||||
|
'curtainColor': null,
|
||||||
|
|
||||||
|
// 动画
|
||||||
|
'globalAnimateObjs': [],
|
||||||
|
'floorAnimateObjs': [],
|
||||||
|
'boxAnimateObjs': [],
|
||||||
|
'autotileAnimateObjs': [],
|
||||||
|
"globalAnimateStatus": 0,
|
||||||
|
'animateObjs': [],
|
||||||
|
};
|
||||||
|
// 标记的楼层列表,用于数据统计
|
||||||
|
this.markedFloorIds = {};
|
||||||
|
this.status = {};
|
||||||
|
this.dymCanvas = {};
|
||||||
|
|
||||||
|
if (main.mode == 'editor') {
|
||||||
|
document.documentElement.style.setProperty('--size', this.__SIZE__);
|
||||||
|
document.documentElement.style.setProperty('--pixel', this.__PIXELS__ + 'px');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////// 系统事件相关 ///////////
|
||||||
|
|
||||||
|
////// 初始化 //////
|
||||||
|
core.prototype.init = function (coreData, callback) {
|
||||||
|
this._forwardFuncs();
|
||||||
|
for (var key in coreData)
|
||||||
|
core[key] = coreData[key];
|
||||||
|
this._init_flags();
|
||||||
|
this._init_platform();
|
||||||
|
this._init_others();
|
||||||
|
this._init_plugins();
|
||||||
|
var b = main.mode == 'editor';
|
||||||
|
// 初始化画布
|
||||||
|
for (var name in core.canvas) {
|
||||||
|
if (core.domStyle.hdCanvas.indexOf(name) >= 0)
|
||||||
|
core.maps._setHDCanvasSize(core.canvas[name], b ? core.__PIXELS__ : core._PX_, b ? core.__PIXELS__ : core._PY_);
|
||||||
|
else {
|
||||||
|
core.canvas[name].canvas.width = (b ? core.__PIXELS__ : core._PX_);
|
||||||
|
core.canvas[name].canvas.height = (b ? core.__PIXELS__ : core._PY_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.loader._load(function () {
|
||||||
|
core.extensions._load(function () {
|
||||||
|
core._afterLoadResources(callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
core.dom.musicBtn.style.display = 'block';
|
||||||
|
core.setMusicBtn();
|
||||||
|
}
|
||||||
|
|
||||||
|
core.prototype._init_flags = function () {
|
||||||
|
core.flags = core.clone(core.data.flags);
|
||||||
|
core.values = core.clone(core.data.values);
|
||||||
|
core.firstData = core.clone(core.data.firstData);
|
||||||
|
this._init_sys_flags();
|
||||||
|
|
||||||
|
// 让你总是拼错!
|
||||||
|
window.on = true;
|
||||||
|
window.off = false;
|
||||||
|
window.ture = true;
|
||||||
|
window.flase = false;
|
||||||
|
|
||||||
|
core.dom.versionLabel.innerText = core.firstData.version;
|
||||||
|
core.dom.logoLabel.innerText = core.firstData.title;
|
||||||
|
document.title = core.firstData.title + " - HTML5魔塔";
|
||||||
|
document.getElementById("startLogo").innerText = core.firstData.title;
|
||||||
|
(core.firstData.shops || []).forEach(function (t) { core.initStatus.shops[t.id] = t; });
|
||||||
|
|
||||||
|
core.maps._initFloors();
|
||||||
|
// 初始化怪物、道具等
|
||||||
|
core.material.enemys = core.enemys.getEnemys();
|
||||||
|
core.material.items = core.items.getItems();
|
||||||
|
core.material.icons = core.icons.getIcons();
|
||||||
|
|
||||||
|
// 初始化自动事件
|
||||||
|
for (var floorId in core.floors) {
|
||||||
|
var autoEvents = core.floors[floorId].autoEvent || {};
|
||||||
|
for (var loc in autoEvents) {
|
||||||
|
var locs = loc.split(","), x = parseInt(locs[0]), y = parseInt(locs[1]);
|
||||||
|
for (var index in autoEvents[loc]) {
|
||||||
|
var autoEvent = core.clone(autoEvents[loc][index]);
|
||||||
|
if (autoEvent && autoEvent.condition && autoEvent.data) {
|
||||||
|
autoEvent.floorId = floorId;
|
||||||
|
autoEvent.x = x;
|
||||||
|
autoEvent.y = y;
|
||||||
|
autoEvent.index = index;
|
||||||
|
autoEvent.symbol = floorId + "@" + x + "@" + y + "@" + index;
|
||||||
|
autoEvent.condition = core.replaceValue(autoEvent.condition);
|
||||||
|
autoEvent.data = core.precompile(autoEvent.data);
|
||||||
|
core.initStatus.autoEvents.push(autoEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 道具的穿上/脱下,视为自动事件
|
||||||
|
for (var equipId in core.material.items) {
|
||||||
|
var equip = core.material.items[equipId];
|
||||||
|
if (equip.cls != 'equips' || !equip.equip) continue;
|
||||||
|
if (!equip.equip.equipEvent && !equip.equip.unequipEvent) continue;
|
||||||
|
var equipFlag = '_equipEvent_' + equipId;
|
||||||
|
var autoEvent1 = {
|
||||||
|
symbol: "_equipEvent_" + equipId,
|
||||||
|
currentFloor: false,
|
||||||
|
multiExecute: true,
|
||||||
|
condition: "core.hasEquip('" + equipId + "') && !core.hasFlag('" + equipFlag + "')",
|
||||||
|
data: core.precompile([{ "type": "setValue", "name": "flag:" + equipFlag, "value": "true" }].concat(equip.equip.equipEvent || [])),
|
||||||
|
};
|
||||||
|
var autoEvent2 = {
|
||||||
|
symbol: "_unequipEvent_" + equipId,
|
||||||
|
currentFloor: false,
|
||||||
|
multiExecute: true,
|
||||||
|
condition: "!core.hasEquip('" + equipId + "') && core.hasFlag('" + equipFlag + "')",
|
||||||
|
data: core.precompile([{ "type": "setValue", "name": "flag:" + equipFlag, "value": "null" }].concat(equip.equip.unequipEvent || [])),
|
||||||
|
};
|
||||||
|
core.initStatus.autoEvents.push(autoEvent1);
|
||||||
|
core.initStatus.autoEvents.push(autoEvent2);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.initStatus.autoEvents.sort(function (e1, e2) {
|
||||||
|
if (e1.floorId == null) return 1;
|
||||||
|
if (e2.floorId == null) return -1;
|
||||||
|
if (e1.priority != e2.priority) return e2.priority - e1.priority;
|
||||||
|
if (e1.floorId != e2.floorId) return core.floorIds.indexOf(e1.floorId) - core.floorIds.indexOf(e2.floorId);
|
||||||
|
if (e1.x != e2.x) return e1.x - e2.x;
|
||||||
|
if (e1.y != e2.y) return e1.y - e2.y;
|
||||||
|
return e1.index - e2.index;
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
core.prototype._init_sys_flags = function () {
|
||||||
|
if (core.flags.equipboxButton) core.flags.equipment = true;
|
||||||
|
core.flags.displayEnemyDamage = core.getLocalStorage('enemyDamage', true);
|
||||||
|
core.flags.displayCritical = core.getLocalStorage('critical', true);
|
||||||
|
core.flags.displayExtraDamage = core.getLocalStorage('extraDamage', true);
|
||||||
|
core.flags.enableEnemyPoint = core.getLocalStorage('enableEnemyPoint', core.flags.enableEnemyPoint);
|
||||||
|
core.flags.leftHandPrefer = core.getLocalStorage('leftHandPrefer', false);
|
||||||
|
core.flags.extraDamageType = core.getLocalStorage('extraDamageType', 2);
|
||||||
|
// 行走速度
|
||||||
|
core.values.moveSpeed = core.getLocalStorage('moveSpeed', core.values.moveSpeed || 100);
|
||||||
|
core.values.floorChangeTime = core.getLocalStorage('floorChangeTime', core.values.floorChangeTime);
|
||||||
|
if (core.values.floorChangeTime == null) core.values.floorChangeTime = 500;
|
||||||
|
core.flags.enableHDCanvas = core.getLocalStorage('enableHDCanvas', !core.platform.isIOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.prototype._init_platform = function () {
|
||||||
|
core.platform.isOnline = location.protocol.indexOf("http") == 0;
|
||||||
|
if (!core.platform.isOnline) alert("请勿直接打开html文件!使用启动服务或者APP进行离线游戏。");
|
||||||
|
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
|
||||||
|
core.musicStatus.bgmStatus = core.getLocalStorage('bgmStatus', true);
|
||||||
|
core.musicStatus.soundStatus = core.getLocalStorage('soundStatus', true);
|
||||||
|
//新增 userVolume 默认值0.7
|
||||||
|
core.musicStatus.userVolume = core.getLocalStorage('userVolume', 0.7);
|
||||||
|
core.musicStatus.userVolume2 = core.getLocalStorage('userVolume2', 0.7);
|
||||||
|
try {
|
||||||
|
core.musicStatus.audioContext = new window.AudioContext();
|
||||||
|
core.musicStatus.gainNode = core.musicStatus.audioContext.createGain();
|
||||||
|
core.musicStatus.gainNode.gain.value = core.musicStatus.userVolume2;
|
||||||
|
core.musicStatus.gainNode.connect(core.musicStatus.audioContext.destination);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("该浏览器不支持AudioContext");
|
||||||
|
core.musicStatus.audioContext = null;
|
||||||
|
}
|
||||||
|
["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"].forEach(function (t) {
|
||||||
|
if (navigator.userAgent.indexOf(t) >= 0) {
|
||||||
|
if (t == 'iPhone' || t == 'iPad' || t == 'iPod') core.platform.isIOS = true;
|
||||||
|
if (t == 'Android') core.platform.isAndroid = true;
|
||||||
|
core.platform.isPC = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
core.platform.string = core.platform.isPC ? "PC" : core.platform.isAndroid ? "Android" : core.platform.isIOS ? "iOS" : "";
|
||||||
|
core.platform.supportCopy = document.queryCommandSupported && document.queryCommandSupported("copy");
|
||||||
|
var chrome = /Chrome\/(\d+)\./i.exec(navigator.userAgent);
|
||||||
|
if (chrome && parseInt(chrome[1]) >= 50) core.platform.isChrome = true;
|
||||||
|
core.platform.isSafari = /Safari/i.test(navigator.userAgent) && !/Chrome/i.test(navigator.userAgent);
|
||||||
|
core.platform.isQQ = /QQ/i.test(navigator.userAgent);
|
||||||
|
core.platform.isWeChat = /MicroMessenger/i.test(navigator.userAgent);
|
||||||
|
if (window.FileReader) {
|
||||||
|
core.platform.fileReader = new FileReader();
|
||||||
|
core.platform.fileReader.onload = function () {
|
||||||
|
core.readFileContent(core.platform.fileReader.result);
|
||||||
|
};
|
||||||
|
core.platform.fileReader.onerror = function () {
|
||||||
|
if (core.platform.errorCallback)
|
||||||
|
core.platform.errorCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.flags.enableHDCanvas = core.getLocalStorage('enableHDCanvas', !core.platform.isIOS);
|
||||||
|
if (main.mode != 'editor') {
|
||||||
|
core.domStyle.scale = core.getLocalStorage('scale', 1);
|
||||||
|
if (core.flags.enableHDCanvas) core.domStyle.ratio = Math.max(window.devicePixelRatio || 1, core.domStyle.scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.prototype._init_others = function () {
|
||||||
|
// 一些额外的东西
|
||||||
|
core.material.groundCanvas = document.createElement('canvas').getContext('2d');
|
||||||
|
core.material.groundCanvas.canvas.width = core.material.groundCanvas.canvas.height = 32;
|
||||||
|
core.material.groundPattern = core.material.groundCanvas.createPattern(core.material.groundCanvas.canvas, 'repeat');
|
||||||
|
core.bigmap.tempCanvas = document.createElement('canvas').getContext('2d');
|
||||||
|
core.bigmap.cacheCanvas = document.createElement('canvas').getContext('2d');
|
||||||
|
core.loadImage("materials", 'fog', function (name, img) { core.animateFrame.weather.fog = img; });
|
||||||
|
core.loadImage("materials", "cloud", function (name, img) { core.animateFrame.weather.cloud = img; })
|
||||||
|
core.loadImage("materials", "sun", function (name, img) { core.animateFrame.weather.sun = img; })
|
||||||
|
core.loadImage("materials", 'keyboard', function (name, img) { core.material.images.keyboard = img; });
|
||||||
|
// 记录存档编号
|
||||||
|
core.saves.saveIndex = core.getLocalStorage('saveIndex', 1);
|
||||||
|
core.control.getSaveIndexes(function (indexes) { core.saves.ids = indexes; });
|
||||||
|
}
|
||||||
|
|
||||||
|
core.prototype._afterLoadResources = function (callback) {
|
||||||
|
// 初始化地图
|
||||||
|
core.initStatus.maps = core.maps._initMaps();
|
||||||
|
core.control._setRequestAnimationFrame();
|
||||||
|
// 图片裁剪
|
||||||
|
(main.splitImages || []).forEach(function (one) {
|
||||||
|
var name = core.getMappedName(one.name);
|
||||||
|
if (!core.material.images.images[name]) {
|
||||||
|
console.warn('找不到图片:' + name + ',无法裁剪');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!name.endsWith('.png')) {
|
||||||
|
console.warn('无法裁剪非png格式图片:' + name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var arr = core.splitImage(core.material.images.images[name], one.width, one.height);
|
||||||
|
for (var i = 0; i < arr.length; ++i) {
|
||||||
|
core.material.images.images[(one.prefix || "") + i + '.png'] = arr[i];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (core.plugin._afterLoadResources)
|
||||||
|
core.plugin._afterLoadResources();
|
||||||
|
core.showStartAnimate();
|
||||||
|
if (callback) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
core.prototype._init_plugins = function () {
|
||||||
|
core.plugin = new function () { };
|
||||||
|
|
||||||
|
for (var name in plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1) {
|
||||||
|
if (plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1[name] instanceof Function) {
|
||||||
|
try {
|
||||||
|
plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1[name].apply(core.plugin);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
console.error("无法初始化插件" + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core._forwardFunc("plugin");
|
||||||
|
}
|
||||||
|
|
||||||
|
core.prototype._forwardFuncs = function () {
|
||||||
|
for (var i = 0; i < main.loadList.length; ++i) {
|
||||||
|
var name = main.loadList[i];
|
||||||
|
if (name == 'core') continue;
|
||||||
|
this._forwardFunc(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.prototype._forwardFunc = function (name, funcname) {
|
||||||
|
if (funcname == null) {
|
||||||
|
for (funcname in core[name]) {
|
||||||
|
if (funcname.charAt(0) != "_" && core[name][funcname] instanceof Function) {
|
||||||
|
this._forwardFunc(name, funcname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (core[funcname]) {
|
||||||
|
console.error("ERROR: 无法转发 " + name + " 中的函数 " + funcname + " 到 core 中!同名函数已存在。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var parameterInfo = /^\s*function\s*[\w_$]*\(([\w_,$\s]*)\)\s*\{/.exec(core[name][funcname].toString());
|
||||||
|
var parameters = (parameterInfo == null ? "" : parameterInfo[1]).replace(/\s*/g, '').replace(/,/g, ', ');
|
||||||
|
// core[funcname] = new Function(parameters, "return core."+name+"."+funcname+"("+parameters+");");
|
||||||
|
eval("core." + funcname + " = function (" + parameters + ") {\n\treturn core." + name + "." + funcname + ".apply(core." + name + ", arguments);\n}");
|
||||||
|
}
|
||||||
|
|
||||||
|
core.prototype.doFunc = function (func, _this) {
|
||||||
|
if (typeof func == 'string') {
|
||||||
|
func = core.plugin[func];
|
||||||
|
_this = core.plugin;
|
||||||
|
}
|
||||||
|
return func.apply(_this, Array.prototype.slice.call(arguments, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// return new Core();
|
||||||
|
|
||||||
|
// })();
|
||||||
|
var core = new core();
|
152
data.d.ts
vendored
Normal file
152
data.d.ts
vendored
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
interface MainData {
|
||||||
|
/**
|
||||||
|
* 所有的楼层id
|
||||||
|
*/
|
||||||
|
readonly floorIds: FloorIds[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分区指定
|
||||||
|
*/
|
||||||
|
readonly floorPartitions: [FloorIds, FloorIds?][];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的额外素材
|
||||||
|
*/
|
||||||
|
readonly tilesets: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的动画
|
||||||
|
*/
|
||||||
|
readonly animates: AnimationIds[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的bgm
|
||||||
|
*/
|
||||||
|
readonly bgms: BgmIds[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的图片
|
||||||
|
*/
|
||||||
|
readonly images: ImageIds[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的音效
|
||||||
|
*/
|
||||||
|
readonly sounds: SoundIds[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的字体
|
||||||
|
*/
|
||||||
|
readonly fonts: FontIds[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件别名
|
||||||
|
*/
|
||||||
|
readonly nameMap: NameMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 难度选择
|
||||||
|
*/
|
||||||
|
readonly levelChoose: LevelChooseEvent[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装备孔的名称
|
||||||
|
*/
|
||||||
|
readonly equipName: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始界面的bgm
|
||||||
|
*/
|
||||||
|
readonly startBgm: BgmIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主样式
|
||||||
|
*/
|
||||||
|
readonly styles: MainStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片切分信息
|
||||||
|
*/
|
||||||
|
readonly splitImages: SplitImageData;
|
||||||
|
|
||||||
|
readonly plugin: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FirstData {
|
||||||
|
/**
|
||||||
|
* 游戏标题
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏英文名,应当与mota.config.ts中的一致
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏版本
|
||||||
|
*/
|
||||||
|
version: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始地图
|
||||||
|
*/
|
||||||
|
floorId: FloorIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的初始信息
|
||||||
|
*/
|
||||||
|
hero: HeroStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标题界面事件化
|
||||||
|
*/
|
||||||
|
startCanvas: MotaEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始剧情
|
||||||
|
*/
|
||||||
|
startText: MotaEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局商店信息
|
||||||
|
*/
|
||||||
|
shops: ShopEventOf<keyof ShopEventMap>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 升级事件
|
||||||
|
*/
|
||||||
|
levelUp: LevelUpEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏作者
|
||||||
|
*/
|
||||||
|
author: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全塔属性信息
|
||||||
|
*/
|
||||||
|
interface DataCore {
|
||||||
|
/**
|
||||||
|
* 全塔属性的main信息
|
||||||
|
*/
|
||||||
|
readonly main: MainData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化信息
|
||||||
|
*/
|
||||||
|
readonly firstData: FirstData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局数值
|
||||||
|
*/
|
||||||
|
readonly values: CoreValues;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局变量
|
||||||
|
*/
|
||||||
|
readonly flags: CoreFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const data: new () => Omit<DataCore, 'main'>;
|
546
data.js
Normal file
546
data.js
Normal file
@ -0,0 +1,546 @@
|
|||||||
|
var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
|
||||||
|
{
|
||||||
|
"main": {
|
||||||
|
"floorIds": [
|
||||||
|
"MT0",
|
||||||
|
"zzt1",
|
||||||
|
"zhu1",
|
||||||
|
"zhu2",
|
||||||
|
"zhu3",
|
||||||
|
"zhu4",
|
||||||
|
"zhu5",
|
||||||
|
"zhu6",
|
||||||
|
"zhu7",
|
||||||
|
"zhu8",
|
||||||
|
"zhu9",
|
||||||
|
"zhu10",
|
||||||
|
"zhu11",
|
||||||
|
"zhu12",
|
||||||
|
"zhu13",
|
||||||
|
"zhu14",
|
||||||
|
"zhu15",
|
||||||
|
"zhu16",
|
||||||
|
"zhu17",
|
||||||
|
"zhu18",
|
||||||
|
"zhu19",
|
||||||
|
"zhu20",
|
||||||
|
"zhu21",
|
||||||
|
"zhu22",
|
||||||
|
"zhu23",
|
||||||
|
"zhu24",
|
||||||
|
"zhu25",
|
||||||
|
"dong1",
|
||||||
|
"dong2",
|
||||||
|
"dong3",
|
||||||
|
"dong4",
|
||||||
|
"dong5",
|
||||||
|
"dong6",
|
||||||
|
"dong7",
|
||||||
|
"dong8",
|
||||||
|
"dong9"
|
||||||
|
],
|
||||||
|
"floorPartitions": [],
|
||||||
|
"images": [
|
||||||
|
"Anvil.png",
|
||||||
|
"BrewingStand.png",
|
||||||
|
"CraftingTable.png",
|
||||||
|
"EnchantmentTable.png",
|
||||||
|
"Furnace.png",
|
||||||
|
"Smoker-cookingfish.gif",
|
||||||
|
"achi.png",
|
||||||
|
"alex2.png",
|
||||||
|
"anjian.png",
|
||||||
|
"attribute.png",
|
||||||
|
"bag.png",
|
||||||
|
"bear.png",
|
||||||
|
"bg.jpg",
|
||||||
|
"blackheart.png",
|
||||||
|
"blackheart2.png",
|
||||||
|
"blackheart3.png",
|
||||||
|
"blackheartw.png",
|
||||||
|
"blackheartw2.png",
|
||||||
|
"blackheartw3.png",
|
||||||
|
"box1.png",
|
||||||
|
"box10.png",
|
||||||
|
"box2.png",
|
||||||
|
"box3.png",
|
||||||
|
"box4.png",
|
||||||
|
"box5.png",
|
||||||
|
"box6.png",
|
||||||
|
"box7.png",
|
||||||
|
"box8.png",
|
||||||
|
"box9.png",
|
||||||
|
"brave.png",
|
||||||
|
"breathe.png",
|
||||||
|
"buttonA.png",
|
||||||
|
"buttonB.png",
|
||||||
|
"dragon.png",
|
||||||
|
"goldheart.png",
|
||||||
|
"goldheart2.png",
|
||||||
|
"goldheart3.png",
|
||||||
|
"goldheartw.png",
|
||||||
|
"goldheartw2.png",
|
||||||
|
"goldheartw3.png",
|
||||||
|
"greenheart.png",
|
||||||
|
"greenheart2.png",
|
||||||
|
"greenheart3.png",
|
||||||
|
"greenheartw.png",
|
||||||
|
"greenheartw2.png",
|
||||||
|
"greenheartw3.png",
|
||||||
|
"head1.png",
|
||||||
|
"heart.png",
|
||||||
|
"heart2.png",
|
||||||
|
"heart3.png",
|
||||||
|
"heartw.png",
|
||||||
|
"heartw2.png",
|
||||||
|
"heartw3.png",
|
||||||
|
"hero.png",
|
||||||
|
"heropic1.png",
|
||||||
|
"hunger.png",
|
||||||
|
"hunger2.png",
|
||||||
|
"hunger3.png",
|
||||||
|
"hungerw.png",
|
||||||
|
"hungerw2.png",
|
||||||
|
"hungerw3.png",
|
||||||
|
"inventory.png",
|
||||||
|
"jz1.jpg",
|
||||||
|
"jz2.png",
|
||||||
|
"jz3.png",
|
||||||
|
"jz4.png",
|
||||||
|
"jz5.png",
|
||||||
|
"mc.png",
|
||||||
|
"nongtian.png",
|
||||||
|
"playarrow.png",
|
||||||
|
"steve2.png",
|
||||||
|
"task.png",
|
||||||
|
"tech1.png",
|
||||||
|
"tech2.png",
|
||||||
|
"tech3.png",
|
||||||
|
"winskin.png"
|
||||||
|
],
|
||||||
|
"tilesets": [
|
||||||
|
"magictower.png"
|
||||||
|
],
|
||||||
|
"animates": [
|
||||||
|
"atk_d",
|
||||||
|
"atk_l",
|
||||||
|
"atk_r",
|
||||||
|
"atk_u",
|
||||||
|
"guangquan",
|
||||||
|
"hand",
|
||||||
|
"sword",
|
||||||
|
"zone"
|
||||||
|
],
|
||||||
|
"bgms": [
|
||||||
|
"bgm.mp3",
|
||||||
|
"desert1.mp3",
|
||||||
|
"desert2.mp3",
|
||||||
|
"forest2.mp3",
|
||||||
|
"forest5.mp3",
|
||||||
|
"menu1.mp3",
|
||||||
|
"menu3.mp3",
|
||||||
|
"ocean1.mp3",
|
||||||
|
"ocean3.mp3",
|
||||||
|
"snowfield1.mp3",
|
||||||
|
"snowfield2.mp3",
|
||||||
|
"zhu1.mp3",
|
||||||
|
"zhu10.mp3",
|
||||||
|
"zhu11.mp3",
|
||||||
|
"zhu12.mp3",
|
||||||
|
"zhu3.mp3",
|
||||||
|
"zhu4.mp3",
|
||||||
|
"zhu5.mp3",
|
||||||
|
"zhu6.mp3"
|
||||||
|
],
|
||||||
|
"sounds": [
|
||||||
|
"E1_1.ogg",
|
||||||
|
"E1_2.ogg",
|
||||||
|
"E1_3.ogg",
|
||||||
|
"E1_4.ogg",
|
||||||
|
"E21_1.ogg",
|
||||||
|
"E21_2.ogg",
|
||||||
|
"E21_3.ogg",
|
||||||
|
"E21_4.ogg",
|
||||||
|
"E21_5.ogg",
|
||||||
|
"E21_6.ogg",
|
||||||
|
"E25_1.ogg",
|
||||||
|
"E25_2.ogg",
|
||||||
|
"E25_3.ogg",
|
||||||
|
"E25_4.ogg",
|
||||||
|
"E25_5.ogg",
|
||||||
|
"E25_6.ogg",
|
||||||
|
"E33_1.ogg",
|
||||||
|
"E33_2.ogg",
|
||||||
|
"E33_3.ogg",
|
||||||
|
"E33_4.ogg",
|
||||||
|
"E33_5.ogg",
|
||||||
|
"E37_1.ogg",
|
||||||
|
"E37_10.ogg",
|
||||||
|
"E37_2.ogg",
|
||||||
|
"E37_3.ogg",
|
||||||
|
"E37_4.ogg",
|
||||||
|
"E37_5.ogg",
|
||||||
|
"E37_6.ogg",
|
||||||
|
"E37_7.ogg",
|
||||||
|
"E37_8.ogg",
|
||||||
|
"E37_9.ogg",
|
||||||
|
"E41_1.ogg",
|
||||||
|
"E41_2.ogg",
|
||||||
|
"E41_3.ogg",
|
||||||
|
"E41_4.ogg",
|
||||||
|
"E45_1.ogg",
|
||||||
|
"E45_2.ogg",
|
||||||
|
"E45_3.ogg",
|
||||||
|
"E53_1.ogg",
|
||||||
|
"E53_2.ogg",
|
||||||
|
"E53_3.ogg",
|
||||||
|
"E53_4.ogg",
|
||||||
|
"E53_5.ogg",
|
||||||
|
"E53_6.ogg",
|
||||||
|
"E53_7.ogg",
|
||||||
|
"E57_1.ogg",
|
||||||
|
"E57_2.ogg",
|
||||||
|
"E57_3.ogg",
|
||||||
|
"E57_4.ogg",
|
||||||
|
"E57_5.ogg",
|
||||||
|
"E57_6.ogg",
|
||||||
|
"E57_7.ogg",
|
||||||
|
"E57_8.ogg",
|
||||||
|
"E69_1.ogg",
|
||||||
|
"E69_2.ogg",
|
||||||
|
"E69_3.ogg",
|
||||||
|
"E69_4.ogg",
|
||||||
|
"E69_5.ogg",
|
||||||
|
"E9_1.ogg",
|
||||||
|
"E9_2.ogg",
|
||||||
|
"E9_3.ogg",
|
||||||
|
"E9_4.ogg",
|
||||||
|
"E9_5.ogg",
|
||||||
|
"E9_6.ogg",
|
||||||
|
"Villager1.mp3",
|
||||||
|
"Villager2.mp3",
|
||||||
|
"Villager3.ogg",
|
||||||
|
"Villager4.mp3",
|
||||||
|
"Villager5.mp3",
|
||||||
|
"Villager6.ogg",
|
||||||
|
"armour.ogg",
|
||||||
|
"attack.mp3",
|
||||||
|
"attack1.ogg",
|
||||||
|
"attack2.ogg",
|
||||||
|
"attack3.ogg",
|
||||||
|
"bomb.mp3",
|
||||||
|
"break1.ogg",
|
||||||
|
"break2.ogg",
|
||||||
|
"cancel.ogg",
|
||||||
|
"centerFly.mp3",
|
||||||
|
"chestclosed.ogg",
|
||||||
|
"chestopen.ogg",
|
||||||
|
"confirm.ogg",
|
||||||
|
"cursor.ogg",
|
||||||
|
"dig_grass.ogg",
|
||||||
|
"dig_sand.ogg",
|
||||||
|
"dig_snow.ogg",
|
||||||
|
"dig_stone.ogg",
|
||||||
|
"dig_wood.ogg",
|
||||||
|
"door.mp3",
|
||||||
|
"door_open.ogg",
|
||||||
|
"eat1.ogg",
|
||||||
|
"eat2.ogg",
|
||||||
|
"equip.mp3",
|
||||||
|
"error.mp3",
|
||||||
|
"floor.mp3",
|
||||||
|
"gem.mp3",
|
||||||
|
"glass1.ogg",
|
||||||
|
"glass2.ogg",
|
||||||
|
"glass3.ogg",
|
||||||
|
"grass1.ogg",
|
||||||
|
"grass2.ogg",
|
||||||
|
"grass3.ogg",
|
||||||
|
"grass4.ogg",
|
||||||
|
"gravel1.ogg",
|
||||||
|
"gravel2.ogg",
|
||||||
|
"gravel3.ogg",
|
||||||
|
"gravel4.ogg",
|
||||||
|
"hit1.ogg",
|
||||||
|
"hit2.ogg",
|
||||||
|
"hit3.ogg",
|
||||||
|
"icePickaxe.mp3",
|
||||||
|
"item.mp3",
|
||||||
|
"jump.mp3",
|
||||||
|
"lava.ogg",
|
||||||
|
"levelup.ogg",
|
||||||
|
"load.mp3",
|
||||||
|
"open_ui.ogg",
|
||||||
|
"orb.ogg",
|
||||||
|
"pickaxe.mp3",
|
||||||
|
"pop.ogg",
|
||||||
|
"recovery.mp3",
|
||||||
|
"sand1.ogg",
|
||||||
|
"sand2.ogg",
|
||||||
|
"sand3.ogg",
|
||||||
|
"sand4.ogg",
|
||||||
|
"save.mp3",
|
||||||
|
"shop.mp3",
|
||||||
|
"snow1.ogg",
|
||||||
|
"snow2.ogg",
|
||||||
|
"snow3.ogg",
|
||||||
|
"snow4.ogg",
|
||||||
|
"step_grass1.ogg",
|
||||||
|
"step_grass2.ogg",
|
||||||
|
"step_grass3.ogg",
|
||||||
|
"step_grass4.ogg",
|
||||||
|
"step_grass5.ogg",
|
||||||
|
"step_grass6.ogg",
|
||||||
|
"step_gravel1.ogg",
|
||||||
|
"step_gravel2.ogg",
|
||||||
|
"step_gravel3.ogg",
|
||||||
|
"step_gravel4.ogg",
|
||||||
|
"step_sand1.ogg",
|
||||||
|
"step_sand2.ogg",
|
||||||
|
"step_sand3.ogg",
|
||||||
|
"step_sand4.ogg",
|
||||||
|
"step_sand5.ogg",
|
||||||
|
"step_snow1.ogg",
|
||||||
|
"step_snow2.ogg",
|
||||||
|
"step_snow3.ogg",
|
||||||
|
"step_snow4.ogg",
|
||||||
|
"step_stone1.ogg",
|
||||||
|
"step_stone2.ogg",
|
||||||
|
"step_stone3.ogg",
|
||||||
|
"step_stone4.ogg",
|
||||||
|
"step_stone5.ogg",
|
||||||
|
"step_stone6.ogg",
|
||||||
|
"step_swim1.ogg",
|
||||||
|
"step_swim2.ogg",
|
||||||
|
"step_swim3.ogg",
|
||||||
|
"step_swim4.ogg",
|
||||||
|
"step_wood1.ogg",
|
||||||
|
"step_wood2.ogg",
|
||||||
|
"step_wood3.ogg",
|
||||||
|
"step_wood4.ogg",
|
||||||
|
"step_wood5.ogg",
|
||||||
|
"step_wood6.ogg",
|
||||||
|
"stone1.ogg",
|
||||||
|
"stone2.ogg",
|
||||||
|
"stone3.ogg",
|
||||||
|
"stone4.ogg",
|
||||||
|
"wood1.ogg",
|
||||||
|
"wood2.ogg",
|
||||||
|
"wood3.ogg",
|
||||||
|
"wood4.ogg",
|
||||||
|
"worldlevelup.ogg",
|
||||||
|
"zone.mp3"
|
||||||
|
],
|
||||||
|
"fonts": [
|
||||||
|
"IPix",
|
||||||
|
"hanyifengshanghei45w",
|
||||||
|
"number",
|
||||||
|
"pixelmix"
|
||||||
|
],
|
||||||
|
"nameMap": {
|
||||||
|
"确定": "confirm.ogg",
|
||||||
|
"取消": "cancel.ogg",
|
||||||
|
"操作失败": "error.mp3",
|
||||||
|
"光标移动": "cursor.ogg",
|
||||||
|
"打开界面": "open_ui.ogg",
|
||||||
|
"读档": "load.mp3",
|
||||||
|
"存档": "save.mp3",
|
||||||
|
"获得道具": "pop.ogg",
|
||||||
|
"回血": "recovery.mp3",
|
||||||
|
"炸弹": "bomb.mp3",
|
||||||
|
"飞行器": "centerFly.mp3",
|
||||||
|
"开关门": "door.mp3",
|
||||||
|
"上下楼": "floor.mp3",
|
||||||
|
"跳跃": "jump.mp3",
|
||||||
|
"破墙镐": "pickaxe.mp3",
|
||||||
|
"破冰镐": "icePickaxe.mp3",
|
||||||
|
"宝石": "gem.mp3",
|
||||||
|
"阻激夹域": "zone.mp3",
|
||||||
|
"穿脱装备": "equip.mp3",
|
||||||
|
"背景音乐": "bgm.mp3",
|
||||||
|
"攻击": "attack.mp3",
|
||||||
|
"背景图": "bg.jpg",
|
||||||
|
"商店": "shop.mp3",
|
||||||
|
"领域": "zone"
|
||||||
|
},
|
||||||
|
"levelChoose": null,
|
||||||
|
"equipName": [
|
||||||
|
"主手",
|
||||||
|
"副手",
|
||||||
|
"头盔",
|
||||||
|
"胸甲",
|
||||||
|
"护腿",
|
||||||
|
"靴子"
|
||||||
|
],
|
||||||
|
"startBgm": null,
|
||||||
|
"styles": {
|
||||||
|
"startBackground": "project/images/bg.jpg",
|
||||||
|
"startVerticalBackground": "project/images/jz1.jpg",
|
||||||
|
"startLogoStyle": "color: black",
|
||||||
|
"startButtonsStyle": "background-color: #32369F; opacity: 0.85; color: #FFFFFF; border: #FFFFFF 2px solid; caret-color: #FFD700;",
|
||||||
|
"statusLeftBackground": "url(\"project/materials/wood1.png\") repeat",
|
||||||
|
"statusTopBackground": "url(project/images/bg.jpg)",
|
||||||
|
"toolsBackground": "black",
|
||||||
|
"floorChangingStyle": "background-color: black; color: white",
|
||||||
|
"statusBarColor": [
|
||||||
|
255,
|
||||||
|
255,
|
||||||
|
255,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"borderColor": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"selectColor": [
|
||||||
|
255,
|
||||||
|
215,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"font": "IPix"
|
||||||
|
},
|
||||||
|
"splitImages": [
|
||||||
|
{
|
||||||
|
"name": "dragon.png",
|
||||||
|
"width": 384,
|
||||||
|
"height": 96,
|
||||||
|
"prefix": "dragon_"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"firstData": {
|
||||||
|
"title": "我的世界:生存 体验版",
|
||||||
|
"name": "MC_survival0",
|
||||||
|
"version": "Ver 2.10.3",
|
||||||
|
"floorId": "zzt1",
|
||||||
|
"hero": {
|
||||||
|
"image": "steve2.png",
|
||||||
|
"animate": false,
|
||||||
|
"name": "史蒂夫",
|
||||||
|
"lv": 1,
|
||||||
|
"hpmax": 20,
|
||||||
|
"hp": 20,
|
||||||
|
"manamax": 0,
|
||||||
|
"mana": 0,
|
||||||
|
"atk": 2,
|
||||||
|
"def": 0,
|
||||||
|
"mdef": 0,
|
||||||
|
"money": 0,
|
||||||
|
"exp": 0,
|
||||||
|
"equipment": [],
|
||||||
|
"items": {
|
||||||
|
"constants": {},
|
||||||
|
"tools": {},
|
||||||
|
"equips": {}
|
||||||
|
},
|
||||||
|
"loc": {
|
||||||
|
"direction": "up",
|
||||||
|
"x": 6,
|
||||||
|
"y": 7
|
||||||
|
},
|
||||||
|
"flags": {},
|
||||||
|
"followers": [],
|
||||||
|
"steps": 0
|
||||||
|
},
|
||||||
|
"startCanvas": [],
|
||||||
|
"startText": [
|
||||||
|
{
|
||||||
|
"type": "if",
|
||||||
|
"condition": "(!core.isReplaying())",
|
||||||
|
"true": [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": "function(){\ncore.plugin.title();\n}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "flag:start",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "while",
|
||||||
|
"condition": "flag:start",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"type": "sleep",
|
||||||
|
"time": 1,
|
||||||
|
"noSkip": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"false": [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": "function(){\ncore.plugin.replayHardSelect(core.status.replay.toReplay[0], true);\n}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": "function(){\n//core.hideStatusBar();\ncore.ui._createUIEvent();\n//侧边栏扩展插件让加的\nflags.showExtend = true;\n//extend.values.hideInVertical = true;\n}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shops": [],
|
||||||
|
"levelUp": [
|
||||||
|
{
|
||||||
|
"need": "值",
|
||||||
|
"title": "0",
|
||||||
|
"action": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"values": {
|
||||||
|
"lavaDamage": 100,
|
||||||
|
"poisonDamage": 10,
|
||||||
|
"weakValue": 20,
|
||||||
|
"redGem": 3,
|
||||||
|
"blueGem": 3,
|
||||||
|
"greenGem": 5,
|
||||||
|
"redPotion": 100,
|
||||||
|
"bluePotion": 250,
|
||||||
|
"yellowPotion": 500,
|
||||||
|
"greenPotion": 800,
|
||||||
|
"breakArmor": 0.9,
|
||||||
|
"counterAttack": 0.1,
|
||||||
|
"purify": 3,
|
||||||
|
"hatred": 2,
|
||||||
|
"animateSpeed": 300,
|
||||||
|
"moveSpeed": 140,
|
||||||
|
"statusCanvasRowsOnMobile": 3,
|
||||||
|
"floorChangeTime": 500
|
||||||
|
},
|
||||||
|
"flags": {
|
||||||
|
"statusBarItems": [
|
||||||
|
"enableFloor",
|
||||||
|
"enableHPMax",
|
||||||
|
"enableHP",
|
||||||
|
"enableAtk",
|
||||||
|
"enableDef"
|
||||||
|
],
|
||||||
|
"autoScale": true,
|
||||||
|
"extendToolbar": false,
|
||||||
|
"flyNearStair": false,
|
||||||
|
"flyRecordPosition": false,
|
||||||
|
"itemFirstText": false,
|
||||||
|
"equipboxButton": false,
|
||||||
|
"enableAddPoint": false,
|
||||||
|
"enableNegativeDamage": false,
|
||||||
|
"betweenAttackMax": false,
|
||||||
|
"useLoop": true,
|
||||||
|
"startUsingCanvas": true,
|
||||||
|
"statusCanvas": true,
|
||||||
|
"enableEnemyPoint": true,
|
||||||
|
"enableGentleClick": false,
|
||||||
|
"ignoreChangeFloor": true,
|
||||||
|
"canGoDeadZone": true,
|
||||||
|
"enableMoveDirectly": false,
|
||||||
|
"enableRouteFolding": true,
|
||||||
|
"disableShopOnDamage": false,
|
||||||
|
"blurFg": true,
|
||||||
|
"hideLeftStatusBar": false
|
||||||
|
}
|
||||||
|
}
|
2
docsify.min.js
vendored
Normal file
2
docsify.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
514
dynamicMapEditor.js
Normal file
514
dynamicMapEditor.js
Normal file
@ -0,0 +1,514 @@
|
|||||||
|
/**
|
||||||
|
* 运行时可编辑地图的扩展,By fux2(黄鸡)
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function dynamicMapEditor() {
|
||||||
|
// 所有显示的ID
|
||||||
|
this.displayIds = [
|
||||||
|
'none', 'yellowWall', 'blueWall', 'whiteWall', 'yellowDoor', 'blueDoor', 'redDoor', 'star', 'lava', 'lavaNet',
|
||||||
|
'yellowKey', 'blueKey', 'redKey', 'redGem', 'blueGem', 'greenGem', 'yellowGem',
|
||||||
|
'redPotion', 'bluePotion', 'yellowPotion', 'greenPotion', 'pickaxe', 'bomb', 'centerFly',
|
||||||
|
'cls:autotile', 'cls:enemys', 'cls:enemy48'
|
||||||
|
];
|
||||||
|
this.items = [];
|
||||||
|
this.userChanged = [];
|
||||||
|
this.savedItems = [];
|
||||||
|
this.dom = null;
|
||||||
|
this.canvas = null;
|
||||||
|
this.mapRecord = {};
|
||||||
|
this.enemyModified = false;
|
||||||
|
this.valueModified = false;
|
||||||
|
this.pageId = 0;
|
||||||
|
this.pageMaxItems = 21;
|
||||||
|
this.pageMax = 0;
|
||||||
|
this.selectedIndex = 0;
|
||||||
|
this.selectedItem = null;
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ init
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype._init = function () {
|
||||||
|
this.dom = document.createElement("canvas");
|
||||||
|
this.dom.id = 'dynamicMapEditor';
|
||||||
|
this.dom.style.display = 'none';
|
||||||
|
this.dom.style.position = 'absolute';
|
||||||
|
this.dom.style.left = '3px';
|
||||||
|
this.dom.style.top = '3px';
|
||||||
|
this.dom.style.zIndex = 99999;
|
||||||
|
this.canvas = this.dom.getContext("2d");
|
||||||
|
core.dom.gameGroup.appendChild(this.dom);
|
||||||
|
|
||||||
|
this.initInfos();
|
||||||
|
this.pageMax = Math.ceil(this.items.length / this.pageMaxItems);
|
||||||
|
core.registerAction('onkeyUp', 'plugin_dme_keydown', this.onKeyUp.bind(this), 200);
|
||||||
|
core.registerAction('onclick', 'plugin_dme_click', this.onMapClick.bind(this), 200);
|
||||||
|
this.dom.addEventListener("click",this.onBoxClick.bind(this));
|
||||||
|
this.showInitHelp();
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.initInfos = function () {
|
||||||
|
this.items = [];
|
||||||
|
var ids = {};
|
||||||
|
this.displayIds.forEach(function (v) {
|
||||||
|
if (v.startsWith("cls:")) {
|
||||||
|
var cls = v.substr(4);
|
||||||
|
for (var id in core.maps.blocksInfo) {
|
||||||
|
var u = core.maps.blocksInfo[id];
|
||||||
|
if (u && u.cls == cls) {
|
||||||
|
if (ids[u.id]) continue;
|
||||||
|
this.items.push(core.getBlockInfo(u.id));
|
||||||
|
ids[u.id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (v == 'none') {
|
||||||
|
this.items.push({"number": 0, "id": "none", "name": "清除块"});
|
||||||
|
} else {
|
||||||
|
this.items.push(core.getBlockInfo(v));
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
this.items = this.items.filter(function (v) { return v && v.id && v.number >= 0; });
|
||||||
|
this.savedItems = core.getLocalStorage('_dynamicMapEditor_savedItems', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ bind actions
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.isValid = function () {
|
||||||
|
return main.mode == 'play' && core.isPlaying() && !core.isReplaying() && !core.status.lockControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.onKeyUp = function(e) {
|
||||||
|
if (!this.isValid()) return false;
|
||||||
|
if (e.keyCode == 219) {
|
||||||
|
this.openToolBox();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!this.isUsingTool) return false;
|
||||||
|
|
||||||
|
if (e.keyCode == 220) {
|
||||||
|
this.undo();
|
||||||
|
return true;
|
||||||
|
} else if (e.keyCode == 221) {
|
||||||
|
this.applyCurrentChange();
|
||||||
|
return true;
|
||||||
|
} else if (e.keyCode >= 48 && e.keyCode <= 57) {
|
||||||
|
// 0-9
|
||||||
|
if (e.altKey) {
|
||||||
|
this.savedItem(e.keyCode - 48);
|
||||||
|
} else {
|
||||||
|
this.loadItem(e.keyCode - 48);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.onMapClick = function(x, y) {
|
||||||
|
if (!this.isValid()) return false;
|
||||||
|
if (!this.isUsingTool || !this.selectedItem) return false;
|
||||||
|
x += parseInt(core.bigmap.offsetX / 32);
|
||||||
|
y += parseInt(core.bigmap.offsetY / 32);
|
||||||
|
var number = this.selectedItem.number;
|
||||||
|
this.addOperation('put', number, x, y, core.status.floorId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.getClickLoc = function (e) {
|
||||||
|
return {
|
||||||
|
x: (e.clientX - core.dom.gameGroup.offsetLeft - 3) / core.domStyle.scale,
|
||||||
|
y: (e.clientY - core.dom.gameGroup.offsetTop - 3) / core.domStyle.scale,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.onBoxClick = function (e) {
|
||||||
|
if (!this.isValid() || !this.isUsingTool) return false;
|
||||||
|
var loc = this.getClickLoc(e), x = loc.x, y = loc.y;
|
||||||
|
for(var i = 0; i < this.pageMaxItems; i++) {
|
||||||
|
var rect = this.itemRect(i);
|
||||||
|
if(x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h) {
|
||||||
|
this.onItemClick(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(y>=350 && y <= 370) {
|
||||||
|
if (x >= this.offsetX && x <= this.offsetX + 40) {
|
||||||
|
this.changePage(-1);
|
||||||
|
} else if (x >= this.offsetX + 40 && x <= this.offsetX + 80) {
|
||||||
|
this.showHelp(true);
|
||||||
|
} else if (x >= this.offsetX + 80 && x <= this.offsetX + 120) {
|
||||||
|
this.changePage(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.onItemClick = function(index) {
|
||||||
|
var startIndex = this.pageId * this.pageMaxItems;
|
||||||
|
var item = this.items[startIndex + index];
|
||||||
|
if(!item) return;
|
||||||
|
if(index == this.selectedIndex) {
|
||||||
|
if (core.material.enemys[item.id]) {
|
||||||
|
var enemy = core.material.enemys[item.id];
|
||||||
|
var nowData = [enemy.hp, enemy.atk, enemy.def, enemy.special].join(';');
|
||||||
|
core.myprompt("请输入新怪物属性\n血;攻;防;能力,以分号分隔", nowData, function (result) {
|
||||||
|
if (result) {
|
||||||
|
try {
|
||||||
|
var finalData = result.split(';');
|
||||||
|
if (finalData.length < 4) throw "";
|
||||||
|
var hp = parseInt(finalData[0]) || 0;
|
||||||
|
var atk = parseInt(finalData[1]) || 0;
|
||||||
|
var def = parseInt(finalData[2]) || 0;
|
||||||
|
var special = finalData[3].replace(/[\[\]]/g, "")
|
||||||
|
.split(',').map(function (x) { return parseInt(x); });
|
||||||
|
if (special.length == 0) special = 0;
|
||||||
|
else if (special.length == 1) special = special[0];
|
||||||
|
dynamicMapEditor.addOperation('modify', item.id, hp, atk, def, special);
|
||||||
|
core.drawTip('已更新' + enemy.name + '的数据');
|
||||||
|
return;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
core.drawTip('无效的输入数据');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (core.values[item.id] != null) {
|
||||||
|
var nowData = core.values[item.id];
|
||||||
|
core.myprompt("请输入新" + (item.name || "") + "数值:", core.values[item.id], function (result) {
|
||||||
|
if (result) {
|
||||||
|
dynamicMapEditor.addOperation('value', item.id, parseInt(result) || 0, item.name || "");
|
||||||
|
core.drawTip('已更新' + (item.name || "") + "的数值");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
core.drawTip('无效的输入数据');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selectedIndex = index;
|
||||||
|
this.selectedItem = item;
|
||||||
|
this.refreshToolBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ methods
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.openToolBox = function() {
|
||||||
|
if (!this.isUsingTool && core.domStyle.isVertical) {
|
||||||
|
core.drawTip("竖屏模式下暂不支持此功能。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isUsingTool = !this.isUsingTool;
|
||||||
|
this.selectedItem = null;
|
||||||
|
this.selectedIndex = -1;
|
||||||
|
this.dom.style.display = this.isUsingTool ? 'block' : 'none';
|
||||||
|
this.dom.style.width = core.dom.statusCanvas.style.width;
|
||||||
|
this.dom.width = core.dom.statusCanvas.width / core.domStyle.ratio;
|
||||||
|
this.dom.style.height = core.dom.statusCanvas.style.height;
|
||||||
|
this.dom.height = core.dom.statusCanvas.height / core.domStyle.ratio;
|
||||||
|
this.offsetX = this.dom.width / 2 - 60;
|
||||||
|
this.refreshToolBox();
|
||||||
|
if (this.isUsingTool) this.showHelp();
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.addOperation = function() {
|
||||||
|
var operation = {};
|
||||||
|
var type = arguments[0];
|
||||||
|
operation.type = type;
|
||||||
|
if (type == 'put') {
|
||||||
|
operation.number = arguments[1];
|
||||||
|
operation.x = arguments[2];
|
||||||
|
operation.y = arguments[3];
|
||||||
|
operation.floorId = arguments[4];
|
||||||
|
operation.originNumber = core.floors[operation.floorId].map[operation.y][operation.x];
|
||||||
|
core.floors[operation.floorId].map[operation.y][operation.x] = operation.number;
|
||||||
|
core.setBlock(operation.number, operation.x, operation.y, operation.floorId);
|
||||||
|
this.mapRecord[operation.floorId] = true;
|
||||||
|
} else if (type == 'modify') {
|
||||||
|
operation.enemyId = arguments[1];
|
||||||
|
operation.hp = arguments[2];
|
||||||
|
operation.atk = arguments[3];
|
||||||
|
operation.def = arguments[4];
|
||||||
|
operation.special = arguments[5];
|
||||||
|
var enemy = core.material.enemys[operation.enemyId];
|
||||||
|
operation.originHp = enemy.hp;
|
||||||
|
operation.originAtk = enemy.atk;
|
||||||
|
operation.originDef = enemy.def;
|
||||||
|
operation.originSpecial = enemy.special;
|
||||||
|
enemy.hp = operation.hp;
|
||||||
|
enemy.atk = operation.atk;
|
||||||
|
enemy.def = operation.def;
|
||||||
|
enemy.special = operation.special;
|
||||||
|
this.enemyModified = true;
|
||||||
|
} else if (type == 'value') {
|
||||||
|
operation.id = arguments[1];
|
||||||
|
operation.value = arguments[2];
|
||||||
|
operation.name = arguments[3];
|
||||||
|
operation.originValue = core.values[operation.id];
|
||||||
|
core.values[operation.id] = operation.value;
|
||||||
|
this.valueModified = true;
|
||||||
|
}
|
||||||
|
this.userChanged.push(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.undo = function() {
|
||||||
|
var operation = this.userChanged.pop();
|
||||||
|
if(!operation) {
|
||||||
|
core.drawTip('没有动作可以撤销');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var type = operation.type;
|
||||||
|
if(type == 'put') { // {originNumber, x, y, floorId}
|
||||||
|
var originNumber = operation.originNumber;
|
||||||
|
var x = operation.x;
|
||||||
|
var y = operation.y;
|
||||||
|
var floorId = operation.floorId;
|
||||||
|
core.floors[floorId].map[y][x] = originNumber;
|
||||||
|
core.setBlock(originNumber, x, y, floorId);
|
||||||
|
this.mapRecord[floorId] = true;
|
||||||
|
core.drawTip('已撤销' + floorId + '在(' + x + ',' + y + ')的图块操作');
|
||||||
|
} else if (type == 'modify') { // {enemyId, originHp, originAtk, originDef, originSpecial}
|
||||||
|
var enemyId = operation.enemyId;
|
||||||
|
var hp = operation.originHp;
|
||||||
|
var atk = operation.originAtk;
|
||||||
|
var def = operation.originDef;
|
||||||
|
var special = operation.originSpecial;
|
||||||
|
var enemy = core.material.enemys[enemyId];
|
||||||
|
enemy.hp = hp;
|
||||||
|
enemy.atk = atk;
|
||||||
|
enemy.def = def;
|
||||||
|
enemy.special = special;
|
||||||
|
core.drawTip('已撤销对' + enemy.name + '的属性修改');
|
||||||
|
this.enemyModified = true;
|
||||||
|
} else if (type == 'value') { // {id, value, originValue}
|
||||||
|
var id = operation.id;
|
||||||
|
var value = operation.originValue;
|
||||||
|
core.values[operation.id] = operation.originValue;
|
||||||
|
core.drawTip('已撤销对' + operation.name + "数值的修改");
|
||||||
|
this.valueModified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.changePage = function(delta) {
|
||||||
|
var newId = this.pageId + delta;
|
||||||
|
if (newId < 0 || newId >= this.pageMax) return;
|
||||||
|
this.pageId = newId;
|
||||||
|
this.selectedItem = null;
|
||||||
|
this.selectedIndex = -1;
|
||||||
|
this.refreshToolBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.savedItem = function (number) {
|
||||||
|
if (!this.isUsingTool || this.selectedItem < 0) return;
|
||||||
|
this.savedItems[number] = [this.pageId, this.selectedIndex];
|
||||||
|
core.setLocalStorage('_dynamicMapEditor_savedItems', this.savedItems);
|
||||||
|
core.drawTip("已保存此图块");
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.loadItem = function (number) {
|
||||||
|
if (!this.isUsingTool) return;
|
||||||
|
var u = this.savedItems[number];
|
||||||
|
if (!u) return core.drawTip("没有保存的图块!");
|
||||||
|
this.pageId = u[0];
|
||||||
|
this.selectedIndex = u[1];
|
||||||
|
this.selectedItem = this.items[this.pageId * this.pageMaxItems + this.selectedIndex];
|
||||||
|
this.refreshToolBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ draw
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.itemRect = function(index) {
|
||||||
|
return {
|
||||||
|
'x' : this.offsetX + (index % 3) * 40,
|
||||||
|
'y' : Math.floor(index / 3) * 50,
|
||||||
|
'w' : 40,
|
||||||
|
'h' : 50
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.refreshToolBox = function() {
|
||||||
|
if (!this.isUsingTool) return;
|
||||||
|
core.fillRect(this.canvas, 0, 0, this.dom.width, this.dom.height, '#000000');
|
||||||
|
var startIndex = this.pageId * this.pageMaxItems;
|
||||||
|
for (var i = 0; i < this.pageMaxItems; ++i) {
|
||||||
|
var item = this.items[startIndex + i];
|
||||||
|
if (!item) break;
|
||||||
|
var rect = this.itemRect(i);
|
||||||
|
if (item.image) core.drawImage(this.canvas, item.image, 0, item.height * item.posY, 32, 32, rect.x + 4, rect.y, 32, 32);
|
||||||
|
if (item.name) {
|
||||||
|
this.canvas.textAlign = 'center';
|
||||||
|
core.fillText(this.canvas, item.name, rect.x + 20, rect.y + 44, '#FFFFFF', '11px Verdana', 40);
|
||||||
|
}
|
||||||
|
if (core.material.enemys[item.id]) {
|
||||||
|
this.canvas.textAlign = 'left';
|
||||||
|
var damageString = core.enemys.getDamageString(item.id);
|
||||||
|
core.fillBoldText(this.canvas, damageString.damage, rect.x + 5, rect.y + 31, damageString.color, null, '11px Verdana');
|
||||||
|
var critical = core.enemys.nextCriticals(item.id, 1);
|
||||||
|
critical = core.formatBigNumber((critical[0]||[])[0], true);
|
||||||
|
if (critical == '???') critical = '?';
|
||||||
|
core.fillBoldText(this.canvas, critical, rect.x+5, rect.y+21, '#FFFFFF');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvas.textAlign = 'center';
|
||||||
|
this.canvas.fillStyle = '#FFFFFF';
|
||||||
|
if(this.pageId > 0) core.fillText(this.canvas, '上一页', this.offsetX + 20, 365, '#FFFFFF', '11px Verdana');
|
||||||
|
if(this.pageId < this.pageMax-1) core.fillText(this.canvas, '下一页',this.offsetX + 100, 365, '#FFFFFF', '11px Verdana');
|
||||||
|
core.fillText(this.canvas, '帮助', this.offsetX + 60, 365, '#FFFFFF');
|
||||||
|
var text1 = core.formatBigNumber(core.getRealStatus('hp'), true) + "/" +
|
||||||
|
core.formatBigNumber(core.getRealStatus('atk'), true) + "/" +
|
||||||
|
core.formatBigNumber(core.getRealStatus("def"), true) + "/" +
|
||||||
|
core.formatBigNumber(core.getRealStatus("mdef"), true);
|
||||||
|
core.fillText(this.canvas, text1, this.offsetX + 60, 380, '#FF7F00', '11px Verdana', 120);
|
||||||
|
var text2 = core.formatBigNumber(core.getRealStatus('money', true)) + "/" +
|
||||||
|
core.formatBigNumber(core.getRealStatus('exp'), true) + "/" +
|
||||||
|
core.itemCount('yellowKey') + '/' + core.itemCount('blueKey') + '/' +
|
||||||
|
core.itemCount('redKey');
|
||||||
|
core.fillText(this.canvas, text2, this.offsetX + 60, 395, '#FF7F00', '11px Verdana', 120);
|
||||||
|
var text3 = core.itemCount('pickaxe') + '/' + core.itemCount('bomb') + '/' +
|
||||||
|
core.itemCount('centerFly');
|
||||||
|
if (core.hasFlag('poison')) text3 += "/毒";
|
||||||
|
if (core.hasFlag('weak')) text3 += "/衰";
|
||||||
|
if (core.hasFlag('curse')) text3 += "/咒";
|
||||||
|
core.fillText(this.canvas, text3, this.offsetX + 60, 410, '#FF7F00', '11px Verdana', 120);
|
||||||
|
if(this.selectedItem) {
|
||||||
|
var rect = this.itemRect(this.selectedIndex);
|
||||||
|
core.strokeRect(this.canvas, rect.x, rect.y, rect.w, rect.h, '#FF7F00', 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.showInitHelp = function () {
|
||||||
|
if (main.mode != 'play' || core.getLocalStorage('_dynamicMapEditor_init')) return;
|
||||||
|
var text = "新拓展:运行时动态编辑地图!\n\n在此状态下你可以一边游戏一边编辑地图或者修改数据。\n\n";
|
||||||
|
text += "进游戏后按 [ 键可以激活,快来尝试吧!\n\n";
|
||||||
|
text += "点取消后将不再提示本页面。";
|
||||||
|
core.myconfirm(text, null, function () {
|
||||||
|
core.setLocalStorage('_dynamicMapEditor_init', true);
|
||||||
|
if (core.firstData.name != 'template') {
|
||||||
|
localStorage.removeItem('template__dynamicMapEditor_init');
|
||||||
|
localStorage.removeItem('template__dynamicMapEditor_help');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.showHelp = function (fromButton) {
|
||||||
|
if (main.mode != 'play' || (!fromButton && core.getLocalStorage('_dynamicMapEditor_help'))) return;
|
||||||
|
var text = "欢迎使用黄鸡编写的运行时编辑拓展!你可以一边游戏一边编辑地图或者修改数据。\n\n";
|
||||||
|
text += "基本操作:\n - 点击图块再点地图可以放置;\n - 双击图块可以编辑数据;\n";
|
||||||
|
text += " - [ 键将开关此模式;\n - ] 键将会把改动保存到文件;\n - \\ 键将撤销上步操作。\n";
|
||||||
|
text += " - Alt+0~9 保存当前图块 \n - 0~9 读取当前图块\n";
|
||||||
|
text += "最下面三行数据分别是:\n"
|
||||||
|
text += "血攻防护盾;金经黄蓝红;破炸飞和debuff。";
|
||||||
|
if (!fromButton) text += "\n\n点取消将不再提示本页面。";
|
||||||
|
core.myconfirm(text, null, function () {
|
||||||
|
if (!fromButton) core.setLocalStorage("_dynamicMapEditor_help", true);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ save
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.applyCurrentChange = function() {
|
||||||
|
this.saveEnemys();
|
||||||
|
this.saveValues();
|
||||||
|
this.saveFloors();
|
||||||
|
this.enemyModified = false;
|
||||||
|
this.valueModified = false;
|
||||||
|
this.mapRecord = {};
|
||||||
|
core.drawTip('已将所有改动应用到文件,记得刷新编辑器哦');
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.saveEnemys = function () {
|
||||||
|
if (!this.enemyModified) return;
|
||||||
|
core.enemys.enemys = core.clone(core.material.enemys);
|
||||||
|
var datastr = 'var enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80 = \n';
|
||||||
|
var emap = {};
|
||||||
|
var estr = JSON.stringify(core.enemys.enemys, function (k, v) {
|
||||||
|
var t = core.clone(v);
|
||||||
|
if (t.hp != null) {
|
||||||
|
delete t.id;
|
||||||
|
var id_ = ":" + k + ":";
|
||||||
|
emap[id_] = JSON.stringify(t);
|
||||||
|
return id_;
|
||||||
|
} else return t;
|
||||||
|
}, '\t');
|
||||||
|
for (var id_ in emap) {
|
||||||
|
estr = estr.replace('"' + id_ + '"', emap[id_])
|
||||||
|
}
|
||||||
|
datastr += estr;
|
||||||
|
fs.writeFile('project/enemys.js', core.encodeBase64(datastr), 'base64', function (e, d) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.saveValues = function () {
|
||||||
|
if (!this.valueModified) return;
|
||||||
|
core.data.values = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.values = core.clone(core.values);
|
||||||
|
var datastr = 'var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d = \n' +
|
||||||
|
JSON.stringify(data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d, null, '\t');
|
||||||
|
fs.writeFile('project/data.js', core.encodeBase64(datastr), 'base64', function (e, d) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.saveFloors = function () {
|
||||||
|
if (Object.keys(this.mapRecord).length == 0) return;
|
||||||
|
core.initStatus.maps = core.maps._initMaps();
|
||||||
|
for (var floorId in this.mapRecord) {
|
||||||
|
if (this.mapRecord[floorId]) {
|
||||||
|
this.saveFloor(floorId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.saveFloor = function (floorId) {
|
||||||
|
var floorData = core.floors[floorId];
|
||||||
|
var filename = 'project/floors/' + floorId + '.js';
|
||||||
|
var datastr = ['main.floors.', floorId, '=\n'];
|
||||||
|
|
||||||
|
var tempJsonObj = core.clone(floorData);
|
||||||
|
var tempMap = [['map', ':map:'], ['bgmap', ':bgmap:'], ['fgmap', ':fgmap:']];
|
||||||
|
tempMap.forEach(function (v) {
|
||||||
|
v[2] = tempJsonObj[v[0]];
|
||||||
|
tempJsonObj[v[0]] = v[1];
|
||||||
|
});
|
||||||
|
var tempJson = JSON.stringify(tempJsonObj, null, 4);
|
||||||
|
tempMap.forEach(function (v) {
|
||||||
|
tempJson = tempJson.replace('"' + v[1] + '"', '[\n' + this.formatMap(v[2], v[0] != 'map') + '\n]')
|
||||||
|
}, this);
|
||||||
|
datastr = datastr.concat([tempJson]);
|
||||||
|
datastr = datastr.join('');
|
||||||
|
fs.writeFile(filename, core.encodeBase64(datastr), 'base64', function (e, d) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.prototype.formatMap = function (mapArr, trySimplify) {
|
||||||
|
if (!mapArr || JSON.stringify(mapArr) == JSON.stringify([])) return '';
|
||||||
|
if (trySimplify) {
|
||||||
|
//检查是否是全0二维数组
|
||||||
|
var jsoncheck = JSON.stringify(mapArr).replace(/\D/g, '');
|
||||||
|
if (jsoncheck == Array(jsoncheck.length + 1).join('0')) return '';
|
||||||
|
}
|
||||||
|
//把二维数组格式化
|
||||||
|
var formatArrStr = '';
|
||||||
|
var si = mapArr.length - 1, sk = mapArr[0].length - 1;
|
||||||
|
for (var i = 0; i <= si; i++) {
|
||||||
|
formatArrStr += ' [';
|
||||||
|
for (var k = 0; k <= sk; k++) {
|
||||||
|
var num = parseInt(mapArr[i][k]);
|
||||||
|
formatArrStr += Array(Math.max(4 - String(num).length, 0)).join(' ') + num + (k == sk ? '' : ',');
|
||||||
|
}
|
||||||
|
formatArrStr += ']' + (i == si ? '' : ',\n');
|
||||||
|
}
|
||||||
|
return formatArrStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ rewrite
|
||||||
|
|
||||||
|
var dynamicMapEditor = new dynamicMapEditor();
|
||||||
|
|
||||||
|
dynamicMapEditor._resize_statusBar = core.control._resize_statusBar;
|
||||||
|
core.control._resize_statusBar = function (obj) {
|
||||||
|
dynamicMapEditor._resize_statusBar.call(this,obj);
|
||||||
|
dynamicMapEditor.refreshToolBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicMapEditor.updateStatusBar = core.control.updateStatusBar;
|
||||||
|
core.control.updateStatusBar = function () {
|
||||||
|
dynamicMapEditor.refreshToolBox();
|
||||||
|
dynamicMapEditor.updateStatusBar.apply(core.control, Array.prototype.slice.call(arguments));
|
||||||
|
}
|
322
editor.md
Normal file
322
editor.md
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
# 修改编辑器
|
||||||
|
|
||||||
|
?> 在这一节中,让我们来了解如何修改编辑器(包括配置表格和事件编辑器)
|
||||||
|
|
||||||
|
在改动core时, 有时会相应的更改project中存储数据的结构, 而表格和事件编辑器不做相应更改的话就无法顺畅的编辑改动了结构的数据了, 此文档帮助造塔者进行`配置表格`的修改, 以及修改_server/MotaAction.g4和其他相关文件来调整事件编辑器的图块.
|
||||||
|
|
||||||
|
## 修改表格
|
||||||
|
|
||||||
|
### demo - 给怪物增加 _速度_ 属性
|
||||||
|
|
||||||
|
点击素材区的怪物后点击配置表格, 或者直接打开_server/table/comment.js
|
||||||
|
拉到`【怪物】相关的表格配置`的部分
|
||||||
|
``` js
|
||||||
|
// --------------------------- 【怪物】相关的表格配置 --------------------------- //
|
||||||
|
"enemys": {
|
||||||
|
"_type": "object",
|
||||||
|
"_data": {
|
||||||
|
"id": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "disable",
|
||||||
|
"_docs": "怪物ID",
|
||||||
|
"_data": "怪物ID,可于页面底部修改"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_string": true,
|
||||||
|
"_data": "名称"
|
||||||
|
},
|
||||||
|
"displayIdInBook": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_string": true,
|
||||||
|
"_docs": "手册映射ID",
|
||||||
|
"_data": "在怪物手册中映射到的怪物ID。如果此项不为null,则在怪物手册中,将用目标ID来替换该怪物原本的ID。常被运用在同一个怪物的多朝向上。"
|
||||||
|
},
|
||||||
|
"hp": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_data": "生命值"
|
||||||
|
},
|
||||||
|
"atk": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_data": "攻击力"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
可以看到, project/enemys.js 中怪物的属性
|
||||||
|
```js
|
||||||
|
"greenSlime": {"name":"绿头怪","hp":100,"atk":120,"def":0,"money":1,"exp":1,"point":0,"special":[1,5,7,8]},
|
||||||
|
```
|
||||||
|
被逐条列出并解释
|
||||||
|
|
||||||
|
把hp的部分复制一份并修改
|
||||||
|
```js
|
||||||
|
"speed": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "textarea",
|
||||||
|
"_data": "速度"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
刷新之后, 怪物的表格在hp下面就多出了speed一项, 编辑后出现在了怪物属性中
|
||||||
|
```js
|
||||||
|
"greenSlime": {"name":"绿头怪","hp":100,"atk":120,"def":0,"money":1,"exp":1,"point":0,"special":[1,5,7,8],"speed":123123},
|
||||||
|
```
|
||||||
|
### 表格配置项含义
|
||||||
|
|
||||||
|
可以看出表格配置中的对象是一层套一层的结构, `_`开头的和数据的名字间隔着展开, 忽略所有`_`的层级的话, 这个对象和project中的对象是有相同的结构
|
||||||
|
|
||||||
|
**_leaf** 为真的项对应的数据将作为表格的编辑的内容, 数字/字符串/空对象和数组会被自动计算为true, 非空的对象和数组会被计算为false, 手动填写_leaf的值可以强制不展开对象和数组来进行编辑
|
||||||
|
|
||||||
|
**_type** 决定了表格如何展示该数据
|
||||||
|
+ select 使用下拉菜单展示
|
||||||
|
+ checkbox 使用勾选框展示
|
||||||
|
+ checkboxSet 使用勾选框组展示
|
||||||
|
+ 其他 使用文本框展示, 根据类型区分, 双击或点编辑会有以下行为
|
||||||
|
- textarea 文本编辑器(_type的默认值)
|
||||||
|
- event 事件编辑器
|
||||||
|
- material 素材选取
|
||||||
|
- color 取色器
|
||||||
|
- point 地图选点
|
||||||
|
- disable 不允许编辑
|
||||||
|
- popCheckBoxSet 以弹窗形式多选框
|
||||||
|
|
||||||
|
当某个event例如 门信息编辑 无法适应修改后的数据结构, 可以修改事件编辑器, 也可以把_type改成textarea
|
||||||
|
以上类型的格式要如何写请搜索例如`"_type": "checkboxSet"`来查找例子, 此处不展示
|
||||||
|
|
||||||
|
**_range** 被编辑的值会作为`thiseval`来进行检测, 当字符串为真时才接收这个值, 同时当删除表格时会判定_range是否接收null
|
||||||
|
|
||||||
|
**拓展性**
|
||||||
|
|
||||||
|
注意这是js文件, 可以使用表达式, 如下例子引用了别处的数据作为下拉菜单
|
||||||
|
```js
|
||||||
|
"bgm": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "select",
|
||||||
|
"_select": {
|
||||||
|
"values": [null].concat(Object.keys(editor.core.material.bgms))
|
||||||
|
},
|
||||||
|
"_docs": "背景音乐",
|
||||||
|
"_data": "到达该层后默认播放的BGM"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
同时`_`开头的项可以使用函数. 同一级中`_data`最先被计算. 复杂的结构的注释可以利用函数动态生成
|
||||||
|
`_data`的参数`key`是各子项的名字, 其他`_`开头的参数是`args`详见editor_table.prototype.objToTable的注释
|
||||||
|
例如: 自动事件数组, `"_leaf": false`强制展开, 通过`_action`函数即使为空也显示两个空白的项, 同时`_data`函数给自动事件的每一项标记为event
|
||||||
|
```js
|
||||||
|
"autoEvent": {
|
||||||
|
"_type": "object",
|
||||||
|
"_leaf": false,
|
||||||
|
"_action": function (args) {
|
||||||
|
args.vobj = args.vobj || {};
|
||||||
|
for (var ii = 0; ii < 2; ii++) {
|
||||||
|
args.vobj[ii] = args.vobj[ii] || null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_data": function (key) {
|
||||||
|
return {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "event",
|
||||||
|
"_event": "autoEvent",
|
||||||
|
"_data": "自动事件"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## 修改事件编辑器
|
||||||
|
|
||||||
|
_type为event的表格项, 在双击时会进入事件编辑器. 是由[antlr-blockly](https://github.com/zhaouv/antlr-blockly)生成的图块式的可视化编辑器
|
||||||
|
|
||||||
|
_event的内容例如autoEvent则是使用的入口图块的类型
|
||||||
|
|
||||||
|
请先查看[antlr-blockly的文档](https://zhaouv.github.io/antlr-blockly/docs/#/README)来了解.g4语法文件和可视化编辑器blockly以及库antlr-blockly的基础知识
|
||||||
|
|
||||||
|
### 事件编辑器的运行机制简介
|
||||||
|
|
||||||
|
事件编辑器编辑一个表格项的流程有以下几步
|
||||||
|
+ 加载表格中的json, 将其根据类别转化成图块
|
||||||
|
+ 通过拖拽填写图块编辑内容
|
||||||
|
+ 将图块转化回json置入表格
|
||||||
|
|
||||||
|
其中图块到json 是由_server/MotaAction.g4完成的
|
||||||
|
json到图块 是由_server/MotaActionParser.js完成的, 并依赖图块在g4中的定义
|
||||||
|
|
||||||
|
图块到json首先由入口方块出发, 入口方块又依次去获取了嵌入在其中的方块和域, 嵌入的方块再递归访问其嵌入的内容, 从而访问了所有方块上域中的信息.
|
||||||
|
例如 event_m 通过 action_0 获取了其中嵌入的所有语句生成的json, 插入到其自身的键'data'中
|
||||||
|
|
||||||
|
```antlr
|
||||||
|
//事件 事件编辑器入口之一
|
||||||
|
event_m
|
||||||
|
: '事件' BGNL? Newline '覆盖触发器' Bool '启用' Bool '通行状态' B_0_List '显伤' Bool BGNL? Newline action+ BEND
|
||||||
|
|
||||||
|
|
||||||
|
/* event_m
|
||||||
|
tooltip : 编辑魔塔的事件
|
||||||
|
helpUrl : https://h5mota.com/games/template/_docs/#/event
|
||||||
|
default : [false,null,null,null,null]
|
||||||
|
B_0_List_0=eval(B_0_List_0);
|
||||||
|
var code = {
|
||||||
|
'trigger': Bool_0?'action':null,
|
||||||
|
'enable': Bool_1,
|
||||||
|
'noPass': B_0_List_0,
|
||||||
|
'displayDamage': Bool_2,
|
||||||
|
'data': 'data_asdfefw'
|
||||||
|
}
|
||||||
|
if (!Bool_0 && Bool_1 && (B_0_List_0===null) && Bool_2) code = 'data_asdfefw';
|
||||||
|
code=JSON.stringify(code,null,2).split('"data_asdfefw"').join('[\n'+action_0+']\n');
|
||||||
|
return code;
|
||||||
|
*/;
|
||||||
|
```
|
||||||
|
|
||||||
|
值得注意的是shoplist和action这样的语句集合, 语句集合会选择其中的最后一项作为父方块的默认块, 所以在希望其允许空时, 要制作一个空项放在语句集合定义的末尾, 例如
|
||||||
|
```antlr
|
||||||
|
shoplist
|
||||||
|
: shopsub
|
||||||
|
| shopitem
|
||||||
|
| shopcommonevent
|
||||||
|
| emptyshop
|
||||||
|
;
|
||||||
|
|
||||||
|
emptyshop
|
||||||
|
: Newline
|
||||||
|
|
||||||
|
|
||||||
|
/* emptyshop
|
||||||
|
var code = ' \n';
|
||||||
|
return code;
|
||||||
|
*/;
|
||||||
|
```
|
||||||
|
|
||||||
|
json到图块则是从ActionParser.prototype.parse出发, 递归的将js对象变为图块, 例如event类型的json, 使用event_m入口图块, 将obj.trigger/enable/noPass/displayDamage填到域中, obj.data交给this.parseList转化为语句的图块, 与g4中注入的语句做相反的事情
|
||||||
|
```js
|
||||||
|
ActionParser.prototype.parse = function (obj,type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'event':
|
||||||
|
if(!obj)obj={};
|
||||||
|
if(typeof(obj)===typeof('')) obj={'data':[obj]};
|
||||||
|
if(obj instanceof Array) obj={'data':obj};
|
||||||
|
return MotaActionBlocks['event_m'].xmlText([
|
||||||
|
obj.trigger==='action',obj.enable,obj.noPass,obj.displayDamage,this.parseList(obj.data)
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo - 给已有图块添加新域
|
||||||
|
|
||||||
|
例如给'播放背景音乐'图块再额外加一个参数列表
|
||||||
|
|
||||||
|
不熟悉时, 对任意图块来说这都算是最简单粗暴的修改方式了, 复杂的修改需求也只需要改这一下, 只是填图块时没那么方便
|
||||||
|
|
||||||
|
编辑g4时推荐使用vscode, 搜索并安装插件mota-js
|
||||||
|
|
||||||
|
<pre><code>playBgm_s
|
||||||
|
: '播放背景音乐' EvalString '开始播放秒数' Int '持续到下个本事件' Bool <b style='color:green'>'参数列表' JsonEvalString?</b> Newline
|
||||||
|
|
||||||
|
|
||||||
|
/* playBgm_s
|
||||||
|
tooltip : playBgm: 播放背景音乐
|
||||||
|
helpUrl : https://h5mota.com/games/template/_docs/#/event?id=playbgm%EF%BC%9A%E6%92%AD%E6%94%BE%E8%83%8C%E6%99%AF%E9%9F%B3%E4%B9%90
|
||||||
|
default : ["bgm.mp3", 0, true]
|
||||||
|
allBgms : ['EvalString_0']
|
||||||
|
colour : this.soundColor
|
||||||
|
Int_0 = Int_0 ? (', "startTime": '+Int_0) : '';
|
||||||
|
Bool_0 = Bool_0 ? ', "keep": true' : '';
|
||||||
|
<b style='color:green'>if (JsonEvalString_0) {
|
||||||
|
JsonEvalString_0 = ', "args": ' +JsonEvalString_0;
|
||||||
|
}</b>
|
||||||
|
var code = '{"type": "playBgm", "name": "'+EvalString_0+'"'+Int_0+Bool_0+<b style='color:green'>JsonEvalString_0+</b>'},\n';
|
||||||
|
return code;
|
||||||
|
*/;</code></pre>
|
||||||
|
|
||||||
|
使用了类型JsonEvalString, 并在注入的代码中处理了其新增出的JsonEvalString_0要如何体现在这个方块产生的json
|
||||||
|
此处使用了antlr-blockly产生的'类型名_次数'的默认名称, 也可以开头加一行`name : [null,null,null,'args']`来把这个变量命名为args, 对于setText_s这种域比较多的块会很有帮助.
|
||||||
|
在MotaAction中, 除了默认的Int/Number等
|
||||||
|
常规的字符串使用EvalString
|
||||||
|
ID类型的使用IdString
|
||||||
|
允许空的自然数使用IntString
|
||||||
|
位置坐标使用PosString
|
||||||
|
不转义的多行文本使用RawEvalString
|
||||||
|
json类型的文本使用JsonEvalString
|
||||||
|
|
||||||
|
相应的也需要改json到图块的部分
|
||||||
|
|
||||||
|
<pre><code> break;
|
||||||
|
case "playBgm":
|
||||||
|
this.next = MotaActionBlocks['playBgm_s'].xmlText([
|
||||||
|
data.name,data.startTime||0,data.keep||false<b style='color:green'>,data.args</b>,this.next]);
|
||||||
|
break
|
||||||
|
case "pauseBgm":</code></pre>
|
||||||
|
|
||||||
|
|
||||||
|
### demo - 增加新语句图块
|
||||||
|
|
||||||
|
假设已经制作了一个名为`陨石坠落`的公共事件, 其参数列表是长度3的数组, 依次是陨石颜色, 陨石位置, 爆炸范围.
|
||||||
|
|
||||||
|
每次要使用插入公共事件并要填`陨石坠落`和形如`[[231,231,41],[5,7],1]`的长而且带有格式的内容, 并且无法使用取色器和地图选点功能. 此时就可以借助'注册一个自定义事件'并填加新语句图块来解决这个问题.
|
||||||
|
|
||||||
|
首先注册成名为'meteorite'的事件, 在插件编写的init中添加以下内容
|
||||||
|
```js
|
||||||
|
core.registerEvent('meteorite', function(data){
|
||||||
|
core.insertAction({"type": "insert", "name": "陨石坠落", "args": [data.color, data.loc, data.range]});
|
||||||
|
core.doAction();
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在g4中修改以下位置(请善用搜索来定位)
|
||||||
|
|
||||||
|
<pre><code> | drawSelector_1_s
|
||||||
|
| unknown_s
|
||||||
|
| function_s<b style='color:green'>
|
||||||
|
| meteorite_s</b>
|
||||||
|
| pass_s
|
||||||
|
;
|
||||||
|
|
||||||
|
text_0_s</code></pre>
|
||||||
|
|
||||||
|
并在分号后面添加
|
||||||
|
|
||||||
|
```antlr
|
||||||
|
meteorite_s
|
||||||
|
: '陨石坠落' '颜色' ColorString? Colour 'x' PosString ',' 'y' PosString '爆炸范围' Int Newline
|
||||||
|
|
||||||
|
|
||||||
|
/* meteorite_s
|
||||||
|
tooltip : 陨石坠落
|
||||||
|
helpUrl : https://h5mota.com/games/template/_docs/#/event
|
||||||
|
default : ["255,0,0,1",'rgba(255,0,0,1)',"0","0",1]
|
||||||
|
selectPoint : ["PosString_0", "PosString_1"]
|
||||||
|
colour : this.soundColor
|
||||||
|
var loc = ', "loc": ['+PosString_0+','+PosString_1+']';
|
||||||
|
ColorString_0 = ColorString_0 ? (', "color": ['+ColorString_0+']') : '';
|
||||||
|
var code = '{"type": "meteorite"'+ColorString_0+loc+', "range": '+Int_0+'},\n';
|
||||||
|
return code;
|
||||||
|
*/;
|
||||||
|
```
|
||||||
|
|
||||||
|
希望是特效的颜色, 于是在g4搜索'画面闪烁', 找到了`colour : this.soundColor`, 抄出其中的颜色的写法, 地图选点的写法同理, 搜索'强制战斗'后模仿其写法
|
||||||
|
|
||||||
|
在MotaActionParser.js中添加json到图块的代码, 同理模仿'画面闪烁'和'强制战斗'
|
||||||
|
|
||||||
|
<pre><code> break;
|
||||||
|
case "playBgm":
|
||||||
|
this.next = MotaActionBlocks['playBgm_s'].xmlText([
|
||||||
|
data.name,data.startTime||0,data.keep||false,this.next]);
|
||||||
|
break;<b style='color:green'>
|
||||||
|
case "meteorite":
|
||||||
|
data.color = this.Colour(data.color)
|
||||||
|
this.next = MotaActionBlocks['meteorite_s'].xmlText([
|
||||||
|
data.color,'rgba('+data.color+')',data.loc[0],data.loc[1],data.range,this.next]);
|
||||||
|
break;
|
||||||
|
</b>case "pauseBgm":</code></pre>
|
||||||
|
|
||||||
|
最后在editor_blocklyconfig.js中将其加入到工具栏中
|
||||||
|
|
||||||
|
<pre><code> '特效/声音':[<b style='color:green'>
|
||||||
|
MotaActionBlocks['meteorite_s'].xmlText(),
|
||||||
|
</b>MotaActionBlocks['sleep_s'].xmlText(),</code></pre>
|
||||||
|
|
||||||
|
==========================================================================================
|
||||||
|
|
||||||
|
[继续阅读下一章:UI编辑器](ui-editor)
|
1213
editor_blockly.js
Normal file
1213
editor_blockly.js
Normal file
File diff suppressed because it is too large
Load Diff
705
editor_blocklyconfig.js
Normal file
705
editor_blocklyconfig.js
Normal file
@ -0,0 +1,705 @@
|
|||||||
|
editor_blocklyconfig=(function(){
|
||||||
|
// start mark sfergsvae
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
var getCategory = function(name,custom){
|
||||||
|
for(var node of document.getElementById('toolbox').children) {
|
||||||
|
if(node.getAttribute('name')==name) return node;
|
||||||
|
}
|
||||||
|
var node = document.createElement('category');
|
||||||
|
node.setAttribute('name',name);
|
||||||
|
if(custom)node.setAttribute('custom',custom);
|
||||||
|
document.getElementById('toolbox').appendChild(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
var toolboxObj = {
|
||||||
|
'入口方块':[
|
||||||
|
'<label text="入口方块会根据当前类型在此数组中筛选,具体控制在editor_blockly.entranceCategoryCallback中"></label>',
|
||||||
|
MotaActionFunctions.actionParser.parse([
|
||||||
|
"欢迎使用事件编辑器",
|
||||||
|
"本事件触发一次后会消失",
|
||||||
|
{"type": "hide", "time": 500},
|
||||||
|
],'event'),
|
||||||
|
MotaActionFunctions.actionParser.parse({
|
||||||
|
"condition": "flag:__door__===2",
|
||||||
|
"currentFloor": true,
|
||||||
|
"priority": 0,
|
||||||
|
"delayExecute": false,
|
||||||
|
"multiExecute": false,
|
||||||
|
"data": [
|
||||||
|
{"type": "openDoor", "loc": [10,5]}
|
||||||
|
],
|
||||||
|
},'autoEvent'),
|
||||||
|
MotaActionBlocks['changeFloor_m'].xmlText(),
|
||||||
|
MotaActionFunctions.actionParser.parse([{
|
||||||
|
"id": "shop1",
|
||||||
|
"text": "\t[贪婪之神,moneyShop]勇敢的武士啊, 给我${20+2*flag:shop1}金币就可以:",
|
||||||
|
"textInList": "1F金币商店",
|
||||||
|
"choices": [
|
||||||
|
{"text": "生命+800", "need": "status:money>=20+2*flag:shop1", "action": [
|
||||||
|
{"type": "comment", "text": "新版商店中需要手动扣减金币和增加访问次数"},
|
||||||
|
{"type": "setValue", "name": "status:money", "operator": "-=", "value": "20+2*flag:shop1"},
|
||||||
|
{"type": "setValue", "name": "flag:shop1", "operator": "+=", "value": "1"},
|
||||||
|
{"type": "setValue", "name": "status:hp", "operator": "+=", "value": "800"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"id": "itemShop",
|
||||||
|
"item": true,
|
||||||
|
"textInList": "道具商店",
|
||||||
|
"choices": [
|
||||||
|
{"id": "yellowKey", "number": 10, "money": 10}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"id": "keyShop1",
|
||||||
|
"textInList": "回收钥匙商店",
|
||||||
|
"commonEvent": "回收钥匙商店",
|
||||||
|
"args": ""
|
||||||
|
}],'shop'),
|
||||||
|
MotaActionBlocks['common_m'].xmlText(),
|
||||||
|
MotaActionBlocks['beforeBattle_m'].xmlText(),
|
||||||
|
MotaActionBlocks['afterBattle_m'].xmlText(),
|
||||||
|
MotaActionBlocks['afterGetItem_m'].xmlText(),
|
||||||
|
MotaActionBlocks['afterOpenDoor_m'].xmlText(),
|
||||||
|
MotaActionBlocks['firstArrive_m'].xmlText(),
|
||||||
|
MotaActionBlocks['eachArrive_m'].xmlText(),
|
||||||
|
MotaActionBlocks['level_m'].xmlText(),
|
||||||
|
MotaActionFunctions.actionParser.parse([
|
||||||
|
['MTx', '']
|
||||||
|
], 'floorPartition'),
|
||||||
|
MotaActionBlocks['commonEvent_m'].xmlText(),
|
||||||
|
MotaActionBlocks['item_m'].xmlText(),
|
||||||
|
MotaActionFunctions.actionParser.parse([
|
||||||
|
{"title":"简单", "name": "Easy", "hard": 1, "action": [
|
||||||
|
{"type": "comment", "text": "在这里写该难度需执行的事件"}
|
||||||
|
]}
|
||||||
|
], 'levelChoose'),
|
||||||
|
MotaActionFunctions.actionParser.parse({
|
||||||
|
"type": 0, "value": {"atk": 10}, "percentage": {"speed": 10},
|
||||||
|
}, 'equip'),
|
||||||
|
MotaActionFunctions.actionParser.parse([{
|
||||||
|
"name": "bg.jpg", "x": 0, "y": 0, "canvas": "bg"
|
||||||
|
}], 'floorImage'),
|
||||||
|
MotaActionFunctions.actionParser.parse({
|
||||||
|
"time": 160, "openSound": "door.mp3", "closeSound": "door.mp3", "keys": {"yellowKey": 1, "orangeKey": 1}
|
||||||
|
}, 'doorInfo'),
|
||||||
|
MotaActionBlocks['faceIds_m'].xmlText(),
|
||||||
|
MotaActionBlocks['mainStyle_m'].xmlText(),
|
||||||
|
MotaActionFunctions.actionParser.parse({
|
||||||
|
"背景音乐": "bgm.mp3", "确定": "confirm.mp3", "攻击": "attack.mp3", "背景图": "bg.jpg", "领域": "zone", "文件名": "file.jpg"
|
||||||
|
}, 'nameMap'),
|
||||||
|
MotaActionFunctions.actionParser.parse([
|
||||||
|
{"name": "hero.png", "width": 32, "height": 32, "prefix": "hero_"},
|
||||||
|
], 'splitImages'),
|
||||||
|
],
|
||||||
|
'显示文字':[
|
||||||
|
MotaActionBlocks['text_0_s'].xmlText(),
|
||||||
|
MotaActionBlocks['text_1_s'].xmlText(),
|
||||||
|
MotaActionFunctions.actionParser.parseList("\t[小妖精,fairy]\f[fairy.png,0,0]欢迎使用事件编辑器(双击方块可直接预览)"),
|
||||||
|
MotaActionBlocks['moveTextBox_s'].xmlText(),
|
||||||
|
MotaActionBlocks['clearTextBox_s'].xmlText(),
|
||||||
|
MotaActionBlocks['comment_s'].xmlText(),
|
||||||
|
MotaActionBlocks['autoText_s'].xmlText(),
|
||||||
|
MotaActionBlocks['scrollText_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setText_s'].xmlText(),
|
||||||
|
MotaActionBlocks['tip_s'].xmlText(),
|
||||||
|
MotaActionBlocks['confirm_s'].xmlText(),
|
||||||
|
MotaActionBlocks['choices_s'].xmlText([
|
||||||
|
'选择剑或者盾','流浪者','man',0,'',MotaActionBlocks['choicesContext'].xmlText([
|
||||||
|
'剑','','',null,'','',MotaActionFunctions.actionParser.parseList([{"type": "openDoor", "loc": [3,3]}]),
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
MotaActionBlocks['win_s'].xmlText(),
|
||||||
|
MotaActionBlocks['lose_s'].xmlText(),
|
||||||
|
MotaActionBlocks['restart_s'].xmlText(),
|
||||||
|
],
|
||||||
|
'数据相关':[
|
||||||
|
MotaActionBlocks['setValue_s'].xmlText([
|
||||||
|
MotaActionBlocks['idIdList_e'].xmlText(['status','生命']), '=', '', false
|
||||||
|
]),
|
||||||
|
MotaActionBlocks['setEnemy_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setEnemyOnPoint_s'].xmlText(),
|
||||||
|
MotaActionBlocks['resetEnemyOnPoint_s'].xmlText(),
|
||||||
|
MotaActionBlocks['moveEnemyOnPoint_s'].xmlText(),
|
||||||
|
MotaActionBlocks['moveEnemyOnPoint_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setEquip_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setFloor_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setGlobalAttribute_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setGlobalValue_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setGlobalFlag_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setNameMap_s'].xmlText(),
|
||||||
|
MotaActionBlocks['input_s'].xmlText(),
|
||||||
|
MotaActionBlocks['input2_s'].xmlText(),
|
||||||
|
MotaActionBlocks['update_s'].xmlText(),
|
||||||
|
MotaActionBlocks['moveAction_s'].xmlText(),
|
||||||
|
MotaActionBlocks['changeFloor_s'].xmlText(),
|
||||||
|
MotaActionBlocks['changePos_s'].xmlText(),
|
||||||
|
MotaActionBlocks['battle_s'].xmlText(),
|
||||||
|
MotaActionBlocks['useItem_s'].xmlText(),
|
||||||
|
MotaActionBlocks['loadEquip_s'].xmlText(),
|
||||||
|
MotaActionBlocks['unloadEquip_s'].xmlText(),
|
||||||
|
MotaActionBlocks['openShop_s'].xmlText(),
|
||||||
|
MotaActionBlocks['disableShop_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setHeroIcon_s'].xmlText(),
|
||||||
|
MotaActionBlocks['follow_s'].xmlText(),
|
||||||
|
MotaActionBlocks['unfollow_s'].xmlText(),
|
||||||
|
],
|
||||||
|
'地图处理':[
|
||||||
|
MotaActionBlocks['battle_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['openDoor_s'].xmlText(),
|
||||||
|
MotaActionBlocks['closeDoor_s'].xmlText(),
|
||||||
|
MotaActionBlocks['show_s'].xmlText(),
|
||||||
|
MotaActionBlocks['hide_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setBlock_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setBlockOpacity_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setBlockFilter_s'].xmlText(),
|
||||||
|
MotaActionBlocks['turnBlock_s'].xmlText(),
|
||||||
|
MotaActionBlocks['moveHero_s'].xmlText(),
|
||||||
|
MotaActionBlocks['move_s'].xmlText(),
|
||||||
|
MotaActionBlocks['jumpHero_s'].xmlText(),
|
||||||
|
MotaActionBlocks['jumpHero_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['jump_s'].xmlText(),
|
||||||
|
MotaActionBlocks['jump_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['showBgFgMap_s'].xmlText(),
|
||||||
|
MotaActionBlocks['hideBgFgMap_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setBgFgBlock_s'].xmlText(),
|
||||||
|
MotaActionBlocks['showFloorImg_s'].xmlText(),
|
||||||
|
MotaActionBlocks['hideFloorImg_s'].xmlText(),
|
||||||
|
],
|
||||||
|
'事件控制':[
|
||||||
|
MotaActionBlocks['if_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['if_s'].xmlText(),
|
||||||
|
MotaActionFunctions.actionParser.parseList({"type": "switch", "condition": "判别值", "caseList": [
|
||||||
|
{"action": [{"type": "comment", "text": "当判别值是值的场合执行此事件"}]},
|
||||||
|
{"case": "default", "action": [{"type": "comment", "text": "当没有符合的值的场合执行default事件"}]},
|
||||||
|
]}),
|
||||||
|
MotaActionFunctions.actionParser.parseList({"type": "for", "name": "temp:A", "from": "0", "to": "12", "step": "1", "data": []}),
|
||||||
|
MotaActionFunctions.actionParser.parseList({"type": "forEach", "name": "temp:A", "list": ["status:atk","status:def"], "data": []}),
|
||||||
|
MotaActionBlocks['while_s'].xmlText(),
|
||||||
|
MotaActionBlocks['dowhile_s'].xmlText(),
|
||||||
|
MotaActionBlocks['break_s'].xmlText(),
|
||||||
|
MotaActionBlocks['continue_s'].xmlText(),
|
||||||
|
MotaActionBlocks['exit_s'].xmlText(),
|
||||||
|
MotaActionBlocks['trigger_s'].xmlText(),
|
||||||
|
MotaActionBlocks['insert_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['insert_2_s'].xmlText(),
|
||||||
|
],
|
||||||
|
'特效表现':[
|
||||||
|
MotaActionBlocks['sleep_s'].xmlText(),
|
||||||
|
MotaActionFunctions.actionParser.parseList({"type": "wait", "timeout": 0, "data": [
|
||||||
|
{"case": "keyboard", "keycode": "13,32", "action": [{"type": "comment", "text": "当按下回车(keycode=13)或空格(keycode=32)时执行此事件\n超时剩余时间会写入flag:timeout"}]},
|
||||||
|
{"case": "mouse", "px": [0,32], "py": [0,32], "action": [{"type": "comment", "text": "当点击地图左上角时执行此事件\n超时剩余时间会写入flag:timeout"}]},
|
||||||
|
{"case": "condition", "condition": "flag:type==0\n&&flag:keycode==13", "action": [{"type": "comment", "text": "当满足自定义条件时会执行此事件\n超时剩余时间会写入flag:timeout"}]},
|
||||||
|
{"case": "timeout", "action": [{"type": "comment", "text": "当超时未操作时执行此事件"}]},
|
||||||
|
]}),
|
||||||
|
MotaActionBlocks['waitAsync_s'].xmlText(),
|
||||||
|
MotaActionBlocks['stopAsync_s'].xmlText(),
|
||||||
|
MotaActionBlocks['vibrate_s'].xmlText(),
|
||||||
|
MotaActionBlocks['animate_s'].xmlText(),
|
||||||
|
MotaActionBlocks['animate_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['stopAnimate_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setViewport_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setViewport_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['lockViewport_s'].xmlText(),
|
||||||
|
MotaActionBlocks['showStatusBar_s'].xmlText(),
|
||||||
|
MotaActionBlocks['hideStatusBar_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setHeroOpacity_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setCurtain_0_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setCurtain_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['screenFlash_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setWeather_s'].xmlText(),
|
||||||
|
MotaActionBlocks['callBook_s'].xmlText(),
|
||||||
|
MotaActionBlocks['callSave_s'].xmlText(),
|
||||||
|
MotaActionBlocks['autoSave_s'].xmlText(),
|
||||||
|
MotaActionBlocks['forbidSave_s'].xmlText(),
|
||||||
|
MotaActionBlocks['callLoad_s'].xmlText(),
|
||||||
|
],
|
||||||
|
'音像处理':[
|
||||||
|
MotaActionBlocks['showImage_s'].xmlText(),
|
||||||
|
MotaActionBlocks['showImage_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['hideImage_s'].xmlText(),
|
||||||
|
MotaActionBlocks['showTextImage_s'].xmlText(),
|
||||||
|
MotaActionBlocks['moveImage_s'].xmlText(),
|
||||||
|
MotaActionBlocks['rotateImage_s'].xmlText(),
|
||||||
|
MotaActionBlocks['scaleImage_s'].xmlText(),
|
||||||
|
MotaActionBlocks['showGif_s'].xmlText(),
|
||||||
|
MotaActionBlocks['playBgm_s'].xmlText(),
|
||||||
|
MotaActionBlocks['pauseBgm_s'].xmlText(),
|
||||||
|
MotaActionBlocks['resumeBgm_s'].xmlText(),
|
||||||
|
MotaActionBlocks['loadBgm_s'].xmlText(),
|
||||||
|
MotaActionBlocks['freeBgm_s'].xmlText(),
|
||||||
|
MotaActionBlocks['playSound_s'].xmlText(),
|
||||||
|
MotaActionBlocks['playSound_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['stopSound_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setVolume_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setBgmSpeed_s'].xmlText(),
|
||||||
|
],
|
||||||
|
'UI绘制':[
|
||||||
|
MotaActionBlocks['previewUI_s'].xmlText(),
|
||||||
|
MotaActionBlocks['clearMap_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setAttribute_s'].xmlText(),
|
||||||
|
MotaActionBlocks['setFilter_s'].xmlText(),
|
||||||
|
MotaActionBlocks['fillText_s'].xmlText(),
|
||||||
|
MotaActionBlocks['fillBoldText_s'].xmlText(),
|
||||||
|
MotaActionBlocks['drawTextContent_s'].xmlText(),
|
||||||
|
MotaActionBlocks['fillRect_s'].xmlText(),
|
||||||
|
MotaActionBlocks['strokeRect_s'].xmlText(),
|
||||||
|
MotaActionBlocks['drawLine_s'].xmlText(),
|
||||||
|
MotaActionBlocks['drawArrow_s'].xmlText(),
|
||||||
|
MotaActionBlocks['fillPolygon_s'].xmlText(),
|
||||||
|
MotaActionBlocks['strokePolygon_s'].xmlText(),
|
||||||
|
MotaActionBlocks['fillEllipse_s'].xmlText(),
|
||||||
|
MotaActionBlocks['strokeEllipse_s'].xmlText(),
|
||||||
|
MotaActionBlocks['fillArc_s'].xmlText(),
|
||||||
|
MotaActionBlocks['strokeArc_s'].xmlText(),
|
||||||
|
MotaActionBlocks['drawImage_s'].xmlText(),
|
||||||
|
MotaActionBlocks['drawImage_1_s'].xmlText(),
|
||||||
|
MotaActionBlocks['drawIcon_s'].xmlText(),
|
||||||
|
MotaActionBlocks['drawBackground_s'].xmlText(),
|
||||||
|
MotaActionBlocks['drawSelector_s'].xmlText(),
|
||||||
|
MotaActionBlocks['drawSelector_1_s'].xmlText(),
|
||||||
|
],
|
||||||
|
'原生脚本':[
|
||||||
|
MotaActionBlocks['function_s'].xmlText(),
|
||||||
|
MotaActionBlocks['unknown_s'].xmlText(),
|
||||||
|
],
|
||||||
|
'值块':[
|
||||||
|
MotaActionBlocks['setValue_s'].xmlText([
|
||||||
|
MotaActionBlocks['idIdList_e'].xmlText(['status','生命']), '=', '', false
|
||||||
|
]),
|
||||||
|
MotaActionBlocks['expression_arithmetic_0'].xmlText(),
|
||||||
|
MotaActionBlocks['idFlag_e'].xmlText(),
|
||||||
|
MotaActionBlocks['idTemp_e'].xmlText(),
|
||||||
|
MotaActionBlocks['negate_e'].xmlText(),
|
||||||
|
MotaActionBlocks['unaryOperation_e'].xmlText(),
|
||||||
|
MotaActionBlocks['bool_e'].xmlText(),
|
||||||
|
MotaActionBlocks['idString_e'].xmlText(),
|
||||||
|
MotaActionBlocks['idIdList_e'].xmlText(),
|
||||||
|
MotaActionBlocks['idFixedList_e'].xmlText(),
|
||||||
|
MotaActionBlocks['enemyattr_e'].xmlText(),
|
||||||
|
MotaActionBlocks['blockId_e'].xmlText(),
|
||||||
|
MotaActionBlocks['blockNumber_e'].xmlText(),
|
||||||
|
MotaActionBlocks['blockCls_e'].xmlText(),
|
||||||
|
MotaActionBlocks['hasEquip_e'].xmlText(),
|
||||||
|
MotaActionBlocks['equip_e'].xmlText(),
|
||||||
|
MotaActionBlocks['nextXY_e'].xmlText(),
|
||||||
|
MotaActionBlocks['isReplaying_e'].xmlText(),
|
||||||
|
MotaActionBlocks['hasVisitedFloor_e'].xmlText(),
|
||||||
|
MotaActionBlocks['isShopVisited_e'].xmlText(),
|
||||||
|
MotaActionBlocks['canBattle_e'].xmlText(),
|
||||||
|
MotaActionBlocks['damage_e'].xmlText(),
|
||||||
|
MotaActionBlocks['damage_1_e'].xmlText(),
|
||||||
|
MotaActionBlocks['rand_e'].xmlText(),
|
||||||
|
MotaActionBlocks['evalString_e'].xmlText(),
|
||||||
|
],
|
||||||
|
'常见事件模板':[
|
||||||
|
'<label text="检测音乐如果没有开启则系统提示开启"></label>',
|
||||||
|
MotaActionFunctions.actionParser.parseList({"type": "if", "condition": "!core.musicStatus.bgmStatus",
|
||||||
|
"true": [
|
||||||
|
"\t[系统提示]你当前音乐处于关闭状态,本塔开音乐游戏效果更佳"
|
||||||
|
],
|
||||||
|
"false": []
|
||||||
|
}),
|
||||||
|
'<label text="仿新新魔塔一次性商人"></label>',
|
||||||
|
MotaActionFunctions.actionParser.parse([
|
||||||
|
{
|
||||||
|
"type": "if",
|
||||||
|
"condition": "switch:A",
|
||||||
|
"true": [
|
||||||
|
"\t[行商,trader]\b[this]这是购买我的道具后我给玩家的提示。",
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"text": "下一条指令可视情况使用或不使用"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "hide",
|
||||||
|
"remove": true,
|
||||||
|
"time": 250
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"false": [
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"text": "我有3把黄钥匙,\n你出50金币就卖给你。",
|
||||||
|
"yes": [
|
||||||
|
{
|
||||||
|
"type": "if",
|
||||||
|
"condition": "status:money>=50",
|
||||||
|
"true": [
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "status:money",
|
||||||
|
"operator": "-=",
|
||||||
|
"value": "50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "item:yellowKey",
|
||||||
|
"operator": "+=",
|
||||||
|
"value": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "playSound",
|
||||||
|
"name": "确定",
|
||||||
|
"stop": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "switch:A",
|
||||||
|
"value": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"false": [
|
||||||
|
{
|
||||||
|
"type": "playSound",
|
||||||
|
"name": "操作失败"
|
||||||
|
},
|
||||||
|
"\t[行商,trader]\b[this]你的金币不足!"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
], 'event'),
|
||||||
|
'<label text="全地图选中一个点"></label>',
|
||||||
|
MotaActionFunctions.actionParser.parse([
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"text": "全地图选中一个点,需要用鼠标或触屏操作"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "temp:X",
|
||||||
|
"value": "status:x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "temp:Y",
|
||||||
|
"value": "status:y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tip",
|
||||||
|
"text": "再次点击闪烁位置确认"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "while",
|
||||||
|
"condition": "true",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"type": "drawSelector",
|
||||||
|
"image": "winskin.png",
|
||||||
|
"code": 1,
|
||||||
|
"x": "32*temp:X",
|
||||||
|
"y": "32*temp:Y",
|
||||||
|
"width": 32,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wait"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "if",
|
||||||
|
"condition": "(flag:type === 1)",
|
||||||
|
"true": [
|
||||||
|
{
|
||||||
|
"type": "if",
|
||||||
|
"condition": "((temp:X===flag:x)&&(temp:Y===flag:y))",
|
||||||
|
"true": [
|
||||||
|
{
|
||||||
|
"type": "break",
|
||||||
|
"n": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "temp:X",
|
||||||
|
"value": "flag:x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "temp:Y",
|
||||||
|
"value": "flag:y"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "drawSelector",
|
||||||
|
"code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"text": "流程进行到这里可以对[X,Y]点进行处理,比如"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "closeDoor",
|
||||||
|
"id": "yellowDoor",
|
||||||
|
"loc": [
|
||||||
|
"temp:X",
|
||||||
|
"temp:Y"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],'event'),
|
||||||
|
'<label text="多阶段Boss战斗"></label>',
|
||||||
|
MotaActionFunctions.actionParser.parse([
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"text": "多阶段boss,请直接作为战后事件使用"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "switch:A",
|
||||||
|
"operator": "+=",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "switch",
|
||||||
|
"condition": "switch:A",
|
||||||
|
"caseList": [
|
||||||
|
{
|
||||||
|
"case": "1",
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"type": "setBlock",
|
||||||
|
"number": "redSlime"
|
||||||
|
},
|
||||||
|
"\t[2阶段boss,redSlime]\b[this]你以为你已经打败我了吗?没听说过史莱姆有九条命吗?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "2",
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"type": "setBlock",
|
||||||
|
"number": "blackSlime"
|
||||||
|
},
|
||||||
|
"\t[3阶段boss,blackSlime]\b[this]不能消灭我的,只会让我更强大!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "3",
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"type": "setBlock",
|
||||||
|
"number": "slimelord"
|
||||||
|
},
|
||||||
|
"\t[4阶段boss,slimelord]\b[this]我还能打!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "4",
|
||||||
|
"action": [
|
||||||
|
"\t[4阶段boss,slimelord]我一定会回来的!"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],'afterBattle'),
|
||||||
|
],
|
||||||
|
'最近使用事件':[
|
||||||
|
'<label text="此处只是占位符,实际定义在editor_blockly.searchBlockCategoryCallback中"></label>',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
var toolboxgap = '<sep gap="5"></sep>'
|
||||||
|
//xml_text = MotaActionFunctions.actionParser.parse(obj,type||'event')
|
||||||
|
//MotaActionBlocks['idString_e'].xmlText()
|
||||||
|
|
||||||
|
for (var name in toolboxObj){
|
||||||
|
var custom = null;
|
||||||
|
if(name=='最近使用事件')custom='searchBlockCategory';
|
||||||
|
if(name=='入口方块')custom='entranceCategory';
|
||||||
|
getCategory(name,custom).innerHTML = toolboxObj[name].join(toolboxgap);
|
||||||
|
}
|
||||||
|
|
||||||
|
var blocklyArea = document.getElementById('blocklyArea');
|
||||||
|
var blocklyDiv = document.getElementById('blocklyDiv');
|
||||||
|
var workspace = Blockly.inject(blocklyDiv,{
|
||||||
|
media: '_server/blockly/media/',
|
||||||
|
toolbox: document.getElementById('toolbox'),
|
||||||
|
zoom:{
|
||||||
|
controls: true,
|
||||||
|
wheel: false,//滚轮改为上下(shift:左右)翻滚
|
||||||
|
startScale: 1.0,
|
||||||
|
maxScale: 3,
|
||||||
|
minScale: 0.3,
|
||||||
|
scaleSpeed: 1.08
|
||||||
|
},
|
||||||
|
trashcan: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
editor_blockly.isCommonEntry = function () {
|
||||||
|
var commonEntries = ['beforeBattle', 'afterBattle', 'afterOpenDoor', 'firstArrive', 'eachArrive', 'commonEvent', 'item'];
|
||||||
|
return commonEntries.indexOf(editor_blockly.entryType) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_blockly.entranceCategoryCallback = function(workspace) {
|
||||||
|
var list=toolboxObj['入口方块']
|
||||||
|
var xmlList = [];
|
||||||
|
var eventType = (editor_blockly.isCommonEntry() ? 'common' : editor_blockly.entryType)+'_m';
|
||||||
|
for(var ii=0,blockText;blockText=list[ii];ii++){
|
||||||
|
if(new RegExp('<block type="'+eventType+'">').exec(blockText)){
|
||||||
|
var block = Blockly.Xml.textToDom('<xml>'+blockText+'</xml>').firstChild;
|
||||||
|
block.setAttribute("gap", 5);
|
||||||
|
xmlList.push(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return xmlList;
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.registerToolboxCategoryCallback(
|
||||||
|
'entranceCategory', editor_blockly.entranceCategoryCallback);
|
||||||
|
|
||||||
|
editor_blockly.searchBlockCategoryCallback = function(workspace) {
|
||||||
|
var xmlList = [];
|
||||||
|
var labels = editor_blockly.searchBlock();
|
||||||
|
for (var i = 0; i < labels.length; i++) {
|
||||||
|
var blockText = '<xml>' +
|
||||||
|
MotaActionBlocks[labels[i]].xmlText() +
|
||||||
|
'</xml>';
|
||||||
|
var block = Blockly.Xml.textToDom(blockText).firstChild;
|
||||||
|
block.setAttribute("gap", 5);
|
||||||
|
xmlList.push(block);
|
||||||
|
}
|
||||||
|
return xmlList;
|
||||||
|
};
|
||||||
|
|
||||||
|
workspace.registerToolboxCategoryCallback(
|
||||||
|
'searchBlockCategory', editor_blockly.searchBlockCategoryCallback);
|
||||||
|
|
||||||
|
var onresize = function(e) {
|
||||||
|
blocklyDiv.style.width = blocklyArea.offsetWidth + 'px';
|
||||||
|
blocklyDiv.style.height = blocklyArea.offsetHeight + 'px';
|
||||||
|
Blockly.svgResize(workspace);
|
||||||
|
};
|
||||||
|
if(typeof editor !== "undefined" && !editor.isMobile)window.addEventListener('resize', onresize, false);
|
||||||
|
onresize();
|
||||||
|
//Blockly.svgResize(workspace);
|
||||||
|
|
||||||
|
//Blockly.bindEventWithChecks_(workspace.svgGroup_,"wheel",workspace,function(e){});
|
||||||
|
document.getElementById('blocklyDiv').onmousewheel = function(e){
|
||||||
|
//console.log(e);
|
||||||
|
e.preventDefault();
|
||||||
|
var hvScroll = e.shiftKey?'hScroll':'vScroll';
|
||||||
|
var mousewheelOffsetValue=20/380*workspace.scrollbar[hvScroll].handleLength_*3;
|
||||||
|
workspace.scrollbar[hvScroll].handlePosition_+=( ((e.deltaY||0)+(e.detail||0)) >0?mousewheelOffsetValue:-mousewheelOffsetValue);
|
||||||
|
workspace.scrollbar[hvScroll].onScroll_();
|
||||||
|
// workspace.setScale(workspace.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
var doubleClickCheck=[[0,'abc']];
|
||||||
|
function omitedcheckUpdateFunction(event) {
|
||||||
|
if(event.type==='create'){
|
||||||
|
editor_blockly.addIntoLastUsedType(event.blockId);
|
||||||
|
}
|
||||||
|
if(event.type==='ui' && event.element == 'click'){
|
||||||
|
var newClick = [new Date().getTime(),event.blockId];
|
||||||
|
var lastClick = doubleClickCheck.shift();
|
||||||
|
doubleClickCheck.push(newClick);
|
||||||
|
if(newClick[0]-lastClick[0]<500){
|
||||||
|
if(newClick[1]===lastClick[1]){
|
||||||
|
editor_blockly.doubleClickBlock(newClick[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only handle these events
|
||||||
|
if (["create", "move", "change", "delete"].indexOf(event.type) < 0) return;
|
||||||
|
if(editor_blockly.workspace.topBlocks_.length>=2){
|
||||||
|
editor_blockly.setValue('入口方块只能有一个');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var eventType = editor_blockly.entryType;
|
||||||
|
if(editor_blockly.workspace.topBlocks_.length==1){
|
||||||
|
var blockType = editor_blockly.workspace.topBlocks_[0].type;
|
||||||
|
if(blockType!==eventType+'_m' && !(editor_blockly.isCommonEntry() && blockType == 'common_m')){
|
||||||
|
editor_blockly.setValue('入口方块类型错误');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var code = Blockly.JavaScript.workspaceToCode(workspace).replace(/\\(i|c|d|e|g|z)/g, '\\\\$1');
|
||||||
|
editor_blockly.setValue(code);
|
||||||
|
} catch (error) {
|
||||||
|
editor_blockly.setValue(String(error));
|
||||||
|
if (error instanceof OmitedError){
|
||||||
|
var blockName = error.blockName;
|
||||||
|
var varName = error.varName;
|
||||||
|
var block = error.block;
|
||||||
|
}
|
||||||
|
// console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.addChangeListener(omitedcheckUpdateFunction);
|
||||||
|
|
||||||
|
workspace.addChangeListener(Blockly.Events.disableOrphans);
|
||||||
|
|
||||||
|
editor_blockly.workspace = workspace;
|
||||||
|
|
||||||
|
MotaActionFunctions.workspace = function(){
|
||||||
|
return editor_blockly.workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 因为在editor_blockly.parse里已经HTML转义过一次了,所以这里要覆盖掉以避免在注释中出现<等
|
||||||
|
MotaActionFunctions.xmlText = function (ruleName,inputs,isShadow,comment,collapsed,disabled) {
|
||||||
|
var rule = MotaActionBlocks[ruleName];
|
||||||
|
var blocktext = isShadow?'shadow':'block';
|
||||||
|
var xmlText = [];
|
||||||
|
xmlText.push('<'+blocktext+' type="'+ruleName+'"'+(collapsed ? ' collapsed="true"' : '')+(disabled ? ' disabled="true"' : '')+'>');
|
||||||
|
if(!inputs)inputs=[];
|
||||||
|
for (var ii=0,inputType;inputType=rule.argsType[ii];ii++) {
|
||||||
|
var input = inputs[ii];
|
||||||
|
var _input = '';
|
||||||
|
var noinput = (input===null || input===undefined);
|
||||||
|
if(noinput && inputType==='field' && MotaActionBlocks[rule.argsGrammarName[ii]].type!=='field_dropdown') continue;
|
||||||
|
if(noinput && inputType==='field') {
|
||||||
|
noinput = false;
|
||||||
|
input = rule.fieldDefault(rule.args[ii])
|
||||||
|
}
|
||||||
|
if(noinput) input = '';
|
||||||
|
if(inputType==='field' && MotaActionBlocks[rule.argsGrammarName[ii]].type==='field_checkbox')input=input?'TRUE':'FALSE';
|
||||||
|
if(inputType!=='field') {
|
||||||
|
var subList = false;
|
||||||
|
var subrulename = rule.argsGrammarName[ii];
|
||||||
|
var subrule = MotaActionBlocks[subrulename];
|
||||||
|
if (subrule instanceof Array) {
|
||||||
|
subrulename=subrule[subrule.length-1];
|
||||||
|
subrule = MotaActionBlocks[subrulename];
|
||||||
|
subList = true;
|
||||||
|
}
|
||||||
|
_input = subrule.xmlText([],true);
|
||||||
|
if(noinput && !subList && !isShadow) {
|
||||||
|
//无输入的默认行为是: 如果语句块的备选方块只有一个,直接代入方块
|
||||||
|
input = subrule.xmlText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xmlText.push('<'+inputType+' name="'+rule.args[ii]+'">');
|
||||||
|
xmlText.push(_input+input);
|
||||||
|
xmlText.push('</'+inputType+'>');
|
||||||
|
}
|
||||||
|
if(comment){
|
||||||
|
xmlText.push('<comment>');
|
||||||
|
xmlText.push(comment);
|
||||||
|
xmlText.push('</comment>');
|
||||||
|
}
|
||||||
|
var next = inputs[rule.args.length];
|
||||||
|
if (next) {//next
|
||||||
|
xmlText.push('<next>');
|
||||||
|
xmlText.push(next);
|
||||||
|
xmlText.push('</next>');
|
||||||
|
}
|
||||||
|
xmlText.push('</'+blocktext+'>');
|
||||||
|
return xmlText.join('');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// end mark sfergsvae
|
||||||
|
}).toString().split('// start mark sfergsvae')[1].split('// end mark sfergsvae')[0]
|
52
editor_config.js
Normal file
52
editor_config.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
function editor_config() {
|
||||||
|
this.address = "_server/config.json";
|
||||||
|
this._isWriting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_config.prototype.load = function(callback) {
|
||||||
|
var _this = this;
|
||||||
|
fs.readFile(this.address, "utf-8", function(e, d) {
|
||||||
|
if (e) {
|
||||||
|
console.error("无法读取配置文件, 已重新生成");
|
||||||
|
_this.config = {};
|
||||||
|
_this.save(callback);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
_this.config = JSON.parse(d);
|
||||||
|
if (callback) callback();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
_this.config = {};
|
||||||
|
_this.save(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_config.prototype.get = function(key, defaultValue) {
|
||||||
|
value = this.config[key];
|
||||||
|
return value != null ? value : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_config.prototype.set = function(key, value, callback) {
|
||||||
|
this.config[key] = value;
|
||||||
|
if (callback !== false) this.save(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_config.prototype.save = function(callback) {
|
||||||
|
// 读写锁防止写文件冲突
|
||||||
|
if (this._isWriting) return;
|
||||||
|
try {
|
||||||
|
this._isWriting = true;
|
||||||
|
var _this = this;
|
||||||
|
fs.writeFile(this.address, JSON.stringify(this.config) ,'utf-8', function(e) {
|
||||||
|
_this._isWriting = false;
|
||||||
|
if (e) console.error("写入配置文件失败");
|
||||||
|
if (callback instanceof Function) callback();
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
this._isWriting = false;
|
||||||
|
console.error(e);
|
||||||
|
if (callback instanceof Function) callback();
|
||||||
|
}
|
||||||
|
}
|
1228
editor_datapanel.js
Normal file
1228
editor_datapanel.js
Normal file
File diff suppressed because it is too large
Load Diff
1118
editor_file.js
Normal file
1118
editor_file.js
Normal file
File diff suppressed because it is too large
Load Diff
199
editor_game.js
Normal file
199
editor_game.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
editor_game_wrapper = function (editor, main, core) {
|
||||||
|
// 原则上重构后只有此文件允许`\s(main|core)`形式的调用, 以及其初始化 editor_game_wrapper(editor, main, core)
|
||||||
|
|
||||||
|
editor_game = function () {
|
||||||
|
this.replacerRecord = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////// 修改数据相关 ////////////////////
|
||||||
|
// 三个 replacer 和 replacerRecord 暂时放在此处
|
||||||
|
|
||||||
|
editor_game.prototype.replacerForLoading = function (_key, value) {
|
||||||
|
var rmap = editor.game.replacerRecord;
|
||||||
|
if (value instanceof Function) {
|
||||||
|
var guid_ = editor.util.guid()
|
||||||
|
rmap[guid_] = value.toString()
|
||||||
|
return guid_
|
||||||
|
} else if (value === null) {
|
||||||
|
// 为了包含plugins的新建
|
||||||
|
var guid_ = editor.util.guid()
|
||||||
|
rmap[guid_] = null
|
||||||
|
return guid_
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_game.prototype.replacerForSaving = function (_key, value) {
|
||||||
|
var rmap = editor.game.replacerRecord;
|
||||||
|
if (rmap.hasOwnProperty(value)) {
|
||||||
|
return rmap[value]
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_game.prototype.getValue = function (field) {
|
||||||
|
var rmap = editor.game.replacerRecord;
|
||||||
|
var value = eval(field)
|
||||||
|
if (rmap.hasOwnProperty(oldval)) {
|
||||||
|
return rmap[value]
|
||||||
|
} else {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_game.prototype.setValue = function (field, value) {
|
||||||
|
var rmap = editor.game.replacerRecord;
|
||||||
|
var oldval = eval(field)
|
||||||
|
if (rmap.hasOwnProperty(oldval)) {
|
||||||
|
rmap[value] = eval(value)
|
||||||
|
} else {
|
||||||
|
eval(field + '=' + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_game.prototype.replacerWithoutRecord = function (_key, value) {
|
||||||
|
if (value instanceof Function) {
|
||||||
|
return value.toString()
|
||||||
|
} else return value
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_game.prototype.fixFunctionInGameData = function () {
|
||||||
|
var rf = editor.game.replacerWithoutRecord
|
||||||
|
core.floors = JSON.parse(JSON.stringify(core.floors, rf));
|
||||||
|
core.data = JSON.parse(JSON.stringify(core.data, rf));
|
||||||
|
data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d = JSON.parse(JSON.stringify(data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d, rf));
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////// 加载游戏数据相关 ////////////////////
|
||||||
|
|
||||||
|
// 初始化数字与地图图块的对应
|
||||||
|
editor_game.prototype.idsInit = function (maps, icons) {
|
||||||
|
editor.ids = [0];
|
||||||
|
editor.indexs = [];
|
||||||
|
var MAX_NUM = 0;
|
||||||
|
var keys = Object.keys(maps_90f36752_8815_4be8_b32b_d7fad1d0542e);
|
||||||
|
for (var ii = 0; ii < keys.length; ii++) {
|
||||||
|
var v = ~~keys[ii];
|
||||||
|
if (v > MAX_NUM && v < core.icons.tilesetStartOffset) MAX_NUM = v;
|
||||||
|
}
|
||||||
|
editor.MAX_NUM = MAX_NUM;
|
||||||
|
var getInfoById = function (id) {
|
||||||
|
var block = maps.initBlock(0, 0, id);
|
||||||
|
if (Object.prototype.hasOwnProperty.call(block, 'event')) {
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var point = 0;
|
||||||
|
for (var i = 0; i <= MAX_NUM; i++) {
|
||||||
|
var indexBlock = getInfoById(i);
|
||||||
|
editor.indexs[i] = [];
|
||||||
|
if (indexBlock) {
|
||||||
|
var id = indexBlock.event.id;
|
||||||
|
var indexId = indexBlock.id;
|
||||||
|
var allCls = Object.keys(icons);
|
||||||
|
if (i == 17) {
|
||||||
|
editor.ids.push({ 'idnum': 17, 'id': id, 'images': 'terrains' });
|
||||||
|
point++;
|
||||||
|
editor.indexs[i].push(point);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (var j = 0; j < allCls.length; j++) {
|
||||||
|
if (id in icons[allCls[j]]) {
|
||||||
|
editor.ids.push({ 'idnum': indexId, 'id': id, 'images': allCls[j], 'y': icons[allCls[j]][id] });
|
||||||
|
point++;
|
||||||
|
editor.indexs[i].push(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editor.indexs[0] = [0];
|
||||||
|
|
||||||
|
var startOffset = core.icons.tilesetStartOffset;
|
||||||
|
for (var i in core.tilesets) {
|
||||||
|
var imgName = core.tilesets[i];
|
||||||
|
var img = core.material.images.tilesets[imgName];
|
||||||
|
var width = Math.floor(img.width / 32), height = Math.floor(img.height / 32);
|
||||||
|
if (img.width % 32 || img.height % 32) {
|
||||||
|
// alert(imgName + '的长或宽不是32的整数倍, 请修改后刷新页面');
|
||||||
|
console.warn(imgName + '的长或宽不是32的整数倍, 请修改后刷新页面');
|
||||||
|
}
|
||||||
|
if (img.width * img.height > 32 * 32 * 3000) {
|
||||||
|
// alert(imgName + '上的图块数量超过了3000,请修改后刷新页面');
|
||||||
|
console.warn(imgName + '上的图块数量超过了3000,请修改后刷新页面');
|
||||||
|
}
|
||||||
|
for (var id = startOffset; id < startOffset + width * height; id++) {
|
||||||
|
var x = (id - startOffset) % width, y = parseInt((id - startOffset) / width);
|
||||||
|
var indexBlock = getInfoById(id);
|
||||||
|
editor.ids.push({ 'idnum': id, 'id': indexBlock.event.id, 'images': imgName, "x": x, "y": y, isTile: true });
|
||||||
|
point++;
|
||||||
|
editor.indexs[id] = [point];
|
||||||
|
}
|
||||||
|
startOffset += core.icons.tilesetStartOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前地图
|
||||||
|
editor_game.prototype.fetchMapFromCore = function () {
|
||||||
|
var mapArray = core.getMapArray(core.status.floorId);
|
||||||
|
editor.map = mapArray.map(function (v) {
|
||||||
|
return v.map(function (v) {
|
||||||
|
var x = parseInt(v), y = editor.indexs[x];
|
||||||
|
if (y == null) {
|
||||||
|
printe("素材数字" + x + "未定义。是不是忘了注册,或者接档时没有覆盖icons.js和maps.js?");
|
||||||
|
y = [0];
|
||||||
|
}
|
||||||
|
return editor.ids[y[0]]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
editor.currentFloorId = core.status.floorId;
|
||||||
|
editor.currentFloorData = core.floors[core.status.floorId];
|
||||||
|
// 补出缺省的数据
|
||||||
|
editor.currentFloorData.autoEvent = editor.currentFloorData.autoEvent || {};
|
||||||
|
//
|
||||||
|
for (var ii = 0, name; name = editor.dom.canvas[ii]; ii++) {
|
||||||
|
name += 'map';
|
||||||
|
var mapArray = editor.currentFloorData[name];
|
||||||
|
if (!mapArray || JSON.stringify(mapArray) == JSON.stringify([])) {//未设置或空数组
|
||||||
|
//与editor.map同形的全0
|
||||||
|
mapArray = eval('[' + Array(editor.map.length + 1).join('[' + Array(editor.map[0].length + 1).join('0,') + '],') + ']');
|
||||||
|
}
|
||||||
|
mapArray = core.maps._processInvalidMap(mapArray, editor.map[0].length, editor.map.length);
|
||||||
|
editor[name] = mapArray.map(function (v) {
|
||||||
|
return v.map(function (v) {
|
||||||
|
var x = parseInt(v), y = editor.indexs[x];
|
||||||
|
if (y == null) {
|
||||||
|
printe("素材数字" + x + "未定义。是不是忘了注册,或者接档时没有覆盖icons.js和maps.js?");
|
||||||
|
y = [0];
|
||||||
|
}
|
||||||
|
return editor.ids[y[0]]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取地图列表
|
||||||
|
editor_game.prototype.getFloorFileList = function (callback) {
|
||||||
|
// callback([Array<String>,err:String])
|
||||||
|
editor.util.checkCallback(callback);
|
||||||
|
/* editor.fs.readdir('project/floors',function(err, data){
|
||||||
|
callback([data,err]);
|
||||||
|
}); */
|
||||||
|
callback([editor.core.floorIds, null]);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_game.prototype.doCoreFunc = function (funcname) {
|
||||||
|
return core[funcname].apply(core, Array.prototype.slice.call(arguments, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_game.prototype.getEnemy = function (id) {
|
||||||
|
return core.material.enemys[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_game.prototype.getFirstData = function () {
|
||||||
|
return core.firstData;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.constructor.prototype.game = new editor_game();
|
||||||
|
}
|
||||||
|
//editor_game_wrapper(editor);
|
193
editor_listen.js
Normal file
193
editor_listen.js
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
editor_listen_wrapper = function (editor) {
|
||||||
|
|
||||||
|
editor.constructor.prototype.listen = function () {
|
||||||
|
|
||||||
|
editor.dom.body.onmousedown = editor.uifunctions.body_click;
|
||||||
|
|
||||||
|
editor.dom.eui.oncontextmenu = function (e) { e.preventDefault() } // 自定义了右键菜单, 阻止默认行为
|
||||||
|
editor.dom.midMenu.oncontextmenu = function (e) { e.preventDefault() }
|
||||||
|
|
||||||
|
editor.dom.eui.ondblclick = editor.uifunctions.map_doubleClick
|
||||||
|
|
||||||
|
editor.dom.eui.onmousedown = editor.uifunctions.map_ondown
|
||||||
|
editor.dom.eui.onmousemove = editor.uifunctions.map_onmove
|
||||||
|
editor.dom.eui.onmouseup = editor.uifunctions.map_onup
|
||||||
|
editor.dom.eui.onmouseout = editor.uifunctions.map_onmoveout
|
||||||
|
|
||||||
|
editor.dom.mid.onmousewheel = editor.uifunctions.map_mousewheel
|
||||||
|
|
||||||
|
editor.uivalues.shortcut = editor.config.get('shortcut', { 48: 0, 49: 0, 50: 0, 51: 0, 52: 0, 53: 0, 54: 0, 55: 0, 56: 0, 57: 0 });
|
||||||
|
editor.dom.body.onkeydown = editor.uifunctions.body_shortcut
|
||||||
|
|
||||||
|
editor.uivalues.scrollBarHeight = editor.uifunctions.getScrollBarHeight();
|
||||||
|
editor.dom.iconExpandBtn.style.display = 'block';
|
||||||
|
editor.dom.iconExpandBtn.innerText = editor.uivalues.folded ? "展开素材区" : "折叠素材区";
|
||||||
|
editor.dom.iconExpandBtn.onclick = editor.uifunctions.fold_material_click
|
||||||
|
|
||||||
|
editor.dom.iconLib.onmousedown = editor.uifunctions.material_ondown
|
||||||
|
editor.dom.iconLib.onmousemove = editor.uifunctions.material_onmove
|
||||||
|
editor.dom.iconLib.onmouseup = editor.uifunctions.material_onup
|
||||||
|
editor.dom.iconLib.oncontextmenu = function (e) { e.preventDefault() }
|
||||||
|
|
||||||
|
editor.dom.extraEvent.onmouseup = editor.uifunctions.extraEvent_click
|
||||||
|
editor.dom.chooseThis.onmouseup = editor.uifunctions.chooseThis_click
|
||||||
|
editor.dom.chooseInRight.onmouseup = editor.uifunctions.chooseInRight_click
|
||||||
|
editor.dom.copyLoc.onmouseup = editor.uifunctions.copyLoc_click
|
||||||
|
editor.dom.pasteLoc.onmouseup = editor.uifunctions.pasteLoc_click
|
||||||
|
editor.dom.clearEvent.onmouseup = editor.uifunctions.clearEvent_click
|
||||||
|
editor.dom.clearLoc.onmouseup = editor.uifunctions.clearLoc_click
|
||||||
|
editor.dom.undoFloor.onclick = editor.uifunctions.undoFloor_click
|
||||||
|
editor.dom.selectFloorBtn.onclick = editor.uifunctions.selectFloorBtn_click
|
||||||
|
editor.dom.editorTheme.onchange = editor.uifunctions.editorTheme_onchange
|
||||||
|
|
||||||
|
editor.dom.lastUsed.onmouseup = editor.uifunctions.lastUsed_click;
|
||||||
|
editor.dom.lastUsed.oncontextmenu = function (e) { e.preventDefault(); }
|
||||||
|
editor.dom.clearLastUsedBtn.onclick = editor.uifunctions.clearLastUsedBtn_click;
|
||||||
|
editor.dom.showMovable.onchange = editor.uifunctions.showMovable_onchange;
|
||||||
|
|
||||||
|
editor.dom.brushMod.onchange = editor.uifunctions.brushMod_onchange
|
||||||
|
if (editor.dom.brushMod2) editor.dom.brushMod2.onchange = editor.uifunctions.brushMod2_onchange;
|
||||||
|
if (editor.dom.brushMod3) editor.dom.brushMod3.onchange = editor.uifunctions.brushMod3_onchange;
|
||||||
|
if (editor.dom.brushMod4) editor.dom.brushMod4.onchange = editor.uifunctions.brushMod4_onchange;
|
||||||
|
|
||||||
|
editor.dom.layerMod.onchange = editor.uifunctions.layerMod_onchange
|
||||||
|
if (editor.dom.layerMod2) editor.dom.layerMod2.onchange = editor.uifunctions.layerMod2_onchange;
|
||||||
|
if (editor.dom.layerMod3) editor.dom.layerMod3.onchange = editor.uifunctions.layerMod3_onchange;
|
||||||
|
|
||||||
|
editor.uifunctions.viewportButtons_func()
|
||||||
|
|
||||||
|
window.onbeforeunload = function () {
|
||||||
|
var saveFloor = document.getElementById('saveFloor');
|
||||||
|
if (saveFloor && saveFloor.classList.contains('highlight')) {
|
||||||
|
return '你尚未保存地图,确定退出么?';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.constructor.prototype.mobile_listen = function () {
|
||||||
|
if (!editor.isMobile) return;
|
||||||
|
|
||||||
|
var mobileview = document.getElementById('mobileview');
|
||||||
|
var mid = document.getElementById('mid');
|
||||||
|
var right = document.getElementById('right');
|
||||||
|
// var mobileeditdata = document.getElementById('mobileeditdata');
|
||||||
|
|
||||||
|
|
||||||
|
editor.showdataarea = function (callShowMode) {
|
||||||
|
mid.style = 'z-index:-1;opacity: 0;';
|
||||||
|
right.style = 'z-index:-1;opacity: 0;';
|
||||||
|
// mobileeditdata.style = '';
|
||||||
|
if (callShowMode) editor.mode.showMode(editor.dom.editModeSelect.value);
|
||||||
|
editor.uifunctions.hideMidMenu();
|
||||||
|
}
|
||||||
|
mobileview.children[0].onclick = function () {
|
||||||
|
editor.showdataarea(true)
|
||||||
|
}
|
||||||
|
mobileview.children[1].onclick = function () {
|
||||||
|
mid.style = 'z-index:110';
|
||||||
|
right.style = 'z-index:-1;opacity: 0;';
|
||||||
|
// mobileeditdata.style = 'z-index:-1;opacity: 0;';
|
||||||
|
editor.lastClickId = '';
|
||||||
|
}
|
||||||
|
mobileview.children[3].onclick = function () {
|
||||||
|
mid.style = 'z-index:-1;opacity: 0;';
|
||||||
|
right.style = 'z-index:110';
|
||||||
|
// mobileeditdata.style = 'z-index:-1;opacity: 0;';
|
||||||
|
editor.lastClickId = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
var gettrbyid = function () {
|
||||||
|
if (!editor.lastClickId) return false;
|
||||||
|
thisTr = document.getElementById(editor.lastClickId);
|
||||||
|
input = thisTr.children[2].children[0].children[0];
|
||||||
|
field = thisTr.children[0].getAttribute('title');
|
||||||
|
cobj = JSON.parse(thisTr.children[1].getAttribute('cobj'));
|
||||||
|
return [thisTr, input, field, cobj];
|
||||||
|
}
|
||||||
|
mobileeditdata.children[0].onclick = function () {
|
||||||
|
var info = gettrbyid()
|
||||||
|
if (!info) return;
|
||||||
|
info[1].ondblclick()
|
||||||
|
}
|
||||||
|
mobileeditdata.children[1].onclick = function () {
|
||||||
|
var info = gettrbyid()
|
||||||
|
if (!info) return;
|
||||||
|
printf(info[2])
|
||||||
|
}
|
||||||
|
mobileeditdata.children[2].onclick = function () {
|
||||||
|
var info = gettrbyid()
|
||||||
|
if (!info) return;
|
||||||
|
printf(info[0].children[1].getAttribute('title'))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
//=====
|
||||||
|
|
||||||
|
document.body.ontouchstart = document.body.onmousedown;
|
||||||
|
document.body.onmousedown = null;
|
||||||
|
|
||||||
|
editor.dom.eui.ontouchstart = editor.dom.eui.onmousedown
|
||||||
|
editor.dom.eui.onmousedown = null
|
||||||
|
editor.dom.eui.ontouchmove = editor.dom.eui.onmousemove
|
||||||
|
editor.dom.eui.onmousemove = null
|
||||||
|
editor.dom.eui.ontouchend = editor.dom.eui.onmouseup
|
||||||
|
editor.dom.eui.onmouseup = null
|
||||||
|
|
||||||
|
|
||||||
|
editor.dom.chooseThis.ontouchstart = editor.dom.chooseThis.onmousedown
|
||||||
|
editor.dom.chooseThis.onmousedown = null
|
||||||
|
editor.dom.chooseInRight.ontouchstart = editor.dom.chooseInRight.onmousedown
|
||||||
|
editor.dom.chooseInRight.onmousedown = null
|
||||||
|
editor.dom.copyLoc.ontouchstart = editor.dom.copyLoc.onmousedown
|
||||||
|
editor.dom.copyLoc.onmousedown = null
|
||||||
|
editor.dom.pasteLoc.ontouchstart = editor.dom.pasteLoc.onmousedown
|
||||||
|
editor.dom.pasteLoc.onmousedown = null
|
||||||
|
editor.dom.clearLoc.ontouchstart = editor.dom.clearLoc.onmousedown
|
||||||
|
editor.dom.clearLoc.onmousedown = null
|
||||||
|
|
||||||
|
// 不使用以下6语句, 会使得素材区手机无法拖动, 手机的框选素材只能放弃, 要通过弹框实现框选
|
||||||
|
// editor.dom.iconLib.ontouchstart = editor.dom.iconLib.onmousedown
|
||||||
|
// editor.dom.iconLib.onmousedown = null
|
||||||
|
// editor.dom.iconLib.ontouchmove = editor.dom.iconLib.onmousemove
|
||||||
|
// editor.dom.iconLib.onmousemove = null
|
||||||
|
// editor.dom.iconLib.ontouchend = editor.dom.iconLib.onmouseup
|
||||||
|
// editor.dom.iconLib.onmouseup = null
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.constructor.prototype.mode_listen = function (callback) {
|
||||||
|
|
||||||
|
// 这里的函数还没有写jsdoc, 通过_func()的方式先完成分类
|
||||||
|
|
||||||
|
editor.uifunctions.newIdIdnum_func()
|
||||||
|
editor.uifunctions.changeId_func()
|
||||||
|
editor.uifunctions.copyPasteEnemyItem_func();
|
||||||
|
|
||||||
|
editor.uifunctions.selectFloor_func()
|
||||||
|
editor.uifunctions.saveFloor_func()
|
||||||
|
editor.uifunctions.openDoc_func();
|
||||||
|
|
||||||
|
editor.uifunctions.newMap_func()
|
||||||
|
|
||||||
|
editor.uifunctions.createNewMaps_func()
|
||||||
|
|
||||||
|
editor.uifunctions.changeFloorId_func()
|
||||||
|
editor.uifunctions.changeFloorSize_func()
|
||||||
|
|
||||||
|
// editor.uifunctions.fixCtx_func()
|
||||||
|
editor.uifunctions.appendPic_func();
|
||||||
|
/*
|
||||||
|
editor.uifunctions.selectAppend_func()
|
||||||
|
editor.uifunctions.selectFileBtn_func()
|
||||||
|
editor.uifunctions.changeColorInput_func()
|
||||||
|
editor.uifunctions.picClick_func()
|
||||||
|
editor.uifunctions.appendConfirm_func()
|
||||||
|
*/
|
||||||
|
|
||||||
|
editor.dom.editModeSelect.onchange = editor.mode.editModeSelect_onchange
|
||||||
|
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1190
editor_mappanel.js
Normal file
1190
editor_mappanel.js
Normal file
File diff suppressed because it is too large
Load Diff
243
editor_materialpanel.js
Normal file
243
editor_materialpanel.js
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
editor_materialpanel_wrapper = function (editor) {
|
||||||
|
|
||||||
|
// 由于历史遗留原因, 以下变量作为全局变量使用
|
||||||
|
// selectBox
|
||||||
|
|
||||||
|
window.selectBox=document.getElementById('selectBox')
|
||||||
|
selectBox._isSelected=false
|
||||||
|
selectBox.isSelected=function(value){
|
||||||
|
if(value!=null){
|
||||||
|
selectBox._isSelected=value;
|
||||||
|
editor.dom.dataSelection.style.display=value?'':'none'
|
||||||
|
}
|
||||||
|
return selectBox._isSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
var locToPos = function (loc) {
|
||||||
|
return { 'x': ~~(loc.x / loc.size), 'y': ~~(loc.y / loc.size) };
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.uifunctions.getScrollBarHeight = function () {
|
||||||
|
var outer = document.createElement("div");
|
||||||
|
outer.style.visibility = "hidden";
|
||||||
|
outer.style.width = "100px";
|
||||||
|
outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
|
||||||
|
|
||||||
|
document.body.appendChild(outer);
|
||||||
|
|
||||||
|
var widthNoScroll = outer.offsetWidth;
|
||||||
|
// force scrollbars
|
||||||
|
outer.style.overflow = "scroll";
|
||||||
|
|
||||||
|
// add innerdiv
|
||||||
|
var inner = document.createElement("div");
|
||||||
|
inner.style.width = "100%";
|
||||||
|
outer.appendChild(inner);
|
||||||
|
|
||||||
|
var widthWithScroll = inner.offsetWidth;
|
||||||
|
|
||||||
|
// remove divs
|
||||||
|
outer.parentNode.removeChild(outer);
|
||||||
|
|
||||||
|
return widthNoScroll - widthWithScroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* editor.dom.iconExpandBtn.onclick
|
||||||
|
*/
|
||||||
|
editor.uifunctions.fold_material_click = function () {
|
||||||
|
if (editor.uivalues.folded) {
|
||||||
|
if (confirm("你想要展开素材吗?\n展开模式下将显示全素材内容。")) {
|
||||||
|
editor.config.set('folded', false, function() {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var perCol = parseInt(prompt("请输入折叠素材模式下每列的个数:", "50")) || 0;
|
||||||
|
if (perCol > 0) {
|
||||||
|
editor.config.set('foldPerCol', perCol, false);
|
||||||
|
editor.config.set('folded', true, function() {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* editor.dom.iconLib.onmousedown
|
||||||
|
* 素材区的单击/拖拽事件
|
||||||
|
*/
|
||||||
|
editor.uifunctions.material_ondown = function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
editor.uivalues.lastMoveMaterE=e;
|
||||||
|
if (!editor.isMobile && e.clientY >= editor.dom.iconLib.offsetHeight - editor.uivalues.scrollBarHeight) return;
|
||||||
|
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
|
||||||
|
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
||||||
|
editor.uivalues.startLoc={
|
||||||
|
'x': scrollLeft + e.clientX + editor.dom.iconLib.scrollLeft - right.offsetLeft - editor.dom.iconLib.offsetLeft,
|
||||||
|
'y': scrollTop + e.clientY + editor.dom.iconLib.scrollTop - right.offsetTop - editor.dom.iconLib.offsetTop,
|
||||||
|
'px': e.clientX,
|
||||||
|
'py': e.clientY,
|
||||||
|
'size': 32
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* editor.dom.iconLib.onmousemove
|
||||||
|
* 素材区的单击/拖拽事件
|
||||||
|
*/
|
||||||
|
editor.uifunctions.material_onmove = function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
editor.uivalues.lastMoveMaterE=e;
|
||||||
|
if (!editor.uivalues.startLoc) return;
|
||||||
|
var pos0 = locToPos(editor.uivalues.startLoc);
|
||||||
|
|
||||||
|
editor.dom.dataSelection.style.left = 32 * pos0.x + 'px';
|
||||||
|
editor.dom.dataSelection.style.top = 32 * pos0.y + 'px';
|
||||||
|
editor.dom.dataSelection.style.width = e.clientX - editor.uivalues.startLoc.px + 'px';
|
||||||
|
editor.dom.dataSelection.style.height = e.clientY - editor.uivalues.startLoc.py + 'px';
|
||||||
|
editor.dom.dataSelection.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* editor.dom.iconLib.onmouseup
|
||||||
|
* 素材区的单击/拖拽事件
|
||||||
|
*/
|
||||||
|
editor.uifunctions.material_onup = function (ee) {
|
||||||
|
var startLoc = editor.uivalues.startLoc;
|
||||||
|
editor.uivalues.startLoc = null;
|
||||||
|
|
||||||
|
var e=editor.uivalues.lastMoveMaterE;
|
||||||
|
if (!editor.isMobile && e.clientY >= editor.dom.iconLib.offsetHeight - editor.uivalues.scrollBarHeight) return;
|
||||||
|
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
|
||||||
|
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
||||||
|
var loc = {
|
||||||
|
'x': scrollLeft + e.clientX + editor.dom.iconLib.scrollLeft - right.offsetLeft - editor.dom.iconLib.offsetLeft,
|
||||||
|
'y': scrollTop + e.clientY + editor.dom.iconLib.scrollTop - right.offsetTop - editor.dom.iconLib.offsetTop,
|
||||||
|
'size': 32
|
||||||
|
};
|
||||||
|
editor.loc = loc;
|
||||||
|
editor.uivalues.tileSize = [1,1];
|
||||||
|
var pos0 = locToPos(startLoc);
|
||||||
|
var pos = locToPos(loc);
|
||||||
|
for (var spriter in editor.widthsX) {
|
||||||
|
if (pos.x >= editor.widthsX[spriter][1] && pos.x < editor.widthsX[spriter][2]) {
|
||||||
|
var ysize = spriter.endsWith('48') ? 48 : 32;
|
||||||
|
loc.ysize = ysize;
|
||||||
|
pos.images = editor.widthsX[spriter][0];
|
||||||
|
pos.y = ~~(loc.y / loc.ysize);
|
||||||
|
if (!editor.uivalues.folded && core.tilesets.indexOf(pos.images) == -1) pos.x = editor.widthsX[spriter][1];
|
||||||
|
var autotiles = core.material.images['autotile'];
|
||||||
|
if (pos.images == 'autotile') {
|
||||||
|
var imNames = Object.keys(autotiles);
|
||||||
|
if (editor.uivalues.folded) {
|
||||||
|
pos.y = Math.min(pos.y, imNames.length - 1);
|
||||||
|
pos.images = imNames[pos.y];
|
||||||
|
} else {
|
||||||
|
if ((pos.y + 1) * ysize > editor.widthsX[spriter][3])
|
||||||
|
pos.y = ~~(editor.widthsX[spriter][3] / ysize) - 4;
|
||||||
|
else {
|
||||||
|
for (var i = 0; i < imNames.length; i++) {
|
||||||
|
if (pos.y >= 4 * i && pos.y < 4 * (i + 1)) {
|
||||||
|
pos.images = imNames[i];
|
||||||
|
pos.y = 4 * i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var height = editor.widthsX[spriter][3], col = height / ysize;
|
||||||
|
if (spriter == 'terrains') col += 2;
|
||||||
|
if (editor.uivalues.folded && core.tilesets.indexOf(pos.images) == -1) {
|
||||||
|
col = (pos.x == editor.widthsX[spriter][2] - 1) ? ((col - 1) % editor.uivalues.foldPerCol + 1) : editor.uivalues.foldPerCol;
|
||||||
|
}
|
||||||
|
pos.y = Math.min(pos.y, col - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectBox.isSelected(true);
|
||||||
|
// console.log(pos,core.material.images[pos.images].height)
|
||||||
|
editor.dom.dataSelection.style.left = pos.x * 32 + 'px';
|
||||||
|
editor.dom.dataSelection.style.top = pos.y * ysize + 'px';
|
||||||
|
editor.dom.dataSelection.style.height = ysize - 6 + 'px';
|
||||||
|
editor.dom.dataSelection.style.width = 32 - 6 + 'px';
|
||||||
|
|
||||||
|
if (pos.x == 0 && pos.y == 0) {
|
||||||
|
// editor.info={idnum:0, id:'empty','images':'清除块', 'y':0};
|
||||||
|
editor.info = 0;
|
||||||
|
} else if (pos.x == 0 && pos.y == 1) {
|
||||||
|
editor.info = editor.ids[editor.indexs[17]];
|
||||||
|
} else {
|
||||||
|
if (autotiles[pos.images]) editor.info = { 'images': pos.images, 'y': 0 };
|
||||||
|
else if (core.tilesets.indexOf(pos.images) != -1) editor.info = { 'images': pos.images, 'y': pos.y, 'x': pos.x - editor.widthsX[spriter][1] };
|
||||||
|
else {
|
||||||
|
var y = pos.y;
|
||||||
|
if (editor.uivalues.folded) {
|
||||||
|
y += editor.uivalues.foldPerCol * (pos.x - editor.widthsX[spriter][1]);
|
||||||
|
}
|
||||||
|
if (pos.images == 'terrains') y -= 2;
|
||||||
|
editor.info = { 'images': pos.images, 'y': y }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var idindex = 0; idindex < editor.ids.length; idindex++) {
|
||||||
|
if ((core.tilesets.indexOf(pos.images) != -1 && editor.info.images == editor.ids[idindex].images
|
||||||
|
&& editor.info.y == editor.ids[idindex].y && editor.info.x == editor.ids[idindex].x)
|
||||||
|
|| (Object.prototype.hasOwnProperty.call(autotiles, pos.images) && editor.info.images == editor.ids[idindex].id
|
||||||
|
&& editor.info.y == editor.ids[idindex].y)
|
||||||
|
|| (core.tilesets.indexOf(pos.images) == -1 && editor.info.images == editor.ids[idindex].images
|
||||||
|
&& editor.info.y == editor.ids[idindex].y)
|
||||||
|
) {
|
||||||
|
|
||||||
|
editor.info = editor.ids[idindex];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor.info.isTile && (editor.isMobile || e.button == 2)) { //这段改一改之类的应该能给手机用,就不删了
|
||||||
|
var v = prompt("请输入该额外素材区域绑定宽高,以逗号分隔", "1,1");
|
||||||
|
if (v != null && /^\d+,\d+$/.test(v)) {
|
||||||
|
v = v.split(",");
|
||||||
|
var x = parseInt(v[0]), y = parseInt(v[1]);
|
||||||
|
var widthX = editor.widthsX[editor.info.images];
|
||||||
|
if (x <= 0 || y <= 0 || editor.info.x + x > widthX[2] - widthX[1] || 32*(editor.info.y + y) > widthX[3]) {
|
||||||
|
alert("不合法的输入范围,已经越界");
|
||||||
|
} else {
|
||||||
|
editor.uivalues.tileSize = [x, y];
|
||||||
|
editor.dom.dataSelection.style.left = pos.x * 32 + 'px';
|
||||||
|
editor.dom.dataSelection.style.top = pos.y * ysize + 'px';
|
||||||
|
editor.dom.dataSelection.style.height = ysize*y - 6 + 'px';
|
||||||
|
editor.dom.dataSelection.style.width = 32*x - 6 + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (editor.info.isTile && !editor.isMobile && e.button != 2) { //左键拖拽框选
|
||||||
|
|
||||||
|
var x = pos.x-pos0.x+1, y = pos.y-pos0.y+1;
|
||||||
|
var widthX = editor.widthsX[editor.info.images];
|
||||||
|
// 懒得仔细处理了, 只允许左上往右下拉
|
||||||
|
if (x <= 0 || y <= 0 || pos0.x < widthX[1]){
|
||||||
|
|
||||||
|
} else {
|
||||||
|
editor.info = editor.ids[idindex-(x-1)-(y-1)*(widthX[2]-widthX[1])];
|
||||||
|
editor.uivalues.tileSize = [x, y];
|
||||||
|
editor.dom.dataSelection.style.left = pos0.x * 32 + 'px';
|
||||||
|
editor.dom.dataSelection.style.top = pos0.y * ysize + 'px';
|
||||||
|
editor.dom.dataSelection.style.height = ysize*y - 6 + 'px';
|
||||||
|
editor.dom.dataSelection.style.width = 32*x - 6 + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
editor.uifunctions.showBlockInfo(JSON.parse(JSON.stringify(editor.info)));
|
||||||
|
editor_mode.onmode('nextChange');
|
||||||
|
editor_mode.onmode('enemyitem');
|
||||||
|
editor.updateLastUsedMap();
|
||||||
|
//editor_mode.enemyitem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
371
editor_mode.js
Normal file
371
editor_mode.js
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
editor_mode = function (editor) {
|
||||||
|
var core = editor.core;
|
||||||
|
|
||||||
|
function editor_mode() {
|
||||||
|
this.ids = {
|
||||||
|
'loc': 'left2',
|
||||||
|
'enemyitem': 'left3',
|
||||||
|
'floor': 'left4',
|
||||||
|
'tower': 'left5',
|
||||||
|
'functions': 'left8',
|
||||||
|
|
||||||
|
'map': 'left',
|
||||||
|
'appendpic': 'left1',
|
||||||
|
|
||||||
|
'commonevent': 'left9',
|
||||||
|
'plugins': 'left10',
|
||||||
|
}
|
||||||
|
this._ids = {}
|
||||||
|
this.dom = {}
|
||||||
|
this.actionList = [];
|
||||||
|
this.mode = 'tower'; // 初始默认显示全塔属性
|
||||||
|
this.info = {};
|
||||||
|
this.appendPic = {};
|
||||||
|
this.doubleClickMode = 'change';
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.init = function (callback) {
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.init_dom_ids = function (callback) {
|
||||||
|
|
||||||
|
Object.keys(editor_mode.ids).forEach(function (v) {
|
||||||
|
editor_mode.dom[v] = document.getElementById(editor_mode.ids[v]);
|
||||||
|
editor_mode._ids[editor_mode.ids[v]] = v;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.indent = function (field) {
|
||||||
|
var num = '\t';
|
||||||
|
if (field.indexOf("['main']") === 0) return 0;
|
||||||
|
if (field === "['special']") return 0;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.addAction = function (action) {
|
||||||
|
editor_mode.actionList.push(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.doActionList = function (mode, actionList, callback) {
|
||||||
|
if (actionList.length == 0) return;
|
||||||
|
printf('修改中...');
|
||||||
|
var cb = function (objs_) {
|
||||||
|
if (objs_.slice(-1)[0] != null) {
|
||||||
|
printe(objs_.slice(-1)[0]);
|
||||||
|
throw (objs_.slice(-1)[0])
|
||||||
|
}
|
||||||
|
;
|
||||||
|
var str = '修改成功!';
|
||||||
|
if (data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.firstData.name == 'template')
|
||||||
|
str += '<br/>请注意:全塔属性的name尚未修改,请及时予以设置。';
|
||||||
|
if (mode == 'enemyitem') {
|
||||||
|
if (editor.info && editor.info.idnum) {
|
||||||
|
var block = editor.core.maps.blocksInfo[editor.info.idnum];
|
||||||
|
if (block.doorInfo != null && block.doorInfo.keys != null && Object.keys(block.doorInfo.keys).length > 0
|
||||||
|
&& block.trigger != 'openDoor') {
|
||||||
|
str += "<br/>你修改了门信息,但触发器未改成openDoor,请修改否则无法撞击开门。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (editor_mode.info.images == 'enemys' || editor_mode.info.images == 'enemy48') {
|
||||||
|
if (core.getFaceDownId(editor_mode.info.id) != editor_mode.info.id) {
|
||||||
|
str += "<br/>绑定行走图朝向后只需要对应设置朝下怪物的属性,会自动同步而无需修改其他朝向的属性。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf(str);
|
||||||
|
if (callback) callback();
|
||||||
|
}
|
||||||
|
switch (mode) {
|
||||||
|
case 'loc':
|
||||||
|
editor.file.editLoc(editor_mode.pos.x, editor_mode.pos.y, actionList, function (objs_) {
|
||||||
|
cb(objs_);
|
||||||
|
editor.drawPosSelection();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'enemyitem':
|
||||||
|
if (editor_mode.info.images == 'enemys' || editor_mode.info.images == 'enemy48') {
|
||||||
|
editor.file.editEnemy(editor_mode.info.id, actionList, cb);
|
||||||
|
} else if (editor_mode.info.images == 'items') {
|
||||||
|
editor.file.editItem(editor_mode.info.id, actionList, cb);
|
||||||
|
} else {
|
||||||
|
editor.file.editMapBlocksInfo(editor_mode.info.idnum, actionList, cb);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'floor':
|
||||||
|
editor.file.editFloor(actionList, cb);
|
||||||
|
break;
|
||||||
|
case 'tower':
|
||||||
|
editor.file.editTower(actionList, cb);
|
||||||
|
break;
|
||||||
|
case 'functions':
|
||||||
|
editor.file.editFunctions(actionList, cb);
|
||||||
|
break;
|
||||||
|
case 'commonevent':
|
||||||
|
editor.file.editCommonEvent(actionList, cb);
|
||||||
|
break;
|
||||||
|
case 'plugins':
|
||||||
|
editor.file.editPlugins(actionList, cb);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.onmode = function (mode, callback) {
|
||||||
|
if (editor_mode.mode != mode) {
|
||||||
|
if (mode === 'save') editor_mode.doActionList(editor_mode.mode, editor_mode.actionList, callback);
|
||||||
|
if (editor_mode.mode === 'nextChange' && mode) editor_mode.showMode(mode);
|
||||||
|
if (mode !== 'save') editor_mode.mode = mode;
|
||||||
|
editor_mode.actionList = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.showMode = function (mode) {
|
||||||
|
for (var name in this.dom) {
|
||||||
|
editor_mode.dom[name].style = 'z-index:-1;opacity: 0;';
|
||||||
|
}
|
||||||
|
editor_mode.dom[mode].style = '';
|
||||||
|
editor_mode.doubleClickMode = 'change';
|
||||||
|
// clear
|
||||||
|
editor.drawEventBlock();
|
||||||
|
if (editor_mode[mode]) editor_mode[mode]();
|
||||||
|
editor.dom.editModeSelect.value = mode;
|
||||||
|
if (!selectBox.isSelected()) editor.uifunctions.showTips();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.change = function (value) {
|
||||||
|
editor_mode.onmode('nextChange');
|
||||||
|
editor_mode.onmode(value);
|
||||||
|
if (editor.isMobile) editor.showdataarea(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
editor_mode.prototype.checkUnique = function (thiseval) {
|
||||||
|
if (!(thiseval instanceof Array)) return false;
|
||||||
|
var map = {};
|
||||||
|
for (var i = 0; i < thiseval.length; ++i) {
|
||||||
|
if (map[thiseval[i]]) {
|
||||||
|
alert("警告:存在重复定义!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
map[thiseval[i]] = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.checkFloorIds = function (thiseval) {
|
||||||
|
if (!editor_mode.checkUnique(thiseval)) return false;
|
||||||
|
var oldvalue = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main.floorIds;
|
||||||
|
fs.readdir('project/floors', function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
printe(err);
|
||||||
|
throw Error(err);
|
||||||
|
}
|
||||||
|
var newfiles = thiseval.map(function (v) { return v + '.js' });
|
||||||
|
var notExist = '';
|
||||||
|
for (var name, ii = 0; name = newfiles[ii]; ii++) {
|
||||||
|
if (data.indexOf(name) === -1) notExist = name;
|
||||||
|
}
|
||||||
|
if (notExist) {
|
||||||
|
var discard = confirm('文件' + notExist + '不存在, 保存会导致工程无法打开, 是否放弃更改');
|
||||||
|
if (discard) {
|
||||||
|
editor.file.editTower([['change', "['main']['floorIds']", oldvalue]], function (objs_) {//console.log(objs_);
|
||||||
|
if (objs_.slice(-1)[0] != null) {
|
||||||
|
printe(objs_.slice(-1)[0]);
|
||||||
|
throw (objs_.slice(-1)[0])
|
||||||
|
}
|
||||||
|
; printe('已放弃floorIds的修改,请F5进行刷新');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.checkImages = function (thiseval, directory) {
|
||||||
|
if (!directory) return true;
|
||||||
|
if (!editor_mode.checkUnique(thiseval)) return false;
|
||||||
|
fs.readdir(directory, function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
printe(err);
|
||||||
|
throw Error(err);
|
||||||
|
}
|
||||||
|
var notExist = null;
|
||||||
|
thiseval.map(function (v) {
|
||||||
|
var name = v.indexOf('.') < 0 ? (v+'.png') : v;
|
||||||
|
if (data.indexOf(name) < 0) notExist = name;
|
||||||
|
return name;
|
||||||
|
});
|
||||||
|
if (notExist) {
|
||||||
|
alert('警告!图片' + notExist + '不存在!保存可能导致工程无法打开,请及时修改!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.changeDoubleClickModeByButton = function (mode) {
|
||||||
|
({
|
||||||
|
delete: function () {
|
||||||
|
printf('下一次双击表格的项删除,切换下拉菜单可取消;编辑后需刷新浏览器生效。');
|
||||||
|
editor_mode.doubleClickMode = mode;
|
||||||
|
},
|
||||||
|
add: function () {
|
||||||
|
printf('下一次双击表格的项则在同级添加新项,切换下拉菜单可取消;编辑后需刷新浏览器生效。');
|
||||||
|
editor_mode.doubleClickMode = mode;
|
||||||
|
}
|
||||||
|
}[mode])();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
editor_mode.prototype.loc = function (callback) {
|
||||||
|
//editor.pos={x: 0, y: 0};
|
||||||
|
if (!core.isset(editor.pos)) return;
|
||||||
|
editor_mode.pos = editor.pos;
|
||||||
|
document.getElementById('pos_a6771a78_a099_417c_828f_0a24851ebfce').innerText = editor_mode.pos.x + ',' + editor_mode.pos.y;
|
||||||
|
|
||||||
|
var objs = [];
|
||||||
|
editor.file.editLoc(editor_mode.pos.x, editor_mode.pos.y, [], function (objs_) {
|
||||||
|
objs = objs_;
|
||||||
|
//console.log(objs_)
|
||||||
|
});
|
||||||
|
//只查询不修改时,内部实现不是异步的,所以可以这么写
|
||||||
|
var tableinfo = editor.table.objToTable(objs[0], objs[1]);
|
||||||
|
document.getElementById('table_3d846fc4_7644_44d1_aa04_433d266a73df').innerHTML = tableinfo.HTML;
|
||||||
|
tableinfo.listen(tableinfo.guids);
|
||||||
|
editor.drawPosSelection();
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.enemyitem = function (callback) {
|
||||||
|
//editor.info=editor.ids[editor.indexs[201]];
|
||||||
|
if (!core.isset(editor.info)) return;
|
||||||
|
|
||||||
|
if (Object.keys(editor.info).length !== 0 && editor.info.idnum != 17) editor_mode.info = editor.info;//避免editor.info被清空导致无法获得是物品还是怪物
|
||||||
|
|
||||||
|
if (!core.isset(editor_mode.info.id)) {
|
||||||
|
// document.getElementById('table_a3f03d4c_55b8_4ef6_b362_b345783acd72').innerHTML = '';
|
||||||
|
document.getElementById('newIdIdnum').style.display = 'block';
|
||||||
|
document.getElementById('enemyItemTable').style.display = 'none';
|
||||||
|
document.getElementById('changeId').style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('newIdIdnum').style.display = 'none';
|
||||||
|
document.getElementById('enemyItemTable').style.display = 'block';
|
||||||
|
document.getElementById('changeId').style.display = 'block';
|
||||||
|
|
||||||
|
var objs = [];
|
||||||
|
if (editor_mode.info.images == 'enemys' || editor_mode.info.images == 'enemy48') {
|
||||||
|
editor.file.editEnemy(editor_mode.info.id, [], function (objs_) {
|
||||||
|
objs = objs_;
|
||||||
|
//console.log(objs_)
|
||||||
|
});
|
||||||
|
} else if (editor_mode.info.images == 'items') {
|
||||||
|
editor.file.editItem(editor_mode.info.id, [], function (objs_) {
|
||||||
|
objs = objs_;
|
||||||
|
//console.log(objs_)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/* document.getElementById('table_a3f03d4c_55b8_4ef6_b362_b345783acd72').innerHTML='';
|
||||||
|
return; */
|
||||||
|
editor.file.editMapBlocksInfo(editor_mode.info.idnum, [], function (objs_) {
|
||||||
|
objs = objs_;
|
||||||
|
//console.log(objs_)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//只查询不修改时,内部实现不是异步的,所以可以这么写
|
||||||
|
var tableinfo = editor.table.objToTable(objs[0], objs[1]);
|
||||||
|
document.getElementById('table_a3f03d4c_55b8_4ef6_b362_b345783acd72').innerHTML = tableinfo.HTML;
|
||||||
|
tableinfo.listen(tableinfo.guids);
|
||||||
|
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.floor = function (callback) {
|
||||||
|
var objs = [];
|
||||||
|
editor.file.editFloor([], function (objs_) {
|
||||||
|
objs = objs_;
|
||||||
|
//console.log(objs_)
|
||||||
|
});
|
||||||
|
//只查询不修改时,内部实现不是异步的,所以可以这么写
|
||||||
|
var tableinfo = editor.table.objToTable(objs[0], objs[1]);
|
||||||
|
document.getElementById('table_4a3b1b09_b2fb_4bdf_b9ab_9f4cdac14c74').innerHTML = tableinfo.HTML;
|
||||||
|
tableinfo.listen(tableinfo.guids);
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.tower = function (callback) {
|
||||||
|
var objs = [];
|
||||||
|
editor.file.editTower([], function (objs_) {
|
||||||
|
objs = objs_;
|
||||||
|
//console.log(objs_)
|
||||||
|
});
|
||||||
|
//只查询不修改时,内部实现不是异步的,所以可以这么写
|
||||||
|
var tableinfo = editor.table.objToTable(objs[0], objs[1]);
|
||||||
|
document.getElementById('table_b6a03e4c_5968_4633_ac40_0dfdd2c9cde5').innerHTML = tableinfo.HTML;
|
||||||
|
tableinfo.listen(tableinfo.guids);
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.functions = function (callback) {
|
||||||
|
var objs = [];
|
||||||
|
editor.file.editFunctions([], function (objs_) {
|
||||||
|
objs = objs_;
|
||||||
|
//console.log(objs_)
|
||||||
|
});
|
||||||
|
//只查询不修改时,内部实现不是异步的,所以可以这么写
|
||||||
|
var tableinfo = editor.table.objToTable(objs[0], objs[1]);
|
||||||
|
document.getElementById('table_e260a2be_5690_476a_b04e_dacddede78b3').innerHTML = tableinfo.HTML;
|
||||||
|
tableinfo.listen(tableinfo.guids);
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.commonevent = function (callback) {
|
||||||
|
var objs = [];
|
||||||
|
editor.file.editCommonEvent([], function (objs_) {
|
||||||
|
objs = objs_;
|
||||||
|
//console.log(objs_)
|
||||||
|
});
|
||||||
|
//只查询不修改时,内部实现不是异步的,所以可以这么写
|
||||||
|
var tableinfo = editor.table.objToTable(objs[0], objs[1]);
|
||||||
|
document.getElementById('table_b7bf0124_99fd_4af8_ae2f_0017f04a7c7d').innerHTML = tableinfo.HTML;
|
||||||
|
tableinfo.listen(tableinfo.guids);
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.plugins = function (callback) {
|
||||||
|
var objs = [];
|
||||||
|
editor.file.editPlugins([], function (objs_) {
|
||||||
|
objs = objs_;
|
||||||
|
//console.log(objs_)
|
||||||
|
});
|
||||||
|
//只查询不修改时,内部实现不是异步的,所以可以这么写
|
||||||
|
var tableinfo = editor.table.objToTable(objs[0], objs[1]);
|
||||||
|
document.getElementById('table_e2c034ec_47c6_48ae_8db8_4f8f32fea2d6').innerHTML = tableinfo.HTML;
|
||||||
|
tableinfo.listen(tableinfo.guids);
|
||||||
|
if (Boolean(callback)) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* editor.dom.editModeSelect.onchange
|
||||||
|
*/
|
||||||
|
editor_mode.prototype.editModeSelect_onchange = function () {
|
||||||
|
editor_mode.change(editor.dom.editModeSelect.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_mode.prototype.listen = function (callback) {
|
||||||
|
// 移动至 editor_listen.js -> editor.constructor.prototype.mode_listen
|
||||||
|
}
|
||||||
|
|
||||||
|
var editor_mode = new editor_mode();
|
||||||
|
editor_mode.init_dom_ids();
|
||||||
|
|
||||||
|
return editor_mode;
|
||||||
|
}
|
||||||
|
//editor_mode = editor_mode(editor);
|
515
editor_multi.js
Normal file
515
editor_multi.js
Normal file
@ -0,0 +1,515 @@
|
|||||||
|
|
||||||
|
|
||||||
|
editor_multi = function () {
|
||||||
|
|
||||||
|
var editor_multi = {};
|
||||||
|
|
||||||
|
var extraKeys = {
|
||||||
|
"Ctrl-/": function (cm) { cm.toggleComment(); },
|
||||||
|
"Ctrl-B": function (cm) { ternServer.jumpToDef(cm); },
|
||||||
|
"Ctrl-Q": function (cm) { ternServer.rename(cm); },
|
||||||
|
"Cmd-F": CodeMirror.commands.findPersistent,
|
||||||
|
"Ctrl-F": CodeMirror.commands.findPersistent,
|
||||||
|
"Ctrl-R": CodeMirror.commands.replaceAll,
|
||||||
|
"Ctrl-D": function (cm) { cm.foldCode(cm.getCursor()); },
|
||||||
|
"Ctrl-O": function () { editor_multi.openUrl('/_docs/#/api'); },
|
||||||
|
"Ctrl-P": function () { editor_multi.openUrl('https://h5mota.com/plugins/'); }
|
||||||
|
};
|
||||||
|
|
||||||
|
var codeEditor = CodeMirror.fromTextArea(document.getElementById("multiLineCode"), {
|
||||||
|
lineNumbers: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
indentUnit: 4,
|
||||||
|
tabSize: 4,
|
||||||
|
indentWithTabs: true,
|
||||||
|
smartIndent: true,
|
||||||
|
mode: { name: "javascript", globalVars: true, localVars: true },
|
||||||
|
lineWrapping: true,
|
||||||
|
continueComments: "Enter",
|
||||||
|
gutters: ["CodeMirror-lint-markers", "CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||||
|
lint: true,
|
||||||
|
autocomplete: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
styleActiveLine: true,
|
||||||
|
extraKeys: extraKeys,
|
||||||
|
foldGutter: true,
|
||||||
|
inputStyle: "textarea",
|
||||||
|
highlightSelectionMatches: { showToken: /\w/, annotateScrollbar: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
var commandsName = {
|
||||||
|
'Ctrl-/': '注释当前选中行(Ctrl+/)',
|
||||||
|
'Ctrl-B': '跳转到定义(Ctrl+B)',
|
||||||
|
'Ctrl-Q': '重命名变量(Ctrl+Q)',
|
||||||
|
'Ctrl-F': '查找(Ctrl+F)',
|
||||||
|
'Ctrl-R': '全部替换(Ctrl+R)',
|
||||||
|
'Ctrl-D': '折叠或展开块(Ctrl+D)',
|
||||||
|
'Ctrl-O': '打开API列表(Ctrl+O)',
|
||||||
|
'Ctrl-P': '打开在线插件列表(Ctrl+P)'
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('codemirrorCommands').innerHTML =
|
||||||
|
"<option value='' selected>执行操作...</option>" +
|
||||||
|
Object.keys(commandsName).map(function (name) {
|
||||||
|
return "<option value='" + name + "'>" + commandsName[name] + "</option>"
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
var coredef = terndefs_f6783a0a_522d_417e_8407_94c67b692e50[2];
|
||||||
|
Object.keys(core.material.enemys).forEach(function (name) {
|
||||||
|
coredef.core.material.enemys[name] = {
|
||||||
|
"!type": "enemy",
|
||||||
|
"!doc": core.material.enemys[name].name || "怪物"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(core.material.bgms).forEach(function (name) {
|
||||||
|
coredef.core.material.bgms[name] = {
|
||||||
|
"!type": "audio",
|
||||||
|
"!doc": "背景音乐"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(core.material.sounds).forEach(function (name) {
|
||||||
|
coredef.core.material.sounds[name] = {
|
||||||
|
"!type": "audio",
|
||||||
|
"!doc": "音效"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(core.material.animates).forEach(function (name) {
|
||||||
|
coredef.core.material.animates[name] = {
|
||||||
|
"!type": "animate",
|
||||||
|
"!doc": "动画"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(core.material.images).forEach(function (name) {
|
||||||
|
if (core.material.images[name] instanceof Image) {
|
||||||
|
coredef.core.material.images[name] = {
|
||||||
|
"!type": "image",
|
||||||
|
"!doc": "系统图片"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
coredef.core.material.images[name] = {
|
||||||
|
"!doc": name == 'autotile' ? '自动元件' : name == 'tilesets' ? '额外素材' : name == 'images' ? '自定义图片' : '系统图片'
|
||||||
|
}
|
||||||
|
for (var v in core.material.images[name]) {
|
||||||
|
coredef.core.material.images[name][v] = {
|
||||||
|
"!type": "image",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
Object.keys(core.material.items).forEach(function (name) {
|
||||||
|
coredef.core.material.items[name] = {
|
||||||
|
"!type": "item",
|
||||||
|
"!doc": core.material.items[name].name || "道具"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a.enemys.getSpecials().forEach(function (one) {
|
||||||
|
var name = one[1];
|
||||||
|
if (name instanceof Function) name = name({});
|
||||||
|
coredef.core.enemys.hasSpecial["!doc"] += name + "(" + one[0] + "); ";
|
||||||
|
});
|
||||||
|
Object.keys(core.canvas).forEach(function (name) {
|
||||||
|
coredef.core.canvas[name] = {
|
||||||
|
"!type": "CanvasRenderingContext2D",
|
||||||
|
"!doc": "系统画布"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(core.status.maps).forEach(function (name) {
|
||||||
|
coredef.core.status.maps[name] = {
|
||||||
|
"!type": "floor",
|
||||||
|
"!doc": core.status.maps[name].title || ''
|
||||||
|
}
|
||||||
|
coredef.core.status.bgmaps[name] = {
|
||||||
|
"!type": "[[number]]",
|
||||||
|
"!doc": core.status.maps[name].title || ''
|
||||||
|
}
|
||||||
|
coredef.core.status.fgmaps[name] = {
|
||||||
|
"!type": "[[number]]",
|
||||||
|
"!doc": core.status.maps[name].title || ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(core.status.shops).forEach(function (id) {
|
||||||
|
coredef.core.status.shops[id] = {
|
||||||
|
"!doc": core.status.shops[id].textInList || "全局商店"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(core.status.textAttribute).forEach(function (id) {
|
||||||
|
coredef.core.status.textAttribute[id] = {};
|
||||||
|
});
|
||||||
|
// --- 转发函数
|
||||||
|
for (var name in coredef.core) {
|
||||||
|
if (typeof coredef.core[name] === 'object') {
|
||||||
|
for (var funcname in coredef.core[name]) {
|
||||||
|
var one = coredef.core[name][funcname] || {};
|
||||||
|
var type = one["!type"] || "";
|
||||||
|
if (type.startsWith("fn(")) {
|
||||||
|
var forwardname = (functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a[name] || {})[funcname] ? '脚本编辑' : name;
|
||||||
|
coredef.core[funcname] = {
|
||||||
|
"!type": one["!type"],
|
||||||
|
"!doc": one["!doc"] + "<br/>(转发到" + forwardname + "中)"
|
||||||
|
};
|
||||||
|
if (one["!url"]) coredef.core[funcname]["!url"] = one["!url"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var funcname in core[name]) {
|
||||||
|
if (!(core[name][funcname] instanceof Function) || funcname.charAt(0) == '_' || coredef.core[name][funcname]) continue;
|
||||||
|
var parameterInfo = /^\s*function\s*[\w_$]*\(([\w_,$\s]*)\)\s*\{/.exec(core[name][funcname].toString());
|
||||||
|
var parameters = (parameterInfo == null ? "" : parameterInfo[1])
|
||||||
|
.replace(/\s*/g, '').replace(/,/g, ', ').split(', ')
|
||||||
|
.filter(function (one) { return one.trim() != ''; })
|
||||||
|
.map(function (one) { return one.trim() + ': ?'; }).join(', ');
|
||||||
|
coredef.core[funcname] = coredef.core[name][funcname] = {
|
||||||
|
"!type": "fn(" + parameters + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(core.values).forEach(function (id) {
|
||||||
|
var one = data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc._data.values._data[id];
|
||||||
|
if (!one) return;
|
||||||
|
coredef.core.values[id] = {
|
||||||
|
"!type": "number",
|
||||||
|
"!doc": one._data,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(core.flags).forEach(function (id) {
|
||||||
|
var one = data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc._data.flags._data[id];
|
||||||
|
if (!one) return;
|
||||||
|
coredef.core.flags[id] = {
|
||||||
|
"!type": id == 'statusBarItems' ? '[string]' : 'bool',
|
||||||
|
"!doc": one._data,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var ternServer = new CodeMirror.TernServer({
|
||||||
|
defs: terndefs_f6783a0a_522d_417e_8407_94c67b692e50,
|
||||||
|
plugins: {
|
||||||
|
doc_comment: true,
|
||||||
|
complete_strings: true,
|
||||||
|
},
|
||||||
|
useWorker: false
|
||||||
|
});
|
||||||
|
|
||||||
|
editor_multi.ternServer = ternServer;
|
||||||
|
editor_multi.codeEditor = codeEditor;
|
||||||
|
|
||||||
|
codeEditor.on("cursorActivity", function (cm) {
|
||||||
|
var cursor = cm.getCursor();
|
||||||
|
if (codeEditor.getOption("autocomplete") && !(cursor.line == 0 && cursor.ch == 0)) {
|
||||||
|
ternServer.updateArgHints(cm);
|
||||||
|
ternServer.showDocs(cm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var ctrlRelease = new Date();
|
||||||
|
codeEditor.on("keyup", function (cm, event) {
|
||||||
|
var date = new Date();
|
||||||
|
if (event.keyCode == 17 || event.keyCode == 91) { // ctrl, cmd
|
||||||
|
ctrlRelease = date;
|
||||||
|
}
|
||||||
|
else if (codeEditor.getOption("autocomplete") && !event.ctrlKey && date - ctrlRelease >= 1000 && (
|
||||||
|
(event.keyCode >= 65 && event.keyCode <= 90) ||
|
||||||
|
(!event.shiftKey && event.keyCode == 190) || (event.shiftKey && event.keyCode == 189))) {
|
||||||
|
try {
|
||||||
|
ternServer.complete(cm);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
editor_multi.id = '';
|
||||||
|
editor_multi.isString = false;
|
||||||
|
editor_multi.lintAutocomplete = false;
|
||||||
|
|
||||||
|
var lastOffset = {};
|
||||||
|
|
||||||
|
editor_multi.show = function () {
|
||||||
|
if (typeof (selectBox) !== typeof (undefined)) selectBox.isSelected(false);
|
||||||
|
var valueNow = codeEditor.getValue();
|
||||||
|
//try{eval('function _asdygakufyg_() { return '+valueNow+'\n}');editor_multi.lintAutocomplete=true;}catch(ee){}
|
||||||
|
if (valueNow.slice(0, 8) === 'function') editor_multi.lintAutocomplete = true;
|
||||||
|
editor_multi.setLint();
|
||||||
|
document.getElementById('left7').style = '';
|
||||||
|
}
|
||||||
|
editor_multi.hide = function () {
|
||||||
|
document.getElementById('left7').style = 'z-index:-1;opacity: 0;';
|
||||||
|
}
|
||||||
|
editor_multi.setLint = function () {
|
||||||
|
if (editor_multi.lintAutocomplete) {
|
||||||
|
codeEditor.setOption("lint", {
|
||||||
|
options: {
|
||||||
|
esversion: 2021
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
codeEditor.setOption("lint", false);
|
||||||
|
}
|
||||||
|
codeEditor.setOption("autocomplete", editor_multi.lintAutocomplete);
|
||||||
|
document.getElementById("lintCheckbox").checked = editor_multi.lintAutocomplete;
|
||||||
|
}
|
||||||
|
editor_multi.toggerLint = function () {
|
||||||
|
editor_multi.lintAutocomplete = document.getElementById("lintCheckbox").checked;
|
||||||
|
editor_multi.setLint();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.indent = function (field) {
|
||||||
|
if (typeof (editor) !== typeof (undefined) && editor && editor.mode && editor.mode.indent) return editor.mode.indent(field);
|
||||||
|
return '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
var _format = function () {
|
||||||
|
if (!editor_multi.lintAutocomplete) return;
|
||||||
|
var offset = (codeEditor.getScrollInfo() || {}).top || 0;
|
||||||
|
_setValue(beautifier.js(codeEditor.getValue(), {
|
||||||
|
brace_style: "collapse-preserve-inline",
|
||||||
|
indent_with_tabs: true,
|
||||||
|
jslint_happy: true
|
||||||
|
}));
|
||||||
|
codeEditor.scrollTo(0, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
var _setValue = function (val) {
|
||||||
|
codeEditor.setValue(val || '');
|
||||||
|
ternServer.delDoc('doc');
|
||||||
|
ternServer.addDoc('doc', new CodeMirror.Doc(val || '', 'javascript'));
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.format = function () {
|
||||||
|
if (!editor_multi.lintAutocomplete) {
|
||||||
|
alert("只有代码才能进行格式化操作!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_format();
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.hasError = function () {
|
||||||
|
if (!editor_multi.lintAutocomplete) return false;
|
||||||
|
return JSHINT.errors.filter(function (e) {
|
||||||
|
return e.code.startsWith("E")
|
||||||
|
}).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _previewButton = document.getElementById('editor_multi_preview');
|
||||||
|
|
||||||
|
_previewButton.onclick = function () {
|
||||||
|
if (!editor_multi.preview) return;
|
||||||
|
_format();
|
||||||
|
if (editor_multi.hasError()) {
|
||||||
|
alert("当前好像存在严重的语法错误,请处理后再预览。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editor.uievent.previewEditorMulti(editor_multi.preview, codeEditor.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.import = function (id_, args) {
|
||||||
|
var thisTr = document.getElementById(id_);
|
||||||
|
if (!thisTr) return false;
|
||||||
|
var input = thisTr.children[2].children[0].children[0];
|
||||||
|
var field = thisTr.children[0].getAttribute('title');
|
||||||
|
var comment = thisTr.children[1].getAttribute('title');
|
||||||
|
if (!input.type || input.type !== 'textarea') return false;
|
||||||
|
editor_multi.id = id_;
|
||||||
|
editor_multi.isString = false;
|
||||||
|
editor_multi.lintAutocomplete = false;
|
||||||
|
editor_multi.preview = args.preview;
|
||||||
|
_previewButton.style.display = editor_multi.preview ? 'inline' : 'none';
|
||||||
|
if (args.lint === true) editor_multi.lintAutocomplete = true;
|
||||||
|
if ((!input.value || input.value == 'null') && args.template)
|
||||||
|
input.value = '"' + args.template + '"';
|
||||||
|
if ((!input.value || input.value == 'null') && editor_mode.mode == 'plugins')
|
||||||
|
input.value = '"function () {\\n\\t// 在此增加新插件\\n\\t\\n}"';
|
||||||
|
// if ((!input.value || input.value == 'null') && args)
|
||||||
|
if (input.value.slice(0, 1) === '"' || args.string) {
|
||||||
|
editor_multi.isString = true;
|
||||||
|
_setValue(JSON.parse(input.value) || '');
|
||||||
|
} else {
|
||||||
|
var num = editor_multi.indent(field);
|
||||||
|
eval('var tobj=' + (input.value || 'null'));
|
||||||
|
var tmap = {};
|
||||||
|
var tstr = JSON.stringify(tobj, function (k, v) {
|
||||||
|
if (typeof (v) === typeof ('') && v.slice(0, 8) === 'function') {
|
||||||
|
var id_ = editor.util.guid();
|
||||||
|
tmap[id_] = v.toString();
|
||||||
|
return id_;
|
||||||
|
} else return v
|
||||||
|
}, num);
|
||||||
|
for (var id_ in tmap) {
|
||||||
|
tstr = tstr.replace('"' + id_ + '"', tmap[id_])
|
||||||
|
}
|
||||||
|
_setValue(tstr || '');
|
||||||
|
}
|
||||||
|
editor_multi.show();
|
||||||
|
codeEditor.scrollTo(0, lastOffset[editor_multi.id] || 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.cancel = function () {
|
||||||
|
if (editor_multi.id && editor_multi.id != 'callFromBlockly' && editor_multi.id != 'importFile') {
|
||||||
|
lastOffset[editor_multi.id] = (codeEditor.getScrollInfo() || {}).top;
|
||||||
|
}
|
||||||
|
editor_multi.hide();
|
||||||
|
editor_multi.id = '';
|
||||||
|
multiLineArgs = [null, null, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.confirm = function (keep) {
|
||||||
|
if (editor_multi.hasError()) {
|
||||||
|
alert("当前好像存在严重的语法错误,请处理后再保存。\n严重的语法错误可能会导致整个编辑器的崩溃。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!editor_multi.id) {
|
||||||
|
editor_multi.id = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor_multi.id === 'callFromBlockly') {
|
||||||
|
// ----- 自动格式化
|
||||||
|
_format();
|
||||||
|
editor_multi.multiLineDone(keep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor_multi.id === 'importFile') {
|
||||||
|
_format();
|
||||||
|
editor_multi.writeFileDone(keep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var setvalue = function (value) {
|
||||||
|
var thisTr = document.getElementById(editor_multi.id);
|
||||||
|
var input = thisTr.children[2].children[0].children[0];
|
||||||
|
if (editor_multi.isString) {
|
||||||
|
input.value = JSON.stringify(value);
|
||||||
|
} else {
|
||||||
|
eval('var tobj=' + (value || 'null'));
|
||||||
|
var tmap = {};
|
||||||
|
var tstr = JSON.stringify(tobj, function (k, v) {
|
||||||
|
if (v instanceof Function) {
|
||||||
|
var id_ = editor.util.guid();
|
||||||
|
tmap[id_] = v.toString();
|
||||||
|
return id_;
|
||||||
|
} else return v
|
||||||
|
}, 4);
|
||||||
|
for (var id_ in tmap) {
|
||||||
|
tstr = tstr.replace('"' + id_ + '"', JSON.stringify(tmap[id_]))
|
||||||
|
}
|
||||||
|
input.value = tstr;
|
||||||
|
}
|
||||||
|
if (!keep) {
|
||||||
|
editor_multi.id = '';
|
||||||
|
editor_multi.hide();
|
||||||
|
} else {
|
||||||
|
alert('写入成功!');
|
||||||
|
}
|
||||||
|
input.onchange();
|
||||||
|
}
|
||||||
|
lastOffset[editor_multi.id] = (codeEditor.getScrollInfo() || {}).top;
|
||||||
|
// ----- 自动格式化
|
||||||
|
_format();
|
||||||
|
setvalue(codeEditor.getValue() || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.doCommand = function (select) {
|
||||||
|
var value = select.value;
|
||||||
|
select.selectedIndex = 0;
|
||||||
|
if (extraKeys[value]) {
|
||||||
|
extraKeys[value](codeEditor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.openUrl = function (url) {
|
||||||
|
if (editor.isMobile && !confirm('你确定要离开本页面么?')) return;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiLineArgs = [null, null, null];
|
||||||
|
editor_multi.multiLineEdit = function (value, b, f, args, callback) {
|
||||||
|
editor_multi.id = 'callFromBlockly';
|
||||||
|
_setValue(value.split('\\n').join('\n') || '');
|
||||||
|
multiLineArgs[0] = b;
|
||||||
|
multiLineArgs[1] = f;
|
||||||
|
multiLineArgs[2] = callback;
|
||||||
|
editor_multi.lintAutocomplete = Boolean(args.lint);
|
||||||
|
editor_multi.show();
|
||||||
|
}
|
||||||
|
editor_multi.multiLineDone = function (keep) {
|
||||||
|
if (!multiLineArgs[0] || !multiLineArgs[1] || !multiLineArgs[2]) return;
|
||||||
|
var newvalue = codeEditor.getValue() || '';
|
||||||
|
multiLineArgs[2](newvalue, multiLineArgs[0], multiLineArgs[1])
|
||||||
|
if (!keep) {
|
||||||
|
editor_multi.id = '';
|
||||||
|
editor_multi.hide();
|
||||||
|
} else {
|
||||||
|
alert('写入成功!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _fileValues = ['']
|
||||||
|
editor_multi.importFile = function (filename) {
|
||||||
|
editor_multi.id = 'importFile'
|
||||||
|
_fileValues[0] = filename
|
||||||
|
_setValue('loading')
|
||||||
|
editor_multi.show();
|
||||||
|
fs.readFile(filename, 'base64', function (e, d) {
|
||||||
|
if (e) {
|
||||||
|
_setValue('加载文件失败:\n' + e)
|
||||||
|
editor_multi.id = ''
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var str = editor.util.decode64(d)
|
||||||
|
_setValue(str)
|
||||||
|
_fileValues[1] = str
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.writeFileDone = function (keep) {
|
||||||
|
fs.writeFile(_fileValues[0], editor.util.encode64(codeEditor.getValue() || ''), 'base64', function (err, data) {
|
||||||
|
if (err) printe('文件写入失败,请手动粘贴至' + _fileValues[0] + '\n' + err);
|
||||||
|
else {
|
||||||
|
if (!keep) {
|
||||||
|
editor_multi.id = '';
|
||||||
|
editor_multi.hide();
|
||||||
|
} else {
|
||||||
|
alert('写入成功!');
|
||||||
|
}
|
||||||
|
printf(_fileValues[0] + " 写入成功,F5刷新后生效");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_multi.editCommentJs = function (mod) {
|
||||||
|
var dict = {
|
||||||
|
loc: '_server/table/comment.js',
|
||||||
|
enemyitem: '_server/table/comment.js',
|
||||||
|
floor: '_server/table/comment.js',
|
||||||
|
tower: '_server/table/data.comment.js',
|
||||||
|
functions: '_server/table/functions.comment.js',
|
||||||
|
commonevent: '_server/table/events.comment.js',
|
||||||
|
plugins: '_server/table/plugins.comment.js',
|
||||||
|
}
|
||||||
|
editor_multi.lintAutocomplete = true
|
||||||
|
editor_multi.setLint()
|
||||||
|
editor_multi.importFile(dict[mod])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字体大小
|
||||||
|
{
|
||||||
|
const CONFIG_KEY = "editor_multi.fontSize";
|
||||||
|
let fontsize = editor.config.get(CONFIG_KEY, 14);
|
||||||
|
const input = document.getElementById("editor_multi_fontsize");
|
||||||
|
const check = document.getElementById("editor_multi_fontweight")
|
||||||
|
input.value = fontsize;
|
||||||
|
editor_multi.setFontSize = function () {
|
||||||
|
const value = Number(input.value);
|
||||||
|
editor.config.set(CONFIG_KEY, value);
|
||||||
|
const ele = codeEditor.getWrapperElement()
|
||||||
|
ele.style.fontSize = `${value}px`;
|
||||||
|
ele.style.fontWeight = `${check.checked ? 'bold' : 'normal'}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return editor_multi;
|
||||||
|
}
|
||||||
|
//editor_multi=editor_multi();
|
602
editor_table.js
Normal file
602
editor_table.js
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
editor_table_wrapper = function (editor) {
|
||||||
|
|
||||||
|
editor_table = function () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// HTML模板
|
||||||
|
|
||||||
|
editor_table.prototype.select = function (value, values) {
|
||||||
|
if (values.indexOf(value) < 0) values = [value].concat(values);
|
||||||
|
var content = values.map(function (v) {
|
||||||
|
return editor.table.option(v, v == value)
|
||||||
|
}).join('')
|
||||||
|
return /* html */`<select>\n${content}</select>\n`
|
||||||
|
}
|
||||||
|
editor_table.prototype.option = function (value, selected) {
|
||||||
|
return /* html */`<option value='${JSON.stringify(value)}' ${selected ? 'selected' : ''}>${JSON.stringify(value)}</option>\n`
|
||||||
|
}
|
||||||
|
editor_table.prototype.text = function (value) {
|
||||||
|
return /* html */`<input type='text' spellcheck='false' value='${JSON.stringify(value)}'/>\n`
|
||||||
|
}
|
||||||
|
editor_table.prototype.checkbox = function (value) {
|
||||||
|
return /* html */`<input type='checkbox' class='checkbox' ${(value ? 'checked ' : '')}/>\n`
|
||||||
|
}
|
||||||
|
editor_table.prototype.textarea = function (value, indent, disable) {
|
||||||
|
return /* html */`<textarea spellcheck='false' ${disable ? 'disabled readonly' : ''}>${JSON.stringify(value, null, indent || 0)}</textarea>\n`
|
||||||
|
}
|
||||||
|
editor_table.prototype.checkboxSet = function (value, keys, prefixStrings) {
|
||||||
|
if (value == null) value = [];
|
||||||
|
if (!(value instanceof Array)) {
|
||||||
|
if (value == 0) value = [];
|
||||||
|
else value = [value];
|
||||||
|
}
|
||||||
|
keys=Array.from(keys)
|
||||||
|
prefixStrings=Array.from(prefixStrings)
|
||||||
|
for (var index = 0; index < value.length; index++) {
|
||||||
|
if (keys.indexOf(value[index])==-1) {
|
||||||
|
keys.push(value[index])
|
||||||
|
prefixStrings.push('<br>'+value[index]+': ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var content=[]
|
||||||
|
for (var index = 0; index < keys.length; index++) {
|
||||||
|
content.push(editor.table.checkboxSetMember(value.indexOf(keys[index])!=-1,keys[index],prefixStrings[index]))
|
||||||
|
}
|
||||||
|
return /* html */`<div class='checkboxSet'>${content.join('')}</div>\n`;
|
||||||
|
}
|
||||||
|
editor_table.prototype.checkboxSetMember = function (value,key,prefixString) {
|
||||||
|
return /* html */`${prefixString}<input key='${key}' ctype='${typeof key}' type='checkbox' class='checkboxSetMember' onchange='editor.table.checkboxSetMemberOnchange(this)' ${(value ? 'checked ' : '')}/>\n`;
|
||||||
|
}
|
||||||
|
editor_table.prototype.editGrid = function (showComment, type) {
|
||||||
|
var list = [];
|
||||||
|
if (showComment) list.push("<button onclick='editor.table.onCommentBtnClick(this)'>注释</button>");
|
||||||
|
if (type != 'select' && type != 'checkbox' && type != 'checkboxSet' && type != 'popCheckboxSet' && type != 'disable')
|
||||||
|
list.push("<button onclick='editor.table.onEditBtnClick(this)' class='editorTableEditBtn'>编辑</button>");
|
||||||
|
if (type == 'popCheckboxSet')
|
||||||
|
list.push("<button onclick='editor.table.onEditBtnClick(this)' class='editorTableEditBtn'>多选框编辑</button>");
|
||||||
|
if (type == 'disable') list.push("<button onclick='editor.table.onCopyBtnClick(this)'>复制</button>");
|
||||||
|
return list.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_table.prototype.title = function () {
|
||||||
|
return /* html */`\n<tr><td>条目</td><td>注释</td><td>值</td><td>操作</td></tr>\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_table.prototype.gap = function (field) {
|
||||||
|
var tokenlist = field.slice(2, -2).split("']['");
|
||||||
|
var rule = tokenlist.join("-");
|
||||||
|
tokenlist.pop();
|
||||||
|
var self = tokenlist.join("-");
|
||||||
|
var status = !!tokenPool[rule];
|
||||||
|
return /* html */`<tr data-gap="${rule}" data-field="${self}">
|
||||||
|
<td>----</td>
|
||||||
|
<td>----</td>
|
||||||
|
<td>${field}</td>
|
||||||
|
<td><button class='editorTableFoldBtn' onclick='editor.table.onFoldBtnClick(this)' data-fold="${ status ? "true" : "false" }">${ status ? "展开" : "折叠" }</button></td>
|
||||||
|
</tr>\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_table.prototype.tr = function (guid, field, shortField, commentHTMLescape, cobjstr, shortComment, tdstr, type) {
|
||||||
|
return /* html */`<tr id="${guid}" data-field="${field.slice(2, -2).split("']['").join("-")}">
|
||||||
|
<td title="${field}">${shortField}</td>
|
||||||
|
<td title="${commentHTMLescape}" cobj="${cobjstr}">${shortComment || commentHTMLescape}</td>
|
||||||
|
<td><div class="etableInputDiv ${type}">${tdstr}</div></td>
|
||||||
|
<td>${editor.table.editGrid(shortComment, type)}</td>
|
||||||
|
</tr>\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checkboxset中checkbox的onchange
|
||||||
|
* 这个函数本质是模板editor_table.prototype.checkboxSetMember的一部分
|
||||||
|
* 故放在HTML模板分类下
|
||||||
|
*/
|
||||||
|
editor_table.prototype.checkboxSetMemberOnchange = function (onemember) {
|
||||||
|
var thisset=onemember.parentNode
|
||||||
|
var inputs=thisset.children
|
||||||
|
var value=[]
|
||||||
|
for (var i in inputs) {
|
||||||
|
if (inputs[i].nodeName == 'INPUT') {
|
||||||
|
if (inputs[i].checked) {
|
||||||
|
var one = inputs[i].getAttribute('key');
|
||||||
|
if (inputs[i].getAttribute('ctype') == 'number') one = parseFloat(one);
|
||||||
|
value.push(one);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thiseval = value;
|
||||||
|
// if (value.length == 0) thiseval = null;
|
||||||
|
thisset.value=JSON.stringify(thiseval)
|
||||||
|
thisset.onchange()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 表格生成的控制
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注释对象的默认值
|
||||||
|
*/
|
||||||
|
editor_table.prototype.defaultcobj = {
|
||||||
|
// 默认是文本域
|
||||||
|
_type: 'textarea',
|
||||||
|
_data: '',
|
||||||
|
_string: function (args) {//object~[field,cfield,vobj,cobj]
|
||||||
|
var thiseval = args.vobj;
|
||||||
|
return (typeof (thiseval) === typeof ('')) && thiseval[0] === '"';
|
||||||
|
},
|
||||||
|
// 默认情况下 非对象和数组的视为叶节点
|
||||||
|
_leaf: function (args) {//object~[field,cfield,vobj,cobj]
|
||||||
|
var thiseval = args.vobj;
|
||||||
|
if (thiseval == null || thiseval == undefined) return true;//null,undefined
|
||||||
|
if (typeof (thiseval) === typeof ('')) return true;//字符串
|
||||||
|
if (Object.keys(thiseval).length === 0) return true;//数字,true,false,空数组,空对象
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把来自数据文件的obj和来自*comment.js的commentObj组装成表格
|
||||||
|
* commentObj在无视['_data']的意义下与obj同形
|
||||||
|
* 即: commentObj['_data']['a']['_data']['b'] 与 obj['a']['b'] 是对应的
|
||||||
|
* 在此意义下, 两者的结构是一致的
|
||||||
|
* 在commentObj没有被定义的obj的分支, 会取defaultcobj作为默认值
|
||||||
|
* 因此在深度优先遍历时,维护
|
||||||
|
* field="['a']['b']"
|
||||||
|
* cfield="['_data']['a']['_data']['b']"
|
||||||
|
* vobj=obj['a']['b']
|
||||||
|
* cobj=commentObj['_data']['a']['_data']['b']
|
||||||
|
* cobj
|
||||||
|
* cobj = Object.assign({}, defaultcobj, pcobj['_data'][ii])
|
||||||
|
* 每一项若未定义,就从defaultcobj中取
|
||||||
|
* 当其是函数不是具体值时,把args = {field: field, cfield: cfield, vobj: vobj, cobj: cobj}代入算出该值
|
||||||
|
* 得到的叶节点的<tr>结构如下
|
||||||
|
* tr>td[title=field]
|
||||||
|
* >td[title=comment,cobj=cobj:json]
|
||||||
|
* >td>div>input[value=thiseval]
|
||||||
|
* 返回结果
|
||||||
|
* 返回一个对象, 假设被命名为tableinfo
|
||||||
|
* 在把一个 table 的 innerHTML 赋值为 tableinfo.HTML 后
|
||||||
|
* 再调 tableinfo.listen(tableinfo.guids) 进行绑定事件
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} commentObj
|
||||||
|
* @returns {{"HTML":String,"guids":String[],"listen":Function}}
|
||||||
|
*/
|
||||||
|
editor_table.prototype.objToTable = function (obj, commentObj) {
|
||||||
|
// 表格抬头
|
||||||
|
var outstr = [editor.table.title()];
|
||||||
|
var guids = [];
|
||||||
|
var defaultcobj = this.defaultcobj
|
||||||
|
/**
|
||||||
|
* 深度优先遍历, p*即为父节点的四个属性
|
||||||
|
* @param {String} pfield
|
||||||
|
* @param {String} pcfield
|
||||||
|
* @param {Object} pvobj
|
||||||
|
* @param {Object} pcobj
|
||||||
|
*/
|
||||||
|
var recursionParse = function (pfield, pcfield, pvobj, pcobj) {
|
||||||
|
var keysForTableOrder = {};
|
||||||
|
var voidMark = {};
|
||||||
|
// 1. 按照pcobj排序生成
|
||||||
|
if (pcobj && pcobj['_data']) {
|
||||||
|
for (var ii in pcobj['_data']) keysForTableOrder[ii] = voidMark;
|
||||||
|
}
|
||||||
|
// 2. 对每个pvobj且不在pcobj的,再添加到最后
|
||||||
|
keysForTableOrder = Object.assign(keysForTableOrder, pvobj)
|
||||||
|
for (var ii in keysForTableOrder) {
|
||||||
|
// 3. 对于pcobj有但是pvobj中没有的, 弹出提示, (正常情况下editor_file会补全成null)
|
||||||
|
// 事实上能执行到这一步工程没崩掉打不开,就继续吧..
|
||||||
|
if (keysForTableOrder[ii] === voidMark) {
|
||||||
|
if (typeof id_815975ad_ee6f_4684_aac7_397b7e392702 === "undefined") {
|
||||||
|
// alert('comment和data不匹配,请在群 HTML5造塔技术交流群 959329661 内反馈')
|
||||||
|
console.error('comment和data不匹配,请在群 HTML5造塔技术交流群 959329661 内反馈')
|
||||||
|
id_815975ad_ee6f_4684_aac7_397b7e392702 = 1;
|
||||||
|
}
|
||||||
|
pvobj[ii] = null;
|
||||||
|
}
|
||||||
|
var field = pfield + "['" + ii + "']";
|
||||||
|
var cfield = pcfield + "['_data']['" + ii + "']";
|
||||||
|
var vobj = pvobj[ii];
|
||||||
|
var cobj = null;
|
||||||
|
if (pcobj && pcobj['_data'] && pcobj['_data'][ii]) {
|
||||||
|
// cobj存在时直接取
|
||||||
|
cobj = Object.assign({}, defaultcobj, pcobj['_data'][ii]);
|
||||||
|
} else {
|
||||||
|
// 当其函数时代入参数算出cobj, 不存在时只取defaultcobj
|
||||||
|
if (pcobj && (pcobj['_data'] instanceof Function)) cobj = Object.assign({}, defaultcobj, pcobj['_data'](ii));
|
||||||
|
else cobj = Object.assign({}, defaultcobj);
|
||||||
|
}
|
||||||
|
var args = { field: field, cfield: cfield, vobj: vobj, cobj: cobj }
|
||||||
|
// 当cobj的参数为函数时,代入args算出值
|
||||||
|
for (var key in cobj) {
|
||||||
|
if (key === '_data') continue;
|
||||||
|
if (cobj[key] instanceof Function) cobj[key] = cobj[key](args);
|
||||||
|
}
|
||||||
|
pvobj[ii] = vobj = args.vobj;
|
||||||
|
// 标记为_hide的属性不展示
|
||||||
|
if (cobj._hide) continue;
|
||||||
|
if (!cobj._leaf) {
|
||||||
|
// 不是叶节点时, 插入展开的标记并继续遍历, 此处可以改成按钮用来添加新项或折叠等
|
||||||
|
outstr.push(editor.table.gap(field));
|
||||||
|
recursionParse(field, cfield, vobj, cobj);
|
||||||
|
} else {
|
||||||
|
// 是叶节点时, 调objToTr_渲染<tr>
|
||||||
|
var leafnode = editor.table.objToTr(obj, commentObj, field, cfield, vobj, cobj);
|
||||||
|
outstr.push(leafnode[0]);
|
||||||
|
guids.push(leafnode[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 开始遍历
|
||||||
|
recursionParse("", "", obj, commentObj);
|
||||||
|
|
||||||
|
var listen = function (guids) {
|
||||||
|
// 每个叶节点的事件绑定
|
||||||
|
var tableid = editor.util.guid();
|
||||||
|
editor.mode.currentTable=tableid;
|
||||||
|
guids.forEach(function (guid) {
|
||||||
|
editor.table.guidListen(guid, tableid, obj, commentObj)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { "HTML": outstr.join(''), "guids": guids, "listen": listen };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回叶节点<tr>形如
|
||||||
|
* tr>td[title=field]
|
||||||
|
* >td[title=comment,cobj=cobj:json]
|
||||||
|
* >td>div>input[value=thiseval]
|
||||||
|
* 参数意义在 objToTable 中已解释
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} commentObj
|
||||||
|
* @param {String} field
|
||||||
|
* @param {String} cfield
|
||||||
|
* @param {Object} vobj
|
||||||
|
* @param {Object} cobj
|
||||||
|
*/
|
||||||
|
editor_table.prototype.objToTr = function (obj, commentObj, field, cfield, vobj, cobj) {
|
||||||
|
var guid = editor.util.guid();
|
||||||
|
var thiseval = vobj;
|
||||||
|
var comment = String(cobj._data);
|
||||||
|
|
||||||
|
// var charlength = 15;
|
||||||
|
// "['a']['b']" => "b"
|
||||||
|
var shortField = field.split("']").slice(-2)[0].split("['").slice(-1)[0];
|
||||||
|
// 把长度超过 charlength 的字符改成 固定长度+...的形式
|
||||||
|
// shortField = (shortField.length < charlength ? shortField : shortField.slice(0, charlength) + '...');
|
||||||
|
|
||||||
|
// 完整的内容转义后供悬停查看
|
||||||
|
var commentHTMLescape = editor.util.HTMLescape(comment);
|
||||||
|
// 把长度超过 charlength 的字符改成 固定长度+...的形式
|
||||||
|
// var shortCommentHTMLescape = (comment.length < charlength ? commentHTMLescape : editor.util.HTMLescape(comment.slice(0, charlength)) + '...');
|
||||||
|
|
||||||
|
var cobjstr = Object.assign({}, cobj);
|
||||||
|
delete cobjstr._data;
|
||||||
|
// 把cobj塞到第二个td的[cobj]中, 方便绑定事件时取
|
||||||
|
cobjstr = editor.util.HTMLescape(JSON.stringify(cobjstr));
|
||||||
|
|
||||||
|
var tdstr = editor.table.objToTd(obj, commentObj, field, cfield, vobj, cobj)
|
||||||
|
var outstr = editor.table.tr(guid, field, shortField, commentHTMLescape, cobjstr, cobj._docs, tdstr, cobj._type)
|
||||||
|
return [outstr, guid];
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_table.prototype.objToTd = function (obj, commentObj, field, cfield, vobj, cobj) {
|
||||||
|
var thiseval = vobj;
|
||||||
|
switch (cobj._type) {
|
||||||
|
case 'select':
|
||||||
|
return editor.table.select(thiseval, cobj._select.values);
|
||||||
|
case 'checkbox':
|
||||||
|
return editor.table.checkbox(thiseval);
|
||||||
|
case 'checkboxSet':
|
||||||
|
return editor.table.checkboxSet(thiseval, cobj._checkboxSet.key, cobj._checkboxSet.prefix);
|
||||||
|
default:
|
||||||
|
return editor.table.textarea(thiseval, cobj.indent || 0, cobj._type == 'disable');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 表格的用户交互
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查一个值是否允许被设置为当前输入
|
||||||
|
* @param {Object} cobj
|
||||||
|
* @param {*} thiseval
|
||||||
|
*/
|
||||||
|
editor_table.prototype.checkRange = function (cobj, thiseval) {
|
||||||
|
if (cobj._range) {
|
||||||
|
return eval(cobj._range);
|
||||||
|
}
|
||||||
|
if (cobj._select) {
|
||||||
|
return cobj._select.values.indexOf(thiseval) !== -1;
|
||||||
|
}
|
||||||
|
if (cobj._bool) {
|
||||||
|
return [true, false].indexOf(thiseval) !== -1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听一个guid对应的表格项
|
||||||
|
* @param {String} guid
|
||||||
|
*/
|
||||||
|
editor_table.prototype.guidListen = function (guid, tableid, obj, commentObj) {
|
||||||
|
// tr>td[title=field]
|
||||||
|
// >td[title=comment,cobj=cobj:json]
|
||||||
|
// >td>div>input[value=thiseval]
|
||||||
|
var thisTr = document.getElementById(guid);
|
||||||
|
var input = thisTr.children[2].children[0].children[0];
|
||||||
|
var field = thisTr.children[0].getAttribute('title');
|
||||||
|
var cobj = JSON.parse(thisTr.children[1].getAttribute('cobj'));
|
||||||
|
var modeNode = thisTr.parentNode;
|
||||||
|
thisTr.setAttribute('tableid',tableid)
|
||||||
|
while (!editor_mode._ids.hasOwnProperty(modeNode.getAttribute('id'))) {
|
||||||
|
modeNode = modeNode.parentNode;
|
||||||
|
}
|
||||||
|
input.onchange = function () {
|
||||||
|
editor.table.onchange(guid, obj, commentObj, thisTr, input, field, cobj, modeNode)
|
||||||
|
}
|
||||||
|
// 用检测两次单击的方式来实现双击(以支持手机端的双击)
|
||||||
|
var doubleClickCheck = [0];
|
||||||
|
thisTr.onclick = function () {
|
||||||
|
var newClick = new Date().getTime();
|
||||||
|
var lastClick = doubleClickCheck.shift();
|
||||||
|
doubleClickCheck.push(newClick);
|
||||||
|
if (newClick - lastClick < 500) {
|
||||||
|
editor.table.dblclickfunc(guid, obj, commentObj, thisTr, input, field, cobj, modeNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格的值变化时
|
||||||
|
*/
|
||||||
|
editor_table.prototype.onchange = function (guid, obj, commentObj, thisTr, input, field, cobj, modeNode) {
|
||||||
|
editor_mode.onmode(editor_mode._ids[modeNode.getAttribute('id')]);
|
||||||
|
if (editor.mode.currentTable!=thisTr.getAttribute('tableid')) return;
|
||||||
|
var thiseval = null;
|
||||||
|
if (input.checked != null) input.value = input.checked;
|
||||||
|
try {
|
||||||
|
if (input.value == '') input.value = 'null';
|
||||||
|
thiseval = JSON.parse(input.value);
|
||||||
|
} catch (ee) {
|
||||||
|
printe(field + ' : ' + ee);
|
||||||
|
throw ee;
|
||||||
|
}
|
||||||
|
if (editor.table.checkRange(cobj, thiseval)) {
|
||||||
|
editor_mode.addAction(['change', field, thiseval]);
|
||||||
|
editor_mode.onmode('save');//自动保存 删掉此行的话点保存按钮才会保存
|
||||||
|
} else {
|
||||||
|
printe(field + ' : 输入的值不合要求,请鼠标放置在注释上查看说明');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenPool = {};
|
||||||
|
var tokenstyle = document.createElement("style");
|
||||||
|
document.body.appendChild(tokenstyle);
|
||||||
|
|
||||||
|
var tokenPoolRender = function() {
|
||||||
|
var content = "";
|
||||||
|
Object.keys(tokenPool).forEach(function(k) {
|
||||||
|
content += /* CSS */`[data-field|=${k}]{ display: none }`;
|
||||||
|
})
|
||||||
|
tokenstyle.innerHTML = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当"折叠"被按下时
|
||||||
|
*/
|
||||||
|
editor_table.prototype.onFoldBtnClick = function (button) {
|
||||||
|
var tr = button.parentNode.parentNode;
|
||||||
|
if (button.dataset.fold == "true") {
|
||||||
|
delete tokenPool[tr.dataset.gap];
|
||||||
|
tokenPoolRender();
|
||||||
|
button.dataset.fold = "false";
|
||||||
|
button.innerText = "折叠";
|
||||||
|
} else {
|
||||||
|
tokenPool[tr.dataset.gap] = true;
|
||||||
|
tokenPoolRender();
|
||||||
|
button.dataset.fold = "true";
|
||||||
|
button.innerText = "展开";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当"显示完整注释"被按下时
|
||||||
|
*/
|
||||||
|
editor_table.prototype.onCommentBtnClick = function (button) {
|
||||||
|
var tr = button.parentNode.parentNode;
|
||||||
|
printf(tr.children[1].getAttribute('title'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当"编辑表格内容"被按下时
|
||||||
|
*/
|
||||||
|
editor_table.prototype.onEditBtnClick = function (button) {
|
||||||
|
var tr = button.parentNode.parentNode;
|
||||||
|
var guid = tr.getAttribute('id');
|
||||||
|
var cobj = JSON.parse(tr.children[1].getAttribute('cobj'));
|
||||||
|
var input = tr.children[2].children[0].children[0];
|
||||||
|
if (cobj._type === 'event') editor_blockly.import(guid, { type: cobj._event });
|
||||||
|
if (cobj._type === 'textarea') editor_multi.import(guid, { lint: cobj._lint, string: cobj._string, template: cobj._template, preview: cobj._preview });
|
||||||
|
if (cobj._type === 'material') editor.table.selectMaterial(input, cobj);
|
||||||
|
if (cobj._type === 'color') editor.table.selectColor(input);
|
||||||
|
if (cobj._type === 'point') editor.table.selectPoint(input);
|
||||||
|
if (cobj._type === 'popCheckboxSet') editor.table.popCheckboxSet(input, cobj);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_table.prototype.onCopyBtnClick = function (button) {
|
||||||
|
var tr = button.parentNode.parentNode;
|
||||||
|
var input = tr.children[2].children[0].children[0];
|
||||||
|
var value = JSON.parse(input.value);
|
||||||
|
if (value == null) {
|
||||||
|
printe('没有赋值的内容');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (core.copy(value.toString())) {
|
||||||
|
printf('复制成功!');
|
||||||
|
} else {
|
||||||
|
printe('无法复制此内容,请手动选择复制');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 双击表格时
|
||||||
|
* 正常编辑: 尝试用事件编辑器或多行文本编辑器打开
|
||||||
|
* 添加: 在该项的同一级创建一个内容为null新的项, 刷新后生效并可以继续编辑
|
||||||
|
* 删除: 删除该项, 刷新后生效
|
||||||
|
* 在点击按钮 添加/删除 后,下一次双击将被视为 添加/删除
|
||||||
|
*/
|
||||||
|
editor_table.prototype.dblclickfunc = function (guid, obj, commentObj, thisTr, input, field, cobj, modeNode) {
|
||||||
|
if (editor_mode.doubleClickMode === 'change') {
|
||||||
|
if (cobj._type === 'event') editor_blockly.import(guid, { type: cobj._event });
|
||||||
|
if (cobj._type === 'textarea') editor_multi.import(guid, { lint: cobj._lint, string: cobj._string, template: cobj._template, preview: cobj._preview });
|
||||||
|
if (cobj._type === 'material') editor.table.selectMaterial(input, cobj);
|
||||||
|
if (cobj._type === 'color') editor.table.selectColor(input);
|
||||||
|
if (cobj._type === 'point') editor.table.selectPoint(input);
|
||||||
|
if (cobj._type === 'popCheckboxSet') editor.table.popCheckboxSet(input, cobj);
|
||||||
|
} else if (editor_mode.doubleClickMode === 'add') {
|
||||||
|
editor_mode.doubleClickMode = 'change';
|
||||||
|
editor.table.addfunc(guid, obj, commentObj, thisTr, input, field, cobj, modeNode)
|
||||||
|
} else if (editor_mode.doubleClickMode === 'delete') {
|
||||||
|
editor_mode.doubleClickMode = 'change';
|
||||||
|
editor.table.deletefunc(guid, obj, commentObj, thisTr, input, field, cobj, modeNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_table.prototype.selectMaterial = function (input, cobj) {
|
||||||
|
editor.uievent.selectMaterial(input.value, cobj._docs || cobj._data || '请选择素材', cobj._directory, function (one) {
|
||||||
|
if (!/^[-A-Za-z0-9_.]+$/.test(one)) return null;
|
||||||
|
if (cobj._transform) return eval("("+cobj._transform+")(one)");
|
||||||
|
return one;
|
||||||
|
}, function (data) {
|
||||||
|
input.value = JSON.stringify(cobj._onconfirm ? eval("("+cobj._onconfirm+")(JSON.parse(input.value), data)") : data);
|
||||||
|
input.onchange();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_table.prototype.selectColor = function (input) {
|
||||||
|
if (input.value != null) {
|
||||||
|
var str = input.value.toString().replace(/[^\d.,]/g, '');
|
||||||
|
if (/^[0-9 ]+,[0-9 ]+,[0-9 ]+(,[0-9. ]+)?$/.test(str)) {
|
||||||
|
document.getElementById('colorPicker').value = str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var boundingBox = input.getBoundingClientRect();
|
||||||
|
openColorPicker(boundingBox.x, boundingBox.y + boundingBox.height, function (value) {
|
||||||
|
value = value.replace(/[^\d.,]/g, '');
|
||||||
|
input.value = '[' + value +']';
|
||||||
|
input.onchange();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_table.prototype.selectPoint = function (input) {
|
||||||
|
var x = 0, y = 0, value = input.value;
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
var loc = JSON.parse(value);
|
||||||
|
if (loc instanceof Array && loc.length == 2) {
|
||||||
|
x = loc[0];
|
||||||
|
y = loc[1];
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
editor.uievent.selectPoint(editor.currentFloorId, x, y, false, function (floorId, x, y) {
|
||||||
|
input.value = '['+x+','+y+']';
|
||||||
|
input.onchange();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_table.prototype.popCheckboxSet = function (input, cobj) {
|
||||||
|
editor.uievent.popCheckboxSet(JSON.parse(input.value), cobj._checkboxSet, cobj._docs || cobj._data || '请选择多选项', function (value) {
|
||||||
|
input.value = JSON.stringify(value);
|
||||||
|
input.onchange();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除表格项
|
||||||
|
*/
|
||||||
|
editor_table.prototype.deletefunc = function (guid, obj, commentObj, thisTr, input, field, cobj, modeNode) {
|
||||||
|
editor_mode.onmode(editor_mode._ids[modeNode.getAttribute('id')]);
|
||||||
|
if (editor.table.checkRange(cobj, null)) {
|
||||||
|
editor_mode.addAction(['delete', field, undefined]);
|
||||||
|
editor_mode.onmode('save', function () {
|
||||||
|
printf('删除成功,刷新后生效。')
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
printe(field + ' : 该值不允许为null,无法删除');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加表格项
|
||||||
|
*/
|
||||||
|
editor_table.prototype.addfunc = function (guid, obj, commentObj, thisTr, input, field, cobj, modeNode) {
|
||||||
|
if (modeNode) {
|
||||||
|
editor_mode.onmode(editor_mode._ids[modeNode.getAttribute('id')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode = editor.dom.editModeSelect.value;
|
||||||
|
var supportText = mode === 'commonevent' || mode === 'plugins';
|
||||||
|
|
||||||
|
if (obj == null) {
|
||||||
|
if (mode === 'commonevent') obj = events_c12a15a8_c380_4b28_8144_256cba95f760;
|
||||||
|
else if (mode === 'plugins') obj = plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1;
|
||||||
|
else return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.输入id
|
||||||
|
var newid = '2';
|
||||||
|
if (mode == 'loc') {
|
||||||
|
var ae = editor.currentFloorData.autoEvent[editor_mode.pos.x + ',' + editor_mode.pos.y];
|
||||||
|
if (ae != null) {
|
||||||
|
var testid;
|
||||||
|
for (testid = 2; Object.hasOwnProperty.call(ae, testid); testid++); // 从3开始是因为comment中设置了始终显示012
|
||||||
|
newid = testid + '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newid = prompt(supportText ? '请输入新项的ID(支持中文)' : '请输入新项的ID(数字字母下划线)');
|
||||||
|
if (newid == null || newid.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.检查id是否符合规范或与已有id重复
|
||||||
|
if (!supportText) {
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(newid)) {
|
||||||
|
printe('id不符合规范, 请使用大小写字母数字下划线来构成');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var conflict = true;
|
||||||
|
var basefield = (field || "").replace(/\[[^\[]*\]$/, '');
|
||||||
|
if (basefield === "['main']") {
|
||||||
|
printe("全塔属性 ~ ['main'] 不允许添加新值");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var baseobj = eval('obj' + basefield);
|
||||||
|
conflict = newid in baseobj;
|
||||||
|
} catch (ee) {
|
||||||
|
// 理论上这里不会发生错误
|
||||||
|
printe(ee);
|
||||||
|
throw ee;
|
||||||
|
}
|
||||||
|
if (conflict) {
|
||||||
|
printe('id已存在, 请直接修改该项的值');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 3.添加
|
||||||
|
editor_mode.addAction(['add', basefield + "['" + newid + "']", null]);
|
||||||
|
editor_mode.onmode('save', function () {
|
||||||
|
printf('添加成功,刷新后生效;也可以继续新增其他项目。')
|
||||||
|
});//自动保存 删掉此行的话点保存按钮才会保存
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
editor.constructor.prototype.table = new editor_table();
|
||||||
|
}
|
||||||
|
//editor_table_wrapper(editor);
|
339
editor_ui.js
Normal file
339
editor_ui.js
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
editor_ui_wrapper = function (editor) {
|
||||||
|
|
||||||
|
var tip=document.getElementById('tip');
|
||||||
|
var print = function (msg, cls) {
|
||||||
|
if (msg == '') {
|
||||||
|
tip.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tip.innerHTML = '<p class="'+cls+'">' + msg + "</p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
window.printf = function (msg) {
|
||||||
|
selectBox.isSelected(false);
|
||||||
|
print(msg, 'successText');
|
||||||
|
}
|
||||||
|
window.printe = function (msg) {
|
||||||
|
selectBox.isSelected(false);
|
||||||
|
print(msg, 'warnText');
|
||||||
|
}
|
||||||
|
window.printi = function (msg) {
|
||||||
|
print(msg, 'infoText');
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.uifunctions.showBlockInfo = function (value) {
|
||||||
|
if (value == 0) {
|
||||||
|
printi("当前选择为清除块,可擦除地图上块");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var hasId = 'id' in value;
|
||||||
|
if (hasId && value.idnum == 17) {
|
||||||
|
printi("当前选择为空气墙, 在编辑器中可视, 在游戏中隐藏的墙, 用来配合前景/背景的贴图");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var isAutotile = hasId && value.images == "autotile";
|
||||||
|
tip.innerHTML = (hasId?`<p>图块编号:<span class="infoText">${ value['idnum'] }</span></p>
|
||||||
|
<p>图块ID:<span class="infoText">${ value['id'] }</span></p>`:`
|
||||||
|
<p class="warnText">该图块无对应的数字或ID存在,请先前往icons.js和maps.js中进行定义!</p>`)+`
|
||||||
|
<p>图块所在素材:<span class="infoText">${ value['images'] + (isAutotile ? '( '+value['id']+' )' : '') }</span>
|
||||||
|
</p>
|
||||||
|
<p>图块索引:<span class="infoText">${ value['y'] }</span></p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.uifunctions.showTips = function (value) {
|
||||||
|
var tips = [
|
||||||
|
'表格的文本域可以双击进行编辑',
|
||||||
|
'双击地图可以选中素材,右键可以弹出菜单',
|
||||||
|
'双击事件编辑器的图块可以进行长文本编辑/脚本编辑/地图选点/UI绘制预览等操作',
|
||||||
|
'ESC或点击空白处可以自动保存当前修改',
|
||||||
|
'H键可以打开操作帮助哦',
|
||||||
|
'tileset平铺模式可以在地图上拖动来平铺框选的图形',
|
||||||
|
'可以拖动地图上的图块和事件;或按Ctrl+C, Ctrl+X和Ctrl+V进行复制,剪切和粘贴,Delete删除;右键也可以拉框选择区域',
|
||||||
|
'Alt+数字键保存图块,数字键读取保存的图块',
|
||||||
|
];
|
||||||
|
if (value == null) value = Math.floor(Math.random() * tips.length);
|
||||||
|
printf('tips: ' + tips[value])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据鼠标点击, 得到从元素向上到body的所有id
|
||||||
|
*/
|
||||||
|
editor.uifunctions.getClickpath = function (e) {
|
||||||
|
//console.log(e);
|
||||||
|
var clickpath = [];
|
||||||
|
var getpath = function (e) {
|
||||||
|
var path = [];
|
||||||
|
var currentElem = e.target;
|
||||||
|
while (currentElem) {
|
||||||
|
path.push(currentElem);
|
||||||
|
currentElem = currentElem.parentElement;
|
||||||
|
}
|
||||||
|
if (path.indexOf(window) === -1 && path.indexOf(document) === -1)
|
||||||
|
path.push(document);
|
||||||
|
if (path.indexOf(window) === -1)
|
||||||
|
path.push(window);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
getpath(e).forEach(function (node) {
|
||||||
|
if (!node.getAttribute) return;
|
||||||
|
var id_ = node.getAttribute('id');
|
||||||
|
if (id_) {
|
||||||
|
if (['left', 'left1', 'left2', 'left3', 'left4', 'left5', 'left8', 'mobileview'].indexOf(id_) !== -1) clickpath.push('edit');
|
||||||
|
clickpath.push(id_);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return clickpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* editor.dom.body.onmousedown
|
||||||
|
* 检测鼠标点击,
|
||||||
|
* + 如果选中了绘图区域之外, 就保存地图
|
||||||
|
* + 维护绘图区的菜单的隐藏
|
||||||
|
* + 记录最后一次点击的id(主要为了数据区服务)
|
||||||
|
*/
|
||||||
|
editor.uifunctions.body_click = function (e) {
|
||||||
|
var clickpath = editor.uifunctions.getClickpath(e);
|
||||||
|
|
||||||
|
var unselect = true;
|
||||||
|
for (var ii = 0, thisId; thisId = ['edit', 'tip', 'brushMod', 'brushMod2', 'brushMod3', 'brushMode4', 'layerMod', 'layerMod2', 'layerMod3', 'viewportButtons'][ii]; ii++) {
|
||||||
|
if (clickpath.indexOf(thisId) !== -1) {
|
||||||
|
unselect = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (unselect && !editor.uivalues.lockMode) {
|
||||||
|
if (clickpath.indexOf('eui') === -1 && clickpath.indexOf('lastUsed') === -1) {
|
||||||
|
if (selectBox.isSelected()) {
|
||||||
|
editor_mode.onmode('');
|
||||||
|
editor.file.saveFloorFile(function (err) {
|
||||||
|
if (err) {
|
||||||
|
printe(err);
|
||||||
|
throw (err)
|
||||||
|
}
|
||||||
|
; printf('地图保存成功');
|
||||||
|
editor.uifunctions.unhighlightSaveFloorButton();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectBox.isSelected(false);
|
||||||
|
editor.info = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//editor.mode.onmode('');
|
||||||
|
if (e.button != 2 && !editor.isMobile && clickpath.indexOf('midMenu') === -1) {
|
||||||
|
editor.uifunctions.hideMidMenu();
|
||||||
|
}
|
||||||
|
if (clickpath.indexOf('down') !== -1 && clickpath.indexOf('midMenu') === -1 && editor.isMobile) {
|
||||||
|
editor.uifunctions.hideMidMenu();
|
||||||
|
}
|
||||||
|
if (clickpath.length >= 2 && clickpath[0].indexOf('id_') === 0) { editor.lastClickId = clickpath[0] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* editor.dom.body.onkeydown
|
||||||
|
* 绑定快捷键
|
||||||
|
*/
|
||||||
|
editor.uifunctions.body_shortcut = function (e) {
|
||||||
|
editor.uivalues.tileSize = [1,1];
|
||||||
|
|
||||||
|
// UI预览 & 地图选点
|
||||||
|
if (editor.uievent && editor.uievent.isOpen) {
|
||||||
|
editor.uievent.onKeyDown(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听Ctrl+S保存
|
||||||
|
if (e.ctrlKey && e.keyCode == 83) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (editor_multi.id != "") {
|
||||||
|
editor_multi.confirm(); // 保存脚本编辑器
|
||||||
|
}
|
||||||
|
else if (editor_blockly.id != "") {
|
||||||
|
editor_blockly.confirm(); // 保存事件编辑器
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
editor_mode.saveFloor();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是开启事件/脚本编辑器状态,则忽略
|
||||||
|
if (editor_multi.id != "" || editor_blockly.id != "")
|
||||||
|
return;
|
||||||
|
|
||||||
|
// PGUP和PGDOWN切换楼层
|
||||||
|
if (e.keyCode == 33 || e.keyCode == 34) {
|
||||||
|
e.preventDefault();
|
||||||
|
var saveFloor = document.getElementById('saveFloor');
|
||||||
|
if (saveFloor && saveFloor.classList.contains('highlight')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = editor.core.floorIds.indexOf(editor.currentFloorId);
|
||||||
|
var nextIndex = index + (e.keyCode == 33 ? 1 : -1);
|
||||||
|
if (nextIndex >= 0 && nextIndex < editor.core.floorIds.length) {
|
||||||
|
var toId = editor.core.floorIds[nextIndex];
|
||||||
|
editor_mode.onmode('nextChange');
|
||||||
|
editor_mode.onmode('floor');
|
||||||
|
document.getElementById('selectFloor').value = toId;
|
||||||
|
editor.uivalues.recentFloors.push(editor.currentFloorId);
|
||||||
|
editor.changeFloor(toId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var focusElement = document.activeElement;
|
||||||
|
if (!focusElement || focusElement.tagName.toLowerCase() == 'body'
|
||||||
|
|| focusElement.id == 'selectFloor' || focusElement.id == 'bigmapBtn'
|
||||||
|
|| focusElement.id.startsWith('layerMod')) {
|
||||||
|
|
||||||
|
//Ctrl+z 撤销上一步undo
|
||||||
|
if (e.keyCode == 90 && e.ctrlKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (editor.uivalues.preMapData.length > 0) {
|
||||||
|
var data = editor.uivalues.preMapData.pop();
|
||||||
|
editor.dom.maps.forEach(function (one) {
|
||||||
|
editor[one] = JSON.parse(JSON.stringify(data[one]));
|
||||||
|
});
|
||||||
|
editor.updateMap();
|
||||||
|
editor.uivalues.postMapData.push(data);
|
||||||
|
editor.uifunctions.highlightSaveFloorButton();
|
||||||
|
printf("已撤销此操作,你可能需要重新保存地图。");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Ctrl+y 重做一步redo
|
||||||
|
if (e.keyCode == 89 && e.ctrlKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (editor.uivalues.postMapData.length > 0) {
|
||||||
|
var data = editor.uivalues.postMapData.pop();
|
||||||
|
editor.dom.maps.forEach(function (one) {
|
||||||
|
editor[one] = JSON.parse(JSON.stringify(data[one]));
|
||||||
|
});
|
||||||
|
editor.updateMap();
|
||||||
|
editor.uivalues.preMapData.push(data);
|
||||||
|
editor.uifunctions.highlightSaveFloorButton();
|
||||||
|
printf("已重做此操作,你可能需要重新保存地图。");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl+C, Ctrl+X, Ctrl+V
|
||||||
|
if (e.ctrlKey && e.keyCode == 67 && !selectBox.isSelected()) {
|
||||||
|
e.preventDefault();
|
||||||
|
editor.uivalues.copyedInfo = editor.copyFromPos(editor.uivalues.selectedArea);
|
||||||
|
printf('该点事件已复制;请注意右键地图拉框可以复制一个区域;若有时复制失灵请多点几下空白处');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.ctrlKey && e.keyCode == 88 && !selectBox.isSelected()) {
|
||||||
|
e.preventDefault();
|
||||||
|
editor.savePreMap();
|
||||||
|
editor.uivalues.copyedInfo = editor.copyFromPos(editor.uivalues.selectedArea);
|
||||||
|
editor.clearPos(true, editor.uivalues.selectedArea, function () {
|
||||||
|
printf('该点事件已剪切;请注意右键地图拉框可以剪切一个区域;若有时剪切失灵请多点几下空白处');
|
||||||
|
editor.uifunctions.unhighlightSaveFloorButton();
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.ctrlKey && e.keyCode == 86 && !selectBox.isSelected()) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!editor.uivalues.copyedInfo) {
|
||||||
|
printe("没有复制的事件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editor.savePreMap();
|
||||||
|
editor.pasteToPos(editor.uivalues.copyedInfo);
|
||||||
|
editor.updateMap();
|
||||||
|
editor.file.saveFloorFile(function (err) {
|
||||||
|
if (err) {
|
||||||
|
printe(err);
|
||||||
|
throw (err)
|
||||||
|
}
|
||||||
|
; printf('粘贴事件成功;若有时粘贴失灵请多点几下空白处');
|
||||||
|
editor.uifunctions.unhighlightSaveFloorButton();
|
||||||
|
editor.drawPosSelection();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// DELETE
|
||||||
|
if (e.keyCode == 46 && !selectBox.isSelected()) {
|
||||||
|
editor.savePreMap();
|
||||||
|
editor.clearPos(true, editor.uivalues.selectedArea, function () {
|
||||||
|
printf('该点事件已删除;请注意右键地图拉框可以删除一个区域;;若有时删除失灵请多点几下空白处');
|
||||||
|
editor.uifunctions.unhighlightSaveFloorButton();
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ESC
|
||||||
|
if (e.keyCode == 27) {
|
||||||
|
if (selectBox.isSelected()) {
|
||||||
|
editor_mode.onmode('');
|
||||||
|
editor.file.saveFloorFile(function (err) {
|
||||||
|
if (err) {
|
||||||
|
printe(err);
|
||||||
|
throw (err)
|
||||||
|
}
|
||||||
|
; printf('地图保存成功');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectBox.isSelected(false);
|
||||||
|
editor.info = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//alt + 0~9 改变快捷图块
|
||||||
|
if (e.altKey && [48, 49, 50, 51, 52, 53, 54, 55, 56, 57].indexOf(e.keyCode) !== -1) {
|
||||||
|
var infoToSave = JSON.stringify(editor.info || 0);
|
||||||
|
if (infoToSave == JSON.stringify({})) return;
|
||||||
|
editor.uivalues.shortcut[e.keyCode] = JSON.parse(infoToSave);
|
||||||
|
printf('已保存该快捷图块, 数字键 ' + (e.keyCode - 48) + ' 使用.')
|
||||||
|
editor.config.set('shortcut', editor.uivalues.shortcut);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//ctrl + 0~9 切换到快捷图块
|
||||||
|
if ([48, 49, 50, 51, 52, 53, 54, 55, 56, 57].indexOf(e.keyCode) !== -1) {
|
||||||
|
editor.setSelectBoxFromInfo(JSON.parse(JSON.stringify(editor.uivalues.shortcut[e.keyCode] || 0)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (e.keyCode) {
|
||||||
|
// WASD
|
||||||
|
case 87: editor.moveViewport(0, -1); break;
|
||||||
|
case 65: editor.moveViewport(-1, 0); break;
|
||||||
|
case 83: editor.moveViewport(0, 1); break;
|
||||||
|
case 68: editor.moveViewport(1, 0); break;
|
||||||
|
// F
|
||||||
|
case 70: editor.uifunctions.triggerBigmap(); break;
|
||||||
|
// Z~.
|
||||||
|
case 90: editor_mode.change('map'); break; // Z
|
||||||
|
case 88: editor_mode.change('loc'); break; // X
|
||||||
|
case 67: editor_mode.change('enemyitem'); break; // C
|
||||||
|
case 86: editor_mode.change('floor'); break; // V
|
||||||
|
case 66: editor_mode.change('tower'); break; // B
|
||||||
|
case 78: editor_mode.change('functions'); break; // N
|
||||||
|
case 77: editor_mode.change('appendpic'); break; // M
|
||||||
|
case 188: editor_mode.change('commonevent'); break; // ,
|
||||||
|
case 190: editor_mode.change('plugins'); break; // .
|
||||||
|
// H
|
||||||
|
case 72: editor.uifunctions.showHelp(); break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.uifunctions.showHelp = function () {
|
||||||
|
alert(
|
||||||
|
"快捷操作帮助:\n" +
|
||||||
|
"ESC / 点击空白处:自动保存当前修改\n" +
|
||||||
|
"F:切换大地图\n" +
|
||||||
|
"WASD / 长按箭头:平移大地图\n" +
|
||||||
|
"PgUp, PgDn / 鼠标滚轮:上下切换楼层\n" +
|
||||||
|
"Z~.(键盘的第三排):快捷切换标签\n" +
|
||||||
|
"双击地图:选中对应点的素材\n" +
|
||||||
|
"右键地图:弹出菜单栏\n" +
|
||||||
|
"Alt+0~9:保存当前使用的图块\n" +
|
||||||
|
"0~9:选中保存的图块\n" +
|
||||||
|
"Ctrl+Z / Ctrl+Y:撤销/重做上次绘制\n" +
|
||||||
|
"Ctrl+S:事件与脚本编辑器的保存并退出\n" +
|
||||||
|
"双击事件编辑器:长文本编辑/脚本编辑/地图选点/UI绘制预览"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
1068
editor_uievent.js
Normal file
1068
editor_uievent.js
Normal file
File diff suppressed because it is too large
Load Diff
173
editor_util.js
Normal file
173
editor_util.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
editor_util_wrapper = function (editor) {
|
||||||
|
|
||||||
|
editor_util = function () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_util.prototype.guid = function () {
|
||||||
|
return 'id_' + 'xxxxxxxx_xxxx_4xxx_yxxx_xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_util.prototype.HTMLescape = function (str_) {
|
||||||
|
return String(str_).split('').map(function (v) {
|
||||||
|
return '&#' + v.charCodeAt(0) + ';'
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_util.prototype.getPixel = function (imgData, x, y) {
|
||||||
|
var offset = (x + y * imgData.width) * 4;
|
||||||
|
var r = imgData.data[offset + 0];
|
||||||
|
var g = imgData.data[offset + 1];
|
||||||
|
var b = imgData.data[offset + 2];
|
||||||
|
var a = imgData.data[offset + 3];
|
||||||
|
return [r, g, b, a];
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_util.prototype.setPixel = function (imgData, x, y, rgba) {
|
||||||
|
var offset = (x + y * imgData.width) * 4;
|
||||||
|
imgData.data[offset + 0] = rgba[0];
|
||||||
|
imgData.data[offset + 1] = rgba[1];
|
||||||
|
imgData.data[offset + 2] = rgba[2];
|
||||||
|
imgData.data[offset + 3] = rgba[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
// rgbToHsl hue2rgb hslToRgb from https://github.com/carloscabo/colz.git
|
||||||
|
//--------------------------------------------
|
||||||
|
// The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 Carlos Cabo
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//--------------------------------------------
|
||||||
|
// https://github.com/carloscabo/colz/blob/master/public/js/colz.class.js
|
||||||
|
var round = Math.round;
|
||||||
|
var rgbToHsl = function (rgba) {
|
||||||
|
var arg, r, g, b, h, s, l, d, max, min;
|
||||||
|
|
||||||
|
arg = rgba;
|
||||||
|
|
||||||
|
if (typeof arg[0] === 'number') {
|
||||||
|
r = arg[0];
|
||||||
|
g = arg[1];
|
||||||
|
b = arg[2];
|
||||||
|
} else {
|
||||||
|
r = arg[0][0];
|
||||||
|
g = arg[0][1];
|
||||||
|
b = arg[0][2];
|
||||||
|
}
|
||||||
|
|
||||||
|
r /= 255;
|
||||||
|
g /= 255;
|
||||||
|
b /= 255;
|
||||||
|
|
||||||
|
max = Math.max(r, g, b);
|
||||||
|
min = Math.min(r, g, b);
|
||||||
|
l = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max === min) {
|
||||||
|
h = s = 0; // achromatic
|
||||||
|
} else {
|
||||||
|
d = max - min;
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
|
||||||
|
switch (max) {
|
||||||
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||||
|
case g: h = (b - r) / d + 2; break;
|
||||||
|
case b: h = (r - g) / d + 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
//CARLOS
|
||||||
|
h = round(h * 360);
|
||||||
|
s = round(s * 100);
|
||||||
|
l = round(l * 100);
|
||||||
|
|
||||||
|
return [h, s, l];
|
||||||
|
}
|
||||||
|
//
|
||||||
|
var hue2rgb = function (p, q, t) {
|
||||||
|
if (t < 0) { t += 1; }
|
||||||
|
if (t > 1) { t -= 1; }
|
||||||
|
if (t < 1 / 6) { return p + (q - p) * 6 * t; }
|
||||||
|
if (t < 1 / 2) { return q; }
|
||||||
|
if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6; }
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
var hslToRgb = function (hsl) {
|
||||||
|
var arg, r, g, b, h, s, l, q, p;
|
||||||
|
|
||||||
|
arg = hsl;
|
||||||
|
|
||||||
|
if (typeof arg[0] === 'number') {
|
||||||
|
h = arg[0] / 360;
|
||||||
|
s = arg[1] / 100;
|
||||||
|
l = arg[2] / 100;
|
||||||
|
} else {
|
||||||
|
h = arg[0][0] / 360;
|
||||||
|
s = arg[0][1] / 100;
|
||||||
|
l = arg[0][2] / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
} else {
|
||||||
|
|
||||||
|
q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1 / 3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1 / 3);
|
||||||
|
}
|
||||||
|
return [round(r * 255), round(g * 255), round(b * 255)];
|
||||||
|
}
|
||||||
|
editor_util.prototype.rgbToHsl = rgbToHsl
|
||||||
|
editor_util.prototype.hue2rgb = hue2rgb
|
||||||
|
editor_util.prototype.hslToRgb = hslToRgb
|
||||||
|
|
||||||
|
editor_util.prototype.encode64 = function (str) {
|
||||||
|
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
||||||
|
return String.fromCharCode(parseInt(p1, 16))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_util.prototype.decode64 = function (str) {
|
||||||
|
return decodeURIComponent(atob(str.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')).split('').map(function (c) {
|
||||||
|
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
||||||
|
}).join(''))
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_util.prototype.isset = function (val) {
|
||||||
|
return val != null && !(typeof val == 'number' && isNaN(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_util.prototype.checkCallback = function (callback) {
|
||||||
|
if (!editor.util.isset(callback)) {
|
||||||
|
editor.printe('未设置callback');
|
||||||
|
throw ('未设置callback')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.constructor.prototype.util = new editor_util();
|
||||||
|
}
|
||||||
|
//editor_util_wrapper(editor);
|
467
element.md
Normal file
467
element.md
Normal file
@ -0,0 +1,467 @@
|
|||||||
|
# 元件说明
|
||||||
|
|
||||||
|
?> 在这个部分,将详细讲解编辑器的每个部件的用法。
|
||||||
|
|
||||||
|
## 素材区
|
||||||
|
|
||||||
|
素材区在展开状态下,从左到右分为若干列:
|
||||||
|
|
||||||
|
1. `project\materials\terrains.png`:位于素材区第一列(最上面两个图块不在这张图片里,它们分别是擦除和空气墙),其中从楼梯开始往下有系统含义,请勿随意修改其图块属性。
|
||||||
|
2. `project\materials\animates.png`:位于素材区第二列,共4帧。主要为星空、岩浆、三色墙、六色门、四向出入口箭头、四种路障。
|
||||||
|
3. `project\materials\enemys.png`:32×32px(像素,下同)的怪物,您可以随意修改它们的任何属性。如果嫌两帧太少,还可以作为32×48px怪物画在靠下2/3部分。
|
||||||
|
4. `project\materials\enemy48.png`:32×48px的怪物,自带只有四只。您可以随意修改它们的任何属性。
|
||||||
|
5. `project\materials\npcs.png`:32×32px的NPC,如老人、商人、小偷、公主、仙子、木牌、魔龙和章鱼的其他8块,您可以随意修改它们的任何属性。
|
||||||
|
6. `project\materials\npc48.png`:32×48的NPC,自带只有样板0层的小姐姐,但您也可以用它来制作32×48px的门。
|
||||||
|
7. `project\autotiles`:自动元件,会随着在地图上的连续摆放而自动采取适当的绘制方式。
|
||||||
|
8. `project\tilesets`:额外素材,用来突破其他素材合计不得超过10000个的限制。您可以在这个区域拖动来批量框选,再在地图区单击成片绘制或拖动平铺。
|
||||||
|
|
||||||
|
V2.7.3中,`terrains.png`追加了薄墙图块(红线),V2.8中它们被挪到了最下方,可供利用。
|
||||||
|
|
||||||
|
V2.8中,自动元件在素材区折叠模式下会只显示左上角一格,与RPG Maker一致。
|
||||||
|
|
||||||
|
## 地图编辑(快捷键Z)
|
||||||
|
|
||||||
|
![image](img/editor.jpg)
|
||||||
|
|
||||||
|
如图所示,您可以在此对地图进行清空或删除操作,也可以新建或批量新建任意宽高的空白地图。
|
||||||
|
|
||||||
|
其中“导出并复制地图”是指显示出左侧的矩阵并复制(一般用来跨塔复制地图),您也可以直接改动其中的数字(例如将绿色史莱姆批量替换为红色史莱姆),再点击“从框中导入地图”就能将改动的结果同步到地图上。
|
||||||
|
|
||||||
|
下面的“楼层ID、中文名、状态栏名”分别对应楼层属性(快捷键V)的floorId、title和name,其中floorId也作为文件名(不能使用中文,注意大小写问题),title会显示在楼传界面和楼层切换黑屏,name也允许使用中文但请注意控制字数。
|
||||||
|
|
||||||
|
## 图块属性(快捷键C)
|
||||||
|
|
||||||
|
![image](img/mapsc.jpg)
|
||||||
|
|
||||||
|
如上图,除怪物和道具外,所有素材的图块属性都定义在`project\maps.js`中。道具和怪物属性也支持清空和批量复制,下面逐一讲解各条目的含义和用法:
|
||||||
|
|
||||||
|
1. **图块ID:**图块的唯一标识符`core.getBlockId(x, y, floorId, showDisable)`,不允许使用中文和纯数字。请注意,额外素材`tileset`的图块ID由素材图片的顺序和图块在图片上的位置确定,无法更改,也请勿随意调换图片的顺序。样板已注册的图块中建议只修改怪物和NPC的图块ID,修改方法为上图最下方的**修改图块id为**。
|
||||||
|
2. **图块数字:**见前面的描述,额外素材的数字由ID去掉字母X得到。
|
||||||
|
3. **图块类别:**图块素材的类型。
|
||||||
|
4. **图块名称:**怪物在手册中、道具在道具栏中、其他图块在剧情对话中的默认名称,可以随意修改。但原则上不推荐不同的怪物和道具有重复的名称,否则会影响事件编辑器的中文替换功能。
|
||||||
|
|
||||||
|
V2.8中,图块名称在有罗马数字、希腊字母、日文假名的情况下也支持事件编辑器中的中文替换了,这大大方便了一些科幻或魔法题材的作品。
|
||||||
|
|
||||||
|
V2.8中,道具名称支持使用${表达式计算}语法,但这样做会使其在事件编辑器中的中文替换失效。
|
||||||
|
|
||||||
|
你可以随时使用 `core.getBlockId(x, y, floorId, showDisable)` 获得地图上任何一个点的图块ID;`core.getBlockCls(x, y, floorId, showDisable)` 或的地图上任何一个点的图块类别;详见[API列表](api)。
|
||||||
|
|
||||||
|
在讲解其他属性之前,这里简单介绍一下素材的注册机制:
|
||||||
|
|
||||||
|
* 除自动元件和额外素材外,其余图块只有在注册后才拥有上面所说的ID和数字。
|
||||||
|
* 未注册的图块则只有“索引”,索引为n表示该图块在图片的第(n+1)行,
|
||||||
|
* ID和索引的对应关系定义在`project\icons.js`中。
|
||||||
|
* 尝试用未注册的图块(如利用便捷PS工具新追加的图块)在地图上绘制,就会出现红色的问号方框。
|
||||||
|
* 此时请在数据区手动注册此图块,只需填写一个新ID和数字(10000以内)即可。
|
||||||
|
* 也可以点击“自动注册”按钮批量注册该图片的所有未注册素材,自动注册出的ID无任何语义(一般是一个表示图块类别的大写字母加几个数字),建议手动修改成有语义的内容(如史莱姆以Slime结尾)。
|
||||||
|
|
||||||
|
自动元件的注册与此不同,除了替换样板现有的几个外,如果还需要追加新的,请在地图区下方的下拉框中切换到“追加素材”(快捷键M),然后导入文件到画板autotile,再点击“追加”按钮即可。非Windows系统追加其他素材也主要依靠这种方式,具体用法请自行探索。
|
||||||
|
|
||||||
|
V2.7起,电脑端支持将文件直接拖动到素材区对应的列,进行追加。具体规则如下:
|
||||||
|
1. 道具的图片宽高必须为32的倍数,每格会被追加为一个新道具
|
||||||
|
2. 其他类别的多帧图块(不包括自动元件和tileset),规格必须为128×128、128×192、96×128、96×192之一,分别对应32×32和32×48的四行四列或四行三列(后者为RPG Maker VX Ace格式)。
|
||||||
|
3. 向两帧的enemys.png或npcs.png追加时,四列的图片取中间两列,三列的图片取两边两列。
|
||||||
|
4. 向四帧的animates.png、enemy48.png、npc48.png追加时,三列的图片按2123排布。
|
||||||
|
|
||||||
|
V2.8起,图块支持“(注销并)删除”操作,执行后将(对已注册的)删除该图块在maps.js和icons.js的信息(如果是怪物和道具则还会删除对应的信息),然后将该图块从图片上删除,图片上更靠下的图块会被统一上移一格。
|
||||||
|
|
||||||
|
### 非怪物非道具属性
|
||||||
|
|
||||||
|
1. **触发器:**当碰触到地图上此图块时触发的系统事件,详见[事件](event)。
|
||||||
|
* **battle**: (未列出)战斗;当撞上一个怪物且没有覆盖触发器时(参见[事件](event))将自动调用此触发器产生战斗,并触发战前和战后事件。
|
||||||
|
* **getItem**: (未列出)拾获道具;当撞上/轻拾一个道具且没有覆盖触发器时(参见[事件](event))将自动调用此触发器获得它,并触发(拾获)道具后事件。
|
||||||
|
* **changeFloor**: (未列出)楼层切换;对于地图上绑定的绿点(常见于楼梯或彩色箭头)将自动调用此触发器产生楼层切换事件。
|
||||||
|
* **openDoor**: 用于制作门效果,当撞上此图块时将尝试开门(仅对`animates`和`npc48`生效),并触发开门后事件;具体开门动画参见下面的门信息。
|
||||||
|
* **pushBox**: 推箱子;请勿对非箱子使用此触发器。
|
||||||
|
* **ski**: 滑冰;拥有此触发器的图块放置在背景层时,走上去将触发滑冰效果。
|
||||||
|
* **custom**: 自定义系统触发器;你可以使用 `core.registerSystemEvent` 来自己定义一个系统触发器,参见[API列表](api)。
|
||||||
|
2. **可通行性:**勾选后勇士才可以踏入此图块,否则只能撞击此图块。(怪物被锁定为不可通行,道具被锁定为可通行,如有需要可以修改点上的不可通行性)
|
||||||
|
3. **碰触脚本/碰触事件:**勇士踏入或撞击此图块时执行的脚本,该项会被eval,相当于一种自定义的触发器,您可以参考踩灯和四种路障去填写它。V2.8起,新增了“碰触事件”(会覆盖该点的普通事件),使用起来更方便也更安全。
|
||||||
|
4. **不可出入方向:**对三个图层的图块都有效。不可出方向指的是勇士站在这种图块上不能向哪个方向走(包括撞击),不可入方向指的是勇士不能从哪个方向走向这种图块(包括撞击)。例如,不可入方向勾选了“上”则不能“从上方、向下”走向这个图块。请参考素材区第一列的四个灰色箭头(样板1层右下角也有)。
|
||||||
|
* V2.8.1起,编辑器地图区下方提供了“通行度”勾选框,您可以随时勾选它来查看“不可出入方向”(包括图块的和点的)在地图上的实际效果。
|
||||||
|
5. **可破震:**勾选后,此图块将成为破墙镐(pickaxe)和地震卷轴(earthquake)这两个道具的目标。
|
||||||
|
6. **动画帧数:**您可以修改此帧数来让本来有4帧的图块只用前2或3帧循环播放,另外制作门时请务必将此帧数改为1,表示门在打开前静止在第1帧。
|
||||||
|
7. **门信息:**只对`animates`和`npc48`有效,您可以点击“编辑按钮”来填写此图块作为门的开关耗时、开关音效以及需要哪些钥匙各多少把(可以填写任何消耗类道具,也可以选择某些道具只需持有一定数量而不消耗)。修改此信息后,您需要将上面的“动画帧数”改为1,并可能需要将“触发器”改为openDoor(不改的话将无法通过撞击来开门,但可以像三色墙一样用来制作暗墙)。
|
||||||
|
* 该项在V2.7首次提供,只支持“同时需要多种钥匙各多少把、消耗其中哪几种”的“且运算”条件。
|
||||||
|
* 如果您需要指定“或运算”条件(如优先消耗一种钥匙,没有再消耗另一种),或“消耗量”少于“需要持有量”,或者需要判断的条件不是道具而是其他条件(比如勇士状态或flag),请使用“碰触事件”,但这样将无法自动存档。
|
||||||
|
* V2.8新增了(批量)开门后事件,它和(单点)开门后事件的执行先后顺序,由“脚本编辑——开门后脚本”指定,默认先执行单点的。
|
||||||
|
* 因此,复杂的开门条件也可以换个角度思考,把开门条件设为什么都不需要(先斩后奏,这一思想在道具使用条件中也很常见),开门后事件中再进行判断,如果开门成功就按照条件扣除钥匙,开门失败就再把门关上就好了,这样就保留了自动存档功能。
|
||||||
|
* V2.8.1起,npc48支持绑定大型贴图,您可以制作大型门了,开关门的动画会更为震撼!
|
||||||
|
8. **行走图朝向:**设置后,当勇士撞击该图块时,图块会尝试转身面向勇士(对话事件结束前请使用“事件转向”指令将其转回去)。走动时也会尝试自动转向,请参考样板0层使用的小姐姐。
|
||||||
|
* V2.8起,“行走图朝向”可以用于怪物,绑定后只需手动设置脸朝下的怪物属性(包括下面的绑定贴图)而不用管其他的,手册显示、实际战斗、阻激夹域(尤其是夹击)、漏怪检测(`hasEnemyLeft`)等都会强制读取脸朝下的怪物,游戏中动态修改任何一个朝向的怪物属性都会立即强制同步到四个怪物身上。
|
||||||
|
* npc在设置行走图朝向以后,其他属性不具有自同步机制,包括下面的绑定贴图,请自行注意。
|
||||||
|
* 该项的四个方向甚至可以写不同的图块类别,后果未知。
|
||||||
|
9. **绑定贴图:**V2.8.1新增,可以用于NPC和怪物。这很大程度上弥补了H5魔塔比起RPG Maker不能使用大型行走图的缺憾。
|
||||||
|
* 点击此项时,将会直接弹窗请求选择文件,文件首先会判断总高度和总宽度的比例是否大于0.5。
|
||||||
|
* 【1×4】如果这个比例不大于0.5(这个临界值可以通过复写`core.maps._getBigImageInfo`来修改),那么整张图视为1行4列(每列的高度至多为宽度的2倍),即一个图块的单向4帧行走图。方向默认视为朝下,如需修改,可以在其“行走图朝向”只绑定一个需要的方向(id写自己)。
|
||||||
|
* 【4×4】如果这个比例大于0.5,那么整张图视为4行4列共16块,即4个朝向图块各自的4帧行走图(从上到下的朝向分别为“下、左、右、上”),因此您需要注册四个(最好是同类别的)图块并绑定好行走图朝向,贴图则都绑定这一张。
|
||||||
|
* 该图块放在地图的事件层以后本体会透明化,并且根据朝向(默认向下)选择行走图的绘制偏移量。
|
||||||
|
* 例如,朝下的图块,行走图在绘制时会让本体位于图片下缘中央,其他方向同理。
|
||||||
|
* 而在编辑器中,1:1显示模式下大贴图会被缩小到1格,如需预览原始尺寸的效果,请点击地图区下方的“大地图”按钮。
|
||||||
|
* 游戏中该图块会全程使用绑定的贴图(选择项的子选项图标和文本的`\\i[]`转义序列仍然使用本体)进行显示,包括在怪物手册和显示文章的`\t[]`效果(这两种都会被缩小到一定尺寸)。而显示文章的`\b[]`效果则还是根据图块类别使用原始高度,不过V2.8.1中您可以手动去掉尖角然后指定对话框的左上角坐标和限宽。
|
||||||
|
* 对该图块执行“显隐事件、转变图块、开关门(npc48)、不透明度设置和渐变、移动跳跃”都会直接操作大贴图,这比起2.7.x的楼层贴图要好用许多!
|
||||||
|
* 但是,如果您有一张图片希望分成16小格,但是每行对应完全独立的四个图块而不具有行走图朝向关系,该怎么做呢?
|
||||||
|
* “插件编写”的第一项init中,提供了“资源加载后的操作”,您可以在里面用`splitImage`将该图的四行提前裁剪成四张图片并重新命名,就可以使用啦!
|
||||||
|
* 手动使用脚本切分出的新图片无法通过弹窗选取,必须手动填写,敬请谅解。
|
||||||
|
* 如果希望能够弹窗选取,可以使用全塔属性中的“图片切分”功能。
|
||||||
|
``` js
|
||||||
|
this._afterLoadResources = function () {
|
||||||
|
// 这是一个将4by4.png(假设为384*384)按行拆分成四个图片并保存的样例,
|
||||||
|
// 可以用来在没有条件ps(如原图尺寸不支持便捷ps或者手机造塔时)的条件下,
|
||||||
|
// 预处理图片来得到独立朝向的大型行走图(1of4.png到4of4.png)。
|
||||||
|
var arr = core.splitImage('4by4.png', 384, 96); // 以宽386高96进行切分图片
|
||||||
|
for (var i = 1; i <= arr.length; ++i)
|
||||||
|
core.material.images.images[i + 'of4.png'] = arr[i - 1];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 道具属性
|
||||||
|
|
||||||
|
样板自带的道具都在样板0层摆好了,您可以直接进入游戏捡起它们,就会看到该道具的注意事项,这里不再赘述。
|
||||||
|
|
||||||
|
1. **道具类别:**虽然和图块类别的英文缩写都是cls,但有本质区别,请注意区分。道具的图块类别都是items,而道具类别分为以下几种:
|
||||||
|
* items:是的你没看错,又是`items`这个词,请注意和图块类别的`items`相区分。它表示即捡即用类不进背包的道具,如四种血瓶、三种宝石等。这类道具需要用到的其他属性有“即捡即用效果”、“即捡即用提示”、“碰触或使用事件”。
|
||||||
|
* tools:进背包的消耗类道具,如钥匙和解药瓶、便携式血瓶蓝瓶(生命魔杖)、破震炸飞和跳跃靴等。这类道具需要用到的其他属性有“道具描述”、“不显示在道具栏”、“回放不绘制道具栏”、“碰触或使用事件”、“使用效果”、“能否使用或装备”。
|
||||||
|
* constants:进背包的永久道具(每件在背包的数量要么为1要么为0),如手册、楼传、幸运金币、十字架、护符、二倍斩等,这类道具需要用到的其他属性和tools一致。
|
||||||
|
* equips:装备,它需要用到的其他属性有“道具描述”、“道具的装备属性”、“能否使用或装备”。
|
||||||
|
* 例如:如果您想把四种血瓶和三种宝石改为便携式,只需把其道具类别改为tools(当然,楼层属性中的ratio一项也就失效了)。
|
||||||
|
* 如果想把大黄门钥匙变为钥匙盒(红黄蓝钥匙各一把),只需把其道具类别从tools改为items
|
||||||
|
* 如果想把剑盾变成装备,只需把其道具类别改为`equips`;
|
||||||
|
* 如果想修改破墙镐/炸弹/冰冻徽章的目标个数(V2.8支持改为八方向,使用`core.utils.scan2`即可)或让炸弹能够获得金经/触发战后事件,请修改它们的使用效果。
|
||||||
|
2. **道具描述:**对除即捡即用类外的道具都有效。一个字符串,为道具在道具栏里的描述,也作为首次捡到时的提示信息的一部分(如果全塔属性中开启了这一提示功能的话)。支持使用`${表达式计算}`语法(如四种血瓶和三种宝石那样,但不支持中文替换),此语法的详细规则见“显示文章正文的转义序列”,和“值块和冒号缩写量”。
|
||||||
|
3. **不显示在道具栏:**对tools和constants有效,勾选此项后,该道具在背包中将不显示出来。常用于不能主动使用或已有专门的使用按钮的道具来节省显示篇幅,如手册、楼传、幸运金币、十字架、护符和钥匙等。该属性的详细原理,见“脚本编辑(N键)”最下面的“道具栏显示项”。
|
||||||
|
4. **回放不绘制道具栏:**勾选此项后,录像回放中使用此道具将不显示黑黑的道具栏。常用于频繁使用的道具,如楼传、技能和冰冻徽章等。
|
||||||
|
5. **即捡即用效果:**如题,该项会被eval,一般为一行下述的代码: `core.status.hero.xxx += yyy * core.status.thisMap.ratio`
|
||||||
|
* 其中xxx为勇士的某种状态(如生命hp、生命上限hpmax、魔力mana、魔力上限manamax、护盾mdef、攻防、金经)
|
||||||
|
* yyy为此道具的基础效果(如四种血瓶和三种宝石的基础效果定义在了全塔属性中)
|
||||||
|
* `core.status.thisMap.ratio`则是指该道具所在楼层的“楼层属性”最下面的“宝石血瓶效果”。
|
||||||
|
* 此效果会在“B键数据统计”被模拟执行(使用道具所在楼层的ratio)然后计算执行前后勇士状态差异,因此请尽量只在里面用脚本直接操作`core.status.hero`,而不要使用tip或特效。
|
||||||
|
* 可以在此项中使用`core.insertAction([...])`或`core.insertCommonEvent()`插入事件或公共事件,但不会计入数据统计。
|
||||||
|
6. **即捡即用提示:**实际显示时会被接在“获得xxx”后面,所以该项总是一个以逗号开头的字符串,同样支持`${表达式计算}`语法。
|
||||||
|
7. **碰触或使用事件:**对除equips外都有效。该项用于代替“即捡即用效果”(但会使勇士停下脚步,且会晚于地图上的afterGetItem事件被执行,且不计入B键数据统计)和“使用效果”,如样板中的黄宝石和生命魔杖。如果您的js语法基础薄弱,那么它将是您的不二之选。
|
||||||
|
8. **使用效果:**对tools和constants有效。该项会被eval,一般为一个js函数,较为简单的使用效果(如解药瓶)也可能是一行代码,破炸冰的目标个数请直接在该项中修改。总的来说因为事件比起脚本更容易实现异步特效且录像安全性更好,所以如非必要,不建议用此项。
|
||||||
|
9. **能否使用或装备:**对tools、constants、equips有效。该项也会被eval,一般为一个js函数,较为简单的使用条件(如解药瓶)也可能是一行形如`"core.hasFlag('xxx')"`的代码。
|
||||||
|
* 如果该道具在任何情况下都不能主动使用,请留`null`(并最好勾选“不显示在道具栏”以节约显示篇幅)。如果该道具在任何情况下都可以主动使用,请填`"true"`。
|
||||||
|
* 如果使用条件较为复杂,也推荐直接填`"true"`先斩后奏,在使用效果中再行判定,并在使用失败的场合使用`core.addItem('xxx')`静默返还一件该道具,如样板中的破墙镐和炸弹。
|
||||||
|
* 如果用于装备,那么`null`表示任何情况下都可以装备。但是请注意装上以后,即使条件变得不满足也不会自动脱下。
|
||||||
|
10. **道具的装备属性:**在介绍此项之前,请先留意一下“全塔属性”中的“装备孔”一项。该项为一个字符串数组,最多允许6项(13×13样板)或8项(15×15样板)。每一项为装备的类型名称,建议是两个汉字(如“武器”、“防具”)。类型允许重复,如可以让勇士最多同时装备两块盾牌。
|
||||||
|
* 装备类型:一个自然数,和前面的“装备孔”对应,如0表示武器,1表示防具。如果装备孔有重复的名称则这里也直接写名称(不用加引号),穿戴时会自动尝试寻找第一个同类型的空闲装备位(如果同类型的装备位唯一则会不管空闲与否直接替换),没有空闲的话会提示玩家先卸下一件。
|
||||||
|
* 普攻动画:`project\animates`文件夹中任何一个文件的名称(不带后缀,但需要在全塔属性中注册过,支持别名),只对第一个装备孔有效。普攻动画会播放在和勇士战斗的怪物位置处,如果是强制战斗的天降怪物,则会播放在勇士身上并跟随,请自行注意。详见“文件注册”使用动画。
|
||||||
|
* 数值提升项:若干个键值对,表示该装备增加属性的常数值(支持负数)。7个常用属性可以通过下拉框选取,自定义的新属性也可以手动输入。
|
||||||
|
* 百分比提升项:若干个键值对,表示该装备增加属性的百分比(支持负数,如填-10就表示减少10%),修改方法同上。
|
||||||
|
* 请注意,两种提升项只能写常数,请不要认为写个flag变量就能自动变化了。
|
||||||
|
* V2.8中,两种提升项可以在游戏过程中通过事件指令来修改(包括已经穿在身上的装备,但是修改后的值仍然视为常数),同时每件装备新增了“穿脱时事件”属性,该事件以自动事件方式实现,在换好装备后关闭装备栏的瞬间触发。而且下述各种buff可以使用冒号缩写量来读写了,详见[事件](event)。
|
||||||
|
* “穿脱时事件”指的是身上某件装备“从无到有”和“从有到无”,不包括“从多到少”和“从少到多”(在装备孔有重复名称的情况下)。
|
||||||
|
* “穿脱时事件”的自动事件定义在`libs\core.js`,如需修改(例如改为检测“多少”而不是“有无”),请在插件“init”中直接修改`core.initStatus.autoEvents`。
|
||||||
|
* 这是一个一维数组,数组的每项为一个自动事件对象,对象的`symbol`属性含有`"equipEvent_"`时就表明这是一个穿脱时事件。您可以修改其触发条件以及执行内容的第一项(flag变化)。
|
||||||
|
* 装备对属性的影响原理:在穿脱装备时,会根据数值提升项和百分比提升项,分别调用`core.status.hero.xxx += yyy`和`core.addBuff('xxx', yyy)`这两个API(衰弱的附加和解除同理),而状态栏的显示值和战斗中的使用值则是`core.getStatus('xxx')`和buff值相乘再向下取整所得。
|
||||||
|
* 可以按Alt+0~9快速更换套装(在装备界面按这个组合键则是保存套装),手机端的Alt键在V2.8被提供为粘滞键,点击状态栏右下角的难度标签就能看到。
|
||||||
|
|
||||||
|
道具相关API请阅读[API列表](api)。
|
||||||
|
|
||||||
|
### 怪物属性
|
||||||
|
|
||||||
|
1. **手册ID:**【已弃用】,原本是V2.8以前用来不完全实现多朝向怪物和大型怪物缩略图的手段,现在建议全部用新增的“行走图朝向”属性和“单点图块不透明度设置”代替。
|
||||||
|
* 该项设置后怪物将在手册中不显示为原怪物而显示为对应的怪物ID(比“行走图朝向”更优先),如果对应ID的怪物在本楼层恰好也存在,那么看上去就像原怪物在手册中消失了(即RMXP魔塔的“伪装”属性),可以适当利用这一点。
|
||||||
|
* 漏怪检测(`hasEnemyLeft`)和阻激夹域(尤其是夹击)依然只会使用原怪物ID,如有需求,请使用“行走图朝向”属性。
|
||||||
|
2. **生命、攻防、金经:**如题,注意金经必须在“全塔属性”(快捷键B)中的“状态栏显示项”中也勾选才能真正被启用。持有幸运金币时打怪获得的金币翻倍,附加诅咒状态时打怪不获得金经。
|
||||||
|
3. **加点:**若全塔属性勾选了“加点”,则此项为正数时将作为与该怪物每场战斗胜利后传递给“公共事件——加点事件”的参数(即那里的`flag:arg1`,默认表示加点的倍率),您可以自行修改该事件。
|
||||||
|
4. **不可炸:**勾选后该怪物不会成为炸弹的目标,有阻击怪在场的情况下请务必给有(单点)战后事件的怪物(如机关门守卫和boss)勾选此项,否则玩家可能会偷梁换柱地炸掉该怪物并把阻击怪推过去打死来触发战后事件。不过V2.8.1起,炸弹的使用效果中提供了炸单点怪物触发战后事件的示例,可供利用。
|
||||||
|
5. **(批量)战前/战后事件:**V2.8新增,用于解决阻击怪等会移动的怪物不能使用地图上的战前/战后事件的问题。
|
||||||
|
* 两种战前事件都会在“撞击怪物、判定可以战胜、自动存档”后触发,这比起曾经的“覆盖触发器+天降强制战斗”对玩家更为友好。
|
||||||
|
* 批量战前事件默认晚于单点战前事件(修改需要复写函数),批量战后事件与单点战后事件的执行顺序可以直接在“脚本编辑——战后脚本”中调整,默认也是单点先执行。
|
||||||
|
* 两种战前事件在强制战斗时都不会触发,并且都早于支援怪的跳跃动画,因此捕捉怪的战前事件也不会触发。
|
||||||
|
* 两种战前事件中如果使用“立刻结束当前事件”就会取消战斗,必要时您可以利用这一点制作回合制战斗。
|
||||||
|
* 批量战前事件可以方便地制作“怪物先打一下勇士,再被勇士打死”的效果,同时延迟了怪物的消失,使得怪物根据“行走图朝向”转身后的效果能够来得及被看到。
|
||||||
|
* 批量战后事件可以方便地制作“被打败后立即变身为另一种怪物/路障,或掉落一种道具”的效果。
|
||||||
|
* 您新增加的类似先攻、破甲、净化、吸血、仇恨、固伤的属性可以使用战前/战后事件更便捷地实现并无视护盾,同时也避免了下面提到的各项value冲突的问题,但这样做的后果是不计入显伤,详见下面的解释。如果一定要这样实现,建议通过额外的说明提醒玩家。
|
||||||
|
6. **特殊属性:**一个由正整数组成的一维数组,您可以点击“多选框编辑”按钮来修改它。所有特殊属性都定义在“脚本编辑——怪物特殊属性”,您可以在那里追加新的。它们大体分为四类:
|
||||||
|
1. 手册中属性值的修正:(按照结算顺序)模仿、坚固、光环,修正后的属性也将被用于战斗,详见“脚本编辑——怪物真实属性”。
|
||||||
|
2. 战损的修正:这类最多,先攻、魔攻、连击(次数为n)、破甲(比例为defValue)、反击(比例为atkValue,回合数为勇士的攻击回合数)、净化(倍数为n,1表示单纯无视护盾)、吸血(比例为value,是否加到自身为add)、仇恨(每场战斗的仇恨增值由全塔属性指定)、无敌、固伤(数值为damage)、支援。其中又以仇恨和固伤不能被护盾直接抵消而和无敌较为特殊,详见“脚本编辑——战斗伤害信息”。
|
||||||
|
3. 战后的影响:中毒、衰弱、诅咒、仇恨(的累加和减半)、自爆、退化(扣减值分别为atkValue和defValue)、重生,详见“脚本编辑——战后脚本”和“脚本编辑——毒衰咒处理”。由于上面的“批量战前/战后事件”的存在,这种技能不再推荐由脚本实现。
|
||||||
|
4. 阻激夹域捕捉:即对主角行走的妨害,详见“脚本编辑——阻激夹域伤害”,该函数也负责了血网(图块ID为lavaNet,请勿修改)的伤害。
|
||||||
|
|
||||||
|
您会发现一个可怕的事情,那就是上述1和2会影响显伤,而3(例如自爆)和战前/战后事件不会(且后者对勇士生命的影响默认是无视护盾的)。这对于魔塔来说可能是致命的,毕竟没有玩家想看到“一个明明显伤不是红色的怪物打了却会死”等情况。当然,也有一种办法是像flash版新新魔塔1和2那样“战斗过程带有随机性或QTE,手册只显示预估伤害”,这种设计模式在rm和h5魔塔界并不常用,请谨慎尝试。
|
||||||
|
|
||||||
|
支援怪在进行支援时(护盾默认只计算一次)不具有坐标(类似天降强制战斗),因此不支持V2.8的“定点设置怪物属性”,同时基于坐标的一些判定也会失效。
|
||||||
|
|
||||||
|
发生支援时,金经(以及加点塔的加点值)都会累加,但加点事件只执行一次,也就是这次所有点数必须加在同一项属性,如需修改,请修改公共事件。
|
||||||
|
|
||||||
|
阻激域的伤害都为value且在夹击之前结算,领域的形状和半径与光环一致。如果需要更复杂的形状(如米字形激光),请自行研究该函数。
|
||||||
|
|
||||||
|
V2.8起,阻击和捕捉支持九宫格形状,由zoneSquare属性指定。如需实现九宫格夹击,请仿照它们。阻击默认可以推到已隐藏的事件处,如需禁止,请修改所在的函数。
|
||||||
|
|
||||||
|
您甚至可以给同一种怪物设置阻击和捕捉,那么它会先后退然后在原位置留下一个残影和勇士战斗,从而起到刷金经的作用。
|
||||||
|
|
||||||
|
样板的光环是作用于怪物的,如果想制作作用于勇士的光环,需要注意缓存问题以免卡顿。
|
||||||
|
|
||||||
|
可以看到,怪物属性中有很多值是彼此互相冲突的。请自行注意,比如设计新属性时分散给各项而不要都吊死在三个value上。最后介绍一些和怪物相关的API:
|
||||||
|
``` js
|
||||||
|
core.status.hero.flags.no_repulse = true; // 禁用阻击,包括伤害和后退效果
|
||||||
|
core.status.hero.flags.no_laser = true; // 禁用激光
|
||||||
|
core.status.hero.flags.no_betweenAttack = true; // 禁用夹击
|
||||||
|
core.status.hero.flags.no_zone = true; // 禁用领域
|
||||||
|
core.status.hero.flags.no_ambush = true; // 禁用捕捉
|
||||||
|
core.getItem('amulet'); // 禁用血网等路障
|
||||||
|
core.setEnemy('greenSlime', 'atk', 100); // 设置怪物属性,并计入存档
|
||||||
|
core.getDamageString(enemy, x, y, floorId); // 获取某只怪的地图显伤字符串和颜色
|
||||||
|
core.getCurrentEnemys(floorId); // 获取某层楼的(映射后)怪物列表,按战损递增排列
|
||||||
|
core.hasEnemyLeft(enemyId, floorId); // 漏怪检测,两个参数都允许使用一维数组
|
||||||
|
core.hasSpecial(special, test); // 检测special是否有test这一个特殊属性
|
||||||
|
```
|
||||||
|
|
||||||
|
如果您想在数据区的表格中追加新的属性项,或修改已有项的格式、范围和长短注释,请点击数据区顶部的“配置表格”按钮,并参照已有的项去追加和修改,具体可查阅[修改编辑器](editor)。
|
||||||
|
|
||||||
|
至此您会发现,每种怪物不管出现在地图上的什么地方,其属性都是一样的(除非受到局部光环的影响)。所幸的是,V2.8提供了读写/移动单点怪物属性的API和事件,这种属性会在“脚本编辑——怪物真实属性”中最早生效,战后脚本中重置。此外,因受此属性或光环影响而导致同层多个同种怪物在手册中的数值不一致时,玩家可以选择作为多种怪物分别显示(包括具体坐标),详见游戏中的Esc菜单。
|
||||||
|
|
||||||
|
## 楼层属性(快捷键V)
|
||||||
|
|
||||||
|
![image](img/floor.jpg)
|
||||||
|
|
||||||
|
V2.7.1起,楼层支持使用最大128×128的超大地图,您可以在地图区下方点击“大地图”按钮切换到全景进行绘制,或在1:1模式下使用wsad进行视角移动。
|
||||||
|
|
||||||
|
V2.8.1新增了怪物的大贴图绑定,编辑器1:1模式下大贴图会被缩小到一格中,您可以点击“大地图”按钮让大贴图按原始尺寸绘制。
|
||||||
|
|
||||||
|
1. **楼层ID:**`project/floors`中的文件名,不允许使用中文也不能直接修改。修改方法见上图底部,修改后必须立即刷新浏览器页面,该项需要注意大小写问题。另外,作品发布时会统计楼层总数,如果有一些剧情层或连载塔半成品层不希望被统计进去,请使用“sample”+纯数字楼层ID即可,就像三个样板层一样。
|
||||||
|
2. **楼层名:**楼层在楼传、上下楼黑屏和浏览地图界面的名称。
|
||||||
|
3. **状态栏显示:**勇士在此楼层时状态栏左上角“上楼梯”图标右边的文字,允许使用中文,但请注意控制字数。
|
||||||
|
4. **地图宽度和高度:**可在表格最下方修改。
|
||||||
|
* 如果地图被加宽或加高,则“偏移”表示右移或下移的格子数(左边缘或上边缘用空格子填补)。
|
||||||
|
* 如果地图被减窄或减矮,则“偏移”表示左移或上移的格子数(被移出左边缘或上边缘的图块将丢失)。
|
||||||
|
* 在大地图中,您可能需要对地图中的绝对坐标和视野中的相对坐标进行互相转换。这需要用到`core.bigmap`的以下几个属性:
|
||||||
|
* `core.bigmap.width`和`core.bigmap.height`表示大地图的宽高(单位为格子)。
|
||||||
|
* `core.bigmap.offsetX`和`core.bigmap.offsetY`表示视野左上角在大地图中的绝对像素坐标,再除以32就是格子坐标。
|
||||||
|
* 因此,对于任意绝对坐标(x,y),它在视野中的相对坐标就是(x-offsetX/32,y-offsetY/32)。
|
||||||
|
* 在大地图的非边缘区域,`hero.loc.x-core.bigmap.offsetX/32`和`hero.loc.y-core.bigmap.offsetY/32`总是都为6或7(样板尺寸的一半)。
|
||||||
|
* 反之,已知相对坐标求绝对坐标就用加法,请举一反三。
|
||||||
|
5. **几个勾选框:**
|
||||||
|
* 可楼传飞到:如果不勾选,则此楼层禁止成为楼传的目标楼层。该项的具体原理见“脚本编辑——flyTo楼层飞行”。
|
||||||
|
* 可楼传飞出:如果不勾选,则勇士在此楼层禁止使用楼传。
|
||||||
|
* 快捷商店:如果不勾选,则勇士在此楼层禁止快捷使用全局商店。事件中的“启用全局商店同时打开”不受影响,详见“插件编写——全局商店”。
|
||||||
|
* 不可浏览:如果勾选,则此楼层无法通过滚轮/PageUp/PageDown键浏览,也不会计入全塔B键数据统计。
|
||||||
|
* 不可瞬移:如果勾选,则勇士在此楼层无法用E键和点击瞬移,常用于用自动事件去监听勇士坐标时,或者需要严格的计步属性时(如每走10步失去一把黄钥匙)。
|
||||||
|
* 是否是地下层:如果勾选,则楼传非平面模式下(或平面模式下该层还没有锚点时)勇士在此楼层原地使用楼传会传送到上楼点,详见“脚本编辑——楼层飞行”。
|
||||||
|
6. **首次到达事件、每次到达事件:**如题,详见“脚本编辑——切换楼层后”。每层的这两个事件是共用独立开关的,常见用法有“进入新区时清空已到达的楼层列表,只保留当前楼层”从而实现勇士不能再飞回从前区域的效果。
|
||||||
|
7. **并行处理脚本:**一个字符串,为勇士在此楼层时浏览器每帧都会执行一次(eval)的脚本,最快每秒60次。一般用来制作一些定时特效如bgs、bgv,详见“脚本编辑——并行脚本”。
|
||||||
|
8. **上下楼点:**两个自然数构成的一维数组,将作为“楼层转换”事件(在地图上以图块左下角出现绿色小方块作为标记)和“楼层切换”指令中“上下楼梯”以及非平面楼传的目标坐标。
|
||||||
|
* 如果不设置,则在传送时会尝试从地图中搜索上下楼梯图块。因此当某个楼层没有楼梯或有多个楼梯时(如《[新新魔塔](http://h5mota.com/games/xinxin/editor.html)》),请务必设置这个属性。点击“编辑”按钮从地图选点即可。
|
||||||
|
9. **楼传落点:**格式和设置方法同上。如果设置了此项,则楼传在此层的落点将强制变为该点,无视平面模式下的离开点和上面的上下楼点以及该层实际的楼梯位置。
|
||||||
|
10. **地面图块:**可以填写任何一个图块ID(需要加引号)或数字(如1是墙6是冰块),此项也会作为手册和剧情对话中的帧动画背景。填写tileset时可直接填写数字,不需要带字母X和引号。
|
||||||
|
11. **色调:**一行四列的数组,前三项为小于256的自然数(分别表示红、绿、蓝),最后一项为0到1的浮点数表示不透明度,可以点击“编辑”按钮用调色器调色。
|
||||||
|
* 值得一提的是,很多事件也以颜色作为参数,这些都是可以使用调色器调色的。
|
||||||
|
12. **天气:**一行两列的数组,第一项为字符串“rain”、“snow”、“fog”、“cloud”、“sun”,第二项为不大于10的正整数,分别表示1—10级的雨天(雨丝数量和等级正相关)、雪天(由大小不一的白色实心圆组成,详见样板1层,雪花的数量和横飘速度和等级正相关)、雾天、多云、晴天(左上角洒下金色的阳光)。
|
||||||
|
* 色调层在天气层上方、UI层下方(如不透明色调会遮盖天气,浏览地图看不到色调),关于图层的详细说明,参见“个性化”
|
||||||
|
13. **背景音乐:**如题,当在游戏中触发楼层切换时(包括读档),如果`flag:__color__、flag:__weather__、flag:__bgm__`没有值,游戏当时的画面色调、天气、背景音乐就会变为楼层属性中的这三个设置项,详见“脚本编辑——切换楼层中”。
|
||||||
|
* V2.8起,该项允许多选,会随机播放其中一个。
|
||||||
|
14. **宝石血瓶效果:**如题,必须填写且必须为正数。此项的用法为`core.status.thisMap.ratio`,请参考四种血瓶和三种宝石的捡拾效果。
|
||||||
|
* V2.8起,手册中的“1防”改为“加防”,表示加ratio点防御能减伤多少。
|
||||||
|
* 您还可以将其用于其他各种场合作为系数,如血网的伤害、中毒后每步的损血等。
|
||||||
|
15. **楼层贴图:**【V2.8.1起】,怪物和npc可以直接绑定4帧大贴图了,因此楼层贴图主要用于布景。
|
||||||
|
* 由于样板提供的图块只有32×32px和32×48px两种尺寸,且后者只能画在事件层,每个图块最多只能有4帧,因此对于一些多帧大图块十分不便,如npcs.png中被大卸八块的魔龙和章鱼。
|
||||||
|
* 你可以使用“楼层贴图”,该项允许您使用任何尺寸、任何帧数的素材,唯一的缺点是不支持移动跳跃和淡入淡出效果。
|
||||||
|
* 点击“编辑”按钮进入事件编辑器,每张图片的写法为(可从入口方块拖出,然后双击预览第一帧的效果):
|
||||||
|
1. 图片名(name):如题,图片需要放在`project/images`文件夹并注册。
|
||||||
|
2. 翻转(:x/:y/:o):您可以对贴图的每帧进行三种翻转,当然,帧顺序在原图中依然是从左到右的。
|
||||||
|
3. 图层(bg/fg/auto):此项决定贴图绘制在哪个图层,您可以全部画在背景层或前景层。也可以选择“自适配”让贴图的上半部分画在前景层,下半部分画在背景层,比如树木等。如果选择了自适配,最好让下面的绘制坐标和宽高都是32的倍数。
|
||||||
|
4. 绘制坐标(x,y):贴图在地图中的左上角像素坐标,譬如x和y都填32则表示贴图左上角和“地图左上角格子的右下角”重合。
|
||||||
|
5. 初始禁用(Y/N):如果勾选了此项,则此贴图初始时不显示,您可以在事件中再将其显示出来。
|
||||||
|
6. 裁剪起点坐标(x,y)和宽高(w,h):此项规定了贴图在按帧切分前从原图中取哪一部分,x和y为所取部分在原图中的左上角坐标(不填视为两个0),w和h为所取部分的宽高(不填表示一直取到右下角)。
|
||||||
|
7. 帧数(frame):不填视为1,如果填写了大于1的整数,就会把上述裁剪得到的结果再从左到右等分为若干份,并在实际绘制时从左到右逐帧(可能还带有翻转)循环绘制,每帧的持续时间和其他图块一致。
|
||||||
|
* 贴图本身只具有观赏性,您仍然需要使用空气墙等手段去控制其绘制区域各个点的通行性。
|
||||||
|
* 在使用贴图来表现魔龙和章鱼这类大型怪物时,需要预先准备一种32×32(2帧)或32×48(4帧)的行走图,并注册为怪物,放在地图上时指定该点的不透明度为0,最后在该点的战后事件中隐藏贴图即可。
|
||||||
|
* 你可以在插件中复写`drawBg`和`drawFg`函数以控制贴图和图块的绘制顺序(默认先绘制图块),详见[脚本](script)。
|
||||||
|
``` js
|
||||||
|
////// 绘制背景层 //////
|
||||||
|
core.maps.drawBg = function (floorId, ctx) {
|
||||||
|
var onMap = ctx == null;
|
||||||
|
if (onMap) {
|
||||||
|
ctx = core.canvas.bg;
|
||||||
|
core.clearMap(ctx);
|
||||||
|
}
|
||||||
|
this._drawBg_drawBackground(floorId, ctx);
|
||||||
|
// ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块
|
||||||
|
// 后绘制的覆盖先绘制的。
|
||||||
|
this._drawFloorImages(floorId, ctx, 'bg');
|
||||||
|
this._drawBgFgMap(floorId, ctx, 'bg', onMap);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 全塔属性(快捷键B)
|
||||||
|
|
||||||
|
全塔属性共分为四部分:文件注册、初始勇士、全局数值、系统开关,您可以随时折叠其中任何一个部分。
|
||||||
|
|
||||||
|
![image](img/firstdatab.jpg)
|
||||||
|
|
||||||
|
### 文件注册
|
||||||
|
|
||||||
|
这部分基本上都是经由多选框半自动完成的,下面逐一讲解:
|
||||||
|
1. **楼层列表:**`project/floors`文件夹中的文件名(不含后缀,但请务必注意大小写问题),此数组的顺序决定了楼传和上下楼器(fly、upFly、downFly)的顺序。
|
||||||
|
* 如果您不慎将勇士的出生楼层注销了或不慎删除了某些楼层的js文件,导致编辑器页面打开后一片白屏,请手动打开`project/data.js`去小心地修改floorIds以和实际的文件名相匹配,并将出生楼层改为一个存在的楼层。
|
||||||
|
* 其他更复杂的白屏请在控制台根据报错信息(安卓手机则使用ES文件浏览器查看日志)去小心地修改文件(如某个楼层文件有问题则可以注销它),如果难以独立解决,欢迎加QQ群959329661寻求帮助。
|
||||||
|
2. **分区指定:**一行两列的数组,可以使用事件编辑器编辑,每行表示塔的一个区域,该行的两列分别表示该区域的第一层和最后一层(后者不填表示到塔顶),不在任何区域的楼层组成公区。
|
||||||
|
* 游戏中,除了公区和勇士当前所在区以外的地图都将处于冻结状态,无法被浏览、无法被飞到、无法触发其自动事件、地图中的图块无法被读写。冻结状态的地图不会存入存档,从而节约了存档大小并加快了存读档速度,上百层的高层塔必备!
|
||||||
|
3. **使用图片、图片切分:**`project/images`文件夹中的文件名(需要后缀,必须全英数),单击“编辑”按钮,编辑器会自动扫描文件系统中格式合适的图片(如jpg、png和gif)。
|
||||||
|
* 您可以预览并将需要的图片勾选。请注意,勇士的初始行走图必须在这里注册。另外,`winskin.png`只许替换为相同规格的图片而不要注销,否则道具商店等插件无法正常绘制。
|
||||||
|
* V2.8.1起,新增了“图片切分”功能,用于代替插件init中的“资源加载后操作”函数。
|
||||||
|
* 该项最核心的用法就是将4行4列共16块的大型行走图切成4行,绑定给没有朝向关系的独立图块。
|
||||||
|
* 具体用法是,指定一张宽高分别为mw和nh的图片(限png格式),按照w和h的单位宽高裁剪,就会得到mn个小图。
|
||||||
|
* 每个小图会被重命名为您指定的前缀+数字(从0起),按正常的Z字形文本顺序逐行从左到右编号。
|
||||||
|
4. **额外素材:**`project/tilesets`中的文件名(需要后缀,只支持png)。
|
||||||
|
* 注册方法同上,最大的区别在于这个数组的顺序必须保持好。如果随意调换其中的顺序,或注销不在数组末尾的图片,就会导致地图上最终呈现的素材发生错位。因此,新勾选的图片总会被自动追加到数组的末尾。
|
||||||
|
* 比起常规素材,额外素材最大的好处有几点:
|
||||||
|
1. 图片没有数量限制。常规素材的总数量最多只允许不到10000个,而额外素材每张图片上的图块数量最多允许3000个。
|
||||||
|
2. 查看和绘制更为方便。常规素材每个图块独占一行,每列为一帧,导致不方便观察,且用多个图块拼图必须逐图块绘制。额外素材都是静止的,所以每个图块只占一格,多个图块可以在准备素材时就直接以相邻的样子绘制在同一张图片上,绘制地图时直接从素材区拖动来批量框选,再在地图区单击成片绘制或拖动平铺。
|
||||||
|
3. 批量替换也更为方便。譬如您如果想制作形如“一场大战/天灾过后/多年以后,村庄/城镇化为了一片废墟”的效果,可以预先准备两张甚至更多张相同规格的额外素材图片,然后在适当的时候遍历某个/某些楼层的图块ID,将以“X1”开头的图块统一替换为“X2”开头等。发布单机版游戏时,您也可以提供多张相同规格的额外素材图片供玩家直接替换皮肤风格。
|
||||||
|
5. **使用动画:**`project/animates`文件夹中的文件名(不含后缀,请注意与`animates.png`相区分)。
|
||||||
|
* 要使用动画,您可以使用“RM动画导出”工具从RPG Maker XP 1.03及其制作的游戏中导出动画,也可以用动画编辑器修改已有的动画或用图片新建。但这些办法都只适用于Windows系统,非Windows系统建议直接从我们的官网下载他人的作品取用其中的动画。
|
||||||
|
* 每个动画将其用到的各张图片直接以base64硬编码进同一个animate文件,每个动画为一个animate文件。这样做的缺点是如果多个动画使用了相同的图片那么图片会被重复存储,优点则是跨作品迁移动画更加方便。
|
||||||
|
* animate文件为json格式的文本文件,文件末尾记录了动画的帧信息,文件开头则记录了动画的伸缩比和所有音效(第几帧用什么音效,音调是多高)。
|
||||||
|
* 在导出动画时,会出现一个输入框并提示动画的第一音效文件名。不管该文件名是什么语言,请直接点击下一步。音效文件会被尝试自动复制,随后您只需手动注册该动画并在编辑器中预览,然后重新按帧绑定需要的音效。
|
||||||
|
* 可以使用如下动画相关的脚本对动画进行播放,或在事件中使用「播放动画」事件。
|
||||||
|
``` js
|
||||||
|
core.drawAnimate(name, x, y, alignWindow, callback);
|
||||||
|
// 播放一个动画,name为不带后缀的动画文件名,x和y为播放的格子坐标。
|
||||||
|
// alignWindow表示在大地图中该坐标是绝对坐标还是视野中的相对坐标,填true表示相对坐标。
|
||||||
|
// 相对坐标模式下,坐标应填写为小于13或15的自然数,譬如对13×13样板来说,
|
||||||
|
// 填两个6就会强制播放在视野中心。
|
||||||
|
// callback为动画播放完毕后(和音效无关)的回调函数,因为动画播放本身是异步的。
|
||||||
|
core.drawHeroAnimate(name, callback); // 和上面类似,但该动画会跟随勇士移动。
|
||||||
|
// 每场战斗后,都会根据怪物坐标尝试用前者播放普攻动画。若坐标不存在,
|
||||||
|
// (即天降强制战斗),则会尝试用后者播放。看上去就像勇士在打自己,请自行注意。
|
||||||
|
core.stopAnimate(id, doCallback); // 停止一段动画,id为上面两个函数的返回值,
|
||||||
|
// 第二个参数表示本次停止后是否依然执行前两个函数的回调
|
||||||
|
|
||||||
|
// 您可以用前两个函数回调自己,从而做到一个动画反复循环,
|
||||||
|
// 但务必要将每次的id记录在flags中,以便于第三个函数去停止它。
|
||||||
|
```
|
||||||
|
6. **使用音乐:**`project/bgms`文件夹中的文件名(需要后缀,默认只支持wav、mp3、ogg、m4a、flac)。
|
||||||
|
* 如果玩家使用的是手机且没有连接WiFi(iOS和部分浏览器无法获知网络状态,将始终视为流量党),那么背景音乐默认不会开启,可以在标题画面点击右下角的圆形按钮来开启。
|
||||||
|
* V2.8起,这个圆形的音乐开关旁边还增加了一个放大镜按钮,您可以在标题画面连续点击它来获得一个最佳的屏幕缩放倍率。
|
||||||
|
* 发布到官网的作品还可以从<https://dl.h5mota.com/music/>远程加载背景音乐,您可以点击此链接试听和下载其他作品的背景音乐。
|
||||||
|
* 是否启用远程加载、以及启用时远程加载的根目录,由main.js指定。因此从官网下载其他作品的离线版本后请先关闭远程加载功能,才能正常加载本地注册的音乐。
|
||||||
|
* 使用`core.material.bgms`可以查看所有的背景音乐,如果需要(不存档的情况下)同时播放多个背景音乐并独立控制时刻、音量、速度、音调,请直接对它们使用`play()`和`pause()`方法并按照下图操作。
|
||||||
|
* `duration`表示总时长(秒,音效也适用),`currentTime`表示当前进度(秒,可读写)
|
||||||
|
* `volume`表示音量(0到1),`playbackRate`表示播放的倍速
|
||||||
|
* `preservesPitch`表示变速播放时是否不变调,`false`表示变调
|
||||||
|
* V2.8起,背景音乐“变速不变调”和“变速且变调”、音效“变速且变调”正式作为样板API提供,同时也有对应的事件指令,这是样板在追逐RPG Maker上迈出的一大步,且行且珍惜!
|
||||||
|
* V2.8.1起,动画的音效支持“变速且变调”,同时编辑器中试听音频也支持了(但bgm试听不能只变速不变调)。
|
||||||
|
![image](img/bgm.jpg)
|
||||||
|
7. **使用音效:**`project/sounds`文件夹中的文件名(格式要求和写法同上)。
|
||||||
|
* V2.8起,音效的播放和动画一样会返回一个ID并且有回调。音效不能像bgm一样只变速不变调,而是必须一起变。
|
||||||
|
* 可以指定ID来提前停止某个音效(不指定ID则全部停止),但不能像停止动画时一样取消回调。
|
||||||
|
* 对玩家来说音效的音量和bgm是绑定的,无法分开调节。此外,样板没有背景音效(bgs、bgv)的默认实现。如有需求,请使用并行脚本或自我回调处理。
|
||||||
|
* 音乐和音效在使用多选框注册时都支持试听(但此时不支持变速变调),您可以看到它们的总时长和已播时长(精确到秒),从而指定音乐的开播秒数或配合使用“等待n毫秒”事件或并行脚本处理。
|
||||||
|
8. **使用字体:**project\fonts文件夹中的文件名(只支持ttf格式,不写后缀)。不建议给在线游戏版本添加中文字体,因为文件真的很大...
|
||||||
|
9. **文件别名:**如前所述,样板所有需要加载的文件名都必须全部是英文或数字。这一项允许你给文件取别名,别名可以使用任何语言的文字。
|
||||||
|
* V2.8起,此项改由事件编辑器编辑,同时样板新增了大量系统音效,可供灵活配置,务必善用!
|
||||||
|
10. **装备孔:**见“道具的装备属性”。
|
||||||
|
11. **标题音乐:**如题,请注意部分浏览器不会在刚打开某个页面时就播放音频,必须用户操作一下才行。
|
||||||
|
* V2.8起背景音乐支持“变速不变调”或“变速且变调”,但无法直接用于标题音乐。如有需求,请复写`core.control.showStartAnimate`函数。
|
||||||
|
12. **主样式:**一些css设置项,单击“编辑”按钮可以看到具体含义和用法,这里不再赘述(横竖屏标题画面背景支持gif动图)。
|
||||||
|
13. **游戏名:**标题画面和网页选项卡上显示的名字,可以和官网别的作品重名。
|
||||||
|
14. **唯一英文标识符:** **必须修改,且不得和官网别的作品重名**。只能使用字母数字下划线,如果您不确定一个标识符是否已被别的作品使用,请输入`https://h5mota.com/games/xxx`,如出现404就说明此名称未被使用。
|
||||||
|
15. **游戏版本:**当您的游戏发生版本更迭后,旧版本的存档直接读取可能会出现bug.因此届时您可以修改此项来让旧存档无法直接读取,只能回放其录像。
|
||||||
|
16. **难度分歧:**单击“编辑”按钮进入事件编辑器,每个难度分为以下几项。
|
||||||
|
* 名称:标题界面单击“开始游戏”后出现的二级菜单中的文字。一般为该难度的最简要的介绍,如减伤比例、初始福利等。
|
||||||
|
* 简写:横屏状态栏左下角(竖屏右下角,也作为数字键切换按钮)和存读档界面缩略图上的文字,也会出现在在线游戏排行榜和旧版官网的作品卡片上。允许使用中文但请注意控制字数,用`core.status.hard`表示。
|
||||||
|
* 变量hard值:即`core.status.hero.flags.hard`的值,建议填自然数。若同一结局有多个难度有人通关,则站点排行榜只统计其中此值最高的难度。请务必保证有一个难度的此值为0,理由见下。
|
||||||
|
* 颜色:上述“简写”的颜色,用`core.status.hero.flags.__hardColor__`表示,默认为红色。详见“脚本编辑——更新状态栏”。
|
||||||
|
* 事件:此事件比下述的“开场剧情”更早执行,一般用来设置初始福利。
|
||||||
|
* 如果将难度分歧数组留空,那么标题界面单击“开始游戏”就会直接进入开场剧情。
|
||||||
|
* 请注意,游戏中的难度分歧最好只通过`core.status.hero.flags.hard`进行,这是因为除非开启了标题界面事件化,否则`core.status.hard`是可以由玩家在标题画面任意指定的,而未定义过的难度的“变量:hard”值都会为0,这也是为什么上面要求务必要有一个难度指定0值的原因。
|
||||||
|
* 关于如何在标题画面任意指定难度和随机种子,参见[事件](event)一章的最底部。
|
||||||
|
|
||||||
|
### 初始勇士
|
||||||
|
|
||||||
|
1. **初始楼层、朝向和坐标:**如题,请注意初始楼层必须在上述的“楼层列表”中。初始坐标一般通过右击地图上的空地快速绑定,但您也可以手动在这里填写负数或超出初始楼层宽高的值。然后使用“无视地形移动勇士”或“跳跃勇士”等改变勇士位置的事件指令,做出“勇士从地图外进入”的演出效果。
|
||||||
|
* 如需根据难度分歧或用户选项等条件来改变它们,请在“开场剧情”中修改`core.firstData.floorId`和`core.firstData.hero.loc`
|
||||||
|
2. **行走图:**如题,必须在“使用图片”中注册过。宽高必须为4的倍数,宽度至少为128px(即每帧32px)。高度不限,剧情对话中和状态栏中会尝试保持比例压缩到每帧32px宽。
|
||||||
|
* 在游戏中,勇士当前的行走图文件名用`core.status.hero.image`表示(只读)
|
||||||
|
* 游戏中如需更改行走图请使用事件指令,但请注意它并不会计入站点录像验证,而且更换新行走图后如果立即重绘勇士就会重置视角。
|
||||||
|
3. **帧动画:**勾选此项后,勇士在剧情对话中(朝上视为朝下)和原地不动时会循环播放四帧踏步动画,一般用于长翅膀的勇士。但是勾选后会导致视角随时锁定在勇士身上,也就是说大地图中操作视角的指令都会失效!
|
||||||
|
4. **勇士名:**如题,也会作为剧情对话中`\t[hero]`的默认标题。
|
||||||
|
5. **初始等级:**如果开启了自动进阶功能,请不要修改此项。
|
||||||
|
6. **生命上限、魔力上限、初始生命/魔力/攻防/护盾/金经:**如题。注意生命上限和金经需要在系统开关中勾选后才会启用,魔力上限填负数代表没有上限。
|
||||||
|
7. **初始装备、游戏变量:**建议留空(事件中的变量初始时都会视为0,脚本中也有办法做到)。
|
||||||
|
8. **永久道具、消耗道具、初始拥有装备个数:**点击“注释”按钮,按照提示进行修改。
|
||||||
|
9. **标题事件:**需要配合系统开关中勾选“标题开启事件化”来使用,可以在“开始游戏”、“读取存档”之余添加一些额外的功能。如成就系统、音像和回想鉴赏,但这并不是唯一的方法,请自行研究。
|
||||||
|
* 使用此项的后果主要是开局的若干操作也会计入录像,观感较差。
|
||||||
|
* 使用此项后,难度分歧会通过用户操作(如显示选择项)实现,因而成为了录像的一部分。
|
||||||
|
* 使用此项后,会导致因为无法像默认标题一样做成长方形,只能采用“黑色边框+状态栏黑底+隐藏状态栏”的妥协方法得到一个偏右的正方形画面。
|
||||||
|
* V2.8提供了一个插件,可以将这个正方形画面暂时居中,这是没有办法的办法。
|
||||||
|
10. **开场剧情:**会在难度分歧事件之后执行,可以在这里设置各种变量的初始值、穿上初始拥有的装备、隐藏勇士和一些初始不希望显示的图层块、追加跟随者等。
|
||||||
|
* 需要注意的是,此时`core.status.floorId`还没有值,因此无法进行相关操作,包括存档!
|
||||||
|
11. **全局商店:**详见“QTE与全局商店”。
|
||||||
|
12. **等级提升:**需要配合系统开关中勾选“等级”、“经验”和“升级”来使用,每个等级分为以下几项:
|
||||||
|
* 需求:刷新状态栏时,如果勇士当前等级是此等级的前一级,且经验值大于等于此需求,就会触发升级。
|
||||||
|
* 称号:状态栏显示的等级默认是个正整数,会尝试替换为这里的称号(调用core.getLvName()函数),请注意控制字数。
|
||||||
|
* 是否扣除经验:如果勾选了此项,则触发升级时经验值会扣除需求值,这一项主要是为了应对一次升多级的情况。
|
||||||
|
* 事件:触发升级时执行的事件,如全面提升属性。事件中甚至可以降低等级,从而反复触发升同一级。
|
||||||
|
|
||||||
|
### 全局数值
|
||||||
|
|
||||||
|
![image](img/values_flagsb.jpg)
|
||||||
|
|
||||||
|
这个类型的数值会保存在core.values中,可以直接在游戏中修改。
|
||||||
|
|
||||||
|
1. **血网伤害和中毒伤害:**如题,如果不想用常数,请修改“脚本编辑”的“阻激夹域伤害”和“每步后操作”。
|
||||||
|
2. **衰弱效果:**填小于1的正数代表扣减的比例,否则为扣减的常数。扣减和恢复的原理和装备相同,详见“脚本编辑——毒衰咒处理”。
|
||||||
|
3. **三种宝石和四种血瓶的值:**如题,此值为基础值。实际效果还需乘以楼层属性最下面的“宝石血瓶效果”(限即捡即用类,详见这七种道具的属性)。
|
||||||
|
4. **反击、破甲、净化比例:**如果反击、破甲、净化怪物没有指定atkValue、defValue和n,就会用这三个值。请注意反击的总回合数是勇士的攻击回合数,净化比例填1表示单纯无视护盾。
|
||||||
|
5. **仇恨增值:**每进行一场战斗,core.status.hero.flags.hatred的增加量。如果不想用常数,请修改“脚本编辑——战后脚本”。
|
||||||
|
6. **全局帧动画时间:**即怪物和NPC的振动速度,建议改为300毫秒。
|
||||||
|
7. **竖状态栏自绘行数:**需要配合系统开关“开启自绘状态栏”使用,建议改为4.
|
||||||
|
8. **楼层切换时间:**此项可被玩家更改,单位为毫秒,必须为0.1秒的0到10倍。
|
||||||
|
|
||||||
|
如有需求,可以把勇士移速(可被玩家更改)通过“配置表格”追加到这里,并修改`libs\core.js`对上述数值的初始化行为。
|
||||||
|
|
||||||
|
### 系统开关
|
||||||
|
|
||||||
|
这个类型的开关会保存在core.flags中(只读),请注意和core.status.hero.flags相区分。如需在游戏中修改,请使用“设置系统开关”事件。
|
||||||
|
|
||||||
|
1. **状态栏显示项:**如题,总个数请控制在12个以内,否则竖屏可能塞不下。
|
||||||
|
* 这些项的图标都在`project\materials\icons.png`中。该文件夹下也提供了一个`icons_old.png`可供替换。
|
||||||
|
* “血限”、“金币”和“经验”必须勾选才会启用(指会处理生命溢出、金经会显示在手册、打怪也会掉落),
|
||||||
|
* 必须勾选“升级”才会启用自动进阶,“升级扣除模式”如果不勾选就会同时显示下一级的需求(NEXT)和当前经验(EXP,如果勾选了的话),否则会只显示两者的差(依然用NEXT图标),“扣除”这个词用的不太好。
|
||||||
|
* 如果同时勾选了“钥匙”和“绿钥”,则每种钥匙的数量都会被缩小尺寸显示,因此如非必要请不要勾选“绿钥”。
|
||||||
|
2. **楼传需在楼梯边:**如果勾选了此项,则只有勇士在楼梯旁边(包括六种portal旁边)时才允许使用楼传。
|
||||||
|
* 请注意,此项是在楼传道具使用条件之外额外进行的判定,目的是给出不同的提示信息。
|
||||||
|
* 因此如果您要修改楼传的使用条件(指总的使用条件,具体能否飞到某层的条件则在“脚本编辑——楼层飞行”),则可能需要一并取消此勾选。
|
||||||
|
* V2.8起,浏览地图界面按下G键或点击楼传图标则会尝试飞到当前正在浏览的楼层,这大大方便了玩家。
|
||||||
|
3. **楼传开平面模式:**如果勾选了此项,则勇士在使用楼传飞往某层时会落在上次离开该层的位置(锚点)。
|
||||||
|
* 此项和上一项建议要么同时勾选要么同时不勾选,同时勾选以后建议在“脚本编辑——flyTo楼层飞行”中禁止同层传送,否则可能有意想不到的后果。
|
||||||
|
* 楼层切换过程中可以根据局部变量`fromLoad`和`isFlying`进行不同的处理(例如播放不同的音效),前者表示是否为读档,后者表示是否为楼传,建议由这两者导致的楼层切换一律不更新锚点。
|
||||||
|
4. **铁门不消耗钥匙:**如果勾选了此项,则铁门无需钥匙也能撞开。【不建议使用!】因为此项会导致撞铁门时不再自动存档,因此建议修改铁门的`doorInfo`属性将钥匙栏留空即可。
|
||||||
|
5. **首次道具进行提示:**勾选后,首次捡到非即捡即用类道具都会弹窗提示(晚于`afterGetItem`事件被执行)。
|
||||||
|
6. **状态栏装备按钮:**勾选后,状态栏的楼传按钮会变为装备栏按钮,但玩家仍然可以双击道具栏来呼出装备栏。
|
||||||
|
7. **加点:**勾选后怪物的加点值会在“脚本编辑——战后脚本”中作为参数`core.status.hero.flags.arg1`被传递给“公共事件——加点事件”。
|
||||||
|
8. **负伤:**勾选后,战斗结束时如果勇士的护盾没有完全被打破,则剩余的护盾值会加到其生命上。所以勾选此项后,护盾可以这样“间接”抵消掉仇恨伤害和固伤。
|
||||||
|
9. **夹击不超伤害值:**勾选此项后,夹击伤害将封顶至夹击怪的战损。同时被四只怪夹击时,取两个战损中较小的。
|
||||||
|
10. **二分临界:**我们知道,打败怪物所需的回合数,取决于勇士的攻击减去怪物的防御。这个值并非每增大1都能使回合数减少,因而有了“临界”的说法,即“再至少增加多少攻击力,才能减少回合数”。然而,当您修改“脚本编辑——战斗伤害信息”函数后,攻击力的增加可能反而导致回合数也增加,于是临界值计算出错。您可以勾选此开关来修复,代价是游戏可能较卡,请自行权衡。
|
||||||
|
* 目前样板的临界只有回合数法和二分法被真正采用,而循环法则只是保留了代码。如需启用,请修改`main.js`中“循环临界的分界”。
|
||||||
|
* V2.8.1起,未破防怪的临界表中,第一项将用负数表示刚刚破防时的伤害,后面的项正常表示破防后再加攻击的减伤。
|
||||||
|
11. **标题开启事件化:**勾选此项后,标题画面将改为执行前述的“标题事件”,请自行研究,V2.8起建议配合“标题事件居中”插件。
|
||||||
|
12. **开启自绘状态栏:**勾选此项后,状态栏将改用“脚本编辑——自绘状态栏”来绘制,同时“脚本编辑——点击状态栏”也将启用,请自行研究。
|
||||||
|
13. **四个显伤:**略,玩家依然可以在设置菜单中开关之,其中“定点怪显”指的是手册中把受到额外影响而数值不同的同种怪物分开显示。
|
||||||
|
14. **允许轻按:**勾选此项后,玩家可以按下空格/大键盘数字7/双击勇士来拾取相邻的道具(优先面前)。
|
||||||
|
15. **允许穿透楼梯:**在狭窄的区域拦路放置一个可通行的“楼层转换”事件时(图块左下角出现绿色标记),玩家可能希望勇士能直接走过去。您可以逐个去设置其能否被这样走过,或者让其依据本勾选项。
|
||||||
|
* 值得注意的是,当玩家从允许穿透的楼梯向一个不可走的方向(如旁边是墙,或不勾选下一个开关时的致命领域)手动寻路时,可以停在楼梯上(进而再轻按拾取周围物品等)。不建议您利用这类极端情况去迫使玩家进行非常规操作,毕竟穿透楼梯和不能踏入致命领域的本意是方便玩家,不是么?
|
||||||
|
* 确切地说,即使旁边没有障碍物,录像意义下勇士也一定能停在任何可穿透的楼梯上。因此笔者再次强调,非常不建议利用这样的盲点戏弄玩家。
|
||||||
|
16. **允许将死领域:**“脚本编辑——阻激夹域伤害”会将地图中每个点的阻激夹域和血网伤害加总,如果不勾选此开关,则当勇士生命小于等于相邻空格子的总伤害(没有伤害则没关系)时,勇士无法走向该格子。
|
||||||
|
* 值得注意的是,这种判定方式并没有考虑“走这一步后、结算该点伤害前”可能的加血或该点伤害变化,因此如有必要可根据“脚本编辑——每步后操作”去修改core.canMoveHero()函数。
|
||||||
|
* 另外,此项的保护作用只会在“勇士可以自由行动”(`core.status.lockControl`为`false`)时生效,因此事件中(滑冰等场合)踩到致命领域仍然会死亡。
|
||||||
|
17. **允许瞬移:**若不勾选此开关,将全程禁用瞬移功能。一般只建议在需要的楼层逐层勾选禁止瞬移,或通过`flags.cannotMoveDirectly`来控制。
|
||||||
|
18. **录像折叠:**勾选此项后,将开启录像折叠功能。录像折叠将尽可能优化掉在一个地方无意义的行走,从而减少录像长度并提升播放观感。
|
||||||
|
* 当经过一段时间的行走、转向和瞬移后,若勇士的坐标、朝向和状态(步数除外)和之前某个时刻完全相同,那么将会直接删除这中间的录像记录。
|
||||||
|
* 当中毒状态、触发任何系统或自定义事件、图块脚本、楼层切换、受到阻激夹域伤害等等时,将清除录像折叠信息。
|
||||||
|
* 请注意:录像折叠将会优化步数,所以如果游戏和步数有直接关系(比如步数算分)请关闭录像折叠功能。另外,如果你的塔存在楼层并行脚本且对游戏数据有直接影响,也请关闭录像折叠功能。
|
||||||
|
19. **伤害禁用商店:**勾选此项后,每当勇士踩到阻激夹域和血网并受到伤害时,所有全局商店都将被禁用,需要重新去启用(譬如勇士去撞击该商店的实体NPC)。
|
||||||
|
20. **虚化前景层:**前景层会遮挡事件层,这对魔塔来说有时可能不太友好。勾选此项后,事件层有东西(如道具)时将虚化该格子的前景层,使得该东西以半透明状态可见。
|
||||||
|
* 出于布景需要,某点事件层为自动元件或tileset时,必须有“碰触脚本/碰触事件”,该点前景层才会虚化。
|
||||||
|
|
||||||
|
上面就是整个样板中的各个元件说明。通过这种方式,你就已经可以做出一部没有任何事件的塔了。
|
||||||
|
|
||||||
|
尝试着做一个两到三层的塔吧!
|
||||||
|
|
||||||
|
==========================================================================================
|
||||||
|
|
||||||
|
[继续阅读下一章:事件](event)
|
258
enemy.d.ts
vendored
Normal file
258
enemy.d.ts
vendored
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
type PartialNumbericEnemyProperty =
|
||||||
|
| 'value'
|
||||||
|
| 'zone'
|
||||||
|
| 'repulse'
|
||||||
|
| 'laser'
|
||||||
|
| 'breakArmor'
|
||||||
|
| 'counterAttack'
|
||||||
|
| 'vampire'
|
||||||
|
| 'hpBuff'
|
||||||
|
| 'atkBuff'
|
||||||
|
| 'defBuff'
|
||||||
|
| 'range'
|
||||||
|
| 'haloRange'
|
||||||
|
| 'n'
|
||||||
|
| 'purify'
|
||||||
|
| 'atkValue'
|
||||||
|
| 'defValue'
|
||||||
|
| 'damage';
|
||||||
|
|
||||||
|
type BooleanEnemyProperty =
|
||||||
|
| 'zoneSquare'
|
||||||
|
| 'haloSquare'
|
||||||
|
| 'notBomb'
|
||||||
|
| 'add'
|
||||||
|
| 'haloAdd'
|
||||||
|
| 'specialAdd';
|
||||||
|
|
||||||
|
type DetailedEnemy<I extends EnemyIds = EnemyIds> = {
|
||||||
|
specialText: string[];
|
||||||
|
toShowSpecial: string[];
|
||||||
|
toShowColor: Color[];
|
||||||
|
specialColor: Color[];
|
||||||
|
damageColor: Color;
|
||||||
|
criticalDamage: number;
|
||||||
|
critical: number;
|
||||||
|
defDamage: number;
|
||||||
|
} & Enemy<I>;
|
||||||
|
|
||||||
|
type Enemy<I extends EnemyIds = EnemyIds> = {
|
||||||
|
/**
|
||||||
|
* 怪物id
|
||||||
|
*/
|
||||||
|
id: I;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物名称
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物说明
|
||||||
|
*/
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在怪物手册中映射到的怪物ID。如果此项不为null,则在怪物手册中,将用目标ID来替换该怪物原本的ID。
|
||||||
|
* 常被运用在同一个怪物的多朝向上
|
||||||
|
*/
|
||||||
|
displayIdInBook: EnemyIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行走图朝向。在勇士撞上图块时,或图块在移动时,会自动选择最合适的朝向图块(如果存在定义)来进行绘制。
|
||||||
|
*/
|
||||||
|
faceIds: Record<Dir, EnemyIds>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 战前事件
|
||||||
|
*/
|
||||||
|
beforeBattle: MotaEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 战后事件
|
||||||
|
*/
|
||||||
|
afterBattle: MotaEvent;
|
||||||
|
} & {
|
||||||
|
[P in PartialNumbericEnemyProperty]?: number;
|
||||||
|
} & {
|
||||||
|
[P in BooleanEnemyProperty]: boolean;
|
||||||
|
} & EnemyInfoBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物的特殊属性定义
|
||||||
|
*/
|
||||||
|
type EnemySpecialDeclaration = [
|
||||||
|
id: number,
|
||||||
|
name: string | ((enemy: EnemySpecialBase) => string),
|
||||||
|
desc: string | ((enemy: EnemySpecialBase) => string),
|
||||||
|
color: Color,
|
||||||
|
extra?: number
|
||||||
|
];
|
||||||
|
|
||||||
|
interface DamageString {
|
||||||
|
/**
|
||||||
|
* 伤害字符串
|
||||||
|
*/
|
||||||
|
damage: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 伤害颜色
|
||||||
|
*/
|
||||||
|
color: Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EnemySpecialBase {
|
||||||
|
/**
|
||||||
|
* 怪物特殊属性
|
||||||
|
*/
|
||||||
|
special: number[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特殊属性光环
|
||||||
|
*/
|
||||||
|
specialHalo?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EnemyInfoBase extends EnemySpecialBase {
|
||||||
|
/**
|
||||||
|
* 生命值
|
||||||
|
*/
|
||||||
|
hp: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 攻击力
|
||||||
|
*/
|
||||||
|
atk: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防御力
|
||||||
|
*/
|
||||||
|
def: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金币
|
||||||
|
*/
|
||||||
|
money: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经验
|
||||||
|
*/
|
||||||
|
exp: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加点量
|
||||||
|
*/
|
||||||
|
point: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EnemyInfo extends EnemyInfoBase {
|
||||||
|
/**
|
||||||
|
* 支援信息
|
||||||
|
*/
|
||||||
|
guards: [x: number, y: number, id: EnemyIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DamageInfo {
|
||||||
|
/**
|
||||||
|
* 怪物生命值
|
||||||
|
*/
|
||||||
|
mon_hp: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物攻击力
|
||||||
|
*/
|
||||||
|
mon_atk: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物防御力
|
||||||
|
*/
|
||||||
|
mon_def: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 先攻伤害
|
||||||
|
*/
|
||||||
|
init_damage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物的每回合伤害
|
||||||
|
*/
|
||||||
|
per_damage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的每回合伤害
|
||||||
|
*/
|
||||||
|
hero_per_damage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 战斗的回合数
|
||||||
|
*/
|
||||||
|
turn: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士损失的生命值
|
||||||
|
*/
|
||||||
|
damage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookEnemyInfo extends Enemy, EnemyInfo {
|
||||||
|
/**
|
||||||
|
* 怪物的坐标列表
|
||||||
|
*/
|
||||||
|
locs?: [x: number, y: number][];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物的中文名
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特殊属性名称列表
|
||||||
|
*/
|
||||||
|
specialText: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特殊属性的颜色列表
|
||||||
|
*/
|
||||||
|
specialColor: Color[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物的伤害
|
||||||
|
*/
|
||||||
|
damage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第一个临界的加攻的值
|
||||||
|
*/
|
||||||
|
critical: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 临界的减伤值
|
||||||
|
*/
|
||||||
|
criticalDamage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ratio防减伤
|
||||||
|
*/
|
||||||
|
defDamage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物模块
|
||||||
|
*/
|
||||||
|
interface Enemys {
|
||||||
|
/**
|
||||||
|
* 所有的怪物信息
|
||||||
|
*/
|
||||||
|
readonly enemys: {
|
||||||
|
[P in EnemyIds]: Enemy<P>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得所有怪物原始数据的一个副本
|
||||||
|
*/
|
||||||
|
getEnemys(): {
|
||||||
|
[P in EnemyIds]: Enemy<P>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const enemys: new () => Enemys;
|
232
enemys.js
Normal file
232
enemys.js
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
var enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80 =
|
||||||
|
{
|
||||||
|
"greenSlime": {"name":"史莱姆","hp":4,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"redSlime": {"name":"红头怪","hp":4,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"value":10,"faceIds":{"down":"E434","left":"redSlime","right":"blackSlime","up":"slimelord"}},
|
||||||
|
"blackSlime": {"name":"青头怪","hp":30,"atk":2,"def":1,"money":0,"exp":0,"point":0,"special":0,"faceIds":{"down":"E434","left":"redSlime","right":"blackSlime","up":"slimelord"}},
|
||||||
|
"slimelord": {"name":"怪王","hp":100,"atk":120,"def":0,"money":10,"exp":0,"point":0,"special":[],"faceIds":{"down":"E434","left":"redSlime","right":"blackSlime","up":"slimelord"}},
|
||||||
|
"bat": {"name":"小蝙蝠","hp":100,"atk":120,"def":0,"money":2,"exp":0,"point":0,"special":0},
|
||||||
|
"bigBat": {"name":"大蝙蝠","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"redBat": {"name":"红蝙蝠","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"vampire": {"name":"冥灵魔王","hp":888,"atk":888,"def":888,"money":888,"exp":888,"point":0,"special":0,"n":8},
|
||||||
|
"skeleton": {"name":"骷髅人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"skeletonCaptain": {"name":"骷髅队长","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"zombie": {"name":"兽人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"zombieKnight": {"name":"兽人武士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"rock": {"name":"石头人","hp":50,"atk":50,"def":0,"money":3,"exp":0,"point":0,"special":3},
|
||||||
|
"bluePriest": {"name":"初级法师","hp":100,"atk":120,"def":0,"money":3,"exp":0,"point":1,"special":[9]},
|
||||||
|
"redPriest": {"name":"高级法师","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"brownWizard": {"name":"初级巫师","hp":100,"atk":120,"def":0,"money":16,"exp":0,"point":0,"special":15,"value":100,"range":2},
|
||||||
|
"redWizard": {"name":"高级巫师","hp":1000,"atk":1200,"def":0,"money":160,"exp":0,"point":0,"special":15,"value":200,"zoneSquare":true},
|
||||||
|
"swordsman": {"name":"双手剑士","hp":100,"atk":120,"def":0,"money":6,"exp":0,"point":0,"special":4},
|
||||||
|
"soldier": {"name":"冥战士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"yellowKnight": {"name":"金骑士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"redKnight": {"name":"红骑士","hp":500,"atk":200,"def":50,"money":0,"exp":0,"point":0,"special":[7]},
|
||||||
|
"darkKnight": {"name":"黑骑士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"blueKnight": {"name":"蓝骑士","hp":100,"atk":120,"def":0,"money":9,"exp":0,"point":0,"special":8},
|
||||||
|
"goldSlime": {"name":"黄头怪","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"poisonSkeleton": {"name":"紫骷髅","hp":50,"atk":60,"def":70,"money":80,"exp":0,"point":0,"special":13},
|
||||||
|
"poisonBat": {"name":"紫蝙蝠","hp":100,"atk":120,"def":0,"money":14,"exp":0,"point":0,"special":13},
|
||||||
|
"skeletonPriest": {"name":"骷髅法师","hp":100,"atk":100,"def":0,"money":0,"exp":0,"point":0,"special":18,"value":20},
|
||||||
|
"skeletonKing": {"name":"骷髅王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"evilHero": {"name":"迷失勇者","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"demonPriest": {"name":"魔神法师","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"goldHornSlime": {"name":"金角怪","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"silverSlime": {"name":"银头怪","hp":100,"atk":120,"def":0,"money":15,"exp":0,"point":0,"special":14},
|
||||||
|
"whiteHornSlime": {"name":"尖角怪","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"redSwordsman": {"name":"剑王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"poisonZombie": {"name":"绿兽人","hp":100,"atk":120,"def":0,"money":13,"exp":0,"point":0,"special":[12]},
|
||||||
|
"octopus": {"name":"血影","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0,"bigImage":"dragon_1.png"},
|
||||||
|
"princessEnemy": {"name":"假公主","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"silverSlimelord": {"name":"银怪王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"goldSlimelord": {"name":"金怪王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"skeletonWarrior": {"name":"骷髅士兵","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"whiteSlimeman": {"name":"水银战士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"slimeman": {"name":"影子战士","hp":100,"atk":0,"def":0,"money":11,"exp":0,"point":0,"special":[9],"atkValue":2,"defValue":3},
|
||||||
|
"yellowGateKeeper": {"name":"初级卫兵","hp":100,"atk":120,"def":0,"money":10,"exp":0,"point":0,"special":0},
|
||||||
|
"blueGateKeeper": {"name":"中级卫兵","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"redGateKeeper": {"name":"高级卫兵","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"magicMaster": {"name":"黑暗大法师","hp":100,"atk":120,"def":0,"money":12,"exp":0,"point":0,"special":11,"value":0.3333333333333333,"add":true,"notBomb":true},
|
||||||
|
"devilWarrior": {"name":"魔神武士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"fairyEnemy": {"name":"仙子","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"dragon": {"name":"魔龙","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0,"bigImage":"dragon_0.png"},
|
||||||
|
"skeletonKnight": {"name":"骷髅武士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"skeletonPresbyter": {"name":"骷髅巫师","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"ironRock": {"name":"铁面人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"grayRock": {"name":"灰色石头人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"yellowPriest": {"name":"中级法师","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"evilPrincess": {"name":"痛苦魔女","hp":1000,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[10]},
|
||||||
|
"blademaster": {"name":"剑圣","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"evilFairy": {"name":"黑暗仙子","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"greenKnight": {"name":"强盾骑士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"bowman": {"name":"初级弓兵","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"watcherSlime": {"name":"邪眼怪","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"devilKnight": {"name":"恶灵骑士","hp":150,"atk":100,"def":50,"money":0,"exp":0,"point":0,"special":[1,5,7,8]},
|
||||||
|
"grayPriest": {"name":"混沌法师","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"greenGateKeeper": {"name":"卫兵队长","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"ghostSoldier": {"name":"冥队长","hp":200,"atk":100,"def":50,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"frostBat": {"name":"寒蝙蝠","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"blackKing": {"name":"黑衣魔王","hp":1000,"atk":500,"def":0,"money":1000,"exp":1000,"point":0,"special":0,"notBomb":true},
|
||||||
|
"yellowKing": {"name":"黄衣魔王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"greenKing": {"name":"青衣武士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"redKing": {"name":"红衣魔王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"blueKing": {"name":"白衣武士","hp":100,"atk":120,"def":0,"money":17,"exp":0,"point":0,"special":16},
|
||||||
|
"keiskeiFairy": {"name":"铃兰花妖","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"tulipFairy": {"name":"郁金香花妖","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"purpleBowman": {"name":"高级弓兵","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0},
|
||||||
|
"E434": {"name":"史莱姆","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E434","left":"redSlime","right":"blackSlime","up":"slimelord"}},
|
||||||
|
"E435": {"name":"红史莱姆","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E436": {"name":"僵尸","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E437": {"name":"尸壳","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E438": {"name":"溺尸","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":1},
|
||||||
|
"E439": {"name":"骷髅","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E440": {"name":"苦力怕","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E441": {"name":"闪电苦力怕","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E442": {"name":"蜘蛛","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E443": {"name":"末影人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E444": {"name":"狼","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E445": {"name":"守卫者","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E446": {"name":"远古守卫者","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E447": {"name":"幻翼","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E448": {"name":"掠夺者","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E449": {"name":"唤魔者","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E450": {"name":"恼鬼","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E451": {"name":"卫道士","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E452": {"name":"劫掠兽","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E453": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E454": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E455": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E456": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E457": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E458": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E459": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E460": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E461": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E462": {"name":"新敌人","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
|
||||||
|
"E987": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E988": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E989": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E990": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E991": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E992": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E993": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E994": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E995": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E996": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E997": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E998": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E999": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1000": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1001": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1002": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1003": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1004": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1005": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1006": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1007": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1008": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1009": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1010": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1011": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1012": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1013": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1014": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1015": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1016": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1017": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1018": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1019": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1020": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1021": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1022": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1023": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1024": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1025": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1026": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1027": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1028": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1029": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1030": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1031": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1032": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1033": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1034": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1035": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1036": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1037": {"name":"怪","hp":1,"atk":1,"def":0,"as":1000,"ms":1000,"ar":1,"dr":3,"money":0,"exp":0,"point":0,"special":[],"type":0},
|
||||||
|
"E1": {"name":"史莱姆","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0,"faceIds":{"down":"E1","left":"E2","right":"E3","up":"E4"},"type":0},
|
||||||
|
"E2": {"name":"史莱姆","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0,"faceIds":{"down":"E1","left":"E2","right":"E3","up":"E4"},"type":0},
|
||||||
|
"E3": {"name":"铁守卫","hp":50,"atk":50,"def":50,"money":0,"exp":0,"point":0,"special":[18],"value":20,"faceIds":{"down":"E1","left":"E2","right":"E3","up":"E4"},"type":0},
|
||||||
|
"E4": {"name":"邪恶蝙蝠","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":0,"faceIds":{"down":"E1","left":"E2","right":"E3","up":"E4"},"type":0},
|
||||||
|
"E5": {"name":"红史莱姆","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"faceIds":{"down":"E5","left":"E6","right":"E7","up":"E8"},"bigImage":null,"type":0},
|
||||||
|
"E6": {"name":"僵尸","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"faceIds":{"down":"E5","left":"E6","right":"E7","up":"E8"},"type":0},
|
||||||
|
"E7": {"name":"僵尸","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"faceIds":{"down":"E5","left":"E6","right":"E7","up":"E8"},"type":0},
|
||||||
|
"E8": {"name":"僵尸","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[],"faceIds":{"down":"E5","left":"E6","right":"E7","up":"E8"},"type":0},
|
||||||
|
"E9": {"name":"僵尸","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E9","left":"E10","right":"E11","up":"E12"}},
|
||||||
|
"E10": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E9","left":"E10","right":"E11","up":"E12"}},
|
||||||
|
"E11": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E9","left":"E10","right":"E11","up":"E12"}},
|
||||||
|
"E12": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E9","left":"E10","right":"E11","up":"E12"}},
|
||||||
|
"E13": {"name":"尸壳","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E13","left":"E14","right":"E15","up":"E16"}},
|
||||||
|
"E14": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E13","left":"E14","right":"E15","up":"E16"}},
|
||||||
|
"E15": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E13","left":"E14","right":"E15","up":"E16"}},
|
||||||
|
"E16": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E13","left":"E14","right":"E15","up":"E16"}},
|
||||||
|
"E17": {"name":"溺尸","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E17","left":"E18","right":"E19","up":"E20"}},
|
||||||
|
"E18": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E17","left":"E18","right":"E19","up":"E20"}},
|
||||||
|
"E19": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E17","left":"E18","right":"E19","up":"E20"}},
|
||||||
|
"E20": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E17","left":"E18","right":"E19","up":"E20"}},
|
||||||
|
"E21": {"name":"骷髅","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E21","left":"E22","right":"E23","up":"E24"}},
|
||||||
|
"E22": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E21","left":"E22","right":"E23","up":"E24"}},
|
||||||
|
"E23": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E21","left":"E22","right":"E23","up":"E24"}},
|
||||||
|
"E24": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E21","left":"E22","right":"E23","up":"E24"}},
|
||||||
|
"E25": {"name":"苦力怕","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E25","left":"E26","right":"E27","up":"E28"}},
|
||||||
|
"E26": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E25","left":"E26","right":"E27","up":"E28"}},
|
||||||
|
"E27": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E25","left":"E26","right":"E27","up":"E28"}},
|
||||||
|
"E28": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E25","left":"E26","right":"E27","up":"E28"}},
|
||||||
|
"E29": {"name":"闪电苦力怕","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E29","left":"E30","right":"E31","up":"E32"}},
|
||||||
|
"E30": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E29","left":"E30","right":"E31","up":"E32"}},
|
||||||
|
"E31": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E29","left":"E30","right":"E31","up":"E32"}},
|
||||||
|
"E32": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E29","left":"E30","right":"E31","up":"E32"}},
|
||||||
|
"E33": {"name":"蜘蛛","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E33","left":"E34","right":"E35","up":"E36"}},
|
||||||
|
"E34": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E33","left":"E34","right":"E35","up":"E36"}},
|
||||||
|
"E35": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E33","left":"E34","right":"E35","up":"E36"}},
|
||||||
|
"E36": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E33","left":"E34","right":"E35","up":"E36"}},
|
||||||
|
"E37": {"name":"末影人","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E37","left":"E38","right":"E39","up":"E40"}},
|
||||||
|
"E38": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E37","left":"E38","right":"E39","up":"E40"}},
|
||||||
|
"E39": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E37","left":"E38","right":"E39","up":"E40"}},
|
||||||
|
"E40": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E37","left":"E38","right":"E39","up":"E40"}},
|
||||||
|
"E41": {"name":"恶狼","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E41","left":"E42","right":"E43","up":"E44"}},
|
||||||
|
"E42": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E41","left":"E42","right":"E43","up":"E44"}},
|
||||||
|
"E43": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E41","left":"E42","right":"E43","up":"E44"}},
|
||||||
|
"E44": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E41","left":"E42","right":"E43","up":"E44"}},
|
||||||
|
"E45": {"name":"守卫者","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":1,"faceIds":{"down":"E45","left":"E46","right":"E47","up":"E48"}},
|
||||||
|
"E46": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":1,"faceIds":{"down":"E45","left":"E46","right":"E47","up":"E48"}},
|
||||||
|
"E47": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":1,"faceIds":{"down":"E45","left":"E46","right":"E47","up":"E48"}},
|
||||||
|
"E48": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":1,"faceIds":{"down":"E45","left":"E46","right":"E47","up":"E48"}},
|
||||||
|
"E49": {"name":"远古守卫者","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":1,"faceIds":{"down":"E49","left":"E50","right":"E51","up":"E52"}},
|
||||||
|
"E50": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":1,"faceIds":{"down":"E49","left":"E50","right":"E51","up":"E52"}},
|
||||||
|
"E51": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":1,"faceIds":{"down":"E49","left":"E50","right":"E51","up":"E52"}},
|
||||||
|
"E52": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":1,"faceIds":{"down":"E49","left":"E50","right":"E51","up":"E52"}},
|
||||||
|
"E53": {"name":"幻翼","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":2,"faceIds":{"down":"E53","left":"E54","right":"E55","up":"E56"}},
|
||||||
|
"E54": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":2,"faceIds":{"down":"E53","left":"E54","right":"E55","up":"E56"}},
|
||||||
|
"E55": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":2,"faceIds":{"down":"E53","left":"E54","right":"E55","up":"E56"}},
|
||||||
|
"E56": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":2,"faceIds":{"down":"E53","left":"E54","right":"E55","up":"E56"}},
|
||||||
|
"E57": {"name":"掠夺者","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E57","left":"E58","right":"E59","up":"E60"}},
|
||||||
|
"E58": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E57","left":"E58","right":"E59","up":"E60"}},
|
||||||
|
"E59": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E57","left":"E58","right":"E59","up":"E60"}},
|
||||||
|
"E60": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E57","left":"E58","right":"E59","up":"E60"}},
|
||||||
|
"E61": {"name":"唤魔者","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E61","left":"E62","right":"E63","up":"E64"}},
|
||||||
|
"E62": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E61","left":"E62","right":"E63","up":"E64"}},
|
||||||
|
"E63": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E61","left":"E62","right":"E63","up":"E64"}},
|
||||||
|
"E64": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E61","left":"E62","right":"E63","up":"E64"}},
|
||||||
|
"E65": {"name":"恼鬼","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":2,"faceIds":{"down":"E65","left":"E66","right":"E67","up":"E68"}},
|
||||||
|
"E66": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":2,"faceIds":{"down":"E65","left":"E66","right":"E67","up":"E68"}},
|
||||||
|
"E67": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":2,"faceIds":{"down":"E65","left":"E66","right":"E67","up":"E68"}},
|
||||||
|
"E68": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":2,"faceIds":{"down":"E65","left":"E66","right":"E67","up":"E68"}},
|
||||||
|
"E69": {"name":"卫道士","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E69","left":"E70","right":"E71","up":"E72"}},
|
||||||
|
"E70": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E69","left":"E70","right":"E71","up":"E72"}},
|
||||||
|
"E71": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E69","left":"E70","right":"E71","up":"E72"}},
|
||||||
|
"E72": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E69","left":"E70","right":"E71","up":"E72"}},
|
||||||
|
"E73": {"name":"劫掠兽","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E73","left":"E74","right":"E75","up":"E76"}},
|
||||||
|
"E74": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E73","left":"E74","right":"E75","up":"E76"}},
|
||||||
|
"E75": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E73","left":"E74","right":"E75","up":"E76"}},
|
||||||
|
"E76": {"name":"怪","hp":1,"atk":1,"def":0,"money":0,"exp":0,"point":0,"special":[],"type":0,"faceIds":{"down":"E73","left":"E74","right":"E75","up":"E76"}}
|
||||||
|
}
|
762
event.d.ts
vendored
Normal file
762
event.d.ts
vendored
Normal file
@ -0,0 +1,762 @@
|
|||||||
|
/**
|
||||||
|
* 注册的系统事件函数
|
||||||
|
*/
|
||||||
|
type SystemEventFunc = (data: any, callback: (...params: any[]) => any) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册的事件函数
|
||||||
|
*/
|
||||||
|
type EventFunc = (data: any, x?: number, y?: number, prefix?: string) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理所有和事件相关的操作
|
||||||
|
*/
|
||||||
|
interface Events extends EventData {
|
||||||
|
/**
|
||||||
|
* 脚本编辑中的事件相关内容
|
||||||
|
*/
|
||||||
|
eventdata: EventData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共事件信息
|
||||||
|
*/
|
||||||
|
commonEvent: Record<EventDeclaration, MotaEvent>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的系统事件
|
||||||
|
*/
|
||||||
|
systemEvent: Record<string, SystemEventFunc>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册的自定义事件
|
||||||
|
*/
|
||||||
|
actions: Record<string, EventFunc>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始新游戏
|
||||||
|
* @example core.startGame('咸鱼乱撞', 0, ''); // 开始一局咸鱼乱撞难度的新游戏,随机种子为0
|
||||||
|
* @param hard 难度名,会显示在左下角(横屏)或右下角(竖屏)
|
||||||
|
* @param seed 随机种子,相同的种子保证了录像的可重复性
|
||||||
|
* @param route 经由base64压缩后的录像,用于从头开始的录像回放
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
startGame(
|
||||||
|
hard: string,
|
||||||
|
seed?: number,
|
||||||
|
route?: string,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏结束
|
||||||
|
* @example core.gameOver(); // 游戏失败
|
||||||
|
* @param ending 结局名,省略表示失败
|
||||||
|
* @param fromReplay true表示在播放录像
|
||||||
|
* @param norank true表示不计入榜单
|
||||||
|
*/
|
||||||
|
gameOver(ending?: string, fromReplay?: boolean, norank?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新开始游戏;此函数将回到标题页面
|
||||||
|
*/
|
||||||
|
restart(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 询问是否需要重新开始
|
||||||
|
*/
|
||||||
|
confirmRestart(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册一个系统事件
|
||||||
|
* @param type 事件名
|
||||||
|
* @param func 为事件的处理函数,可接受(data,callback)参数
|
||||||
|
*/
|
||||||
|
registerSystemEvent(type: string, func: SystemEventFunc): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销一个系统事件
|
||||||
|
* @param type 事件名
|
||||||
|
*/
|
||||||
|
unregisterSystemEvent(type: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行一个系统事件
|
||||||
|
* @param type 执行的事件名
|
||||||
|
* @param data 数据信息
|
||||||
|
* @param callback 传入事件处理函数的回调函数
|
||||||
|
*/
|
||||||
|
doSystemEvent(
|
||||||
|
type: string,
|
||||||
|
data: any,
|
||||||
|
callback?: (...params: any[]) => any
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发(x,y)点的系统事件;会执行该点图块的script属性,同时支持战斗(会触发战后)、道具(会触发道具后)、楼层切换等等
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
trigger(x: number, y: number, callback?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开门(包括三种基础墙)
|
||||||
|
* @example core.openDoor(0, 0, true, core.jumpHero); // 打开左上角的门,需要钥匙,然后主角原地跳跃半秒
|
||||||
|
* @param x 门的横坐标
|
||||||
|
* @param y 门的纵坐标
|
||||||
|
* @param needKey true表示需要钥匙,会导致机关门打不开
|
||||||
|
* @param callback 门完全打开后或打不开时的回调函数
|
||||||
|
*/
|
||||||
|
openDoor(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
needKey?: boolean,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得道具并提示,如果填写了坐标就会删除该点的该道具
|
||||||
|
* @example core.getItem('book'); // 获得敌人手册并提示
|
||||||
|
* @param id 道具id,必填
|
||||||
|
* @param num 获得的数量,不填视为1,填了就别填坐标了
|
||||||
|
* @param x 道具的横坐标
|
||||||
|
* @param y 道具的纵坐标
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
getItem(
|
||||||
|
id: AllIdsOf<'items'>,
|
||||||
|
num?: number,
|
||||||
|
x?: number,
|
||||||
|
y?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 轻按获得面前的物品或周围唯一物品
|
||||||
|
* @param noRoute 若为true则不计入录像
|
||||||
|
*/
|
||||||
|
getNextItem(noRoute?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景切换
|
||||||
|
* @example core.changeFloor('MT0'); // 传送到主塔0层,主角坐标和朝向不变,黑屏时间取用户设置值
|
||||||
|
* @param floorId 传送的目标地图id,可以填':before'和':after'分别表示楼下或楼上
|
||||||
|
* @param stair 传送的位置,可以填':now'(保持不变,可省略),':symmetry'(中心对称),':symmetry_x'(左右对称),':symmetry_y'(上下对称)或图块id(该图块最好在目标层唯一,一般为'downFloor'或'upFloor')
|
||||||
|
* @param heroLoc 传送的坐标(如果填写了,就会覆盖上述的粗略目标位置)和传送后主角的朝向(不填表示保持不变)
|
||||||
|
* @param time 传送的黑屏时间,单位为毫秒。不填为用户设置值
|
||||||
|
* @param callback 黑屏结束后的回调函数
|
||||||
|
*/
|
||||||
|
changeFloor(
|
||||||
|
floorId: FloorIds,
|
||||||
|
stair?: FloorChangeStair | AllIds,
|
||||||
|
heroLoc?: Partial<Loc>,
|
||||||
|
time?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否到达过某个楼层
|
||||||
|
* @param floorId 楼层id
|
||||||
|
*/
|
||||||
|
hasVisitedFloor(floorId?: FloorIds): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 到达某楼层
|
||||||
|
* @param floorId 楼层id
|
||||||
|
*/
|
||||||
|
visitFloor(floorId?: FloorIds): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推箱子
|
||||||
|
* @param data 图块信息
|
||||||
|
*/
|
||||||
|
pushBox(data?: Block): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否在冰上
|
||||||
|
*/
|
||||||
|
onSki(number?: number): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册一个自定义事件
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param func 事件的处理函数,可接受(data, x, y, prefix)参数
|
||||||
|
* data为事件内容,x和y为当前点坐标(可为null),prefix为当前点前缀
|
||||||
|
*/
|
||||||
|
registerEvent(type: string, func: EventFunc): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销一个自定义事件
|
||||||
|
* @param type 事件类型
|
||||||
|
*/
|
||||||
|
unregisterEvent(type: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行一个自定义事件
|
||||||
|
* @param data 事件信息
|
||||||
|
* @param x 事件横坐标
|
||||||
|
* @param y 事件纵坐标
|
||||||
|
* @param prefix 当前点前缀
|
||||||
|
*/
|
||||||
|
doEvent(data: any, x?: number, y?: number, prefix?: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接设置事件列表
|
||||||
|
* @param list 事件信息
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param callback 事件执行完毕后的回调函数
|
||||||
|
*/
|
||||||
|
setEvents(
|
||||||
|
list: MotaEvent,
|
||||||
|
x?: number,
|
||||||
|
y?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始执行一系列自定义事件
|
||||||
|
* @param list 事件信息
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param callback 事件执行完毕后的回调函数
|
||||||
|
*/
|
||||||
|
startEvents(
|
||||||
|
list?: MotaEvent,
|
||||||
|
x?: number,
|
||||||
|
y?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行下一个事件指令,常作为回调
|
||||||
|
* @example
|
||||||
|
* // 事件中的原生脚本,配合勾选“不自动执行下一个事件”来达到此改变色调只持续到下次场景切换的效果
|
||||||
|
* core.setCurtain([0,0,0,1], undefined, null, core.doAction);
|
||||||
|
*/
|
||||||
|
doAction(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入一段事件;此项不可插入公共事件,请用 core.insertCommonEvent
|
||||||
|
* @example core.insertAction('一段文字'); // 插入一个显示文章
|
||||||
|
* @param action 单个事件指令,或事件指令数组
|
||||||
|
* @param x 新的当前点横坐标
|
||||||
|
* @param y 新的当前点纵坐标
|
||||||
|
* @param callback 新的回调函数
|
||||||
|
* @param addToLast 插入的位置,true表示插入到末尾,否则插入到开头
|
||||||
|
*/
|
||||||
|
insertAction(
|
||||||
|
action: MotaEvent | MotaAction,
|
||||||
|
x?: number,
|
||||||
|
y?: number,
|
||||||
|
callback?: () => void,
|
||||||
|
addToLast?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入一个公共事件
|
||||||
|
* @example core.insertCommonEvent('加点事件', [3]);
|
||||||
|
* @param name 公共事件名;如果公共事件不存在则直接忽略
|
||||||
|
* @param args 参数列表,为一个数组,将依次赋值给 flag:arg1, flag:arg2, ...
|
||||||
|
* @param x 新的当前点横坐标
|
||||||
|
* @param y 新的当前点纵坐标
|
||||||
|
* @param callback 新的回调函数
|
||||||
|
* @param addToLast 插入的位置,true表示插入到末尾,否则插入到开头
|
||||||
|
*/
|
||||||
|
insertCommonEvent(
|
||||||
|
name: EventDeclaration,
|
||||||
|
args?: any[],
|
||||||
|
x?: number,
|
||||||
|
y?: number,
|
||||||
|
callback?: () => void,
|
||||||
|
addToLast?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得一个公共事件
|
||||||
|
* @param name 公共事件名称
|
||||||
|
*/
|
||||||
|
getCommonEvent(name: EventDeclaration): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复一个事件
|
||||||
|
* @param data 事件信息
|
||||||
|
*/
|
||||||
|
recoverEvents(data?: any): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测自动事件
|
||||||
|
*/
|
||||||
|
checkAutoEvents(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否在执行某个自动事件
|
||||||
|
* @param symbol 自动事件的标识符
|
||||||
|
* @param value 不太清楚有什么用
|
||||||
|
*/
|
||||||
|
autoEventExecuting(symbol?: string, value?: any): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否执行过某个自动事件
|
||||||
|
* @param symbol 自动事件的标识符
|
||||||
|
* @param value 不太清楚有什么用
|
||||||
|
*/
|
||||||
|
autoEventExecuted(symbol?: string, value?: any): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将当前点坐标入栈
|
||||||
|
*/
|
||||||
|
pushEventLoc(x: number, y: number, floorId?: FloorIds): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹出事件坐标点
|
||||||
|
*/
|
||||||
|
popEventLoc(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预编辑事件
|
||||||
|
* @param data 事件信息
|
||||||
|
*/
|
||||||
|
precompile(data?: any): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击怪物手册时的打开操作
|
||||||
|
* @param fromUserAction 是否是用户开启的
|
||||||
|
*/
|
||||||
|
openBook(fromUserAction?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击楼层传送器时的打开操作
|
||||||
|
* @param fromUserAction 是否是用户开启的
|
||||||
|
*/
|
||||||
|
useFly(fromUserAction?: boolean): void;
|
||||||
|
|
||||||
|
/** 点击装备栏时的打开操作 */
|
||||||
|
openEquipbox(fromUserAction?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击工具栏时的打开操作
|
||||||
|
* @param fromUserAction 是否是用户开启的
|
||||||
|
*/
|
||||||
|
openToolbox(fromUserAction?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击快捷商店按钮时的打开操作
|
||||||
|
* @param fromUserAction 是否是用户开启的
|
||||||
|
*/
|
||||||
|
openQuickShop(fromUserAction?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击虚拟键盘时的打开操作
|
||||||
|
* @param fromUserAction 是否是用户开启的
|
||||||
|
*/
|
||||||
|
openKeyBoard(fromUserAction?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击存档按钮时的打开操作
|
||||||
|
* @param fromUserAction 是否是用户开启的
|
||||||
|
*/
|
||||||
|
save(fromUserAction?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击读档按钮时的打开操作
|
||||||
|
* @param fromUserAction 是否是用户开启的
|
||||||
|
*/
|
||||||
|
load(fromUserAction?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击设置按钮时的操作
|
||||||
|
* @param fromUserAction 是否是用户开启的
|
||||||
|
*/
|
||||||
|
openSettings(fromUserAction?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否有未处理完毕的异步事件(不包含动画和音效)
|
||||||
|
*/
|
||||||
|
hasAsync(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 立刻停止所有异步事件
|
||||||
|
*/
|
||||||
|
stopAsync(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否有异步动画
|
||||||
|
*/
|
||||||
|
hasAsyncAnimate(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟随
|
||||||
|
* @param name 要跟随的一个合法的4x4的行走图名称,需要在全塔属性注册
|
||||||
|
*/
|
||||||
|
follow(name: ImageIds | NameMapIn<ImageIds>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消跟随
|
||||||
|
* @param name 取消跟随的行走图,不填则取消全部跟随者
|
||||||
|
*/
|
||||||
|
unfollow(name?: ImageIds | NameMapIn<ImageIds>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数值操作
|
||||||
|
* @param name 操作的数值的名称
|
||||||
|
* @param operator 操作运算符
|
||||||
|
* @param value 值
|
||||||
|
* @param prefix 独立开关前缀
|
||||||
|
*/
|
||||||
|
setValue(
|
||||||
|
name: `${EventValuePreffix}:${string}`,
|
||||||
|
operator: MotaOperator,
|
||||||
|
value: number,
|
||||||
|
prefix?: string
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 已失效(大概
|
||||||
|
* 设置一项敌人属性并计入存档
|
||||||
|
* @example core.setEnemy('greenSlime', 'def', 0); // 把绿头怪的防御设为0
|
||||||
|
* @param id 敌人id
|
||||||
|
* @param name 属性的英文缩写
|
||||||
|
* @param value 属性的新值
|
||||||
|
* @param operator 操作符
|
||||||
|
* @param prefix 独立开关前缀,一般不需要
|
||||||
|
* @param norefresh 是否不刷新状态栏
|
||||||
|
*/
|
||||||
|
setEnemy<K extends keyof Enemy>(
|
||||||
|
id: AllIdsOf<'enemys' | 'enemy48'>,
|
||||||
|
name: K,
|
||||||
|
value: Enemy[K],
|
||||||
|
operator?: MotaOperator,
|
||||||
|
prefix?: string,
|
||||||
|
norefresh?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 已失效(大概
|
||||||
|
* 设置某个点的敌人属性
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param floorId 楼层id
|
||||||
|
* @param name 属性的英文缩写
|
||||||
|
* @param value 属性的新值
|
||||||
|
* @param operator 操作符
|
||||||
|
* @param prefix 独立开关前缀,一般不需要
|
||||||
|
* @param norefresh 是否不刷新状态栏
|
||||||
|
*/
|
||||||
|
setEnemyOnPoint<K extends keyof Enemy>(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
floorId: FloorIds,
|
||||||
|
name: K,
|
||||||
|
value: Enemy[K],
|
||||||
|
operator?: MotaOperator,
|
||||||
|
prefix?: string,
|
||||||
|
norefresh?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 已失效(大概
|
||||||
|
* 重置某个点的敌人属性
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param floorId 楼层id
|
||||||
|
* @param norefresh 是否不刷新状态栏
|
||||||
|
*/
|
||||||
|
resetEnemyOnPoint(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
floorId?: FloorIds,
|
||||||
|
norefresh?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 已失效(大概
|
||||||
|
* 将某个点已经设置的敌人属性移动到其他点
|
||||||
|
* @param fromX 起始横坐标
|
||||||
|
* @param fromY 起始纵坐标
|
||||||
|
* @param toX 目标横坐标
|
||||||
|
* @param toY 目标纵坐标
|
||||||
|
* @param floorId 楼层id
|
||||||
|
* @param norefresh 是否不刷新状态栏
|
||||||
|
*/
|
||||||
|
moveEnemyOnPoint(
|
||||||
|
fromX: number,
|
||||||
|
fromY: number,
|
||||||
|
toX: number,
|
||||||
|
toY: number,
|
||||||
|
floorId?: FloorIds,
|
||||||
|
norefresh?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置一项楼层属性并刷新状态栏
|
||||||
|
* @example core.setFloorInfo('ratio', 2, 'MT0'); // 把主塔0层的血瓶和宝石变为双倍效果
|
||||||
|
* @param name 要求改的属性名
|
||||||
|
* @param values 属性的新值
|
||||||
|
* @param floorId 楼层id,不填视为当前层
|
||||||
|
* @param prefix 独立开关前缀,一般不需要,下同
|
||||||
|
*/
|
||||||
|
setFloorInfo<K extends keyof Floor>(
|
||||||
|
name: K,
|
||||||
|
values?: Floor[K],
|
||||||
|
floorId?: FloorIds,
|
||||||
|
prefix?: string
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置全塔属性
|
||||||
|
* @param name 属性名
|
||||||
|
* @param value 属性值
|
||||||
|
*/
|
||||||
|
setGlobalAttribute<K extends keyof GlobalAttribute>(
|
||||||
|
name: K,
|
||||||
|
value: GlobalAttribute[K]
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置一个系统开关
|
||||||
|
* @example core.setGlobalFlag('steelDoorWithoutKey', true); // 使全塔的所有铁门都不再需要钥匙就能打开
|
||||||
|
* @param name 系统开关的英文名
|
||||||
|
* @param value 开关的新值,您可以用!core.flags[name]简单地表示将此开关反转
|
||||||
|
*/
|
||||||
|
setGlobalFlag<K extends keyof CoreFlags>(
|
||||||
|
name: K,
|
||||||
|
value: CoreFlags[K]
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置文件别名
|
||||||
|
* @param name 别名
|
||||||
|
* @param value 别名对应的文件名
|
||||||
|
*/
|
||||||
|
setNameMap(name: string, value?: SourceIds): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置剧情文本的属性
|
||||||
|
*/
|
||||||
|
setTextAttribute(data: Partial<TextAttribute>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动对话框
|
||||||
|
* @param code 对话框的代码
|
||||||
|
* @param loc 目标位置
|
||||||
|
* @param relative 是否是相对模式
|
||||||
|
* @param moveMode 缓动模式
|
||||||
|
* @param time 动画时间
|
||||||
|
* @param callback 移动完毕的回调函数
|
||||||
|
*/
|
||||||
|
moveTextBox(
|
||||||
|
code: number,
|
||||||
|
loc: LocArr,
|
||||||
|
relative?: boolean,
|
||||||
|
moveMode?: EaseMode,
|
||||||
|
time?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除对话框
|
||||||
|
* @param code 对话框的代码
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
clearTextBox(code: number, callback: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关门,目标点必须为空地
|
||||||
|
* @example core.closeDoor(0, 0, 'yellowWall', core.jumpHero); // 在左上角关掉一堵黄墙,然后主角原地跳跃半秒
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param id 门的id,也可以用三种基础墙
|
||||||
|
* @param callback 门完全关上后的回调函数
|
||||||
|
*/
|
||||||
|
closeDoor(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
id: AllIdsOf<Exclude<Cls, 'enemys' | 'enemy48'>>,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示一张图片
|
||||||
|
* @example
|
||||||
|
* // 裁剪winskin.png的最左边128×128px,放大到铺满整个视野,1秒内淡入到50%透明,编号为1
|
||||||
|
* core.showImage(1, core.material.images.images['winskin.png'], [0,0,128,128], [0,0,416,416], 0.5, 1000);
|
||||||
|
* @param code 图片编号,为不大于50的正整数,加上100后就是对应画布层的z值,较大的会遮罩较小的,注意色调层的z值为125,UI层为140
|
||||||
|
* @param image 图片文件名(可以是全塔属性中映射前的中文名)或图片对象(见上面的例子)
|
||||||
|
* @param sloc 一行且至多四列的数组,表示从原图裁剪的左上角坐标和宽高
|
||||||
|
* @param loc 一行且至多四列的数组,表示图片在视野中的左上角坐标和宽高
|
||||||
|
* @param opacityVal 不透明度,为小于1的正数。不填视为1
|
||||||
|
* @param time 淡入时间,单位为毫秒。不填视为0
|
||||||
|
* @param callback 图片完全显示出来后的回调函数
|
||||||
|
*/
|
||||||
|
showImage(
|
||||||
|
code: number,
|
||||||
|
image: ImageIds | NameMapIn<ImageIds> | ImageSource,
|
||||||
|
sloc?: [number, number, number, number],
|
||||||
|
loc?: [number, number, number?, number?],
|
||||||
|
opacityVal?: number,
|
||||||
|
time?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏一张图片
|
||||||
|
* @example core.hideImage(1, 1000, core.jumpHero); // 1秒内淡出1号图片,然后主角原地跳跃半秒
|
||||||
|
* @param code 图片编号
|
||||||
|
* @param time 淡出时间,单位为毫秒
|
||||||
|
* @param callback 图片完全消失后的回调函数
|
||||||
|
*/
|
||||||
|
hideImage(code: number, time?: number, callback?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动一张图片并/或改变其透明度
|
||||||
|
* @example core.moveImage(1, null, 0.5); // 1秒内把1号图片变为50%透明
|
||||||
|
* @param code 图片编号
|
||||||
|
* @param to 新的左上角坐标,省略表示原地改变透明度
|
||||||
|
* @param opacityVal 新的透明度,省略表示不变
|
||||||
|
* @param moveMode 移动模式
|
||||||
|
* @param time 移动用时,单位为毫秒。不填视为1秒
|
||||||
|
* @param callback 图片移动完毕后的回调函数
|
||||||
|
*/
|
||||||
|
moveImage(
|
||||||
|
code: number,
|
||||||
|
to?: LocArr,
|
||||||
|
opacityVal?: number,
|
||||||
|
moveMode?: string,
|
||||||
|
time?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旋转一张图片
|
||||||
|
* @param code 图片编号
|
||||||
|
* @param center 旋转中心像素(以屏幕为基准);不填视为图片本身中心
|
||||||
|
* @param angle 旋转角度;正数为顺时针,负数为逆时针
|
||||||
|
* @param moveMode 旋转模式
|
||||||
|
* @param time 移动用时,单位为毫秒。不填视为1秒
|
||||||
|
* @param callback 图片移动完毕后的回调函数
|
||||||
|
*/
|
||||||
|
rotateImage(
|
||||||
|
code: number,
|
||||||
|
center?: LocArr,
|
||||||
|
angle?: number,
|
||||||
|
moveMode?: EaseMode,
|
||||||
|
time?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 放缩一张图片
|
||||||
|
* @param code 图片编号
|
||||||
|
* @param center 旋转中心像素(以屏幕为基准);不填视为图片本身中心
|
||||||
|
* @param scale 放缩至的比例
|
||||||
|
* @param moveMode 旋转模式
|
||||||
|
* @param time 移动用时,单位为毫秒。不填视为1秒
|
||||||
|
* @param callback 图片移动完毕后的回调函数
|
||||||
|
*/
|
||||||
|
scaleImage(
|
||||||
|
code: number,
|
||||||
|
center?: LocArr,
|
||||||
|
scale?: number,
|
||||||
|
moveMode?: string,
|
||||||
|
time?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制一张动图或擦除所有动图
|
||||||
|
* @example core.showGif(); // 擦除所有动图
|
||||||
|
* @param name 动图文件名,可以是全塔属性中映射前的中文名
|
||||||
|
* @param x 动图在视野中的左上角横坐标
|
||||||
|
* @param y 动图在视野中的左上角纵坐标
|
||||||
|
*/
|
||||||
|
showGif(
|
||||||
|
name?:
|
||||||
|
| Extract<ImageIds, EndsWith<'.gif'>>
|
||||||
|
| NameMapIn<Extract<ImageIds, EndsWith<'.gif'>>>,
|
||||||
|
x?: number,
|
||||||
|
y?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调节bgm的音量
|
||||||
|
* @example core.setVolume(0, 100, core.jumpHero); // 0.1秒内淡出bgm,然后主角原地跳跃半秒
|
||||||
|
* @param value 新的音量,为0或不大于1的正数。注意系统设置中是这个值的平方根的十倍
|
||||||
|
* @param time 渐变用时,单位为毫秒。不填或小于100毫秒都视为0
|
||||||
|
* @param callback 渐变完成后的回调函数
|
||||||
|
*/
|
||||||
|
setVolume(value: number, time?: number, callback?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视野抖动
|
||||||
|
* @example core.vibrate(); // 视野左右抖动1秒
|
||||||
|
* @param direction 抖动方向
|
||||||
|
* @param time 抖动时长,单位为毫秒
|
||||||
|
* @param speed 抖动速度
|
||||||
|
* @param power 抖动幅度
|
||||||
|
* @param callback 抖动平息后的回调函数
|
||||||
|
*/
|
||||||
|
vibrate(
|
||||||
|
direction?: string,
|
||||||
|
time?: number,
|
||||||
|
speed?: number,
|
||||||
|
power?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制移动主角(包括后退),这个函数的作者已经看不懂这个函数了
|
||||||
|
* @example core.eventMoveHero(['forward'], 125, core.jumpHero); // 主角强制前进一步,用时1/8秒,然后主角原地跳跃半秒
|
||||||
|
* @param steps 步伐数组,注意后退时跟随者的行为会很难看
|
||||||
|
* @param time 每步的用时,单位为毫秒。0或不填则取主角的移速,如果后者也不存在就取0.1秒
|
||||||
|
* @param callback 移动完毕后的回调函数
|
||||||
|
*/
|
||||||
|
eventMoveHero(steps: Step[], time?: number, callback?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主角跳跃,跳跃勇士。ex和ey为目标点的坐标,可以为null表示原地跳跃。time为总跳跃时间。
|
||||||
|
* @example core.jumpHero(); // 主角原地跳跃半秒
|
||||||
|
* @param ex 跳跃后的横坐标
|
||||||
|
* @param ey 跳跃后的纵坐标
|
||||||
|
* @param time 跳跃时长,单位为毫秒。不填视为半秒
|
||||||
|
* @param callback 跳跃完毕后的回调函数
|
||||||
|
*/
|
||||||
|
jumpHero(
|
||||||
|
ex?: number,
|
||||||
|
ey?: number,
|
||||||
|
time?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更改主角行走图
|
||||||
|
* @example core.setHeroIcon('npc48.png', true); // 把主角从阳光变成样板0层左下角的小姐姐,但不立即刷新
|
||||||
|
* @param name 新的行走图文件名,可以是全塔属性中映射前的中文名。映射后会被存入core.status.hero.image
|
||||||
|
* @param noDraw true表示不立即刷新(刷新会导致大地图下视野重置到以主角为中心)
|
||||||
|
*/
|
||||||
|
setHeroIcon(name: string, noDraw?: boolean): void;
|
||||||
|
|
||||||
|
/** 检查升级事件 */
|
||||||
|
checkLvUp(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试使用一个道具
|
||||||
|
* @example core.tryUseItem('pickaxe'); // 尝试使用破墙镐
|
||||||
|
* @param itemId 道具id,其中敌人手册、传送器和飞行器会被特殊处理
|
||||||
|
*/
|
||||||
|
tryUseItem(
|
||||||
|
itemId: ItemIdOf<'tools' | 'constants'>,
|
||||||
|
noRoute?: boolean,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
_sys_battle(data: Block, callback?: () => void): void;
|
||||||
|
|
||||||
|
_action_battle(data: any, x?: number, y?: number, prefix?: any): void;
|
||||||
|
|
||||||
|
__action_getLoc(data: any, x?: number, y?: number, prefix?: any): any;
|
||||||
|
|
||||||
|
_changeFloor_beforeChange(info: any, callback: () => void): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const events: new () => Events;
|
329
event.md
Normal file
329
event.md
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
# 事件
|
||||||
|
|
||||||
|
?> 在这一节中,让我们来了解事件系统的基本原理
|
||||||
|
|
||||||
|
## 事件编辑(地图选点,快捷键X)
|
||||||
|
|
||||||
|
![image](img/events.jpg)
|
||||||
|
|
||||||
|
样板所有的事件都是依靠“触发器”完成的。例如,勇士踩到(绑定好的)楼梯可以触发changeFloor,碰到门可以触发openDoor,碰到怪物可以触发battle,踩到/轻拾道具可以触发getItem,推到箱子可以触发pushBox,走上冰面(背景层)可以触发ski.
|
||||||
|
|
||||||
|
这些触发器都是系统自带的(称为系统事件),已经存在处理机制,不需要我们操心。我们真正所需要关心的,其实只是自定义的事件(如NPC)。
|
||||||
|
|
||||||
|
地图选点(快捷键X)一共有八项,在地图中以图块左下角用八色小方块标记。
|
||||||
|
|
||||||
|
* **红色:**普通事件,也包括该点的一些特效。
|
||||||
|
* **橙色:**自动事件,可以设置为“仅执行一次”、“仅在本层检测”。
|
||||||
|
* **暗绿色:**战前事件,强制战斗时不触发。
|
||||||
|
* **黄色:**战后事件。
|
||||||
|
* **亮绿色:**楼层转换,可以设置为“可穿透”。
|
||||||
|
* **青色:**获得道具后事件,可以设置为“轻按时不触发”。
|
||||||
|
* **靛色:**不是事件,为勇士站在该点不能朝哪些方向走。
|
||||||
|
* **紫(粉)色:**开门后事件。
|
||||||
|
|
||||||
|
此外还有一些事件不在地图上,如“首次/每次到达”(这两个的触发原理,详见“脚本编辑——切换楼层后”)、“道具效果”、“碰触事件”、“(批量)战前/战后事件”、“公共事件”和全塔属性中那五个事件。
|
||||||
|
|
||||||
|
### 事件的机制
|
||||||
|
|
||||||
|
地图上的所有事件都存在两种状态:启用和禁用。
|
||||||
|
|
||||||
|
* 启用状态下,该事件才处于可见状态,可被触发、交互与处理。
|
||||||
|
* 禁用状态下,该事件几乎相当于不存在(默认可以被阻击怪和箱子推上去),不可见、不可被触发、不可交互,只能通过“插入事件”指令远程调用。
|
||||||
|
|
||||||
|
所有事件默认情况下初始都是启用的,只有普通事件可以通过不勾选“启用”来初始隐藏。
|
||||||
|
|
||||||
|
在事件列表中使用“显示事件”和“隐藏事件”可以将一个禁用事件给启用,或将一个启用事件给禁用,甚至直接从地图中删除。
|
||||||
|
|
||||||
|
**【重要警告】:**
|
||||||
|
1. 打怪开门捡道具后,如果有对应的坐标,则该点的事件会被从地图中删除(重生怪除外,它只会被暂时隐藏)。然后会尝试执行该点的战后事件、开门后事件或(拾获)道具后事件(您可以勾选“轻按时不触发”)。如果执行,则“当前点坐标”(`core.status.event.data`的坐标)也会变为该点。
|
||||||
|
* 所以,这三个XX后事件,其实是有可能被多次触发或意外触发的(如打死或炸掉某个有战后事件的怪,推了个阻击怪过去打死),请自行注意。
|
||||||
|
2. “移动事件”(如阻击和捕捉)和“跳跃事件”(如支援)会将起点处的事件从地图中删除,如果不勾选“不消失”(如阻击),则会在终点处“转变图块”并“显示事件”。但事件的内容不会被跟着移动到终点,推箱子同理,但在终点只转变图块而不予显示。
|
||||||
|
|
||||||
|
### 楼梯、传送门事件
|
||||||
|
|
||||||
|
![image](img/changefloor.jpg)
|
||||||
|
|
||||||
|
当您在地图上绘制楼梯、或绘制四种三帧箭头并右击绑定后,就创建了一个“楼层转换”事件,您可以在事件编辑器右侧看到一行代码(json),请注意对照。
|
||||||
|
|
||||||
|
此事件与`core.changeFloor(floorId, stair, heroLoc, time, callback)`函数相对应,只是多了一项“穿透性”。
|
||||||
|
|
||||||
|
每个这样的事件都是上图的写法,下面逐一讲解其各项的含义和用法:
|
||||||
|
|
||||||
|
1. **目标楼层(floorId):**如题,如果选择了“楼层ID”就需要在第二个框中输入目标楼层的ID(会自动提示补全,也可以双击整个紫色块从地图选点)。如果选择了其他三种,则json代码中`:before`、`:next`、`:now`分别表示“前一楼”、“后一楼”(顺序由“全塔属性——楼层列表”指定,上下楼器同理)和“当前楼”,函数中也是一样的写法。
|
||||||
|
2. **目标位置(stair):**如果选择了“坐标”则需要在后面两个框里输入(同样可以地图选点,在函数中则将`stair`填`null`并填写`heroLoc`),也可以选择下拉框中的其他几项(在函数中则将`heroLoc`填`null`),如保持不变(`:now`)、中心对称(`:symmetry`)、左右对称(`:symmetry_x`)、上下对称(`:symmetry_y`)或任何一个图块ID.
|
||||||
|
* 填写三种对称位置时请注意,坐标计算以【当前楼层的勇士】位置(对不可踏入的楼梯来说,这个位置与楼梯相邻而不重合)而不是目标楼层或该楼梯为准,注意防止勇士出界。
|
||||||
|
* 填写图块ID时请注意,“上下楼梯”和“楼传落点”提供在了下拉框中,实际传送时会优先尝试取用目标层的“楼层属性——上下楼点”。其次尝试像其他ID(必须写在右侧的json区再点击解析)一样从目标楼层搜索,因此请确保该图块在目标楼层中存在且唯一。
|
||||||
|
3. **朝向:**有八种写法,可以直接填写改变后的朝向(`up, down, left, right`),也可以填写转身的角度(`:left, :right, :back`)或不变。
|
||||||
|
4. **动画时间:**指黑屏的毫秒数,可以填0或不小于100的整数,不填则使用玩家设定值。
|
||||||
|
* V2.8起,楼层切换的音效被延迟到了“屏幕完全变黑”的瞬间(脚本编辑——切换楼层中)才播放,以方便作者根据条件设置不同音效,因此上下楼时间建议尽量缩短。
|
||||||
|
5. **穿透性:**见[系统开关](start)允许穿透楼梯。
|
||||||
|
|
||||||
|
值得注意的是,绑定了楼层转换事件的门、怪物、道具、箱子,其系统行为(开门、战斗、拾取、推行)将不再发生(阻激夹域等地图属性不受影响),而是被覆盖为上下楼。
|
||||||
|
|
||||||
|
## 普通事件(红)
|
||||||
|
|
||||||
|
普通事件在启用后可以被勇士碰触而触发,它们都有着这样的开头:
|
||||||
|
```
|
||||||
|
覆盖触发器(Y/N) 启用(Y/N) 通行状态(Y/N?) 显伤(Y/N)
|
||||||
|
V2.8新增:不透明度(0~1) 虚化 色相(0~359) 灰度(0~1) 反色(Y/N) 阴影
|
||||||
|
```
|
||||||
|
1. **覆盖触发器:**如前所述,门、怪物、道具、箱子都已经被绑定了系统触发器。如果您需要让地图上的个别这类图块在被勇士碰触时不发生系统行为(开门、战斗、拾取、推行),而是执行您在下面自定义的事件(比如需要制作一个“怪物中的叛徒”,它虽然有显伤但其实是个npc),请勾选此项。(阻激夹域捕捉支援等地图属性不受影响)
|
||||||
|
2. **启用:**如果不勾选此项,则此事件初始时是隐藏的。
|
||||||
|
* 非常不推荐仅仅为了初始隐藏一些图块(如战利品、陷阱门墙、埋伏怪等)就去绑定一堆没有指令的普通事件。
|
||||||
|
* 更安全的做法是“从0转变图块”(支持淡入效果)和“关门”(这样还有音效,多香)。
|
||||||
|
* 事实上,除“覆盖触发器”外,其余所有参数本不该作为“普通事件”的一部分而应该移出去放在表格中,这是样板的历史遗留问题。
|
||||||
|
* 如今V2.8加入了六项特效,可能导致无指令的普通事件越来越不可避免,请自行注意。
|
||||||
|
3. **通行状态:**除怪物和道具外,所有图块本身都具有“可通行性”属性。
|
||||||
|
* 您可以设置此项去覆盖地图中这一点的可通行性,譬如强行让一格空地不可通行,勇士撞击时出现一堵墙(51层魔塔第23层的效果)。
|
||||||
|
4. **显伤:**在对怪物使用覆盖触发器后,表面上看不出它和一般怪物的不同。
|
||||||
|
* 如有必要(比如触碰这怪物根本不战斗),可不勾选显伤以示区别。
|
||||||
|
* 如数量较多,可注册一个相同素材的NPC,比如样板中的仙子。
|
||||||
|
* 如需在一段剧情中关闭所有显伤,可以简单地暂时移除怪物手册
|
||||||
|
5. **不透明度:**如题,0表示完全不透明,1表示完全隐身。可以设为1用来配合楼层贴图实现大型怪物,或者设为半透明来表示灵魂等状态的角色。本属性可以在事件中用指令动态修改,支持渐变。
|
||||||
|
6. **虚化:**必须为自然数,设定后图块会变模糊,可以用来配合快速移动/跳跃来表示幻影状态的角色。不建议对多帧图块长时间使用,因为会随着不断的抖动把周围越描越黑。
|
||||||
|
7. **色相:**必须为0~359的整数,180表示互补色。常用于npc以避免过于单调,也可以在游戏的教学环节用此项高亮强调部分图块。
|
||||||
|
8. **灰度:**如题,1表示完全变灰,必要时可以全图变灰来表示回忆/梦境/濒死等场合。
|
||||||
|
9. **反色:**勾选后此图块将反色,必要时可以全图瞬间反色来表示惊吓。
|
||||||
|
10. **阴影:**必须为自然数,设定后图块周围将出现阴影描边,在草地上效果更佳哦。
|
||||||
|
|
||||||
|
可以使用`core.setBlockFilter`更改任何一点的最后五项特效。
|
||||||
|
|
||||||
|
## 自动事件(橙)
|
||||||
|
|
||||||
|
在“右键绑定机关门”中,我们已经初见了自动事件的端倪。
|
||||||
|
|
||||||
|
在游戏中,我们经常需要监听大量乱七八糟的状态,根据状态的变化去做出及时的处理,比如勇士生命小于等于0会触发游戏失败。比如51层魔塔第39层监听9扇黄门的状态来触发中心对称飞行器的出现、第49层监听8名魔法警卫的状态来封印假魔王。
|
||||||
|
|
||||||
|
如果给9扇黄门都绑定类似的开门后事件、给8名警卫都绑定类似的战后事件,就非常繁琐。
|
||||||
|
|
||||||
|
而自动事件则不然,它不需要您去主动考虑所有“改变状态”的原因,而是简单地在每次刷新状态栏时,检查是否满足触发条件,满足的话就执行。
|
||||||
|
|
||||||
|
每个自动事件具有以下几个属性:
|
||||||
|
|
||||||
|
1. **触发条件:**自动事件最重要的属性,为支持“冒号缩写量”的逻辑表达式。
|
||||||
|
* 譬如上述两个例子中,触发条件就是“某两个点没有图块而某7个点图块为黄门”和“某四个点没有图块而某四个点图块为魔法警卫”。
|
||||||
|
* 关于冒号缩写量,详见下面的“值块和冒号缩写量”。
|
||||||
|
* V2.8起,自动事件的触发条件和“值块”支持多行编辑(eval时会忽略\n)。
|
||||||
|
* 因此高级作者可以将此条件写成无名函数,这样就可以在里面使用var进行复杂的多步计算了。
|
||||||
|
2. **优先级:**一般不需要改动。当多个自动事件的条件同时满足时,优先执行此级别较大的。
|
||||||
|
* 此级别也相等时,允许跨层触发则优先执行楼层较矮的。
|
||||||
|
* 同一楼层优先执行横坐标较小的,横坐标相等时优先执行纵坐标较小的,同一点处优先执行页码较小的。
|
||||||
|
3. **仅在本层检测:**如题,一般不需要改动。除非您要制作一些全局的效果,如“勇士生命低于上限的1/3时改变背景音乐”。
|
||||||
|
* V2.8起,即使不勾选此项,也不会在分区砍层模式下触发冻结状态楼层的自动事件!
|
||||||
|
4. **事件流中延迟执行:**勾选此项后,在执行其他事件时触发此自动事件则会将其内容追加到待执行事件的末尾,否则插队到待执行事件的开头。
|
||||||
|
5. **允许多次执行:**如果不勾选,则此事件最多只会被触发一次。即使勾选了,每次执行的过程中也会暂时禁止自己被触发。勾选后,请记得在此事件的指令中将条件变得不满足。
|
||||||
|
|
||||||
|
每个点初始提供了两个自动事件页,您还可以再在数据区最上面单击“添加自动事件页”。
|
||||||
|
|
||||||
|
## 公共事件(逗号键)
|
||||||
|
|
||||||
|
有几个事件,如战后加点、回收钥匙商店,会在战后脚本或全局商店等处频繁用到。于是它们被整理成了公共事件,您也可以追加自己的。
|
||||||
|
|
||||||
|
公共事件在内容上和其他事件并无不同,只是在插入公共事件时(如`core.insertCommonEvent("回收钥匙商店",[...])`)可以提供一个`参数列表`。
|
||||||
|
|
||||||
|
参数列表是一个一维数组,其每项会被依次代入`core.status.hero.flags.arg1、arg2、……`,而`arg0`则会记录公共事件的中文名。
|
||||||
|
|
||||||
|
在整个事件流结束后(即勇士恢复行动,请注意不是公共事件结束后),这些以arg加纯数字命名的变量、以及以@temp@开头的临时变量都会被清空。
|
||||||
|
|
||||||
|
## 滑冰事件
|
||||||
|
|
||||||
|
滑冰(ski)是一个非常特殊的触发器,使用此触发器的图块需要画在背景层。
|
||||||
|
|
||||||
|
冰上可以画门、怪物、道具等任何图块,您还可以在前景层画上四种单向通行箭头(样板1层右下方那些)或红线薄墙(素材区第一列最下方那些),把三个图层的游戏性都发挥出来。
|
||||||
|
|
||||||
|
勇士走上冰面后,将一直“前进一格或撞击”。直到滑出冰面或无法再前进,如撞到墙或事件。
|
||||||
|
|
||||||
|
比如撞到怪物会强制战斗,打不过直接游戏失败。走到道具上会捡起来,撞到门则要看有没有钥匙,有的话会把门打开。V2.7.3起,踩到致命领域会死亡而不是停在前一格。
|
||||||
|
|
||||||
|
默认情况下,触发事件后勇士会停下来。如果要继续滑冰,可以在这些点的战后事件、开门后事件、(拾获)道具后事件(这里最好勾选“轻按时不触发”)中判断勇士还在不在冰上(`core.onSki()`函数),在冰上的话就命令“勇士前进一格或撞击”。
|
||||||
|
|
||||||
|
## 事件编辑器
|
||||||
|
|
||||||
|
![image](img/blockly.jpg)
|
||||||
|
|
||||||
|
当您从数据区单击任何一个事件类属性的“编辑”按钮时,就会呼出事件编辑器,上图给出了事件编辑器的全貌。
|
||||||
|
|
||||||
|
图中左下角为指令类别,可以点击它们呼出各类指令菜单,如左上方的值块菜单。
|
||||||
|
|
||||||
|
中央为指令区,被一个标识事件类型的紫色块包覆(即左下角的“入口方块”,图中为开门后事件),所有的指令都塞在里面。
|
||||||
|
|
||||||
|
右上角为json区,会随着指令区的编辑而自动变化。`core.insertAction()`函数的自变量就是它们,所以学有余力的话也建议您经常左右对照去了解每个指令对应的json写法。
|
||||||
|
|
||||||
|
右下角为调色器,当您单击指令块中的颜色按钮时(如图中“画面闪烁”指令的白块)就会自动呼出,可以进行诸如十进制和十六进制颜色格式互转并预览等操作。
|
||||||
|
|
||||||
|
左上角为功能区,各功能的含义和用法如下:
|
||||||
|
|
||||||
|
1. **确认(Ctrl+S):**将指令区的指令组翻译为json写回表格和文件,并关闭事件编辑器。如果手动修改了json区却没有点击变黄的解析按钮,或指令区存在异步事件没有用“等待所有异步事件执行完毕”(图中第二条褐色指令)去等待,则单击确认按钮会弹窗警告。此时如果想放弃对json区的修改,请随便点一下指令区的一个块即可。
|
||||||
|
2. **解析:**将手动修改后的json区翻译回指令组同步到指令区,一般用于跨作品复制事件,或长距离批量挪动和复制指令。此外,由于下拉框不支持任意输入(如新增的怪物属性、楼层切换的任意目标图块ID),所以也必须这样做。
|
||||||
|
3. **取消:**放弃本次编辑,关闭事件编辑器,表格和文件会停留在打开事件编辑器前的状态。
|
||||||
|
4. **搜索事件块:**当您想不起来需要的指令在哪个类别时,请使用此项,如输入“勇”字就会列出所有带有“勇士”一词的指令。
|
||||||
|
5. **地图选点:**在编辑事件尤其是大篇幅事件的过程中,如果需要频繁保存并退出事件编辑器去浏览地图尤其是浏览别的楼层就很不方便。单击此按钮,您可以浏览所有楼层及其全景。通过点击地图去确认一个点的坐标,并复制任何一个楼层ID以填入需要的地方。需要填坐标的指令也可以直接双击其指令块从地图选点填入坐标和楼层,从而避免了肉眼数格子和手敲的麻烦和出错的隐患。
|
||||||
|
6. **变量出现位置搜索:**您可以搜索所有以“变量:xxx”形式出现在事件中的变量的出现位置,形式必须是冒号缩写量,位置必须是事件而不是脚本。也就是说,`flags.xxx`和`core.insertAction([...])`中的变量都是搜索不到的。
|
||||||
|
7. **开启中文名替换:**勾选此项后,“flag、status、item、switch、temp、global、enemy”等冒号缩写量的前缀以及怪物和道具的ID才会允许用中文在指令块中书写,如“变量、状态、物品、独立开关、临时变量、全局存储、怪物”。当然,json中还是只能用英文的。此外,如果您需要使用名称相同的道具或怪物,或在`${}`以外的文本中将上述几个词和冒号连用,则也可能需要关闭此功能以避免blockly和json互译出错。
|
||||||
|
8. **展开值块逻辑运算:**在值块菜单中我们可以看到第一个就是二元运算块,但是它并不完整,比如没有位运算。因此如果解析的优先级与js的优先级不一致,请在表达式中适当的位置添加圆括弧,或取消勾选此项。
|
||||||
|
|
||||||
|
## 值块和冒号缩写量
|
||||||
|
![image](img/values.jpg)
|
||||||
|
值块并不能单独构成指令,但它们可以被嵌入数值操作、设置怪物属性和很多事件控制类指令。而冒号缩写量的用法就更多了,比如用于`${表达式计算}`和其他任何支持填表达式的地方。
|
||||||
|
|
||||||
|
值块大体上分为三种:勾选框与运算块、只读块、可读写块,其中后两类对应着冒号缩写量。
|
||||||
|
|
||||||
|
### 勾选框与运算块(附js比较运算大图鉴)
|
||||||
|
|
||||||
|
包括勾选框块(`true`和`false`两个逻辑常量)、否定运算块和二元运算块。
|
||||||
|
|
||||||
|
否定运算块(一个“非”字,脚本中写作一个叹号)会把`false、0、null、undefined、NaN`和空字符串”(以下简称广义`false`)变为`true`,而把别的量都变为`false`.
|
||||||
|
|
||||||
|
二元运算块包括四则运算、取余、乘方、比较运算和三种逻辑运算。
|
||||||
|
|
||||||
|
四则运算中,两个整数相除会得到小数,两个0的乘方会得到1(数学上无意义)。
|
||||||
|
|
||||||
|
八种比较运算中,四种比大小的运算如果两项中有一项是数字,就会把另一项也转换成数字去比,所以会出现这些:
|
||||||
|
|
||||||
|
`'2' < 10; 10 <= '10'; '10' < 2; null >= 0; null <= 0; 1/0 > 1/-0`
|
||||||
|
|
||||||
|
![image](img/compare.jpg)
|
||||||
|
|
||||||
|
弱(不)等于(==和!=)非常难用,建议放弃,统一使用(不)等于(===和!==)。
|
||||||
|
|
||||||
|
`NaN`一般是`0/0`或字符串瞎做减乘除得到的,它跟谁都没有可比性,包括自己。
|
||||||
|
|
||||||
|
纯数字字符串(包括十六进制)和对应的数字,都是弱相等关系。如`'0x10' == 16`
|
||||||
|
|
||||||
|
数组和对象,跟自己都是既大于等于又小于等于的关系(因为不是同一个)。
|
||||||
|
|
||||||
|
单个数字(或什么都没有)用方括号还是引号括起来,对外的比较行为都一致。
|
||||||
|
|
||||||
|
`undefined`倒是不用担心,它除了和`null`弱相等外,和其他值都没有可比性。
|
||||||
|
|
||||||
|
三种逻辑运算的原则是,“且”的优先级高于“或”,它俩都不满足交换律。
|
||||||
|
|
||||||
|
若干个由“且”(&&)连起来的量中,取第一个广义`false`(没有则取最后一个量),
|
||||||
|
|
||||||
|
若干个由“或”(||)连起来的量中,取第一个不是广义`false`的量(没有则同上),
|
||||||
|
|
||||||
|
若干个由“异或”(^)连起来的`true`或`false`,总结果取决于其中`true`的个数的奇偶,奇数个`true`则总结果为1,偶数个`true`则总结果为0.
|
||||||
|
|
||||||
|
所以样板中充斥着形如`x=x||y`和`z=x||y`的代码,y算是x的默认值。
|
||||||
|
|
||||||
|
这种做法并不安全,如果您的作品不打算发布到我们的`h5mota.com`,则更建议使用`x??y`,它只会在“x为`null`或`undefined`时”才用y代替x。
|
||||||
|
|
||||||
|
双问号运算在早期浏览器并不被支持,因此如需在我们的站点发布则需要写`if(x==null){x=y;}`。
|
||||||
|
|
||||||
|
再次强调,广义`false`不一定和`false`弱相等(比如`null`),而和`false`弱相等的也不一定是广义`false`(如`[]==![]`)。
|
||||||
|
|
||||||
|
V2.7.3起,一元运算块追加了一些数学函数,如四种取整、开平方、绝对值。还追加了“变量类型”(typeof),只有两个类型相同的变量才有可能满足“相等”(===)。
|
||||||
|
|
||||||
|
V2.8起,二元运算块追加了一些js函数,如“取较大/较小”(`Math.max`和`Math.min`)、“开始于/结束于”(字符串的`startsWith`和`endsWith`)、“包含”(数组和字符串的`includes`),进阶作者可以学习使用。
|
||||||
|
### 只读块(怪物属性、图块ID、图块类别、装备孔)
|
||||||
|
|
||||||
|
尽管这几个值块是只读的,但您仍然可以使用“设置怪物属性”、“穿脱装备”和“转变图块”等指令去改变它们。
|
||||||
|
|
||||||
|
1. **怪物属性:**冒号缩写量写作`怪物:绿头怪:生命`,json代码中写作`enemy:greenSlime:hp`。怪物名称有重复的话可以在事件编辑器顶部关闭中文替换功能,道具名称同理。
|
||||||
|
* 常见的怪物属性都提供在了下拉框中,如果下拉框里没有(如通过配置表格新增的属性),可以修改`_server\MotaAction.g4`文件最后的那些表来追加其中文简称,也可以放弃解析回中文块而是就用缩写量。
|
||||||
|
* V2.8起,怪物支持“行走图朝向”绑定,此值块将始终获取脸朝下的怪物属性。
|
||||||
|
2. **图块ID:**冒号缩写量写作`图块ID:x,y`,json代码中写作`blockId:x,y`。请注意逗号始终要用英文的,此值块实际直接调用的API为`core.getBlockId(x,y)`,即本值块和对应的冒号缩写量只支持本层,可以获知本层某个点的图块ID.
|
||||||
|
3. **图块数字:**冒号缩写量写作`图块数字:x,y`,json代码中写作`blockNumber:x,y`。同上,实际调用`core.getBlockNumber(x,y)`,可以获知本层某个点的图块数字。
|
||||||
|
4. **图块类别:**冒号缩写量写作`图块类别:x,y`,json代码中写作`blockCls:x,y`。同上,实际调用`core.getBlockCls(x,y)`,可以获知本层某个点的图块类别。
|
||||||
|
* V2.8起,这三个值块中的坐标支持填写表达式,会在json区直接翻译为API调用,但这样填写也意味着无法再解析回值块,而且本质上这已经不是冒号缩写量了,不能直接用于${表达式计算}等场合。
|
||||||
|
5. **第n格装备孔:**冒号缩写量写作`装备孔:n`,json代码中写作`equip:n`。实际调用`core.getEquip(n)`,可以获知勇士第n个装备孔中的装备ID,n从0开始!
|
||||||
|
|
||||||
|
### 可读写块(status、item、flag、switch、temp、global、buff)
|
||||||
|
|
||||||
|
比起只读块,可读写块允许作为“数值操作”指令的左块:
|
||||||
|
1. **状态:**冒号缩写量为`状态:生命`等,json代码中写作`status:hp`等。
|
||||||
|
* 作为左块时会调用`core.setStatus()`,其他时候会调用`core.getStatus()`
|
||||||
|
2. **物品:**冒号缩写量为`物品:炸弹`等,json代码中写作`item:bomb`等
|
||||||
|
* 作为左块时会调用`core.getItem()`或`core.setItem()`,其他时候会调用`core.itemCount()`来统计背包中的道具数量。
|
||||||
|
* 此外,最常用的一些勇士状态(包括坐标和朝向)、三色钥匙、三围增益都提供在了下拉框中。您可以修改`_server/MotaAction.g4`文件最后的`FixedId_List`来追加,向“楼层转换”、“门信息”和“装备属性”的下拉框追加新的楼梯ID、钥匙ID和勇士状态名也是一样的道理,当然不追加也不影响手写(如果指令只提供了下拉框版本,那就必须写在json区再解析)。
|
||||||
|
3. **变量:**冒号缩写量为`变量:hatred`等,json代码中写作`flag:hatred`等。
|
||||||
|
* 作为左块时会调用`core.setFlag()`,其他时候会调用`core.getFlag(’xxx’, 0)`
|
||||||
|
* 请注意:变量类型支持中文,如`变量:开门个数`
|
||||||
|
* 这两个API实际操作的就是前文多次提到的`core.status.hero.flags`,只不过在事件中未定义的变量都视为0,更为安全。
|
||||||
|
* 如果您忘记了自己在哪些事件用过哪些变量,请善用事件编辑器顶部的“搜索变量出现位置”。
|
||||||
|
4. **独立开关:**如果您有一大批NPC都具有“首次对话不同于之后各次对话”之类的特点,那么为他们设置一大批不重名的变量就很繁琐。独立开关(叫独立变量更准确)就是为这种场合而生的,它对每层楼(首次到达和每次到达中)和每个坐标都是独立的。
|
||||||
|
* 冒号缩写量写作`独立开关:A—Z`,json代码中写作`switch:A—Z`。
|
||||||
|
* 每个坐标处的独立开关有26个,用大写拉丁字母A—Z表示。MTn层(x,y)点的独立开关A,本质上是一个flag变量——`flag:MTn@x@y@A`,如果需要在其他点访问,就需要用这样的写法。
|
||||||
|
* 首次到达/每次到达楼层的事件中,上述x和y会保留小写字母而不代入任何数字。
|
||||||
|
* 标题事件/开场剧情中,前缀的“楼层ID”视为“`:f`”。
|
||||||
|
* 可以在事件编辑器左侧菜单的“常用事件模板”中看到一个仿51层/新新魔塔的一次性商人的例子。
|
||||||
|
5. **临时变量:**冒号缩写量写作“临时变量:A—Z”,json代码中写作`temp:A-Z`,
|
||||||
|
* 临时变量泛指所有以`@temp@`开头的flag变量,一共也有26个。
|
||||||
|
* 临时变量一般用于计数循环和数组迭代,每当事件流结束(勇士恢复行动)时,临时变量和参数变量(以arg+纯数字命名)都会被清空。
|
||||||
|
* 全局商店状态下,临时变量`@temp@shop`会被启用,从而实现长按连续购买等操作。
|
||||||
|
* 上面提到的“一次性商人”模板中多次出现出售数量和价格常数,如果使用临时变量就很方便,不需要修改多个地方了。
|
||||||
|
6. **全局存储:**冒号缩写量写作`全局存储:xxx`,json代码中写作`global:xxx`,
|
||||||
|
* 和变量一样,冒号右侧本身支持中文。全局存储一般用来保存一些跨存档的信息,如成就系统、回想和音像鉴赏的收集进度、多周目数据等。
|
||||||
|
* 用于左块时会调用`core.setGlobal()`,其他时候会调用`core.getGlobal()`。
|
||||||
|
* `core.setGlobal()`在录像中会被忽略,`core.getGlobal()`在正常游戏中会将读取到的值写进录像,而在录像播放中直接读取这个写进去的值,从而复原了那次游戏时的样子。
|
||||||
|
* 然而,由于`core.getGlobal()`的调用频率在正常游戏和录像播放中可能不一致,因而可能会导致录像中的记录次数过多、过少或位置不合适。
|
||||||
|
* V2.8对这个问题进行了一定的修复,请放心使用。
|
||||||
|
7. **增益:**冒号缩写量写作`增益:生命上限`等,json代码中写作`buff:hpmax`等,冒号右侧支持自定义的勇士新属性英文名。
|
||||||
|
* 用于左块时会调用`core.setBuff()`,其他时候会调用`core.getBuff()`。
|
||||||
|
* 增益这个概念在前面讲解装备属性时提到过,所有的百分比装备和百分比衰弱都是靠它发挥作用。
|
||||||
|
* `buff:xxx`本质上就是`flag:__buff_xxx__`,但前者更安全(默认值为1)。
|
||||||
|
|
||||||
|
## 随机数(扩展内容,了解即可)
|
||||||
|
|
||||||
|
此外,V2.8还新增了几个样板API的值块,包括“当前是否身穿某装备”、“当前是否在录像播放中”、“是否到达过某楼层”、“是否开启了某全局商店”、“能否打败某怪物”、“勇士面前第n格的坐标(负数表示身后)”、“随机数”。
|
||||||
|
|
||||||
|
样板包括两种随机数,一种是上面说的值块提供的`core.rand(n)`,会得到小于正整数n的随机自然数,n不填则会得到小于1的随机正小数。
|
||||||
|
|
||||||
|
一种则是`core.rand2(n)`,同样会得到小于n的随机自然数,n必须填正整数,不要省略!
|
||||||
|
|
||||||
|
rand本质上是`flag:__rand__`不断一步步推进的结果,初始值为`flag:__seed__`,初始值(又叫随机种子)和游戏难度可以在标题画面通过`core.startGame(hard,seed)`函数指定(如果开启了标题界面事件化,则该函数还必须作为游戏重启函数`core.hideStartAnimate()`的回调来使用,但这样只能指定种子),种子必须为正整数。
|
||||||
|
|
||||||
|
rand2则是通过js自带的`Math.random()`函数得到的真随机小数经过整数化处理的结果,会被直接写入录像(`random:n`),因此多次调用会导致录像(和存档)变得很庞大。而且正因为是写入录像,所以玩家也可以直接编辑录像文件或`core.status.route`来控制随机的结果。
|
||||||
|
|
||||||
|
直观地说,rand的结果只和它的调用次数有关,因此玩家不能通过简单的SL操作来优化结果,必须调整路线或干脆重开游戏。
|
||||||
|
|
||||||
|
而rand2的结果可以通过SL来改变,如果需要连续大量使用“可被SL改变的随机数”(期间不能存档)但又不希望录像变得太庞大,可以先在存档后使用一次`n=core.rand2(114514)`,再使用n次`core.rand()`来推进,效果就能让人满意了。(新新魔塔1就是这样的思路,每次撞怪时先自动存档然后生成n值,战斗中只使用rand)。
|
||||||
|
|
||||||
|
然而,如果rand和rand2的使用场合不当,那么其调用频率在正常游戏中和录像回放时可能不一致。rand会因为推进步数不一致而导致游戏流程发生变化,rand2则会导致录像中记录的随机数出现多余或缺失。
|
||||||
|
|
||||||
|
V2.8对rand2(以及“全局存储”、“弹窗输入”、“选择项”、“确认框”)的此问题进行了一定的修复,如果录像中出现了多余的随机数记录,就会在播放时跳过并在控制台警告,如果需要rand2但未记录或记录越界则会现场重新随机生成并补录。在V2.8之前这两种情况都会直接报错,而在V2.8中会导致首次播放结果的二次记录不一致,请不要惊慌,按R键从头再次播放一遍即可。
|
||||||
|
|
||||||
|
总之,不计入录像的纯观赏性的随机效果(如捡到某道具随机播放一个音效甚至随机指定音调高低,以及样板雨雪效果中的随机)建议直接用`Math.random()`实现(例如V2.8每次上下楼后会在楼层属性的背景音乐中随机播放一个)。
|
||||||
|
|
||||||
|
## 录像回放(扩展内容,了解即可)
|
||||||
|
|
||||||
|
魔塔具有时空离散性和完全可重复性,因此可以像棋类运动一样记录类似棋谱的东西,我们称之为【录像】。
|
||||||
|
|
||||||
|
正常游戏中,已录制的内容被保存在一维数组`core.status.route`中并不断从尾部延长,录像回放时,即将播放的内容会保存在一维数组`core.status.replay.toReplay`中并不断从头部缩减。
|
||||||
|
|
||||||
|
V2.8.1起,录像中勇士死亡将会报错并询问是否回到上一个节点,比起只能读取自动存档更为友好。
|
||||||
|
|
||||||
|
您可以在正常游戏中自由行动时随时按下R键进行回放,上述数组具体的内容含义如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
up down left right 勇士向某个方向行走一步
|
||||||
|
item:ID 打开背包使用某件道具,如item:bomb表示使用炸弹
|
||||||
|
unEquip:n 卸掉身上第n件装备(n从0开始),如unEquip:1默认表示卸掉盾牌
|
||||||
|
equip:ID 打开背包穿上一件装备,如equip:sword1表示装上铁剑
|
||||||
|
saveEquip:n 将身上的当前套装保存到第n套快捷套装(n从0开始)
|
||||||
|
loadEquip:n 快捷换上之前保存好的第n套套装
|
||||||
|
fly:ID 使用楼传飞到某一层,如fly:MT10表示飞到主塔10层
|
||||||
|
choices:none 确认框/选择项界面超时(作者未设置超时时间则此项视为缺失)
|
||||||
|
choices:n 确认框/选择项界面选择第n项(选择项中n从0开始,确认框界面0表示确定,1表示取消),此项越界将报错。此项缺失的话,确认框将选择作者指定的默认项(初始光标位置),选择项将弹窗请求补选(后台录像验证中则选默认项)。
|
||||||
|
shop:ID 打开某个全局商店,如shop:itemShop表示打开道具商店。因此连载塔千万不要中途修改商店ID!
|
||||||
|
turn 单击勇士(Z键)转身
|
||||||
|
turn:dir 勇士转向某个方向,dir可以为up down left right,注意此项在正常游戏中无法随意触发。
|
||||||
|
getNext 轻按获得身边道具,优先获得面前的,身边如果没有道具则此项会被跳过。
|
||||||
|
input:none “等待用户操作事件”中超时(作者未设置超时时间则此项会导致报错)
|
||||||
|
input:xxx 可能表示“等待用户操作事件”的一个操作,也可能表示一个“接受用户输入数字”的输入,后者的情况下xxx为输入的数字。此项缺失的话前者将直接报错,后者将用0代替
|
||||||
|
input2:xxx 可能表示“读取全局存储(core.getGlobal)”读取到的值,也可能表示一个“接受用户输入文本”的输入,两种情况下xxx都为base64编码。此项缺失的话前者将重新现场读取,后者将用空字符串代替
|
||||||
|
no 走到可穿透的楼梯上不触发楼层切换事件,注意正常游戏中勇士无法随意停在旁边没有障碍物的楼梯上。
|
||||||
|
move:x:y 尝试瞬移到[x,y]点,注意正常游戏中勇士无法随意瞬移到相邻格,而回放时连续的瞬移操作将被合并。
|
||||||
|
key:n 按下键值为n的键,如key:49表示按下大键盘数字键1,默认会触发使用破墙镐
|
||||||
|
click:n:px:py 点击自绘状态栏,n为0表示横屏1表示竖屏,[px,py]为点击的像素坐标
|
||||||
|
random:n 生成了随机数n,即core.rand2(num)的返回结果,n必须在[0,num-1]范围,num必须为正整数。此项缺失或越界将导致现场重新随机生成数值,可能导致回放结果不一致!
|
||||||
|
作者自定义的新项(一般为js对象,可以先JSON.stringify()再core.encodeBase64()得到纯英文数字的内容)需要用(半角圆括弧)括起来。
|
||||||
|
```
|
||||||
|
|
||||||
|
[在线插件库](https://h5mota.com/plugins/)中提供了“录像自助精修”插件,手机也适用,可供研究。
|
||||||
|
|
||||||
|
开门打怪前会先转身再自动存档,因此该存档被读取后,已录制内容的最后一步将变成转身,导致录像变长,请自行注意。
|
||||||
|
|
||||||
|
==========================================================================================
|
||||||
|
|
||||||
|
[继续阅读下一章:事件指令](instruction)
|
313
eventDec.d.ts
vendored
Normal file
313
eventDec.d.ts
vendored
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
type MotaAction = any;
|
||||||
|
type MotaEvent = any[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 某种类型的商店
|
||||||
|
*/
|
||||||
|
type ShopEventOf<T extends keyof ShopEventMap> = ShopEventMap[T];
|
||||||
|
|
||||||
|
interface ShopEventMap {
|
||||||
|
/**
|
||||||
|
* 普通商店
|
||||||
|
*/
|
||||||
|
common: CommonShopEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具商店
|
||||||
|
*/
|
||||||
|
item: ItemShopEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共事件商店
|
||||||
|
*/
|
||||||
|
event: CommonEventShopEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShopEventBase {
|
||||||
|
/**
|
||||||
|
* 商店的id
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商店快捷名称
|
||||||
|
*/
|
||||||
|
textInList: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否在未开启状态下快捷商店不显示该商店
|
||||||
|
*/
|
||||||
|
mustEnable: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否不可预览
|
||||||
|
*/
|
||||||
|
disablePreview: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通商店的一个商店选项
|
||||||
|
*/
|
||||||
|
interface CommonShopChoice {
|
||||||
|
/**
|
||||||
|
* 选项文字
|
||||||
|
*/
|
||||||
|
text: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选项需求,需要是一个表达式
|
||||||
|
*/
|
||||||
|
need: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图标
|
||||||
|
*/
|
||||||
|
icon: AllIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文字的颜色
|
||||||
|
*/
|
||||||
|
color: Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该选项被选中时执行的事件
|
||||||
|
*/
|
||||||
|
action: MotaEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通商店
|
||||||
|
*/
|
||||||
|
interface CommonShopEvent extends ShopEventBase {
|
||||||
|
/**
|
||||||
|
* 商店中显示的文字
|
||||||
|
*/
|
||||||
|
text: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通商店的选项
|
||||||
|
*/
|
||||||
|
choices: CommonShopChoice[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具商店的一个选项
|
||||||
|
*/
|
||||||
|
interface ItemShopChoice {
|
||||||
|
/**
|
||||||
|
* 该选项的道具id
|
||||||
|
*/
|
||||||
|
id: AllIdsOf<'items'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具存量
|
||||||
|
*/
|
||||||
|
number: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 购买时消耗的资源数量,是字符串大概是因为这玩意可以用${}
|
||||||
|
*/
|
||||||
|
money: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卖出时获得的资源数量
|
||||||
|
*/
|
||||||
|
sell: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出现条件
|
||||||
|
*/
|
||||||
|
condition: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具商店
|
||||||
|
*/
|
||||||
|
interface ItemShopEvent extends ShopEventBase {
|
||||||
|
/**
|
||||||
|
* 道具商店标识
|
||||||
|
*/
|
||||||
|
item: true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 购买消耗什么东西,金币还是经验
|
||||||
|
*/
|
||||||
|
use: 'money' | 'exp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每个选项
|
||||||
|
*/
|
||||||
|
choices: ItemShopChoice[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommonEventShopEvent {
|
||||||
|
/**
|
||||||
|
* 使用的公共事件
|
||||||
|
*/
|
||||||
|
commonEvent: EventDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AutoEventBase {
|
||||||
|
/**
|
||||||
|
* 自动事件的触发条件
|
||||||
|
*/
|
||||||
|
condition: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否只在当前层检测
|
||||||
|
*/
|
||||||
|
currentFloor: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级,优先级越高越优先执行
|
||||||
|
*/
|
||||||
|
priority: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否在事件流中延迟执行
|
||||||
|
*/
|
||||||
|
delayExecute: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否允许多次执行
|
||||||
|
*/
|
||||||
|
multiExecute: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当条件满足时执行的事件
|
||||||
|
*/
|
||||||
|
data: MotaEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AutoEvent extends AutoEventBase {
|
||||||
|
/**
|
||||||
|
* 当前的楼层id
|
||||||
|
*/
|
||||||
|
floorId: FloorIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动事件的索引
|
||||||
|
*/
|
||||||
|
index: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件所在的横坐标
|
||||||
|
*/
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件所在的纵坐标
|
||||||
|
*/
|
||||||
|
y: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件的唯一标识符
|
||||||
|
*/
|
||||||
|
symbol: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LevelChooseEvent {
|
||||||
|
/**
|
||||||
|
* 难度名称
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 难度简称
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 难度的hard值
|
||||||
|
*/
|
||||||
|
hard: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 难度的颜色
|
||||||
|
*/
|
||||||
|
color: RGBArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择该难度时执行的事件
|
||||||
|
*/
|
||||||
|
action: MotaEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LevelUpEvent {
|
||||||
|
/**
|
||||||
|
* 升级所需经验
|
||||||
|
*/
|
||||||
|
need: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个等级的等级名
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 升级时执行的事件
|
||||||
|
*/
|
||||||
|
action: MotaEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 门信息
|
||||||
|
*/
|
||||||
|
interface DoorInfo {
|
||||||
|
/**
|
||||||
|
* 开门时间
|
||||||
|
*/
|
||||||
|
time: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开门音效
|
||||||
|
*/
|
||||||
|
openSound: SoundIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关门音效
|
||||||
|
*/
|
||||||
|
closeSound: SoundIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要的钥匙
|
||||||
|
*/
|
||||||
|
keys: Partial<Record<ItemIdOf<'tools'> | `${ItemIdOf<'tools'>}:o`, number>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开门后事件
|
||||||
|
*/
|
||||||
|
afterOpenDoor?: MotaEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChangeFloorEvent {
|
||||||
|
/**
|
||||||
|
* 到达的楼层
|
||||||
|
*/
|
||||||
|
floorId: ':before' | ':after' | ':now' | FloorIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 到达的坐标,填了的话stair就无效了
|
||||||
|
*/
|
||||||
|
loc?: LocArr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 到达的坐标
|
||||||
|
*/
|
||||||
|
stair?: FloorChangeStair;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士朝向
|
||||||
|
*/
|
||||||
|
direction?: HeroTurnDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 楼层转换时间
|
||||||
|
*/
|
||||||
|
time?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否不可穿透
|
||||||
|
*/
|
||||||
|
ignoreChangeFloor?: boolean;
|
||||||
|
}
|
456
eventStatus.d.ts
vendored
Normal file
456
eventStatus.d.ts
vendored
Normal file
@ -0,0 +1,456 @@
|
|||||||
|
interface EventStatusDataMap {
|
||||||
|
/**
|
||||||
|
* 执行事件中
|
||||||
|
*/
|
||||||
|
action: ActionStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 怪物手册的信息,是当前选择了哪个怪物
|
||||||
|
*/
|
||||||
|
book: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 楼层传送器中当前楼层索引
|
||||||
|
*/
|
||||||
|
fly: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览地图时的信息
|
||||||
|
*/
|
||||||
|
viewMaps: ViewMapStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装备栏的信息
|
||||||
|
*/
|
||||||
|
equipbox: EquipboxStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具栏的信息
|
||||||
|
*/
|
||||||
|
toolbox: ToolboxStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存档界面的信息
|
||||||
|
*/
|
||||||
|
save: SaveStatusData;
|
||||||
|
load: SaveStatusData;
|
||||||
|
replayLoad: SaveStatusData;
|
||||||
|
replayRemain: SaveStatusData;
|
||||||
|
replaySince: SaveStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本框界面的信息
|
||||||
|
*/
|
||||||
|
text: TextStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认框界面的信息
|
||||||
|
*/
|
||||||
|
confirmBox: ConfirmStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关于界面,帮助界面,怪物手册详细信息界面,虚拟键盘界面,系统设置界面,系统选项栏界面,
|
||||||
|
* 快捷商店界面,存档笔记界面,同步存档界面,光标界面,录像回放界面,游戏信息界面,没有东西
|
||||||
|
*/
|
||||||
|
about: null;
|
||||||
|
help: null;
|
||||||
|
'book-detail': null;
|
||||||
|
keyBoard: null;
|
||||||
|
switchs: null;
|
||||||
|
'switchs-sounds': null;
|
||||||
|
'switchs-display': null;
|
||||||
|
'switchs-action': null;
|
||||||
|
settings: null;
|
||||||
|
selectShop: null;
|
||||||
|
notes: null;
|
||||||
|
syncSave: null;
|
||||||
|
syncSelect: null;
|
||||||
|
localSaveSelect: null;
|
||||||
|
storageRemove: null;
|
||||||
|
cursor: null;
|
||||||
|
replay: null;
|
||||||
|
gameInfo: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface _EventStatusSelectionMap {
|
||||||
|
/**
|
||||||
|
* 执行事件中,一般是选择框的当前选中项
|
||||||
|
*/
|
||||||
|
action: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装备栏中当前选中了哪个装备
|
||||||
|
*/
|
||||||
|
equipbox: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具栏中当前选中了哪个道具
|
||||||
|
*/
|
||||||
|
toolbox: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否是删除模式
|
||||||
|
*/
|
||||||
|
save: boolean;
|
||||||
|
load: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前选择了确认(0)还是取消(1)
|
||||||
|
*/
|
||||||
|
confirmBox: 0 | 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统设置界面,存档笔记界面,同步存档界面,录像回放界面,游戏信息界面,当前的选择项
|
||||||
|
*/
|
||||||
|
switchs: number;
|
||||||
|
'switchs-sounds': number;
|
||||||
|
'switchs-display': number;
|
||||||
|
'switchs-action': number;
|
||||||
|
settings: number;
|
||||||
|
notes: number;
|
||||||
|
syncSave: number;
|
||||||
|
syncSelect: number;
|
||||||
|
localSaveSelect: number;
|
||||||
|
storageRemove: number;
|
||||||
|
replay: number;
|
||||||
|
gameInfo: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface _EventStatusUiMap {
|
||||||
|
/**
|
||||||
|
* 执行事件中,一般是与选择框有关的
|
||||||
|
*/
|
||||||
|
action: ActionStatusUi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果是从浏览地图界面呼出的怪物手册,该项是当前正在浏览的地图的索引(注意不是id)
|
||||||
|
*/
|
||||||
|
book: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认框中显示的文字
|
||||||
|
*/
|
||||||
|
confirmBox: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示设置的选择信息
|
||||||
|
*/
|
||||||
|
'switchs-display': SwitchsStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统选项栏的选择信息
|
||||||
|
*/
|
||||||
|
settings: SwitchsStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快捷商店界面,存档笔记界面,同步存档界面,录像回放界面,游戏信息界面的绘制信息
|
||||||
|
*/
|
||||||
|
selectShop: SelectShopStatusUi;
|
||||||
|
notes: SelectShopStatusUi;
|
||||||
|
syncSave: SelectShopStatusUi;
|
||||||
|
syncSelect: SelectShopStatusUi;
|
||||||
|
localSaveSelect: SelectShopStatusUi;
|
||||||
|
storageRemove: SelectShopStatusUi;
|
||||||
|
gameInfo: SelectShopStatusUi;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface _EventStatusIntervalMap {
|
||||||
|
/**
|
||||||
|
* 执行事件中,一般用于呼出某个界面时暂存当前事件信息(?
|
||||||
|
*/
|
||||||
|
action: ActionStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果是从事件中呼出的,用于存储当前事件信息,当退出怪物手册时恢复事件
|
||||||
|
*/
|
||||||
|
book: ActionStatusData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果是从事件中呼出的,用于存储当前事件信息,当退出存档节目时恢复事件
|
||||||
|
*/
|
||||||
|
save: ActionStatusData;
|
||||||
|
load: ActionStatusData;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface _EventStatusTimeoutMap {
|
||||||
|
/**
|
||||||
|
* 执行事件中,一般是等待用户操作事件等事件中的超时时间的判定
|
||||||
|
*/
|
||||||
|
action: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface _EventStatusAnimateUiMap {
|
||||||
|
/**
|
||||||
|
* 执行事件中,一般是对话框事件的动画定时器
|
||||||
|
*/
|
||||||
|
action: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventStatus = keyof EventStatusDataMap;
|
||||||
|
|
||||||
|
type _FillEventStatus<T> = {
|
||||||
|
[P in EventStatus]: P extends keyof T ? T[P] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EventStatusSelectionMap = _FillEventStatus<_EventStatusSelectionMap>;
|
||||||
|
type EventStatusUiMap = _FillEventStatus<_EventStatusUiMap>;
|
||||||
|
type EventStatusIntervalMap = _FillEventStatus<_EventStatusIntervalMap>;
|
||||||
|
type EventStatusTimeoutMap = _FillEventStatus<_EventStatusTimeoutMap>;
|
||||||
|
type EventStatusAnimateUiMap = _FillEventStatus<_EventStatusAnimateUiMap>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 某个事件状态下的信息
|
||||||
|
*/
|
||||||
|
interface EventStatusOf<T extends EventStatus = EventStatus> {
|
||||||
|
/**
|
||||||
|
* 当前事件状态的类型
|
||||||
|
*/
|
||||||
|
id: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前事件状态的信息
|
||||||
|
*/
|
||||||
|
data: EventStatusDataMap[T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前事件状态的选择信息
|
||||||
|
*/
|
||||||
|
selection: EventStatusSelectionMap[T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前事件状态的ui信息
|
||||||
|
*/
|
||||||
|
ui: EventStatusUiMap[T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前事件状态的定时器信息
|
||||||
|
*/
|
||||||
|
interval: EventStatusIntervalMap[T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前事件状态的计时器信息
|
||||||
|
*/
|
||||||
|
timeout: EventStatusTimeoutMap[T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前事件状态的动画信息
|
||||||
|
*/
|
||||||
|
animateUi: EventStatusAnimateUiMap[T];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionStatusEventList {
|
||||||
|
/**
|
||||||
|
* 要执行的事件列表
|
||||||
|
*/
|
||||||
|
todo: MotaEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全部的事件列表
|
||||||
|
*/
|
||||||
|
total: MotaEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行条件
|
||||||
|
*/
|
||||||
|
contidion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionLocStackInfo {
|
||||||
|
/**
|
||||||
|
* 横坐标
|
||||||
|
*/
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纵坐标
|
||||||
|
*/
|
||||||
|
y: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 楼层id
|
||||||
|
*/
|
||||||
|
floorId: FloorIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行事件中
|
||||||
|
*/
|
||||||
|
interface ActionStatusData {
|
||||||
|
/**
|
||||||
|
* 当前的事件列表
|
||||||
|
*/
|
||||||
|
list: DeepReadonly<ActionStatusEventList>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件执行的横坐标,或者对话框的横坐标
|
||||||
|
*/
|
||||||
|
x?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件执行的纵坐标,或者对话框的纵坐标
|
||||||
|
*/
|
||||||
|
y?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件执行完毕的回调函数
|
||||||
|
*/
|
||||||
|
callback?: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不太清楚用处,可能是与自动事件有关的
|
||||||
|
*/
|
||||||
|
appendingEvents: MotaEvent[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件的坐标栈
|
||||||
|
*/
|
||||||
|
locStack: any[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前的事件类型
|
||||||
|
*/
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前事件
|
||||||
|
*/
|
||||||
|
current: MotaAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionStatusUi {
|
||||||
|
/**
|
||||||
|
* 显示文字事件的文字,包括确认框等
|
||||||
|
*/
|
||||||
|
text: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认框中确定时执行的事件
|
||||||
|
*/
|
||||||
|
yes?: MotaEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认框中取消时执行的事件
|
||||||
|
*/
|
||||||
|
no?: MotaEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是选择事件时所有的选项
|
||||||
|
*/
|
||||||
|
choices?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是选择事件时选项框的宽度
|
||||||
|
*/
|
||||||
|
width?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ViewMapStatusData {
|
||||||
|
/**
|
||||||
|
* 当前浏览的楼层索引
|
||||||
|
*/
|
||||||
|
index: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否显示伤害
|
||||||
|
*/
|
||||||
|
damage: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大地图是否显示全部地图
|
||||||
|
*/
|
||||||
|
all: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大地图不显示全部地图时当前的横坐标,单位格子
|
||||||
|
*/
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大地图不显示全部地图时当前的纵坐标,单位格子
|
||||||
|
*/
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EquipboxStatusData {
|
||||||
|
/**
|
||||||
|
* 拥有装备的当前页数
|
||||||
|
*/
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前选中的装备
|
||||||
|
*/
|
||||||
|
selectId: ItemIdOf<'equips'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolboxStatusData {
|
||||||
|
/**
|
||||||
|
* 消耗道具的当前页码数
|
||||||
|
*/
|
||||||
|
toolsPage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 永久道具的当前页码数
|
||||||
|
*/
|
||||||
|
constantsPage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前选中的道具id
|
||||||
|
*/
|
||||||
|
selectId: ItemIdOf<'constants' | 'tools'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SaveStatusData {
|
||||||
|
/**
|
||||||
|
* 当前存读档界面的页码数
|
||||||
|
*/
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择的框的偏移量,在不同状态下意义不同
|
||||||
|
*/
|
||||||
|
offset: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前存读档界面的模式,fav表示收藏,all表示所有存档
|
||||||
|
*/
|
||||||
|
mode: 'fav' | 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TextStatusData {
|
||||||
|
/**
|
||||||
|
* 文本框要显示的文字列表
|
||||||
|
*/
|
||||||
|
list: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文字显示完毕后的回调函数
|
||||||
|
*/
|
||||||
|
callback: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfirmStatusData {
|
||||||
|
/**
|
||||||
|
* 点击确认时
|
||||||
|
*/
|
||||||
|
yes: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击取消时
|
||||||
|
*/
|
||||||
|
no: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SwitchsStatusData {
|
||||||
|
/**
|
||||||
|
* 选择项
|
||||||
|
*/
|
||||||
|
choices: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SelectShopStatusUi {
|
||||||
|
/**
|
||||||
|
* 选择框的偏移量
|
||||||
|
*/
|
||||||
|
offset: number;
|
||||||
|
}
|
147
events.js
Normal file
147
events.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
var events_c12a15a8_c380_4b28_8144_256cba95f760 =
|
||||||
|
{
|
||||||
|
"commonEvent": {
|
||||||
|
"加点事件": [
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"text": "通过传参,flag:arg1 表示当前应该的加点数值"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "choices",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"text": "攻击+${1*flag:arg1}",
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "status:atk",
|
||||||
|
"operator": "+=",
|
||||||
|
"value": "1*flag:arg1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "防御+${2*flag:arg1}",
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "status:def",
|
||||||
|
"operator": "+=",
|
||||||
|
"value": "2*flag:arg1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "生命+${200*flag:arg1}",
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "status:hp",
|
||||||
|
"operator": "+=",
|
||||||
|
"value": "200*flag:arg1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"回收钥匙商店": [
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"text": "此事件在全局商店中被引用了(全局商店keyShop)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"text": "解除引用前勿删除此事件"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comment",
|
||||||
|
"text": "玩家在快捷列表(V键)中可以使用本公共事件"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "while",
|
||||||
|
"condition": "1",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"type": "choices",
|
||||||
|
"text": "\t[商人,trader]你有多余的钥匙想要出售吗?",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"text": "黄钥匙(10金币)",
|
||||||
|
"color": [
|
||||||
|
255,
|
||||||
|
255,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"type": "if",
|
||||||
|
"condition": "item:yellowKey >= 1",
|
||||||
|
"true": [
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "item:yellowKey",
|
||||||
|
"operator": "-=",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "status:money",
|
||||||
|
"operator": "+=",
|
||||||
|
"value": "10"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"false": [
|
||||||
|
"\t[商人,trader]你没有黄钥匙!"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "蓝钥匙(50金币)",
|
||||||
|
"color": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
255,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"type": "if",
|
||||||
|
"condition": "item:blueKey >= 1",
|
||||||
|
"true": [
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "item:blueKey",
|
||||||
|
"operator": "-=",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "setValue",
|
||||||
|
"name": "status:money",
|
||||||
|
"operator": "+=",
|
||||||
|
"value": "50"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"false": [
|
||||||
|
"\t[商人,trader]你没有蓝钥匙!"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "离开",
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"type": "exit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
51
extensions.js
Normal file
51
extensions.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
extensions.js:负责拓展插件
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function extensions () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.prototype._load = function (callback) {
|
||||||
|
if (main.replayChecking) return callback();
|
||||||
|
if (!window.fs) {
|
||||||
|
this._loadJs('_server/fs.js', function () {
|
||||||
|
core.extensions._listExtensions(callback);
|
||||||
|
}, callback);
|
||||||
|
} else this._listExtensions(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.prototype._loadJs = function (file, callback, onerror) {
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.src = file + '?v=' + main.version;
|
||||||
|
script.onload = callback;
|
||||||
|
script.onerror = onerror;
|
||||||
|
main.dom.body.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.prototype._listExtensions = function (callback) {
|
||||||
|
if (!window.fs) return callback();
|
||||||
|
fs.readdir('extensions', function (error, data) {
|
||||||
|
if (error || !(data instanceof Array)) return callback();
|
||||||
|
var list = [];
|
||||||
|
data.forEach(function (name) {
|
||||||
|
if (/^[\w.-]+\.js$/.test(name)) {
|
||||||
|
list.push(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
list.sort();
|
||||||
|
core.extensions._loadExtensions(list, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.prototype._loadExtensions = function (list, callback) {
|
||||||
|
var i = 0;
|
||||||
|
var load = function () {
|
||||||
|
if (i == list.length) return callback();
|
||||||
|
core.extensions._loadJs('extensions/' + list[i++], load, load);
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}
|
186
fs.js
Normal file
186
fs.js
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
(function () {
|
||||||
|
window.fs = {};
|
||||||
|
|
||||||
|
|
||||||
|
var _isset = function (val) {
|
||||||
|
if (val == undefined || val == null || (typeof val == 'number' && isNaN(val))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var _http = function (type, url, formData, success, error, mimeType, responseType) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open(type, url, true);
|
||||||
|
if (_isset(mimeType))
|
||||||
|
xhr.overrideMimeType(mimeType);
|
||||||
|
if (_isset(responseType))
|
||||||
|
xhr.responseType = responseType;
|
||||||
|
xhr.onload = function (e) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
if (_isset(success)) {
|
||||||
|
success(xhr.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (_isset(error))
|
||||||
|
error("HTTP " + xhr.status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onabort = function () {
|
||||||
|
if (_isset(error))
|
||||||
|
error("Abort");
|
||||||
|
}
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
if (_isset(error))
|
||||||
|
error("Timeout");
|
||||||
|
}
|
||||||
|
xhr.onerror = function () {
|
||||||
|
if (_isset(error))
|
||||||
|
error("Error on Connection");
|
||||||
|
}
|
||||||
|
if (_isset(formData))
|
||||||
|
xhr.send(formData);
|
||||||
|
else xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var postsomething = function (data, _ip, callback) {
|
||||||
|
if (typeof (data) == typeof ([][0]) || data == null) data = JSON.stringify({ 1: 2 });
|
||||||
|
|
||||||
|
_http("POST", _ip, data, function (data) {
|
||||||
|
if (data.slice(0, 6) == 'error:') {
|
||||||
|
callback(data, null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback(null, data);
|
||||||
|
}
|
||||||
|
}, function (e) {
|
||||||
|
if (window.main != null) console.log(e);
|
||||||
|
else console.log(e);
|
||||||
|
callback(e + ":请检查启动服务是否处于正常运行状态。");
|
||||||
|
}, "text/plain; charset=x-user-defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readFile = function (filename, encoding, callback) {
|
||||||
|
if (typeof (filename) != typeof (''))
|
||||||
|
throw 'Type Error in fs.readFile';
|
||||||
|
if (encoding == 'utf-8') {
|
||||||
|
//读文本文件
|
||||||
|
//filename:支持"/"做分隔符
|
||||||
|
//callback:function(err, data)
|
||||||
|
//data:字符串
|
||||||
|
var data = '';
|
||||||
|
data += 'type=utf8&';
|
||||||
|
data += 'name=' + filename;
|
||||||
|
postsomething(data, '/readFile', callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (encoding == 'base64') {
|
||||||
|
//读二进制文件
|
||||||
|
//filename:支持"/"做分隔符
|
||||||
|
//callback:function(err, data)
|
||||||
|
//data:base64字符串
|
||||||
|
var data = '';
|
||||||
|
data += 'type=base64&';
|
||||||
|
data += 'name=' + filename;
|
||||||
|
postsomething(data, '/readFile', callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw 'Type Error in fs.readFile';
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFile = function (filename, datastr, encoding, callback) {
|
||||||
|
if (typeof (filename) != typeof ('') || typeof (datastr) != typeof (''))
|
||||||
|
throw 'Type Error in fs.writeFile';
|
||||||
|
if (encoding == 'utf-8') {
|
||||||
|
//写文本文件
|
||||||
|
//filename:支持"/"做分隔符
|
||||||
|
//callback:function(err)
|
||||||
|
//datastr:字符串
|
||||||
|
var data = '';
|
||||||
|
data += 'type=utf8&';
|
||||||
|
data += 'name=' + filename;
|
||||||
|
data += '&value=' + datastr;
|
||||||
|
postsomething(data, '/writeFile', callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (encoding == 'base64') {
|
||||||
|
//写二进制文件
|
||||||
|
//filename:支持"/"做分隔符
|
||||||
|
//callback:function(err)
|
||||||
|
//datastr:base64字符串
|
||||||
|
var data = '';
|
||||||
|
data += 'type=base64&';
|
||||||
|
data += 'name=' + filename;
|
||||||
|
data += '&value=' + datastr;
|
||||||
|
postsomething(data, '/writeFile', callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw 'Type Error in fs.writeFile';
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeMultiFiles = function (filenames, datastrs, callback) {
|
||||||
|
postsomething('name=' + filenames.join(';') + '&value=' + datastrs.join(';'), '/writeMultiFiles', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readdir = function (path, callback) {
|
||||||
|
//callback:function(err, data)
|
||||||
|
//path:支持"/"做分隔符,不以"/"结尾
|
||||||
|
//data:[filename1,filename2,..] filename是字符串,只包含文件不包含目录
|
||||||
|
if (typeof (path) != typeof (''))
|
||||||
|
throw 'Type Error in fs.readdir';
|
||||||
|
var data = '';
|
||||||
|
data += 'name=' + path;
|
||||||
|
postsomething(data, '/listFile', function (err, data) {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
err = "Invalid /listFile";
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
callback(err, data);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path 支持"/"做分隔符
|
||||||
|
* @param {() => {err: string, data}} callback
|
||||||
|
*/
|
||||||
|
fs.mkdir = function (path, callback) {
|
||||||
|
//callback:function(err, data)
|
||||||
|
if (typeof (path) != typeof (''))
|
||||||
|
throw 'Type Error in fs.readdir';
|
||||||
|
var data = '';
|
||||||
|
data += 'name=' + path;
|
||||||
|
postsomething(data, '/makeDir', callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path 支持"/"做分隔符, 不以"/"结尾
|
||||||
|
* @param {() => {err: string, data}} callback
|
||||||
|
*/
|
||||||
|
fs.moveFile = function (src, dest, callback) {
|
||||||
|
if (typeof (src) != typeof ('') || typeof (dest) != typeof (''))
|
||||||
|
throw 'Type Error in fs.readdir';
|
||||||
|
var data = '';
|
||||||
|
data += 'src=' + src + "&dest=" + dest;
|
||||||
|
postsomething(data, '/moveFile', callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path 支持"/"做分隔符, 不以"/"结尾
|
||||||
|
* @param {() => {err: string, data}} callback
|
||||||
|
*/
|
||||||
|
fs.deleteFile = function (path, callback) {
|
||||||
|
if (typeof (path) != typeof (''))
|
||||||
|
throw 'Type Error in fs.readdir';
|
||||||
|
var data = '';
|
||||||
|
data += 'name=' + path;
|
||||||
|
postsomething(data, '/deleteFile', callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})();
|
70
fsTest_cs.html
Normal file
70
fsTest_cs.html
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="./fs.js"></script>
|
||||||
|
<script>
|
||||||
|
fs.writeFile('_test.txt', '123中a文bc', 'utf-8', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.writeFile('_test_bin.txt', 'abc=', 'base64', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 1000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.readFile('_test.txt', 'utf-8', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 2000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.readFile('_test_bin.txt', 'base64', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 3000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.readdir('.', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 4000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.writeMultiFiles(['_test.txt','_test_multi.txt'], ['abc=','abe='], function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 5000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.mkdir('__test__', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 6000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.moveFile('_test_bin.txt', '__test__/_test_bin.txt', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 7000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.moveFile('_test.txt', '__test__/_test.txt', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 8000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.moveFile('_test_multi.txt', '__test__/_test.txt', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 8000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.deleteFile('__test__/_test_bin.txt', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 9000);
|
||||||
|
setTimeout(function () {
|
||||||
|
fs.deleteFile('__test__', function (e, d) {
|
||||||
|
console.log(e, d);
|
||||||
|
})
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
162
function.d.ts
vendored
Normal file
162
function.d.ts
vendored
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
interface ActionData {
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 当按键弹起时
|
||||||
|
* @param keyCode 按键的keyCode
|
||||||
|
* @param altKey 当前是否按下了alt键
|
||||||
|
*/
|
||||||
|
onKeyUp(keyCode: number, altKey: boolean): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ControlData {
|
||||||
|
/**
|
||||||
|
* 获取保存信息
|
||||||
|
*/
|
||||||
|
saveData(): Save;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取一个存档
|
||||||
|
* @param data 存档信息
|
||||||
|
* @param callback 读取完毕后的回调函数
|
||||||
|
*/
|
||||||
|
loadData(data: Save, callback?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 立即仅更新状态栏
|
||||||
|
*/
|
||||||
|
updateStatusBar(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每步移动后执行的函数
|
||||||
|
* @param callback 回调函数(好像没什么必要吧
|
||||||
|
*/
|
||||||
|
moveOneStep(callback?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 瞬移到某一点
|
||||||
|
* @param x 瞬移至的横坐标
|
||||||
|
* @param y 瞬移至的纵坐标
|
||||||
|
* @param ignoreSteps 忽略的步数,不填则会自动计算
|
||||||
|
*/
|
||||||
|
moveDirectly(x: number, y: number, ignoreSteps?: number): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UiData {
|
||||||
|
/**
|
||||||
|
* 数据统计界面统计的道具数量
|
||||||
|
*/
|
||||||
|
drawStatistics(): AllIdsOf<'items'>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventData {
|
||||||
|
/**
|
||||||
|
* 重置游戏
|
||||||
|
* @param hero 勇士信息
|
||||||
|
* @param hard 难度信息
|
||||||
|
* @param floorId 勇士所在楼层
|
||||||
|
* @param maps 所有的地图信息
|
||||||
|
* @param values 全局数值信息
|
||||||
|
*/
|
||||||
|
resetGame(
|
||||||
|
hero: HeroStatus,
|
||||||
|
hard: string,
|
||||||
|
floorId: FloorIds,
|
||||||
|
maps: GameStatus['maps'],
|
||||||
|
values: Partial<CoreValues>
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏获胜
|
||||||
|
* @param reason 胜利原因
|
||||||
|
* @param norank 是否不计榜
|
||||||
|
* @param noexit 是否不退出
|
||||||
|
*/
|
||||||
|
win(reason: string, norank?: boolean, noexit?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏失败
|
||||||
|
* @param reason 失败原因
|
||||||
|
*/
|
||||||
|
lose(reason?: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换楼层中,即屏幕完全变黑的那一刻
|
||||||
|
* @param floorId 目标楼层
|
||||||
|
* @param heroLoc 勇士到达的位置
|
||||||
|
*/
|
||||||
|
changingFloor(floorId: FloorIds, heroLoc: Loc): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换楼层后
|
||||||
|
* @param floorId 目标楼层
|
||||||
|
*/
|
||||||
|
afterChangeFloor(floorId: FloorIds): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 飞往某个楼层
|
||||||
|
* @param toId 目标楼层
|
||||||
|
* @param callback 飞到后的回调函数
|
||||||
|
*/
|
||||||
|
flyTo(toId: FloorIds, callback?: () => void): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与怪物战斗后
|
||||||
|
* @param enemyId 打败的怪物
|
||||||
|
* @param x 怪物横坐标
|
||||||
|
* @param y 怪物纵坐标
|
||||||
|
*/
|
||||||
|
afterBattle(enemyId: any, x?: number, y?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开门后
|
||||||
|
* @param doorId 门的id
|
||||||
|
* @param x 门的横坐标
|
||||||
|
* @param y 门的纵坐标
|
||||||
|
*/
|
||||||
|
afterOpenDoor(
|
||||||
|
doorId: AllIdsOf<Exclude<Cls, 'enemys' | 'enemy48'>>,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得道具后
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @param x 道具横坐标
|
||||||
|
* @param y 道具纵坐标
|
||||||
|
* @param isGentleClick 是否是轻按
|
||||||
|
*/
|
||||||
|
afterGetItem(
|
||||||
|
itemId: AllIdsOf<'items'>,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
isGentleClick?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推箱子后
|
||||||
|
*/
|
||||||
|
afterPushBox(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FunctionsData {
|
||||||
|
/**
|
||||||
|
* 交互信息
|
||||||
|
*/
|
||||||
|
actions: ActionData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏的逻辑信息
|
||||||
|
*/
|
||||||
|
control: ControlData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ui信息
|
||||||
|
*/
|
||||||
|
ui: UiData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件信息
|
||||||
|
*/
|
||||||
|
events: EventData;
|
||||||
|
}
|
2165
functions.js
Normal file
2165
functions.js
Normal file
File diff suppressed because it is too large
Load Diff
77
icon.d.ts
vendored
Normal file
77
icon.d.ts
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
type IconIds =
|
||||||
|
| keyof MaterialIcon['animates']
|
||||||
|
| keyof MaterialIcon['autotile']
|
||||||
|
| keyof MaterialIcon['enemy48']
|
||||||
|
| keyof MaterialIcon['enemys']
|
||||||
|
| keyof MaterialIcon['hero']
|
||||||
|
| keyof MaterialIcon['items']
|
||||||
|
| keyof MaterialIcon['items']
|
||||||
|
| keyof MaterialIcon['npc48']
|
||||||
|
| keyof MaterialIcon['npcs']
|
||||||
|
| keyof MaterialIcon['terrains'];
|
||||||
|
|
||||||
|
interface IconOffsetInfo {
|
||||||
|
/**
|
||||||
|
* 图块所在额外素材的id
|
||||||
|
*/
|
||||||
|
image: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图块所在图片位于额外素材的横坐标
|
||||||
|
*/
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图块所在图片位于额外素材的纵坐标
|
||||||
|
*/
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 和图标相关的内容
|
||||||
|
*/
|
||||||
|
interface Icons {
|
||||||
|
/**
|
||||||
|
* 图标信息
|
||||||
|
*/
|
||||||
|
readonly icons: MaterialIcon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 额外素材偏移起点
|
||||||
|
*/
|
||||||
|
readonly tilesetStartOffset: 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图标的id
|
||||||
|
*/
|
||||||
|
readonly allIconIds: IconIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得所有图标类型
|
||||||
|
*/
|
||||||
|
getIcons(): MaterialIcon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获得图块类型
|
||||||
|
*/
|
||||||
|
getClsFromId<T extends AllIds>(id: T): ClsOf<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得所有图标的ID
|
||||||
|
*/
|
||||||
|
getAllIconIds(): IconIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据图块数字或ID获得所在的tileset和坐标信息
|
||||||
|
* @param id 图块数字或id
|
||||||
|
*/
|
||||||
|
getTilesetOffset(id: string | number): IconOffsetInfo | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取动画帧数
|
||||||
|
* @param cls 类型
|
||||||
|
*/
|
||||||
|
getAnimateFrames<T extends Cls>(cls: T): FrameOf<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const icons: new () => Icons;
|
385
index.html
385
index.html
@ -1,208 +1,197 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="ch_ZN">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv='content-type' content='text/html' charset='utf-8'>
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv='X-UA-Compatible' content='IE=Edge, chrome=1'>
|
<title>HTML5魔塔样板</title>
|
||||||
<meta name='author' content='ckcz123'>
|
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
||||||
<meta name='viewport'
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||||
content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=yes'>
|
<meta name="description" content="Description">
|
||||||
<title>HTML5魔塔</title>
|
<meta http-equiv="pragma" content="no-cache">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta http-equiv="cache-control" content="no-cache">
|
||||||
<meta name="screen-orientation" content="portrait">
|
<meta http-equiv="expires" content="0">
|
||||||
<meta name="full-screen" content="yes">
|
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
<meta name="browsermode" content="application">
|
<link href="vue.css" rel="stylesheet">
|
||||||
<meta name="x5-orientation" content="portrait">
|
|
||||||
<meta name="x5-fullscreen" content="true">
|
|
||||||
<meta name="x5-page-mode" content="app">
|
|
||||||
<link type='text/css' href='styles.css' rel='stylesheet'>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<!-- <div id="stars"></div>
|
|
||||||
<div id="stars2"></div>
|
|
||||||
<div id="stars3"></div> -->
|
|
||||||
<div id='startImageBackgroundDiv'>
|
|
||||||
<div id='startImageDiv'></div>
|
|
||||||
<img id='startImageLogo' />
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
|
//先下载着
|
||||||
|
|
||||||
|
/*
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', 'https://cdn.bootcss.com/docsify/4.5.5/docsify.min.js', true);
|
||||||
|
xhr.send(null);
|
||||||
|
xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', 'https://cdn.bootcss.com/docsify/4.5.5/plugins/search.min.js', true);
|
||||||
|
xhr.send(null);
|
||||||
|
|
||||||
(function(){
|
(function(){
|
||||||
var startImageBackgroundDiv = document.getElementById('startImageBackgroundDiv');
|
window.bg={replaceToken:{}}
|
||||||
var startImageLogo = document.getElementById('startImageLogo');
|
bg.guid=function () {
|
||||||
var startImageDiv = document.getElementById('startImageDiv');
|
return 'id_' + 'xxxxxxxx_xxxx_4xxx_yxxx_xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
startImageLogo.onload = function () {
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
startImageBackgroundDiv.style.display = 'block';
|
return v.toString(16);
|
||||||
var onAnimationEnd = function () {
|
});
|
||||||
startImageBackgroundDiv.style.display = 'none';
|
|
||||||
startImageLogo.classList.remove("startImageAnimation");
|
|
||||||
startImageDiv.classList.remove("startImageDivAnimation");
|
|
||||||
}
|
}
|
||||||
startImageDiv.addEventListener("webkitAnimationEnd", onAnimationEnd);
|
bg.table=function(ths,tdss,args){
|
||||||
startImageDiv.addEventListener("animationend", onAnimationEnd);
|
return `<table class="${((args||{}).class||[]).join(' ')}"><thead><tr>${ths.map(v=>`<th style="text-align:left">${v}</th>`).join('')}</tr></thead><tbody>${tdss.map(tds=>`<tr>${tds.map(v=>`<td style="text-align:left">${v}</td>`).join('')}</tr>`).join('')}</tbody></table>`;
|
||||||
startImageLogo.classList.add("startImageAnimation");
|
|
||||||
startImageDiv.classList.add("startImageDivAnimation");
|
|
||||||
// 注释下面这句话以禁止单击立刻跳过开场动画
|
|
||||||
startImageBackgroundDiv.onclick = onAnimationEnd;
|
|
||||||
}
|
}
|
||||||
startImageLogo.onerror = function () { }
|
bg.pattern=/```\s*MotaAction.*?\r?\n[^]*?\r?\n\s*```/g;
|
||||||
startImageLogo.src = "logo.png";
|
bg.replaceFunc=function(str){
|
||||||
})();
|
var content=null;
|
||||||
</script>
|
try {
|
||||||
<!-- injection -->
|
content=eval(str.split('\n').slice(1,-1).join('\n'))
|
||||||
<div id='gameGroup'>
|
} catch (ee) {
|
||||||
<p id='mainTips'>请稍候...</p>
|
}
|
||||||
<img id='musicBtn'>
|
var match=/```\s*MotaAction(\.)?(\w+)?/.exec(str);
|
||||||
<div id='startPanel'>
|
var blocksvg=''
|
||||||
<div id='startTop'>
|
if (!match[2] || match[2]=='action')blocksvg= bg.parseList(content);
|
||||||
<div id='startTopProgressBar'>
|
else blocksvg=bg.parse(content,match[2]);
|
||||||
<div id='startTopProgress'></div>
|
return '<p>'+blocksvg+'</p>\n\n';
|
||||||
</div>
|
}
|
||||||
<p id='startTopLoadTips'>资源即将开始加载</p>
|
bg.parse=function(obj,type){
|
||||||
<p id='startTopHint'>HTML5魔塔游戏平台,享受更多魔塔游戏:<br />https://h5mota.com/</p>
|
|
||||||
</div>
|
|
||||||
<img id='startBackground'>
|
|
||||||
<p id='startLogo'></p>
|
|
||||||
<div id='startButtonGroup'>
|
|
||||||
<div id='startButtons'>
|
|
||||||
<span class='startButton' id='playGame'>开始游戏</span>
|
|
||||||
<span class='startButton' id='loadGame'>载入游戏</span>
|
|
||||||
<span class='startButton' id='replayGame'>录像回放</span>
|
|
||||||
</div>
|
|
||||||
<div id='levelChooseButtons'></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id='floorMsgGroup'>
|
|
||||||
<p id='logoLabel'></p>
|
|
||||||
<p id='versionLabel'></p>
|
|
||||||
<p id='floorNameLabel'></p>
|
|
||||||
</div>
|
|
||||||
<div id='statusBar' class="clearfix">
|
|
||||||
<div class="status" id="floorCol">
|
|
||||||
<img id="img-floor">
|
|
||||||
<p class='statusLabel statusText' id='floor'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id="nameCol">
|
|
||||||
<img id="img-name">
|
|
||||||
<p class='statusLabel statusText' id='name'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id="lvCol">
|
|
||||||
<img id="img-lv">
|
|
||||||
<p class='statusLabel statusText' id='lv'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id='hpmaxCol'>
|
|
||||||
<img id="img-hpmax">
|
|
||||||
<p class='statusLabel statusText' id='hpmax'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id='hpCol'>
|
|
||||||
<img id="img-hp">
|
|
||||||
<p class='statusLabel statusText' id='hp'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id='manaCol'>
|
|
||||||
<img id="img-mana">
|
|
||||||
<p class='statusLabel statusText' id='mana'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id='atkCol'>
|
|
||||||
<img id="img-atk">
|
|
||||||
<p class='statusLabel statusText' id='atk'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id='defCol'>
|
|
||||||
<img id="img-def">
|
|
||||||
<p class='statusLabel statusText' id='def'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id="mdefCol">
|
|
||||||
<img id="img-mdef">
|
|
||||||
<p class='statusLabel statusText' id='mdef'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id="moneyCol">
|
|
||||||
<img id="img-money">
|
|
||||||
<p class='statusLabel statusText' id='money'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id="expCol">
|
|
||||||
<img id="img-exp">
|
|
||||||
<p class='statusLabel statusText' id='exp'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id="upCol">
|
|
||||||
<img id="img-up">
|
|
||||||
<p class='statusLabel statusText' id='up'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id="skillCol">
|
|
||||||
<img id="img-skill">
|
|
||||||
<p class='statusLabel statusText' id='skill' style='font-style: normal'></p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id='keyCol'>
|
|
||||||
<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>
|
|
||||||
<span class='statusLabel' id='greenKey' style="color:#88FF88"></span>
|
|
||||||
</div>
|
|
||||||
<div class="status" id='pzfCol'>
|
|
||||||
<span class='statusLabel' id='pickaxe' style="color: #BC6E27"></span>
|
|
||||||
<span class='statusLabel' id='bomb' style="color: #FA14B9"></span>
|
|
||||||
<span class='statusLabel' id='fly' style="color: #8DB600"></span>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 状态栏canvas化 -->
|
MotaActionFunctions.workspace().clear();
|
||||||
<canvas id="statusCanvas" style="position: absolute; left: 0; top: 0;"></canvas>
|
xml_text = MotaActionFunctions.actionParser.parse(obj,type||'event');
|
||||||
</div>
|
xml = Blockly.Xml.textToDom('<xml>'+xml_text+'</xml>');
|
||||||
<div id="toolBar" class="clearfix">
|
Blockly.Xml.domToWorkspace(xml, MotaActionFunctions.workspace());
|
||||||
<img class="tools" id='img-book'>
|
|
||||||
<img class="tools" id='img-fly'>
|
svgBlock=document.querySelector("g.blocklyBlockCanvas > g.blocklyDraggable")
|
||||||
<img class="tools" id='img-toolbox'>
|
svgBlock.setAttribute('transform','translate(0,0)')
|
||||||
<img class="tools" id='img-keyboard'>
|
tmp={width:svgBlock.getBBox().width,height:svgBlock.getBBox().height}
|
||||||
<img class="tools" id='img-shop'>
|
|
||||||
<img class="tools" id='img-save'>
|
var id=`<span>${bg.guid()}</span>`
|
||||||
<img class="tools" id='img-load'>
|
bg.replaceToken[id]=`<svg width="${tmp.width}px" height="${tmp.height}px">`+svgBlock.outerHTML+'</svg>'
|
||||||
<img class="tools" id='img-settings'>
|
return id;
|
||||||
<img class="tools" id='img-btn1' style='display:none'>
|
}
|
||||||
<img class="tools" id='img-btn2' style='display:none'>
|
bg.parseList=function(obj){
|
||||||
<img class="tools" id='img-btn3' style='display:none'>
|
|
||||||
<img class="tools" id='img-btn4' style='display:none'>
|
MotaActionFunctions.workspace().clear();
|
||||||
<img class="tools" id='img-btn5' style='display:none'>
|
xml_text = MotaActionFunctions.actionParser.parseList(obj);
|
||||||
<img class="tools" id='img-btn6' style='display:none'>
|
xml = Blockly.Xml.textToDom('<xml>'+xml_text+'</xml>');
|
||||||
<img class="tools" id='img-btn7' style='display:none'>
|
Blockly.Xml.domToWorkspace(xml, MotaActionFunctions.workspace());
|
||||||
<img class="tools" id='img-btn8' style='display:none'>
|
|
||||||
<p class="statusLabel tools" id="hard"></p>
|
svgBlock=document.querySelector("g.blocklyBlockCanvas > g.blocklyDraggable")
|
||||||
</div>
|
svgBlock.setAttribute('transform','translate(0,0)')
|
||||||
<div id="gameDraw">
|
tmp={width:svgBlock.getBBox().width,height:svgBlock.getBBox().height}
|
||||||
<div id="gif"></div>
|
|
||||||
<div id="gif2"></div>
|
var id=`<span>${bg.guid()}</span>`
|
||||||
<canvas class='gameCanvas anti-aliasing' id='bg'></canvas>
|
bg.replaceToken[id]=`<svg width="${tmp.width}px" height="${tmp.height}px">`+svgBlock.outerHTML+'</svg>'
|
||||||
<canvas class='gameCanvas anti-aliasing' id='event'></canvas>
|
return id;
|
||||||
<canvas class='gameCanvas anti-aliasing' id='hero'></canvas>
|
}
|
||||||
<canvas class='gameCanvas anti-aliasing' id='event2'></canvas>
|
})()
|
||||||
<canvas class='gameCanvas anti-aliasing' id='fg'></canvas>
|
*/
|
||||||
<canvas class='gameCanvas' id='damage'></canvas>
|
</script>
|
||||||
<canvas class='gameCanvas' id='animate'></canvas>
|
</head>
|
||||||
<canvas class='gameCanvas' id='curtain'></canvas>
|
<body>
|
||||||
<canvas class='gameCanvas' id='ui'></canvas>
|
<div id="app"></div>
|
||||||
<canvas class='gameCanvas' id='data'>此浏览器不支持HTML5</canvas>
|
<xml id="toolbox" style="display:none"></xml>
|
||||||
<div id="next"></div>
|
<div id="blocklyArea" style="opacity: 0;z-index: -1;"><div id="blocklyDiv"></div></div>
|
||||||
</div>
|
<textarea id="codeArea" style="display:none" spellcheck="false"></textarea>
|
||||||
</div>
|
|
||||||
<div id='inputDiv'>
|
|
||||||
<div id='inputDialog'>
|
|
||||||
<p id="inputMessage">请输入文字...</p>
|
|
||||||
<input id='inputBox' type="text" autocomplete="off" />
|
|
||||||
<button id='inputYes'>确定</button>
|
|
||||||
<button id='inputNo'>取消</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="ui-editor"></div>
|
|
||||||
<!-- injection -->
|
|
||||||
<script src='libs/thirdparty/lz-string.min.js'></script>
|
|
||||||
<script src='libs/thirdparty/priority-queue.min.js'></script>
|
|
||||||
<script src='libs/thirdparty/localforage.min.js'></script>
|
|
||||||
<script src='libs/thirdparty/zip.min.js'></script>
|
|
||||||
<script id='mainScript' src='main.js'></script>
|
|
||||||
<script>main.init('play'); main.listen();</script>
|
|
||||||
<script>
|
<script>
|
||||||
</script>
|
window.$docsify = {
|
||||||
</body>
|
homepage: 'index.md',
|
||||||
|
loadSidebar: true,
|
||||||
|
name: 'HTML5魔塔样板',
|
||||||
|
repo: 'https://github.com/ckcz123/mota-js',
|
||||||
|
// basepath: '../docs/',
|
||||||
|
|
||||||
|
// Search Support
|
||||||
|
search: {
|
||||||
|
maxAge: 43200000, // 过期时间,单位毫秒,默认一天
|
||||||
|
paths: 'auto',
|
||||||
|
placeholder: {
|
||||||
|
'/': '搜索文档...',
|
||||||
|
},
|
||||||
|
noData: {
|
||||||
|
'/': '找不到结果',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// load sidebar from _sidebar.md
|
||||||
|
loadSidebar: '_sidebar',
|
||||||
|
subMaxLevel: 2,
|
||||||
|
autoHeader: true,
|
||||||
|
auto2top: true,
|
||||||
|
mergeNavbar: true,
|
||||||
|
formatUpdated: '{YYYY}-{MM}-{DD} {HH}:{mm}:{ss}',
|
||||||
|
plugins: [
|
||||||
|
/*
|
||||||
|
function(hook){
|
||||||
|
|
||||||
|
var renderScriptNode=function(str){
|
||||||
|
return str.replace(/```.*?\r?\n['"]run['"];[^]*?\r?\n```/g,function(x){
|
||||||
|
return eval(`(function(){${x.replace(/```.*?\r?\n['"]run['"];/,'').slice(0,-3)}})()`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var renderMotaAction=function(str){
|
||||||
|
return str.replace(bg.pattern,function(x){
|
||||||
|
return bg.replaceFunc(x)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
hook.beforeEach(function(content){
|
||||||
|
return renderMotaAction(renderScriptNode(
|
||||||
|
content
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
hook.doneEach(function(){
|
||||||
|
var map=bg.replaceToken
|
||||||
|
var node=document.querySelector('.markdown-section')
|
||||||
|
var str=node.innerHTML
|
||||||
|
for(var id in map){
|
||||||
|
str=str.replace(id,map[id])
|
||||||
|
}
|
||||||
|
node.innerHTML=str
|
||||||
|
})
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- 为了保证时序用脚本加载这两个 -->
|
||||||
|
<script src="docsify.min.js"></script>
|
||||||
|
<script src="search.min.js"></script>
|
||||||
|
<!--
|
||||||
|
<script src="../_server/blockly/Converter.bundle.min.js"></script>
|
||||||
|
<script src="../_server/blockly/blockly_compressed.js"></script>
|
||||||
|
<script src="../_server/blockly/blocks_compressed.js"></script>
|
||||||
|
<script src="../_server/blockly/javascript_compressed.js"></script>
|
||||||
|
<script src="../_server/blockly/zh-hans.js"></script>
|
||||||
|
<script src='../_server/MotaActionParser.js'></script>
|
||||||
|
<script>
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState != 4) return;
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
alert("图块描述文件加载失败, 请在'启动服务.exe'中打开编辑器");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var grammerFile = xhr.responseText;
|
||||||
|
converter = new Converter().init();
|
||||||
|
converter.generBlocks(grammerFile);
|
||||||
|
//printf(converter.blocks);
|
||||||
|
converter.renderGrammerName();
|
||||||
|
//converter.generToolbox();
|
||||||
|
converter.generMainFile();
|
||||||
|
//printf(converter.mainFile.join(''));
|
||||||
|
//console.log(converter);
|
||||||
|
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.innerHTML = converter.mainFile[5] + converter.mainFile[6];
|
||||||
|
window.core={material:{items:[],enemys:[]}}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
MotaActionFunctions.disableReplace = true;
|
||||||
|
MotaActionFunctions.disableExpandCompare = true;
|
||||||
|
|
||||||
|
script = document.createElement('script');
|
||||||
|
script.src='https://cdn.bootcss.com/docsify/4.5.5/docsify.min.js'
|
||||||
|
document.body.appendChild(script);
|
||||||
|
script = document.createElement('script');
|
||||||
|
script.src='https://cdn.bootcss.com/docsify/4.5.5/plugins/search.min.js'
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
|
xhr.open('GET', '../_server/MotaAction.g4', true);
|
||||||
|
xhr.send(null);
|
||||||
|
</script> -->
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
25
index.md
Normal file
25
index.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# HTML5 魔塔样板说明文档
|
||||||
|
|
||||||
|
当您打开这份帮助文档的瞬间,相信您一定是抱着幼年时的游戏开发梦想前来的。众所周知,即时游戏的开发要比非即时游戏难上许多,像素级游戏的开发又要比网格地图游戏难上许多。
|
||||||
|
|
||||||
|
在非即时网格地图游戏(譬如策略战棋)中,有一类叫做“固定数值RPG”,简称“魔塔”。这是一种基于运筹学的数学优化建模游戏,虽然小众,却不失有自己的圈子。
|
||||||
|
|
||||||
|
在当下,魔塔的趋势是向移动端发展,网络上也常常能见到“求手机魔塔”的提问。然而现有的工具中,NekoRPG有着比较大的局限性,游戏感较差,更是完全没法在iOS运行。而一些APP的魔塔虽然可用,但是必须要下载安装,对于安卓和苹果还必须开发不同的版本,非常麻烦。
|
||||||
|
|
||||||
|
但是,现在我们有了HTML5。
|
||||||
|
HTML5的画布(canvas)以及它被Android/iOS内置浏览器所支持的特性,可以让我们做出真正意义上的全平台覆盖的魔塔。
|
||||||
|
|
||||||
|
然而,一般而言使用非RPG
|
||||||
|
Maker制作魔塔往往需要一定的编程技术,HTML5魔塔自然也不例外。但是,为了能让大家更加注重于“做塔”本身,而不用考虑做塔以外的各种脚本问题,@艾之葵(GitHub
|
||||||
|
ckcz123)特意制作了这样一部HTML5魔塔样板。
|
||||||
|
|
||||||
|
这个魔塔样板,可以让你在完全不懂任何编程语言的情况下,做出自己的H5魔塔。不会代码?没关系!只要你想做,就能做出来!
|
||||||
|
|
||||||
|
继续查看文档的详细介绍,让你学会如何使用这一个样板来制作属于自己的HTML5魔塔,或者……任何非即时的网格地图游戏。
|
||||||
|
|
||||||
|
* [新版视频教程](https://www.bilibili.com/video/BV1SB4y1p7bg?share_source=copy_web)
|
||||||
|
* [脚本教程](https://www.bilibili.com/video/BV1uL411J7yZ?share_source=copy_web)
|
||||||
|
|
||||||
|
==========================================================================================
|
||||||
|
|
||||||
|
[继续阅读下一章:现在就做出自己的第一部H5魔塔!](start)
|
656
instruction.md
Normal file
656
instruction.md
Normal file
@ -0,0 +1,656 @@
|
|||||||
|
# 事件指令
|
||||||
|
|
||||||
|
?> 在这一节中,让我们来了解每一类事件的具体介绍
|
||||||
|
|
||||||
|
本样板之所以敢宣称“零编程基础的您也能大展身手”,就是因为它搭载了强大的图形化json编辑器(blockly)。
|
||||||
|
|
||||||
|
熟悉Antlr语法的读者可以根据[修改编辑器](editor)去自行修改`_server\MotaAction.g4`等文件去扩展其功能。
|
||||||
|
|
||||||
|
json代码本身则可以作为`core.insertAction()`函数的自变量,去插入一段事件执行。
|
||||||
|
|
||||||
|
下述提到的“当前点”坐标均指`core.status.event.data.x`和`core.status.event.data.y`。
|
||||||
|
|
||||||
|
## 指令的分类(注意区分块的颜色和地图的七彩点)
|
||||||
|
|
||||||
|
尽管事件指令和`core.events._action_xxx()`函数是一一对应的,但它们进一步调用的底层函数却分布在libs文件夹的不同文件中,大致上:
|
||||||
|
|
||||||
|
* 显示文字类(严格来说需要玩家操作的事件还涉及到actions.js)和UI绘制类在ui.js
|
||||||
|
* 数据相关类(这类也有不少道具相关在items.js)和特效声音类在control.js
|
||||||
|
* 地图处理类在maps.js
|
||||||
|
* 事件控制类(或许应该叫流程控制类)则在events.js,请注意区分libs和project的同名文件。
|
||||||
|
|
||||||
|
另一种分类方法则是按照同步和异步,分成以下几类:
|
||||||
|
1. **瞬间就能执行完的:**如UI绘制、设置XX属性、显隐和转变图层块等。
|
||||||
|
2. **阻塞直到玩家操作的:**如显示文章/选择项/确认框、接受用户输入、等待用户操作、呼出xxx等。
|
||||||
|
3. **阻塞一段固定时间的:**如开关门、显示动画(观赏性)、移动跳跃、淡入淡出等。
|
||||||
|
4. **耗时但不阻塞的:**如播放音效(V2.8支持阻塞)、显示提示等,一般为纯观赏性指令,会和后面的指令同时执行。
|
||||||
|
|
||||||
|
上述第3类指令都可以勾选“不等待执行完毕”(即前面提到的异步事件)变为第4类,从而实现诸如同步开关多个门的效果。
|
||||||
|
|
||||||
|
在json区,每条指令的格式为:`{"type": "xxx", "param1": ..., "param2": ..., ......}`,
|
||||||
|
实际执行的函数为`core.events._action_xxx(data, x, y, prefix)`
|
||||||
|
|
||||||
|
data为整个指令对象,x和y为当前点坐标,prefix为独立开关的楼层前缀。
|
||||||
|
|
||||||
|
您可以自行使用`core.registerEvent`注册一个新的事件。如果需要把新指令做成像已有的指令一样有类别、名称、取色器、勾选框、下拉框、输入框等部件,请查阅[修改编辑器](editor)。
|
||||||
|
|
||||||
|
V2.8.1起,下拉框中没有的项都可以通过在json区输入并点击“解析”按钮来临时追加(刷新网页后失效),如需永久追加请查阅[修改编辑器](editor)。
|
||||||
|
|
||||||
|
与此同时,显示文章、显示选择项、显示确认框都支持双击预览,预览前请先摆一个“设置剧情文本的属性”设置您预览时需要的属性然后双击它。
|
||||||
|
|
||||||
|
另外,原本“显示文字类”的图片相关指令,和“特效声音类”的音频相关指令,在V2.8.1被移出来合并到了一个新类“音像处理类”,请知悉。
|
||||||
|
|
||||||
|
## 显示文字类(黄色)
|
||||||
|
|
||||||
|
![image](img/images_texty.jpg)
|
||||||
|
|
||||||
|
这个类别的指令会负责UI层图文的处理,如图片的移动和淡入淡出,游戏的胜败和重启等。
|
||||||
|
|
||||||
|
### 显示文章
|
||||||
|
|
||||||
|
最基本的就是最灵活的。本指令的讲解将占用大量篇幅,请做好准备。
|
||||||
|
|
||||||
|
上述函数中,第一个自变量为字符串数组或单个字符串,每个字符串为一段需要玩家按下确认键或点击屏幕才会消失的剧情文本,第二个自变量(可选)为全部文本消失后的回调函数。
|
||||||
|
|
||||||
|
每条显示文章分为五部分:标题、图像、对话框效果、正文、立绘。
|
||||||
|
|
||||||
|
写成一个字符串就是`\t[标题,图像]\b[对话框效果]\f[立绘]正文`。
|
||||||
|
|
||||||
|
1. **标题:**可选,一般填说话人的名字。如果不填,则尝试根据图像取中文名(道具除外,道具不会说话所以只填图像不填标题就会没有标题)。如图像填hero但不填标题则以勇士名字作为标题,标题还可以填`null`强制不显示标题(如`\t[null,hero]`只显示勇士头像)。
|
||||||
|
2. **图像:**可选,可以填hero(勇士行走图,如果勇士开了帧动画则会取当前朝向,但朝上会视为朝下)或任何图块ID,或者填this来尝试取当前点图块。
|
||||||
|
* 也可以填一个png格式的图片文件名(需要后缀),图像甚至还可以填null来避免以图块ID为标题被解释成图像(如`\t[bomb,null]`会以英文单词bomb为标题而没有图像,但单独的`\t[bomb]`则会没有标题但以炸弹道具为图像)。
|
||||||
|
* 只填写图像而不填写标题时,会被优先解析到标题框中,请不要惊慌,这并不影响效果。
|
||||||
|
3. **对话框效果:**可选,填法非常灵活,如下(坐标在大地图中均为绝对坐标,省略则取当前点)。
|
||||||
|
1. `up,x,y`:对话框显示在点(x,y)上方,尖角朝下指着这个点(具体指的高度取决于图像。没有图像则判断该点是32×32px还是32×48px的图块,但是不管有无图像,若该点没有图块则都没有尖角)。
|
||||||
|
2. `down,x,y`:对话框显示在点(x,y)下方,尖角朝上指着这个点。比起上面,这个没有高度问题,不过该点没有图块的话则还是没有尖角。
|
||||||
|
* 上述两种写法中,如果把坐标换成hero则显示在勇士上方或下方(尖角朝下的话,高度取决于勇士的行走图)。
|
||||||
|
3. `this,x,y`:在大地图中,点(x,y)可能位于视野上半部分也可能位于下半部分,写this就能让对话框自动适配上下来防止越界。
|
||||||
|
4. `hero`:在大地图上下边缘或小地图,勇士可能位于视野上半部分也可能位于下半部分,只写hero也能自动适配。
|
||||||
|
5. `hero,n`:n为正整数,尖角指向勇士的第n名跟随者,自动适配上下,将hero改为up或down则手动指定上下。
|
||||||
|
6. `up,null`:显示在屏幕最上方,同理up换成down则为最下方。
|
||||||
|
7. `center`:强制显示在屏幕中央,宽度为视野宽度。
|
||||||
|
8. 除最后两种外,其余写法都会给对话框进行宽度自适配:
|
||||||
|
* 如果正文没有自动换行,则会先尝试取一个让文本总行数最接近“几行半”的宽度,可能会再适当加宽来防止标题出界。
|
||||||
|
* 如果正文有自动换行,则会尝试连同标题在内取最长的一行作为宽度。
|
||||||
|
* V2.7起,文本绘制一定程度上支持了排版标点禁则,成对符号的左半部分不会出现在行尾,右半部分和其他某些标点不会出现在行首。
|
||||||
|
9. 最终绘制时会尽量让尖角居中,除非尖角所指的点实在太靠左或太靠右。
|
||||||
|
10. 值得注意的是,使用`project/images/winskin.png`或类似的图片作为文章背景时,尖角的绘制用的是用图片右下角64×32px的两格进行的,所以往往需要您自己准备好。
|
||||||
|
* 技术群`959329661`的群文件“常用素材”提供了一些已经制作好的这样的图片,敬请取用。
|
||||||
|
4. **正文:**双击指令块,进入多行编辑。正文中允许使用很多转义序列,当您键入一个\字符时就会提示补全,后面逐一介绍。
|
||||||
|
5. **立绘:**显示文章的同时可以绘制一张或多张立绘图,请双击预览各张图的效果或右击整个指令预览所有立绘。每张立绘由一大堆参数组成:`\f[name(:x/:y/:o,sx,sy,sw,sh,)x,y(,w,h,alpha,angle)]`
|
||||||
|
1. **文件名:**需要放在project\images文件夹中并注册,这里需要带后缀。
|
||||||
|
2. **翻转:**和楼层贴图一样,支持三种翻转,在json代码中以文件的后缀名之后追加“:x、:y、:o”来表示。
|
||||||
|
3. **绘制坐标:**立绘在视野中的左上角像素坐标,后面的参数一旦省略其中一个则必须省略其后所有。
|
||||||
|
4. **绘制的宽高:**立绘被伸缩后在视野中实际显示的宽高,必须同时填写,不填则不伸缩。
|
||||||
|
5. **裁剪坐标和宽高:**必须同时填写,为从原图中裁剪区域的左上角坐标和区域宽高,不填则取全图。
|
||||||
|
6. **不透明度和旋转角度:**可选,前者填一个不大于1的正数,请自行双击预览。
|
||||||
|
6. **左上角坐标、限宽:**V2.8.1新增,该项不能和上面的`\b[对话框效果]`同时使用。
|
||||||
|
* 使用此项后,本指令将变为正常的json格式而不是字符串格式。
|
||||||
|
* “左上角坐标”指视野中的相对像素坐标,“限宽”必须和左上角坐标一起指定,不能单独指定。
|
||||||
|
* “限宽”的下限,在没有图像的情况下大约为64px,在有图像的情况下大约为128px,可以双击预览来确认到底能否正常显示。
|
||||||
|
* 一种推荐的用法是,在指定下面的非0编号后使用,然后“显示选择项/确认框”,这样就和RPG Maker的行为一致了。
|
||||||
|
* 而且还能充分利用两侧的空间讲清楚每个子选项,这是在“选择项的提示文字无法随光标位置而变化”的现状下的一种妥协做法。
|
||||||
|
7. **编号:**V2.8.1新增,可以同时显示多个不同编号的对话框,常用来表现多名角色的嘈杂对话。
|
||||||
|
* 和上一项一样,此项填写非0值后,指令将变为正常的json格式而不是字符串格式。
|
||||||
|
* 非0编号的对话框将不会自动消失(甚至勇士恢复自由行动后也是),可以被同编号的对话框覆盖或手动清除。
|
||||||
|
* 自由行动时,该项常用来做一些常驻提示,如技能的开关状态、毒衰咒状态、当前剧情任务进度等。
|
||||||
|
* 您甚至可以像图片一样去移动对话框(如和一个npc一起移动),支持四种变速效果,立绘也会被一起移动。
|
||||||
|
|
||||||
|
立绘是画在UI层的,下一个指令执行前就会擦除。如需持续显示请使用“显示图片”指令,另外立绘会被“图像”遮挡。
|
||||||
|
|
||||||
|
### 显示文章正文的转义序列
|
||||||
|
|
||||||
|
1. **表达式计算:**使用`${}`可以计算(eval)一个js表达式,式子中允许使用所有的冒号缩写量和API,详见`core.calValue()`函数。
|
||||||
|
* 此语法也可以用于“道具名称”、“道具描述”和“即捡即用提示”,只不过那里就不支持中文替换了。
|
||||||
|
* 如`勇士当前的攻防相乘是\${状态:攻击\*状态:防御}`(中文替换),`持有三色钥匙共\${item:yellowKey+item:blueKey+item:redKey}把。`(json)
|
||||||
|
* V2.8和更早的版本中,样板对右花括弧的处理是(非贪心)正则匹配,因此`${内部}`不能再出现右花括弧,这也意味着您无法使用对象、复杂的流程控制语句和函数定义,只能使用简单的运算符(包括三元运算)和函数调用。
|
||||||
|
* V2.8.1中,匹配方式改为了堆栈匹配,上述问题得到解决,您可以放心使用匿名函数了,这对道具名称/道具描述这种场合是个福音。
|
||||||
|
2. **局部文字变色:**使用`\r[颜色英文名]`或`\r[\#RrGgBb]`(十六进制)来将这之后的文本变为另一种颜色。
|
||||||
|
* 最常用的17种颜色提供了自动补全,十六进制颜色可以随便找个有颜色参数的指令,呼出调色器去自己调配。只使用\r不带方括号则变回默认颜色。
|
||||||
|
3. **局部字号调节:**使用`\\c[正整数]`改变这之后文本的字号,只使用`\\c`不加方括号则恢复默认字号。
|
||||||
|
4. **手动换行、局部加粗和斜体:**退出多行编辑后,手动换行写作`\n`,另外可以使用`\\d`将局部文本加粗或取消加粗,使用`\\e`将局部文本变为斜体或取消斜体。
|
||||||
|
5. **32×32px图标的绘制:**使用`\\i[图标ID]`绘制一个32×32px的图块的第一帧或系统图标,您可以使用`core.statusBar.icons`查看所有的系统图标。
|
||||||
|
* 出于历史遗留问题,图块ID可以和系统图标ID重复,此时优先使用图块ID.
|
||||||
|
6. **打字速度调节:**开启打字机效果后,文本的打字速度总是匀速的。所以样板提供了名为“时间占位符”的转义序列,使用`\\z[正整数]`可以暂停相当于打这么多个字的时间。
|
||||||
|
|
||||||
|
除`\n,\t,\b,\r,\f`外,其余转义序列的反斜杠在json中必须写两个!
|
||||||
|
|
||||||
|
### 其他文字类指令
|
||||||
|
|
||||||
|
1. **自动剧情文本:**和上面的显示文章基本相同,只不过不是由玩家按下确认键或点击屏幕,而是一定毫秒后自动消失,录像回放中则忽略。
|
||||||
|
* 比起那个,这个不能通过长按Ctrl键或长按屏幕快进,大量使用时一般用来搭配语音。否则对魔塔这种快餐游戏来说可能会非常不友好,建议统一塞进“显示确认框”指令的场合之一。
|
||||||
|
2. **滚动剧情文本:**将一段文字从屏幕最下方滚动到最上方,不支持自动换行,常用于op和ed.
|
||||||
|
* 该指令对应`core.drawScrollText(content, time, lineHeight, callback)`。
|
||||||
|
3. **显示提示:**即诸如打败怪物、捡到道具、打不开门时左上角的提示,只支持`${表达式计算}`和`\r[变色]`。
|
||||||
|
* 可以填写一个图标ID显示在提示文本的左侧(支持32×48px但只画靠上的2/3,也可以使用系统图标)。
|
||||||
|
* 此指令对应`core.drawTip(text, icon, frame)`函数。自然数`frame`表示绘制第几帧,默认为0表示第一帧(但没有在事件中提供)。
|
||||||
|
* 此指令看似显示出的提示会过一会才消失,但此指令本身依然是瞬间完成的,不属于异步事件!
|
||||||
|
* V2.6.4短暂提供了“同时显示多个tip”的功能,这在事件中连续获得多个道具时很友好,
|
||||||
|
* 但是玩家纷纷表示自由行动时此功能遮挡了太多的视野,因而在V2.6.5中就取消了。
|
||||||
|
* 如果需要此功能(包括自定义底色),请在本项目的[github](https://github.com/ckcz123/mota-js/archive/refs/tags/v2.6.4-release.zip)站点自行下载V2.6.4的发布版本并对照研究。
|
||||||
|
4. **游戏胜败和重启:**游戏胜败分别对应“脚本编辑”(快捷键N)的`win`和`lose`函数,在线游戏排行榜中每个结局的每个难度都有一张榜。
|
||||||
|
* 但同一结局只有最高难度有效,您可以勾选“不计入榜单”来让这个本来有效的结局也无效。还可以勾选“不结束游戏”来先停止录像的录制,再演出ed.
|
||||||
|
* win和lose函数最终都会调用`core.gameOver(ending)`函数,区别在于lose不填写ending. 但是,事件中使用gameOver这一异步函数需要额外的技巧,详见“原生脚本”。
|
||||||
|
* 重启游戏对应的函数为`core.showStartAnimate()`
|
||||||
|
5. **设置剧情文本的属性:**可用`core.status.textAttribute`获取当前的剧情文本属性,各项含义:
|
||||||
|
1. **位置:**“显示文章”不使用`\b`对话框效果时文本的位置,默认为屏幕中央。如果您有大量集中的剧情文本都欲使用`up,null`的对话框效果,则可以直接将此项设置为“顶部”,而将剩余的个别剧情文本使用`center`或`down,null`的对话框效果,反之亦然。
|
||||||
|
2. **偏移像素:**上面的“位置”选择“顶部”或“底部”时和屏幕上下边缘的距离,也作为滚动剧情文本和左边缘的距离。
|
||||||
|
3. **对齐:**默认为左对齐,可以修改此项来让显示文章的标题和正文都居中或都右对齐。
|
||||||
|
4. **标题色:**准确地说是“标题和图像边框色”,格式和楼层画面色调一致,可以点击调色器按钮呼出调色器调色。
|
||||||
|
5. **正文色:**如题,修改方法同上,从而避免频繁使用\r进行局部文本变色。
|
||||||
|
6. **背景色:**如题,修改方法同上。但比起前两个,这个也允许填写一个类似`winskin.png`的图片文件名。
|
||||||
|
7. **标题和正文字号:**如题,正文字号推荐设置为偶数。
|
||||||
|
8. **行距和字符间距:**如题,单位都是像素,行距推荐为正文字号的一倍半。
|
||||||
|
9. **粗体(Y/N):**文本是否默认加粗,推荐在大量粗体文本中穿插少量细体文本时使用,以免频繁的`\\d`切换。
|
||||||
|
10. **打字间隔:**0表示不启用打字机效果而是一次显示完,正整数表示每打一个字的毫秒数,也作为`\\z`暂停的单位时间。
|
||||||
|
11. **淡入淡出时间:**V2.8新增,指定此项后,每个“显示文章”指令都会增加淡入淡出效果,建议用于大段的剧情层。
|
||||||
|
|
||||||
|
可以使用`core.clone(core.status.textAttribute)`将文本属性备份到`hero.flags`中,从而做到临时使用另一套文本属性绘制部分内容。
|
||||||
|
|
||||||
|
### 图片类指令
|
||||||
|
|
||||||
|
1. **显示图片:**和立绘的语法基本类似,只不过多了编号(1—50)和淡入时间。
|
||||||
|
* 可以双击预览效果,还可以勾选“不等待执行完毕”来和后面的指令同时执行,比如同时淡入两张图片,或者淡入一张同时淡出/移动另一张。
|
||||||
|
* 编号较大的图片会遮盖较小的,1—24号图片会被色调层遮盖,25—40号图片会遮盖色调层但被UI层遮盖,41—50号图片会遮盖UI层。
|
||||||
|
* 此指令对应`core.showImage()`函数,编号真正的意义,详见[个性化](personalization)
|
||||||
|
2. **清除图片:**如题,需要指定要清除的图片编号和淡出时间(显隐图片的时间都可以填0表示瞬间完成)。
|
||||||
|
* 此指令对应`core.hideImage(code, time, callback)`
|
||||||
|
3. **图片移动:**其实还包括了透明度渐变,“终点像素位置”指移动结束后的图片在视野中的左上角像素坐标(不填则表示单纯的透明度渐变),“不透明度”指渐变结束后的新的不透明度(不填表示单纯的移动)。对应`core.moveImage(code, to, opacityVal, moveMode, time, callback)`
|
||||||
|
* V2.8起,图片和视野的移动支持加速度,分为“匀速、加速、减速、先加速再减速”四种,请任意选用。
|
||||||
|
4. **图片旋转:**V2.8新增,同样支持加速度,旋转中心坐标不填则取图片中心。
|
||||||
|
* 此指令对应`core.rotateImage(code, center, angle, moveMode, time, callback)`函数。
|
||||||
|
* 比起移动,旋转本身不支持同时透明度渐变,您可以先写一个不指定终点的移动指令且“不等待执行完毕”来实现单纯的淡入淡出,然后再写一个耗时相同或略长的旋转指令,这样两个指令就会一起执行了。
|
||||||
|
* 当不指定旋转中心时,本指令可以和移动指令同时使用,从而得到“图片的中心做直线运动、同时图片还在绕着中心自转”的效果。
|
||||||
|
5. **图片放缩:**V2.8.1新增,同样支持加速度,放缩中心坐标不填则取图片中心。
|
||||||
|
* 此指令对应`core.scaleImage(code, center, scale, moveMode, time, callback)`函数。
|
||||||
|
* 可以和“图片移动/旋转”一起使用,做出PowerPoint中常见的动画效果。
|
||||||
|
5. **显示或清除动图:**需要填写动图的文件名(带.gif后缀),“起点像素位置”含义如前且必须填写,可以双击指令块来预览第一帧的效果。
|
||||||
|
* 动图不支持淡入淡出和伸缩移动,如果不填任何参数则清除所有动图(只支持全部清除)。
|
||||||
|
* 该指令对应`core.showGif(name, x, y)`函数。
|
||||||
|
6. **显示图片化文本:**这是您唯一显示镜像文字的机会。
|
||||||
|
* 显示出来后就会视为一张通常的图片,可以被清除、移动、淡入淡出、旋转。
|
||||||
|
|
||||||
|
### 显示确认框,选择项,QTE与全局商店
|
||||||
|
|
||||||
|
QTE,即快速反应事件。一般表现为需要玩家在收到某信号后尽快做出正确操作,如新新魔塔2中面对白银史莱姆王的猜拳战斗就需要根据其出拳的颜色尽快按下相克的数字键。
|
||||||
|
|
||||||
|
样板同样支持这类事件,一共有三种,这里先介绍两种。
|
||||||
|
|
||||||
|
一是**显示确认框**,它会显示一段支持`${表达式求值}`但不支持自动换行、淡入淡出和其他转义序列的文字。然后要求玩家在一定毫秒数内选择“确定”或“取消”之一,如果超时就视为哪个都没选,直接继续执行后面的事件。
|
||||||
|
|
||||||
|
您可以指定闪烁光标的初始停留位置是确定还是取消,还可以指定超时毫秒数为0表示不限时间但玩家必须做出二选一。
|
||||||
|
|
||||||
|
当指定了超时时间并且及时做出选择时,剩余时间会被写入“变量:timeout”,可以用来做一些处理(音游?)。
|
||||||
|
|
||||||
|
此指令对应`core.drawConfirmBox(text, yesCallback, noCallback)`函数,其中两个Callback分别为选择确定和取消后的回调函数。
|
||||||
|
|
||||||
|
V2.8起,显示确认框在录像回放时如果录像中没有记录该选哪一项(或者明明此事件未设置超时时间但录像中记录了超时),就会选择默认项(也就是光标的默认位置),请注意。
|
||||||
|
|
||||||
|
二是**显示选择项**,和RPG Maker不同,我们的选择项不会和它之前的“显示文章”同时出现,可以直接配上除对话框、打字机、淡入淡出外的所有文字效果。
|
||||||
|
|
||||||
|
此指令对应`core.drawChoices(content, choices)`函数,其中content为提示文字,choices为各子选项文字组成的字符串数组。是的,比起上面的函数,这个不直接支持回调。
|
||||||
|
|
||||||
|
在没有提示文字的情况下,一次能同时显示的子选项最多为13或15个。和确认框一样,选择项的超时值填0表示不限时间但玩家必须从中选择一个。大于0的话超时视为什么都没选,直接继续执行后面的事件。
|
||||||
|
|
||||||
|
每个子选项的文字只支持`${表达式求值}`和整行变色,请注意控制字数。文字左侧也支持追加一个图标(多帧图块取第一帧),支持系统图标。
|
||||||
|
|
||||||
|
每个子选项还可以指定“出现条件”(不指定则一定出现),条件的写法和自动事件的触发条件一样,从而做出形如“怒气值满才显示大招选项”的效果。
|
||||||
|
|
||||||
|
如果实际执行时所有子选项都不满足出现条件,则直接跳过。但是如果出现的项都不满足下面的“启用条件”就会导致所有项都不可选,然后游戏卡死,请务必注意这个问题。
|
||||||
|
|
||||||
|
V2.7.2起,每个子选项还可以指定“启用条件”,当出现条件和启用条件都满足时才能真正选中这一项。
|
||||||
|
|
||||||
|
如果只满足前者但不满足后者,该项会变灰,尝试选择时会播放“操作失败”系统音效并显示提示(超时倒计时不会重置)。
|
||||||
|
|
||||||
|
当指定了超时时间并且及时做出有效选择时,剩余时间同样会被写入“变量:timeout”,可以用来做一些处理。
|
||||||
|
|
||||||
|
您或许会疑惑提示文字为什么不做成随光标位置而变化(这在很多电脑/手柄游戏中很常见),这是因为触屏设备无法改变光标位置,如有需要,请自行在此界面提供虚拟方向键(不打算发布触屏版游戏的则无所谓),然后在提示文字中使用${}对`core.status.event.selection`进行判定,从而改变提示文字。
|
||||||
|
|
||||||
|
V2.8起,显示选择项在录像回放时如果录像中没有记录该选哪一项(或者明明此事件未设置超时时间但录像中记录了超时),就会弹窗询问玩家是否补选一项,此时玩家可以输入某项的序号(从0起)来修复录像,当然也可以在弹窗时点取消来放弃修复。
|
||||||
|
|
||||||
|
提交成绩后,站点后端的录像验证时如果发生了同样的问题,则因为无法询问玩家,会选择默认项(即初始光标位置,下面会提到)。
|
||||||
|
|
||||||
|
如果录像回放中尝试选择不满足“启用条件”的灰项,V2.8起会直接报错。这种处理方式比上面的“弹窗请求补选”更为严厉,如您在造塔测试时遇到了这种问题,可以先“回退到上一个节点”,然后在控制台输入`core.status.replay.toReplay`查看接下来该播放的内容(可以按N键单步播放),并在这个一维数组中删除掉那个非法的选项值。
|
||||||
|
|
||||||
|
而如果录像中记录了多余的确认框或选择项(`choices:n`),V2.8起就会在播放时跳过并警告。
|
||||||
|
|
||||||
|
V2.8.1起,“显示选择项”支持限宽和手动指定默认项了(注意最好指定一个100%出现且启用的项),配合“带编号的显示文章”效果更佳哦!
|
||||||
|
|
||||||
|
![image](img/quickshops.jpg)
|
||||||
|
|
||||||
|
在“全塔属性——全局商店”中可以编辑各个商店,商店一共有三种:
|
||||||
|
|
||||||
|
1. **公共事件商店:**最简单的一种商店,或者应该叫做给玩家准备的快捷功能更合适,因为它的内容完全不一定非得是个做买卖做交易的“商店”,也可以是诸如“开启或关闭主动技能”、“快速换上最强套装”之类的便捷功能。
|
||||||
|
* 多说一句,鉴于全局商店列表最多只能同时显示12或14项(还有一项是关闭列表),因此您也可以准备一些永久道具,设置适当的使用条件,并在使用效果事件中去实现这些给玩家的快捷功能。当然全局商店的使用条件更加统一,请自行权衡。
|
||||||
|
* 公共事件商店在用法上和一般的“插入公共事件”并无二致,同样可以提供一个参数列表。
|
||||||
|
2. **道具商店:**这种商店也很简单,由第三种QTE指令实现但没有限时。
|
||||||
|
* 您可以在其中随意填写买卖的道具ID、存量、买卖价和出现条件。
|
||||||
|
* 存量不填视为无限,买入价不填视为只能卖(回收),卖出价不填视为只能买,出现条件的含义和选择项一致。
|
||||||
|
* 如果需要在游戏中对买卖价和对存量进行读写,请读写`core.status.shops`
|
||||||
|
* 请注意,使用道具商店的话务必保留`project/images/winskin.png`及其注册信息,可以换成相同规格的同名图片。
|
||||||
|
3. **新版商店:**用法非常灵活的一种商店,其外形酷似“显示选择项”但有一些不同。
|
||||||
|
* 首先和其他两种商店一样,它多出了“商店id、快捷名称、未开启不显示”。
|
||||||
|
* 商店id只能使用全英数,且必须两两不同。
|
||||||
|
* “快捷名称”为显示在V键快捷菜单的名称,请注意控制字数,最好也两两不同以免玩家混淆。
|
||||||
|
* 勾选“未开启不显示”则此商店在开启前或禁用后不会出现在V键菜单中,当商店总个数超过12或14个且随着游戏流程进度依次开新的关旧的时,这个勾选项就很有必要了。
|
||||||
|
* 其次,和其他两种商店不同,您可以允许玩家预览它(前提是V键菜单中显示了),这对魔塔这种倡导完全信息的游戏来说非常有意义。
|
||||||
|
* 最后,比起常规的“显示选择项”,它不能指定超时毫秒数,但是(V2.8起)允许长按连续购买。
|
||||||
|
* 实际执行中在所有子选项的最后会自动追加一个“离开”选项,选择其他子选项并执行后商店并不会立即关闭而是停在那个界面。就像胖老鼠和两部新新魔塔一样。
|
||||||
|
* “出现条件”和“使用条件”相搭配,让您能够很轻松地做出形如“消耗金币和各种材料的装备合成路线”这样的设定。
|
||||||
|
* 在预览模式下除“离开”外的子选项、以及交易模式下不满足“使用条件”的子选项,都会显示为灰色,尝试选择时会播放“操作失败”系统音效并提示失败原因。
|
||||||
|
* 子选项的执行内容中需要手动处理扣费等问题,此外,在制作像两部新新魔塔一样会涨价的商店时,您也需要自己准备变量(变量名不必与商店id相同)去记录已购次数或者直接记录价格,并手动处理涨价问题。
|
||||||
|
|
||||||
|
有关全局商店的详细实现,请参见“插件编写”(句号键,`project/plugin.js`)。
|
||||||
|
|
||||||
|
其中还提供了一个`core.canUseQuickShop(id)`函数来控制勇士什么时候可以通过V键菜单快捷使用哪些商店,自变量id为商店id.
|
||||||
|
|
||||||
|
本质上,新版商店是套在一个死循环里的。您可以在子选项的执行内容中使用“跳出当前循环”指令来打断该子选项剩余的未执行内容而强制离开商店,
|
||||||
|
|
||||||
|
或使用“提前结束本轮循环”来打断未执行内容而强制重启商店。
|
||||||
|
|
||||||
|
同理,公共事件(包括公共事件商店)和自动事件本质上是“一次性”的条件为`false`的后置条件循环,因此使用这两个指令都能跳出它们。
|
||||||
|
|
||||||
|
另外,全局商店在录像中的记录方式是`"shop:id"`紧接着显示选择项的记录,也就是说“离开”项的序号可能会不固定(尤其是在连载塔中),请稍加留心。
|
||||||
|
|
||||||
|
## 数据相关类(绿色)
|
||||||
|
|
||||||
|
![image](img/control_itemsg.jpg)
|
||||||
|
|
||||||
|
这类的指令会设置各种数据(如怪物属性、楼层属性、全塔属性、七大可读写块),处理弹窗输入和开关全局商店,以及控制玩家最最关心的勇士的各种行为。
|
||||||
|
|
||||||
|
### 设置各种数据的指令
|
||||||
|
|
||||||
|
1. **数值操作:**最简单的就是最灵活的,本指令能够修改七大可读写块(状态、物品、变量、独立开关、临时变量、全局存储、增益)的值。
|
||||||
|
* 修改的运算符有十种,“设为”会将右块的值代入左块,“增加、减少、乘以、除以”则是对左块的值进行增减和扩大缩小。
|
||||||
|
* 除法如想只保留整数商(向零靠近)则改用“除以并取商”,如想要余数(例如想取勇士生命值的后两位)则使用“除以并取余”。
|
||||||
|
* “乘方”指的是将若干个(不一定是正整数)左块连乘起来的积代入左块。
|
||||||
|
* “设为不大于”和“设为不小于”是指在左块大于/小于右块的时候将右块代入左块,也就是“封顶”和“保底”的作用。
|
||||||
|
* 指令的右块为一表达式,可以使用任何值块和运算块,甚至直接使用API.
|
||||||
|
* 如果需要连续使用本指令,建议除最后一个外都勾选“不刷新状态栏”,以降低刷新状态栏的性能损耗,并且避免意外触发自动事件、生命和魔力溢出甚至死亡(生命小于等于0)。
|
||||||
|
2. **设置怪物属性:**可以设置怪物的任何一项属性并计入存档。
|
||||||
|
* 怪物ID在blockly块中也可以填中文(要求没有重复,有的话请在事件编辑器顶部关闭“中文替换”功能),需要设置的属性项在下拉框中选取。通过配置表格自行新增的属性在下拉框里没有,但可以写在json区再单击变黄的“解析”按钮,或修改`_server\MotaAction.g4`文件最后的部分去追加。
|
||||||
|
* 本指令对应`core.setEnemy(id, name, value, operator, prefix)`函数,完全等价。注意value会被eval,因此字符串需要额外套一层引号!
|
||||||
|
* 最后的“值”和“数值操作”的右块写法一致,注意设置怪物名称需要加引号,设置逻辑值(是否)需要填写`true`或`false`,设置“特殊属性”需要填数组且只支持结果值。
|
||||||
|
* V2.8起,怪物支持“行走图朝向”功能,您在四个朝向的怪物中任意设置一个的属性都会强制同步到其他三个。
|
||||||
|
3. **定点设置/移动或重置怪物属性:**V2.8新增,该指令主要是为了实现一些诸如“发动技能后指定某个点,该点怪物被削弱/增强,并计入存档”的功能。
|
||||||
|
* 该指令支持设置的属性有“名称、生命、攻击、防御、金币、经验、加点”,设置后,该属性会在“脚本编辑——怪物真实属性”中最先被使用,然后可以被光环等影响。
|
||||||
|
* 使用时,需要指定楼层和坐标,楼层不写则取当前层,坐标不填则取当前点,支持双击从地图选点,(除移动外)支持选多个点。
|
||||||
|
* “移动某点怪物属性”支持写增量(dx、dy),如写[4,-2]就会让某个点的怪物属性移动到向右4格、向上2格的位置。
|
||||||
|
* 发生战斗后,该点会被自动重置定点属性。怪物移动跳跃时(如阻击)定点属性会自动一起被移动,您无需手动移动。
|
||||||
|
* 该组指令实际调用的API为:
|
||||||
|
```
|
||||||
|
core.setEnemyOnPoint(x, y, floorId, ...)
|
||||||
|
core.moveEnemyOnPoint(fromX, fromY, toX, toY, floorId)
|
||||||
|
core.resetEnemyOnPoint(x, y, floorId)
|
||||||
|
core.enemys.getEnemyValue(enemy, name, x, y, floorId) // 读取
|
||||||
|
```
|
||||||
|
4. **设置装备属性:**V2.8新增,该项可以制作一些“随剧情推进而强化装备”的效果并计入存档。
|
||||||
|
* 使用时,需要指定装备ID、要修改的是常数值还是增益、要修改的属性英文名等。
|
||||||
|
5. **设置楼层属性:**除了贴图和两个到达事件,其他属性都可以方便地修改。
|
||||||
|
* 楼层ID不填则视为当前楼层,可以去“地图选点”浏览各楼层并复制ID.
|
||||||
|
* 注意修改“楼层中文名”、“状态栏中名称”、“默认地面ID”、“背景音乐”(需要后缀名)这些字符串类型都需要加引号(V2.8起背景音乐支持一维数组),几个“能否/是否”只支持修改为`true`或`false`,三个坐标和天气、色调这些数组类型都需要加方括弧。本指令对应`core.events.setFloorInfo(name, value, floorId, prefix)`
|
||||||
|
* 修改当前层的天气/色调/背景音乐后不会立即生效,如需生效请补一个对应的特效指令(如“恢复画面色调”)并且不要勾选“持续”。
|
||||||
|
6. **设置全局属性:**即全塔属性中的“主样式”(无需再加引号)和装备孔列表。
|
||||||
|
* 修改装备孔列表时请注意,如果装备的道具属性中填写的装备类型是自然数,则可以【在列表结尾】随着游戏流程的推进解锁新的装备孔或移除装备孔(请先将身上的此类装备脱下)。
|
||||||
|
* 而如果装备的道具属性中填写的装备类型是装备孔名称,则可以随着游戏流程的推进修改装备孔的类型组成,如本来是两把剑一块盾改成一把剑两块盾(同样需要注意已经穿上的装备问题)。
|
||||||
|
* 本指令对应`core.events.setGlobalAttribute(name, value)`函数。
|
||||||
|
7. **设置全局数值:**如题,可以修改四种宝石和三种血瓶的基础值等,必要时也可以修改图块的每帧时间以及上下楼时间以得到一些演出效果。
|
||||||
|
* 如需使用脚本,请直接修改`core.values`,完全等价。但是竖屏状态栏的自绘行数如果动态修改有可能会出问题,请注意。
|
||||||
|
8. **设置系统开关:**如题,可以用来随着游戏流程的推进解锁/移除状态栏的显示项或改动其他开关。
|
||||||
|
* 比如中途开关生命上限、加点和负伤,中途显隐魔力、技能、绿钥匙和破炸飞毒衰咒。
|
||||||
|
* 在游戏胜利时会将生命值作为分数上传到在线游戏排行榜,因此请在胜利前关闭生命上限再修改生命,比如根据钥匙等道具的持有情况进行加分。
|
||||||
|
* 请注意,即使您在游戏中途才将楼传变为平面塔模式,访问过的楼层依然已经记录了最后离开的位置。
|
||||||
|
* 本指令对应`core.setGlobalFlag(name, value)`函数,实际会修改`core.flags`(但请不要用脚本直接修改它)
|
||||||
|
9. **设置文件别名:**V2.8新增,您可以修改一个中文名实际指向的英文文件名,从而做到随剧情推进采用不同的系统音效(如上下楼)等效果,如果英文文件名不填则表示恢复到全塔属性中的默认值。
|
||||||
|
|
||||||
|
### 导致勇士位置变化的指令
|
||||||
|
|
||||||
|
这类指令都支持填写负坐标、超出地图宽高的坐标或小数坐标(大地图中请慎用小数坐标),
|
||||||
|
|
||||||
|
当勇士在这些异常坐标时【除第一个指令外】都可以正常执行。
|
||||||
|
|
||||||
|
可以用于一些特殊演出,但请记得在事件结束(玩家恢复行动)前改回正常。
|
||||||
|
|
||||||
|
1. **勇士前进一格或撞击:**如题,会让勇士像自由行动时一样尝试前进一格。
|
||||||
|
* 如果可以前进但前方不可被踏入(如门、怪物、箱子、NPC)则会撞击并触发事件,走到道具、踩灯或路障或用普通事件制作的陷阱等也会触发。
|
||||||
|
* 本指令可以正常触发跑毒和阻激夹域捕捉(可以致死),滑冰事件就是在冰上执行了它。
|
||||||
|
* 本指令对应`core.moveAction(callback)`函数,但请勿直接调用它。
|
||||||
|
2. **无视地形移动勇士:**“动画时间”为每步的时间,不填则取玩家设定值,该值从V2.8起允许在移动过程中修改(最少为16)。
|
||||||
|
* 可以勾选“不等待执行完毕”来和后面的指令同时执行,比如让勇士和一个NPC肩并肩走。
|
||||||
|
* 本指令不会触发跑毒和阻激夹域捕捉,且会无视地形可进出性、可通行性。
|
||||||
|
* 移动过程中不会触发任何事件,就像开启调试模式时按住Ctrl键移动一样(与之不同的是,也可以移动出地图外)
|
||||||
|
* 勇士后退时,跟随者们会照常前进,数不清楚格子时记得善用地图选点功能浏览地图。
|
||||||
|
* V2.8起,支持斜向移动,支持移动过程中单纯转向(步数填0)。
|
||||||
|
* 斜向移动时行走图以左右为准,但“后退”依然以勇士朝向为准而不考虑上一步的行走方向(这点和图块的移动不同,勇士不可能斜向后退但图块可能)。
|
||||||
|
* 本指令对应`core.eventMoveHero(steps, time, callback)`函数,请注意不是`core.moveHero()`
|
||||||
|
* 多说一句,您可能会发现勇士在移动时会在行走图的2、4两帧之间反复切换(尤其是在大地图中心时很明显),这和图块以及RPG Maker的行为很不一致而且观感较差,如需改成1234帧循环,请启用“勇士四帧行走动画”插件(V2.8该插件有更新,从V2.7.x接档的用户需要重新抄写一下)。
|
||||||
|
3. **跳跃勇士:**可以填写目标点坐标(支持双击从地图选点),坐标允许使用带有冒号缩写量甚至API的表达式。
|
||||||
|
* 比如`["core.nextX(2)", "core.nextY(2)"]`(json)表示勇士越过面前一格,即道具“跳跃靴”的效果。
|
||||||
|
* V2.7.3起,跳跃的目标坐标支持写增量(dx、dy),如写[4,-2]就会让勇士跳到向右4格向上2格的位置。
|
||||||
|
* 跳跃高度和距离有关,原地跳跃的高度只有半格(可在下述函数中修改)。跳跃过程中跟随者消失,跳跃结束时跟随者聚集。
|
||||||
|
* 跳跃也支持异步效果(如和NPC一起跳),对应`core.jumpHero(ex, ey, time, callback)`函数,其中`callback`为异步跳跃完毕的回调函数。
|
||||||
|
* 跳跃默认没有音效,您可以自行像支援怪和道具“跳跃靴”一样配上音效(具体方法在“插件复写”一节有讲)。
|
||||||
|
* 和“无视地形移动勇士”一样,勇士跳跃也会无视目标点的地形和阻激夹域捕捉,不会触发目标点的任何事件。
|
||||||
|
* “无视地形移动勇士”和“跳跃勇士”因为经常和图块的这两个行为一起使用进行演出且都没有碰撞效果,因此V2.7.3起移动到了“地图处理类”,请注意。
|
||||||
|
4. **楼层切换:**和前面的“楼梯、传送门”绿点事件用法完全一样,但不可穿透。
|
||||||
|
* 此指令同样支持双击从地图选点(坐标支持表达式)和在json区填写传送的目标点图块ID(在目标层唯一)再点击变黄的“解析”按钮。
|
||||||
|
* 另外,正如本小节开头提到的,本指令比起“楼梯、传送门”事件更多地用于演出,因此您可以填写异常坐标。
|
||||||
|
* 楼层ID只能填写常量,如需使用变量,请使用“原生脚本”插入事件。
|
||||||
|
5. **位置朝向切换:**“跳跃勇士”不会改变勇士朝向,“楼层切换”又会导致重生怪复活。且这两个都会导致跟随者聚集,所以同楼层内改变勇士位置可以使用本指令(坐标和跳跃一样支持双击从地图选点以及表达式)。
|
||||||
|
* 本指令还可以用来让勇士原地转身(不填坐标,这样也不会聚集跟随者),支持4种绝对朝向和4种相对转向。
|
||||||
|
|
||||||
|
### “数据相关”类的其他杂牌指令
|
||||||
|
|
||||||
|
以下杂牌指令负责弹窗输入、显伤战斗、道具装备、全局商店、行走图和队伍:
|
||||||
|
|
||||||
|
1. **接受用户输入:**弹窗请求用户输入一个自然数或字符串,提示文字允许使用`${表达式计算}`。
|
||||||
|
* 请求输入自然数(支持十六进制)时,负整数会被取绝对值。小数会被向0取整,其他非法输入会变为0,“输入自然数”在录像中会记录为`input:n`。
|
||||||
|
* 请求输入字符串时,玩家点击取消则视为输入了空字符串。
|
||||||
|
* 输入的结果会保存在值块“`变量:input`(`flag:input`)”中,可供后续处理。
|
||||||
|
* 比如样板的生命魔杖就是一个例子,它允许玩家一次使用多个同种道具。
|
||||||
|
* 读取“全局存储”这一行为,在录像中和“输入字符串”的记录方式一致(`input2:base64`)。
|
||||||
|
* V2.8起,录像回放中如果出现了多余的`input:`或`input2:`,都会警告并跳过。反之,“接受用户输入”事件在录像中缺失了值则会使用0或空字符串并补录。
|
||||||
|
2. **更新状态栏和地图显伤:**如题,可以勾选“不检查自动事件”来不检查。
|
||||||
|
* 本指令实际执行“脚本编辑——更新状态栏”,即`core.updateStatusBar(doNotCheckAutoEvents);`
|
||||||
|
3. **强制战斗(点名):**和天降怪物(指定ID,中文替换只支持不重复的中文名)强制战斗。
|
||||||
|
* 此指令战后不会从地图删除图块也不会触发各点的战后事件(黄点),但可以触发战后脚本和怪物属性中的(批量)战后事件。
|
||||||
|
* 此指令的战斗是强制的,打不过直接死亡(V2.8可以用值块预先判定能否打过)。
|
||||||
|
* 此指令一般用于boss战(通过走到某个点或开启某个门来触发),可以制作战前剧情,然后强制战斗。
|
||||||
|
* 战后boss不立即消失(从而避免基于漏怪检测的自动事件被误触发),可以继续进行一些演出,如51层魔塔四区骑士队长的逃跑效果。
|
||||||
|
* 因为是天降怪物(没有坐标),所以对这只怪物在属性修正以及战损计算等处涉及到怪物坐标的代码一律不起作用。
|
||||||
|
* 比如它不会受局部光环的加成,也不会被任何怪支援,也无法被V2.8的“定点设置怪物属性”影响。
|
||||||
|
* 另一种强制战斗指令在“地图处理类”,指定的是坐标而不是怪物ID.
|
||||||
|
* V2.8新增了两种战前事件,两种强制战斗指令都不会触发它们,如需触发,请使用“触发系统事件”指令。
|
||||||
|
* 由于V2.8提供了战前事件,因此不再建议使用曾经的“覆盖触发器+天降强制战斗”方式实现战前剧情,因为这样做不会自动存档,对玩家不太友好。
|
||||||
|
4. **尝试使用道具和穿脱装备:**使用道具和穿戴装备需要指定ID(中文替换规则和强制战斗一样)。
|
||||||
|
* 不能使用怪物手册(请使用“特效声音类”的“呼出怪物手册”指令)和楼层传送器(如果“覆盖楼传事件”则没有关系),使用中心对称飞行器则会跳过确认画面。实际对应`core.useItem(itemId)`函数。
|
||||||
|
* 穿脱装备对应`core.loadEquip(equipId)`和`core.unloadEquip(type)`函数。脱下装备需要指定类型,这里只能写自然数不能写名称。
|
||||||
|
* 道具使用失败或穿不上装备(比如没有或不满足条件)时会播放音效并提示。
|
||||||
|
5. **开关全局商店:**本指令可以设置一个全局商店的启用和禁用状态,设为启用时也支持立即打开。
|
||||||
|
* 一般用于商店的实体NPC处,再配合独立开关可以让勇士首次接触时先进行一些对话,然后启用(并打开)全局商店。
|
||||||
|
* V2.8新增了值块可以用来判定一个全局商店是否是开启状态。
|
||||||
|
6. **更改角色行走图:**如题,文件名必须填写(支持双击选文件)。
|
||||||
|
* 文件必须放在`project/images`文件夹并注册,且规格必须符合要求(4帧总宽度至少128px,高度不限。宽高必须为4的倍数)。
|
||||||
|
* 如果勾选“不重绘”就不会立即刷新,从而避免大地图视角重置到以勇士为中心。本指令对应`core.setHeroIcon(image, noDraw)`函数。
|
||||||
|
7. **跟随者入队和离队:**您可以用这一对指令来让跟随者入队和离队,同样支持双击选文件。本指令对应`core.follow()`和`core.unfollow()`函数。
|
||||||
|
* 行走图和勇士的规格要求(尺寸不需要一样)、文件位置和注册方法相同。
|
||||||
|
* 离队可以不填文件名表示解散队伍只留勇士,如果填写文件名则尝试踢出队伍中第一个以此为行走图的跟随者。
|
||||||
|
* 入队成功后、以及尝试离队后队伍都会聚拢,大地图视角也会重置。
|
||||||
|
|
||||||
|
## 地图处理类(浅蓝)
|
||||||
|
|
||||||
|
![image](img/maps_waits_raw.jpg)
|
||||||
|
|
||||||
|
这个类型的指令会影响三层地图矩阵的阵元,如果您觉得三层还不够用,“插件编写”(句号键)五图层插件欢迎您。
|
||||||
|
|
||||||
|
开始介绍前,首先明确一点:改变地图数据不会立即影响事件的进程(自动事件除外)。
|
||||||
|
|
||||||
|
比如因为踩灯、路障和阻激夹域捕捉怪的分布变化导致勇士行走被妨害的区域发生变化,但不会立即触发妨害效果,而是要等到勇士下次行走。
|
||||||
|
|
||||||
|
在勇士所在点转变成(显示)一个门/怪物/道具/箱子/楼梯什么的(包括在脚下转变成/显示冰面)都不会立即触发事件,把这些图块移动/跳跃到勇士身上也是。
|
||||||
|
|
||||||
|
反之,“隐藏事件”(转变图块为0)也不会立即中止当前的事件处理,只是下次不会触发。
|
||||||
|
|
||||||
|
1. **强制战斗(定点):**这是另一种强制战斗,它指定坐标而不是怪物ID.
|
||||||
|
* 可以双击从地图选点(只能选当前楼层的,不填则取当前点),也可以用表达式指定坐标,坐标一次只能写【一个点】。
|
||||||
|
* 战斗后会自动从地图删除该点的怪物(重生怪则是隐藏),并尝试插入该点的战后事件(黄点)以及怪物属性的(批量)战后事件,成功插入前者时会改变当前点坐标到该点。
|
||||||
|
* V2.8新增了两种战前事件,它们无法被“强制战斗”指令触发,如需触发,请使用“触发系统事件”指令。
|
||||||
|
2. **开关门:**坐标写法同上(限1个点),同层开门时楼层ID可以略去不写。
|
||||||
|
* 关门的位置必须是空地,“需要钥匙”只对同层开门有效。跨层开门请自己加判定,本指令对应`core.openDoor(x, y, needKey, callback)`函数。
|
||||||
|
* 这对指令支持所有完整填写了“门信息”的四帧图块(自动元件除外),比如样板自带的三色墙和六色门。
|
||||||
|
* 可以勾选“不等待执行完毕”来实现异步效果(如同时开关多个门,具体速度取决于门信息)。
|
||||||
|
* 和上面的强制战斗一样,开门后将尝试插入该点的开门后事件(紫点),成功插入时会改变当前点坐标到该点。
|
||||||
|
3. **显隐事件和图层块:**这两对指令可以令三层地图矩阵的某些阵元在0与非0之间切换。
|
||||||
|
* 还以51层魔塔为例,二楼右下角的小偷在游戏开始时是不显示的,勇士进入四区后才出现。
|
||||||
|
* 也就是说这个小偷是一个“普通事件”(红),内容是一些对话和打开35层魔龙处的暗道,只不过没有勾选“启用”。
|
||||||
|
* 在适当的时候(这个例子中是和29楼小偷对话后),执行一个“显示MT2层(12,12)点处的事件”指令,就能显示出二楼的小偷。
|
||||||
|
* 同理,勇士接触此小偷并处理事件,事件结束前执行一个“隐藏(同时删除)当前点事件,500毫秒”指令,小偷就会从画面中淡出,勇士可以任意在小偷存在过的位置走来走去而不会再触发什么。
|
||||||
|
* 所以,一次性陷阱(走到某个地方关上墙/机关门、冒出埋伏怪)在触发后一定要及时隐藏。不然就会反复触发,样板1层有例子可供参考。
|
||||||
|
* “显隐事件”都可以双击从地图选点,支持选多个点(只想要楼层ID的话可以点击“复制楼层ID”按钮)。在指令块中可以使用表达式作为坐标(但这样只能写一个点),多个点可以把横坐标依次填在x处而纵坐标对应填在y处(json中写作多行两列的二维数组,但只支持常数),从而同时显隐多个点。
|
||||||
|
* 楼层ID省略则取当前楼层,“动画时间”用于同层显隐,从而表现出淡入淡出的效果。
|
||||||
|
* “不等待执行完毕”的含义如前,您可以淡入一些点同时淡出另一些点。
|
||||||
|
* 值得注意的是,“隐藏事件”还提供了一个“同时删除”勾选框,勾选后无法再用“显示事件”指令显示出来(例如永久移除一个重生怪)。
|
||||||
|
* 请注意,隐藏或删除后不影响正在进行的事件流(不会立刻结束),您可以把该点安全地直接转变为别的图块或让别的图块移动/跳跃到此点,比如把箱子/阻击怪推过来(根据该点是否覆盖触发器,推过来以后的行为可能有变化)。
|
||||||
|
* 其他两个图层的图块也支持显隐,对游戏性的影响主要体现在显隐背景层的滑冰图块以及两个图层的单向通行箭头/薄墙。坐标和楼层ID的填法同上,只不过这两个就没有淡入淡出效果了。因为其他两个图层的图块不支持什么初始隐藏,如有需要,可以在“开场剧情”中统一提前隐藏。
|
||||||
|
* 显隐事件对应`core.showBlock(x, y, floorId)`和`core.hideBlock(x, y, floorId)`,同时删除对应`core.removeBlock(x, y, floorId)`函数;显隐图层块对应`core.maps._triggerFloorImage(type, loc, floorId, callback)`
|
||||||
|
4. **转变图块和图层块、事件转向:**这组指令可以修改三层地图矩阵的阵元。
|
||||||
|
* 先说图层块吧(前景、背景),坐标和楼层的填法同上,不支持淡入淡出。转变图层块后,块的显隐状态不变,原来是显示/隐藏还是显示/隐藏。
|
||||||
|
* 接着说事件层,坐标和楼层的填法同上。有几个注意事项:
|
||||||
|
1. 新图块为0时“动画时间”全部用来淡出,用于没有普通事件和“楼梯、传送门”的点会视为删除。
|
||||||
|
2. 转变图块也不影响显隐状态,该点原来是显示/隐藏还是显示/隐藏。
|
||||||
|
3. 同层把一种非0图块转变为另一种非0图块(空气墙`airwall`算非0),“动画时间”的前一半用来淡出原图块,后一半用来淡入新图块。
|
||||||
|
4. 同层把0图块转变为非0图块,“动画时间”全部用来淡入。
|
||||||
|
* 这对指令可以填写新图块的ID也可以填写数字(如17是空气墙,201起是怪物)。
|
||||||
|
* 如需让绑定了“行走图朝向”的图块转向,也可以直接使用“事件转向”指令(和勇士一样支持7种转法),从而避免一个个手写行走图ID的麻烦。
|
||||||
|
* 转变图块和图层块对应`core.setBlock(number, x, y, floorId)`和`core.maps._triggerBgFgMap(type, name, loc, floorId, callback)`
|
||||||
|
5. **设置图块不透明度和特效:**如题,V2.8新增。前者支持渐变效果,可以用来制作亡灵状态的角色或配合楼层贴图实现大型多帧怪物。
|
||||||
|
6. **显隐贴图:**这个指令可以用来显隐之前在“楼层属性”中介绍的楼层贴图。
|
||||||
|
* 显隐贴图不支持淡入淡出,坐标为贴图左上角的【像素坐标】因此不支持地图选点,楼层ID不填则取当前层。实际执行`core.maps._triggerFloorImage(type, loc, floorId, callback)`
|
||||||
|
7. **移动和跳跃事件:**这两个指令可以将地图一点的图块转移到另一点。
|
||||||
|
* 首先明确一点,这两个指令转移的【仅仅是图块】。起点的七彩事件不会被一同转移(但不消失的情况下定点属性会一同转移),终点的七彩事件也不会被覆盖。
|
||||||
|
* 从游戏性上讲,最终的效果是起点被“隐藏事件+同时删除”,勾选“不消失”时终点被“转变图块+显示事件”(终点原来的图块被覆盖)。
|
||||||
|
* 比如,阻击怪是“移动后不消失”,捕捉怪是“移动后消失”,支援怪是“跳跃后消失”。
|
||||||
|
* 这两个指令一次只能转移一个图块,双击从地图选点选择的是移动的起点和跳跃的终点(跳跃的起点请右击选取)。
|
||||||
|
* 任何一个坐标不填都视为当前点,比如“跳跃事件”什么坐标都不填就会让当前点图块原地跳跃。
|
||||||
|
* 和无视地形移动勇士一样,“移动事件”也没有碰撞效果,移动过程中会穿过勇士和一切地形。
|
||||||
|
* “动画时间”为每步移动的时间或跳跃的用时,以及不勾选“不消失”时淡出的时间。
|
||||||
|
* 和“跳跃勇士”一样,“跳跃事件”默认也没有音效,可以自己搭配。
|
||||||
|
* V2.7.3起,跳跃的目标坐标支持写增量(dx、dy),如写[4,-2]就会让某个图块跳到其原位置向右4格向上2格的位置。
|
||||||
|
* 移动和跳跃实际对应`core.moveBlock(x, y, steps, time, keep, callback)`和`core.jumpBlock(sx, sy, ex, ey, time, keep, callback)`函数。
|
||||||
|
* V2.8起,这两个函数在“不消失”的情况下,会将起点处的定点怪物数据也一并移动到终点,这对阻击怪来说是个福音。
|
||||||
|
* V2.8起,“移动事件”指令支持斜向移动(行走图仍取左右)、中途变速(速度不得小于16)、中途转向(步数填0),“后退”指令如果用在开头则必须是绑定了“行走图朝向”的图块,如果用在中途则会根据上一步移动/转向的方向后退(注意这一点和勇士不同,勇士是不可能斜向后退的)。
|
||||||
|
* “不等待执行完毕”的用法如前,但几个图块再加上勇士以各异的速度和总步数移动时安排起来很麻烦,需要用到下述的“等待三姐妹”。
|
||||||
|
|
||||||
|
## 等待三姐妹、注释和原生js/json
|
||||||
|
|
||||||
|
在讲解“事件控制”(流程控制)类指令之前,这里插播几个比较杂牌的指令。
|
||||||
|
|
||||||
|
1. **等待固定时间:**如题,可以用来实现复杂的集体移动、跳跃效果。
|
||||||
|
* 比如说51层魔塔一区结尾的骷髅埋伏圈,就是九只骷髅和四扇机关门组成的复杂演出。
|
||||||
|
* 每只骷髅开始移动时都“不等待执行完毕”,但又需要“等待一小段时间”再让下一只骷髅开始移动。
|
||||||
|
* 本指令还提供了一个勾选项“不可被Ctrl跳过”,如果不勾选此项且当前【没有】正在执行的异步事件(动画、音效、气泡提示不算),则Ctrl可以跳过等待。
|
||||||
|
2. **等待所有异步事件执行完毕:**让我们来想象这样一个情景。
|
||||||
|
* 您使用了“移动事件”来移动一只怪物到勇士面前,并且“不等待执行完毕”。而下一条指令是“勇士前进一格或撞击”,以期触发战斗。然而因为怪物移动需要时间,两个指令同时执行,所以战斗没法触发。
|
||||||
|
* 类似地,如果您在一个异步事件执行完毕之前就结束了整个事件流,让勇士恢复行动,那么可能这些异步事件还没来得及在游戏性方面生效,导致接下来会发生的事取决于玩家操作的时机和勇士的移速。
|
||||||
|
* 考虑到录像系统,在录像回放时很多耗时的东西和所有需要用户响应的东西会被跳过,勇士的移速又可以很快(倍速播放),导致回放结果和原游戏不一致。
|
||||||
|
* 总之,当您希望确保一些异步事件完全生效后再开始执行新的指令或结束事件流,“等待所有异步事件执行完毕”就是您的不二之选了,事件编辑器也会在发现缺少本指令时弹窗警告。
|
||||||
|
* 动画默认会被等待,而音效不会被默认等待;V2.8.1提供了两个勾选框允许您对其进行控制。
|
||||||
|
* 另外,您可以随时使用`core.getPlayAnimates()`和`core.getSounds()`获取当前未结束的所有动画和音效的id(支持自变量填名称来筛选)。
|
||||||
|
3. **等待用户操作并获得键鼠/触屏信息:**前面提到三种QTE指令,这是最后一种。
|
||||||
|
* 之前提到的“确认框”和“选择项”可以复现RPG Maker的回合制战斗,但没法做出更复杂的交互界面,比如技能/天赋树,以及样板的道具商店,这就需要用到本指令了。
|
||||||
|
* 本指令会阻塞事件的执行,直到玩家按下键盘上的某个键(滚轮视为PageUp/PageDown键)、或点击【视野】中的某个点、或经过了超时毫秒数(不设置则不限时)。
|
||||||
|
* 解除阻塞后,下列值块可能发生变化:
|
||||||
|
1. `变量:type`(`flag:type`),解除的原因,0表示按键,1表示点击,-1表示超时。
|
||||||
|
2. `变量:keycode`(`flag:keycode`),按键情况下的键值,48—57为大键盘0—9,65—90为字母键A—Z,其他键请右击该子块查询(查不到的自己百度)。
|
||||||
|
3. `变量:timeout`(`flag:timeout`),进行有效操作后,距离超时的剩余倒计时,可以用来进行一些处理(音游?)。
|
||||||
|
4. `变量:x`和`变量:y`(`flag:x`和`flag:y`),点击情况下所点格子在视野中的相对坐标,一定在0—12或0—14范围。
|
||||||
|
5. `变量:px`和`变量:py`(`flag:px`和`flag:py`),点击情况下所点像素在视野中的相对坐标,一定在0—415或0—479范围。
|
||||||
|
* 上述后两项如需转换为大地图中的绝对坐标,请查阅“楼层属性——修改楼层宽高”一节。
|
||||||
|
* 您可以根据这些值块去做流程控制,较为简单的场合(如几个键执行同一段指令,或横平竖直的单个矩形点击区)也可直接使用图中的场合块。
|
||||||
|
* 其中点击的场合块还支持双击预览判定区,用半透明的红色标出。
|
||||||
|
* 默认情况下,一次操作同时满足多个场合块时会依次执行这几个块的内容,除非您使用了下面的“不进行剩余判定”。
|
||||||
|
* V2.7.3起,场合块支持“不进行剩余判定”,当满足勾选了此项的场合块时,它下面的其他场合块会全部视为不满足。这允许您实现“点击地图上某个小区域做某些事,点击其他地方做另一些事”而不需要在后者的场合中专门排除前者。
|
||||||
|
* V2.8起,提供了超时场合块和自定义条件的场合块,同时还支持“只检测子块”。
|
||||||
|
* 在“只检测子块”模式下,如果玩家进行了不符合任何子块的操作,那么会继续阻塞而不是进入下一个指令,超时倒计时也不会重置。
|
||||||
|
* 但是如果设置了超时时间,即使未提供超时场合,超时时依然会解除阻塞直接进入下面的指令。
|
||||||
|
* 此指令获取到的玩家操作,在录像中会像“接受用户输入数字”一样记录为`input:...`。例如不带超时的键盘按键,就会记录为`input:keycode`。
|
||||||
|
* 录像回放时,如果出现了多余的`input:...`,就会跳过并警告。如果遇到本指令但录像中未记录操作或记录了非法/无效操作,就会直接报错。
|
||||||
|
* 非法操作指“本指令没有设置超时时间但录像中记录为超时”,无效操作指“本指令设置为只检测子块,但录像中记录的是一个不满足任何子块的操作”。
|
||||||
|
4. **添加注释:**在上述的场合块里您还可以看到两个注释块。
|
||||||
|
* 注释块在游戏中会被忽略,一般用来帮您记住写的指令是用来做什么的。
|
||||||
|
* 极端情况下,样板某些场合会检测指令数组是否为空及其长度,此时您可能需要被迫用注释指令来占位。
|
||||||
|
5. **自定义事件:**自带的指令毕竟有限,但事件可以与脚本任意联动。
|
||||||
|
* 原生脚本分为两种,原生js和原生json.其中后者会实际执行您通过`core.registerEvent`注册的事件处理函数(即`{"type":"xxx",...}`对应`core.events._action_xxx()`),请注意这类函数在执行结束前务必调用`core.doAction()`函数去继续执行下一条指令。
|
||||||
|
* 如果学有余力,还可根据[修改编辑器](editor)来代替原生json,就可以使用调色器、地图选点、选文件等功能啦。
|
||||||
|
6. **原生JS脚本执行**:允许您执行任意JS脚本,例如造塔测试时发现事件流程不符合预期,此时可以使用`console.log()`语句在控制台输出日志信息进行检查。
|
||||||
|
|
||||||
|
原生js的用法就更广了,首先它可以做一些事件做不到的事,比如:
|
||||||
|
|
||||||
|
如果用事件增加道具的话就会有提示和音效,而这有时不是我们需要的,尤其是在设计新道具时将“能否使用”填`"true"`并在使用效果事件中使用失败的场合返还道具时;因此我们可以直接调用脚本:
|
||||||
|
```js
|
||||||
|
core.addItem(itemId, n); // 静默增加n个道具,n可为负数,不填视为1
|
||||||
|
```
|
||||||
|
|
||||||
|
其次,受制于Antlr—blockly的数据类型,很多指令的参数只能写常数,比如楼层ID。这时我们就需要在原生js中使用万能的`core.insertAction()`大法,来突破这些限制。
|
||||||
|
|
||||||
|
比如说我们有一座20层高的塔,楼层ID为MT0—MT19,某个事件中我们想让勇士随机传送到某个楼层,坐标不变。那么就可以使用下列原生js:
|
||||||
|
```js
|
||||||
|
core.insertAction({"type": "changeFloor", "floorId": "MT" + core.rand2(20)})
|
||||||
|
```
|
||||||
|
|
||||||
|
连续使用多条json指令时,请先声明一个空的指令数组(`var todo = [];`)然后将需要的指令分批追加到其末尾(`core.push(todo, [...]);`),最后再一次性`core.insertAction(todo);`插入执行,可以选择插入到剩余事件的开头或结尾。
|
||||||
|
|
||||||
|
另外您可能会问:既然都用js了,为什么不直接用之前提到的`core.changeFloor()`函数呢?
|
||||||
|
|
||||||
|
这是因为,原生js在不勾选“不自动执行下一个事件”的情况下,**只能使用瞬间完成的函数(或者drawTip、动画和音效这种虽然耗时但不影响游戏性的),不能使用任何异步函数(包括阻塞直到玩家操作的)**!
|
||||||
|
|
||||||
|
系统常见可能会被造塔用到的API都在[API列表](api)中给出,一般而言异步API的最后一个自变量都叫`callback`(回调)。在勾选“不自动执行下一个事件”的情况下,原生js可以使用一个异步API,只需将其`callback`参数填`core.doAction`,请谨慎使用。
|
||||||
|
|
||||||
|
比如说,我们知道天降强制战斗没有坐标所以不受光环等影响也无法触发(单点)战后事件,那捕捉怪的战斗是怎么实现的呢?答案是在原生js中使用了异步的`core.battle(id, x, y, force, callback)`函数,这里`force`填`true`表示强制战斗,`callback`填`core.doAction`表示战斗结束后继续处理事件。
|
||||||
|
|
||||||
|
熟练运用后,还可以使用多个异步API,每个以下一个作为回调。
|
||||||
|
|
||||||
|
## 事件控制类(深蓝)
|
||||||
|
|
||||||
|
![image](img/flowctrl.jpg)
|
||||||
|
|
||||||
|
在三个QTE指令中,我们已经初见了流程控制的端倪。只不过,它们的流程走向是由玩家的选择直接左右的。能否通过对值块的比较等操作自动走向不同的流程分支呢?答案是肯定的。
|
||||||
|
|
||||||
|
1. **条件分歧:**和“显示确认框”很相似,只不过这个是自动的。
|
||||||
|
* 和js的`if (...) {...;} else {...;}`完全等价,本指令需要内嵌一个值块(可以填逻辑表达式,常常是两个值块的比较,比如某道具是否足够)。
|
||||||
|
* 当此值块的值不为`false、0、null、undefined、NaN和空字符串`(即之前提到的六大广义`false`)时,执行“如果:”和“否则:”之间的指令,当此值块的值为这六个值之一时,执行“否则:”后面的指令。
|
||||||
|
2. **多重分歧:**本指令和js的`switch`语句有一定差别,即使您有js或其他编程语言基础,也请务必仔细阅读接下来的说明。
|
||||||
|
* 事件执行到多重分歧指令时,会先计算一次“判别值”,结果记作`key`.然后准备一个空的指令数组,记作`list`;
|
||||||
|
* 接下来从上到下扫描每个场合块,如果一个场合块的“值”为`default`或计算结果和`key`相等,就把这个场合的指令组追加到`list`的末尾,即`core.push(list, [...]);`。
|
||||||
|
* 每次成功追加后,如果被追加的此场合块未勾选“不跳出”,则无视下面的所有场合块直接结束扫描,否则继续扫描下一个场合块。
|
||||||
|
* 扫描完全结束后,调用`core.insertAction(list);`付诸执行。
|
||||||
|
* 所以各场合块的顺序一定要安排好,比如`default`(可以没有此场合)如果不勾选“不跳出”则一定要放在最后。
|
||||||
|
* 多重分歧常见的用法是,判别值填未知量,所有场合块都不勾选“不跳出”且“值”填写为两两不同的常量(如果有相同的则只有第一个有效)。从而执行唯一的相等场合,没有的话还可以补一个`default`场合,类似js的`else if`语法。
|
||||||
|
* 或者,判别值填常量,各场合块填未知量且都勾选“不跳出”。把所有相等的场合筛选出来依次执行,类似js的`filter`语法。
|
||||||
|
* 第二种用法中,最常见的是判别值填`true`,各场合块填逻辑表达式。此时如果都勾选“不跳出”并且不提供`default`场合,就相当于一堆没有`else`的if语句。
|
||||||
|
* 而如果在这个基础上改成都不勾选“不跳出”就相当于正常的`else if`语句(可以把`default`场合提供在最后或不提供),而且比起上面的“条件分歧”指令不需要嵌套了。
|
||||||
|
3. **循环遍历(计数):**使用临时变量A—Z(事件流结束时会和arg+纯数字的变量一起被清空)可以进行循环遍历,它们都属于前置条件循环。
|
||||||
|
* 循环遍历有两种,第一种是计数型。`从`和`到`之间的框填写临时变量的初始值,`到`和`步增`之间的框填写临时变量允许的边界值。
|
||||||
|
* 每轮循环开始前会【重新计算】边界值和步增,如果临时变量位于界外(减去边界值后和步增同号)则跳出循环。
|
||||||
|
* 每轮循环结束时,临时变量会加上步增值(可以为负数,请注意变号问题)。
|
||||||
|
4. **循环遍历(迭代):**这是另一种循环遍历,可以迭代一个列表。
|
||||||
|
* 每轮循环前,会检查列表是否已空。已空则跳出循环,否则将列表的第一项【移除并代入】临时变量。
|
||||||
|
* 使用此项时请注意,列表中的字符串不会被自动解释为冒号缩写量。显示文章等处如有需求,请使用`${core.calValue(temp:A)}`来计算。
|
||||||
|
* 分歧和循环都可以嵌套,嵌套循环遍历请给内外层使用不同的临时变量。
|
||||||
|
5. **前/后置条件循环:**
|
||||||
|
* 前置条件循环和一个没有“否则”分支的条件分歧很相似,区别仅仅在于每次执行完分支内容后都会【跳转】回到条件检测之前,从而再次检测条件,还满足就再执行,如此往复。
|
||||||
|
* 而后置条件循环比起前置,区别在于第一轮循环是无视条件强制执行的。
|
||||||
|
* 它们对应的js语法分别为`while (...) {...;}`和`do {...;} while(...);`
|
||||||
|
6. **跳出当前循环:**如题,遇到此指令时,将检测当前是否在某个循环中(还包括自动事件、公共事件、全局商店),并跳出所在的【最内层】循环(V2.7.3起支持指定跳出层数)。如果不在任何循环中,则什么也不做。大致相当于js的`break;`
|
||||||
|
7. **提前结束本轮循环:**生效范围同上,不过是结束最内层的【本轮】循环。换言之:
|
||||||
|
* 对一般的前/后置条件循环会立刻跳转到下次检测条件前;
|
||||||
|
* 对循环遍历(计数)会立刻跳转到下次计算边界和步增并累加前;
|
||||||
|
* 对循环遍历(迭代)会立刻跳转到下次检查列表是否为空前。
|
||||||
|
* 还可用来“重启新版商店或道具商店”。若不在任何循环中,则什么也不做。
|
||||||
|
* 大致相当于js的`continue;`,V2.7.3起支持指定层数。
|
||||||
|
8. **立刻结束当前事件:**此指令将清空临时变量(以`@temp@`开头)和参数变量(`arg+纯数字`),清空事件列表,中断一切事件处理,恢复勇士行动。
|
||||||
|
* V2.8起,您可以在两种“战前事件”中使用它取消战斗。
|
||||||
|
9. **触发系统事件:**模拟勇士撞击/踩踏本楼层的某个点(实际执行`core.trigger(x, y, callback)`),支持双击从地图选点。
|
||||||
|
* 该指令把触发的事件(包括图块属性的“碰触脚本/碰触事件”,不包括阻激夹域捕捉和血网)插队到当前事件列表中。
|
||||||
|
* 譬如该点是道具则会捡起来,是怪物则会触发战前事件然后战斗,是门则会尝试开门,还会连带触发对应的`afterXxx`事件。
|
||||||
|
* 如果该点是普通事件(红)则会触发之,是楼梯事件(绿)则会直接触发传送(目标点是“保持不变”或三种对称点也以勇士位置而不是楼梯位置计算)。
|
||||||
|
* 该点是路障则会触发对应的毒衰咒(血网除外),是踩灯则会把勇士脚下变成踩过的灯。滑冰在背景层,所以无法触发。至于推箱子,请自行探索。
|
||||||
|
* V2.8起,您可以使用“强制战斗(定点)”指令跳过战前事件强制与地图上某个点的怪物战斗,也可以用“触发系统事件”指令指定某个有怪物的点然后先触发战前事件再战斗,请注意区分。
|
||||||
|
10. **插入公共事件:**如题,“参数列表”为一数组,其各项会被依次代入值块`变量:arg1`、`变量:arg2`...而`变量:arg0`会记录公共事件的名称。
|
||||||
|
* 实际执行`core.insertCommonEvent(name, args, x, y, callback, addToLast)`
|
||||||
|
11. **插入事件:**此指令可以无视目标点启用与否,跨楼层插入任何点的普通事件(不需要覆盖触发器)、战前事件、afterXxx事件执行。支持双击从地图选点。
|
||||||
|
|
||||||
|
## 特效声音类(褐色)
|
||||||
|
|
||||||
|
![image](img/blockly.jpg)
|
||||||
|
|
||||||
|
这个类别的指令会负责动画、视角、色调、天气、音频、存读档等其他一些细节。
|
||||||
|
|
||||||
|
1. **画面震动:**会让画面震动,可以选择四种振动方向,可以指定总时间、震动速度和振幅。
|
||||||
|
* 实际执行`core.vibrate(direction, time, speed, power, callback)`函数。
|
||||||
|
2. **显示动画:**如题,可以双击选文件并预览(预览的坐标锁定为视野中心)和试听/修改音效。
|
||||||
|
* 如需从地图选点请右击指令块,也可用另一个指令让动画跟随勇士移动。
|
||||||
|
* 坐标不写则取当前点,如果勾选“相对窗口坐标”则坐标应填写为0—12或0—14表示视野中的相对坐标(如13×13样板填两个6表示视野中心)。
|
||||||
|
* 如果不勾选“不等待执行完毕”,则等待的实际时长只取决于动画,和音效无关。
|
||||||
|
* 实际调用`core.drawAnimate(name, x, y, alignWindow, callback)`和`core.drawHeroAnimate(name, callback)`
|
||||||
|
3. **设置视角:**设置视角支持双击从地图选点,不填坐标则重置视角。动画时间指移动的总时间,支持四种变速移动。
|
||||||
|
* 勇士的`status:animate`为`true`(原地抖动开启)时,禁止使用本指令!
|
||||||
|
* V2.7.3起,坐标支持写增量,如[4,-2]表示视角直线移动到向右4格、向上2格的位置。
|
||||||
|
* 请注意,勇士重绘时(`core.drawHero()`函数)视角也会随之重置。所以视角变化后勇士的坐标、朝向、显隐、行走图(事件和API都提供了不重绘参数)和跟随者情况暂时都不要动。
|
||||||
|
* 实际调用`core.setViewport(x, y)`和`core.moveViewport(x, y, moveMode, time, callback)`,其中前者的自变量为【像素坐标】且不必为32的倍数,必要时可作为原生js使用来实现特殊的演出。
|
||||||
|
* V2.8.1起,支持暂时锁定视角,视角锁定期间,只有上下楼后才会将视角重置到勇士身上(但依然保持锁定)。
|
||||||
|
4. **显隐状态栏:**如题,如果隐藏状态栏期间勇士需要恢复行动,则建议不隐藏竖屏工具栏以方便手机玩家。
|
||||||
|
* 实际调用`core.showStatusBar()`和`core.hideStatusBar(showToolbox)`
|
||||||
|
5. **设置勇士不透明度:**如题,动画时间为淡入淡出时间,异步勾选框用法如前。
|
||||||
|
* 实际调用`core.setHeroOpacity(opacity, moveMode, time, callback)`
|
||||||
|
6. **更改画面色调:**色调可以用调色器调配,“动画时间”为渐变的总时间。
|
||||||
|
* 请注意渐变是在RGBA颜色空间中直线运动(V2.8.1支持加速度),因此效果可能不好,画面闪烁同理。
|
||||||
|
* 如需在勇士自由行动时反复执行,请使用并行脚本或自我回调。
|
||||||
|
7. **恢复画面色调:**指将更改后的色调恢复到楼层的默认色调,更改当前层的默认色调后您可以使用此指令刷新。
|
||||||
|
8. **画面闪烁:**“单次时间”必须为3的倍数,前1/3时间用于将画面色调转变为目标色调,后2/3时间用于恢复当前色调,执行次数如题。
|
||||||
|
* 实际调用`screenFlash(color, time, times, callback)`
|
||||||
|
9. **更改天气:**如题,可以选择“无、雨、雪、雾、晴”之一,强度需填小于等于10的正整数。
|
||||||
|
10. **播放背景音乐:**如题,可以双击选文件并试听(支持别名),并指定开始播放的秒数。
|
||||||
|
* 当在游戏中触发楼层切换时(包括读档),如果`flag:__color__、flag:__weather__、flag:__bgm__`这三个值块没有值,游戏当时的画面色调、天气、背景音乐就会变为楼层属性中的这三个设置项。
|
||||||
|
* 以上几个指令都提供了“持续到下个本事件”勾选框,勾选后,本次设置的值将会计入这三个值块。它们的拦截行为在“脚本编辑——切换楼层中”。
|
||||||
|
* 若不勾选,或恢复画面色调、设置天气为无(晴),就会清除对应的值块。您也可以随时对这三个值块进行手动干预。
|
||||||
|
* V2.8.1起,支持由作者注册新的自定义天气,新天气在下拉框中默认未提供,可以手动填写在json区再点击“解析”按钮。
|
||||||
|
11. **暂停和恢复背景音乐:**如题,暂停时会记录暂停在了第几秒,恢复时可以选择从这个秒数继续或从头重播。
|
||||||
|
12. **预加载背景音乐和释放其缓存:**在线游戏使用,前者的原理是静音播放。
|
||||||
|
* 最多同时缓存8首背景音乐(由`libs/core.js`控制),会自动释放最久未使用的,但您也可以手动释放。
|
||||||
|
13. **播放音效和停止所有音效:**如题,开播一个音效的同时可以停止其他的。V2.8播放系统音效可以从下拉框选取,其他音效也支持使用别名。
|
||||||
|
* V2.8起,播放音效支持“等待播放完毕”,支持同时“变调且变速”。100表示正常速度和音调,可以填30到300之间的值。
|
||||||
|
14. **设置音量:**只对背景音乐有效,音量为小于等于100的自然数(玩家设置值其实被开了平方),渐变时间如题。
|
||||||
|
15. **设置背景音乐播放速度和音调:**V2.8新增,需要填30到300之间的值。100表示正常速度和音调,可以只改变速度(RPG Maker做不到哦),也可以同时改变速度和音调。
|
||||||
|
* 该项的效果不会进存档,必要时您可以配合使用“禁止存档”指令。
|
||||||
|
16. **呼出怪物手册和SL界面:**
|
||||||
|
* 呼出手册只在勇士持有手册时生效,关闭手册后事件继续。
|
||||||
|
* 呼出存档界面最多只能存一个档然后事件继续(读档后事件现场会恢复)。
|
||||||
|
* 呼出读档界面如果不读档则事件继续,录像回放中这三条指令都被忽略。
|
||||||
|
17. **自动存档:**读档后也会恢复事件现场,录像回放中会照常存档。
|
||||||
|
* 如不勾选“不提示”则会`core.drawTip(“已自动存档”)`(即使在下面禁止的情况下!),此指令一般用于选择项/确认框之前。
|
||||||
|
18. **是否禁止存档:**该项允许暂时禁止玩家存档(包括撞怪撞门的自动存档,不包括上面的呼出存档界面和事件中自动存档的指令),从而做出RPG中常见的“迷宫中只有特定位置可以存档,或者必须消耗道具进行存档”的效果。
|
||||||
|
|
||||||
|
## UI绘制类(瞬间)
|
||||||
|
|
||||||
|
![image](img/uievent.jpg)
|
||||||
|
|
||||||
|
这个类别的指令全部是瞬间完成的,有一一对应的API,请放心使用,除“绘制多行文本”外都可以逐个双击预览(多行文本需要右击预览)。游戏中,这些指令都是画在`uievent`层的。
|
||||||
|
|
||||||
|
1. **UI绘制并预览:**您可以把一堆UI绘制类指令塞进去然后双击黄框整体预览。
|
||||||
|
2. **清除画布:**擦除`uievent`层的一部分,x和y为左上角坐标。
|
||||||
|
* 四个参数都支持使用表达式,任何一个参数不填都会删除整个`uievent`层。
|
||||||
|
* 实际调用`core.clearMap("uievent",x,y,width,height)`和`core.deleteCanvas("uievent")`,熟练后对其他画布使用也是可以的。
|
||||||
|
3. **设置画布属性:**
|
||||||
|
1. 字体:`italic和bold`表示斜体和加粗,可省略,字号和字体用于绘制文本。
|
||||||
|
2. 填充样式:绘制实心图形时的默认填色,可用调色器调配。
|
||||||
|
3. 边框样式:绘制空心图形时的默认边框颜色,可用调色器调配。
|
||||||
|
4. 线宽度:绘制线段、箭头和空心图形时的默认线宽,单位为像素。
|
||||||
|
5. 不透明度:不大于1的非负数,此项为“画笔”的不透明度。只影响接下来画的内容,已经画上去的不受影响。
|
||||||
|
6. 对齐:绘制单行文本的对齐方式,左对齐、左右居中、右对齐。
|
||||||
|
7. 基准线:绘制单行文本的基准线,有六种写法。绘制单行文本的坐标其实是文本的基准点,
|
||||||
|
8. z值:初始为135,z值较大的画布将覆盖较小的,详见“个性化”。闪烁光标的z值总是比上述的值大1,即默认为136.
|
||||||
|
4. **绘制文本:**
|
||||||
|
* 这里仅绘制单行文本,坐标为基准点的像素坐标,需配合上述“对齐”和“基准线”使用。
|
||||||
|
* 如果设置了最大宽度,那么在超出此宽度时就会保持比例压缩到这个宽度。
|
||||||
|
* 正文只支持`${js表达式求值}`和`\r[变色]`,不支持其他一切转义序列,更不能手动换行。
|
||||||
|
* 实际执行`core.fillText("uievent", text, x, y, style, font, maxWidth)`
|
||||||
|
5. **绘制描边文本:**同上,但不支持限宽,描边效果同状态栏数字的黑边。
|
||||||
|
* 底层实现为`ctx.strokeText()`,描边颜色可以自己指定。
|
||||||
|
* 本指令对应的js函数为`core.ui.fillBoldText('uievent', text, x, y, style, font)`
|
||||||
|
6. **绘制多行文本:**双击进入多行编辑,预览请右击。
|
||||||
|
* 起点像素为左上角,只有设置了最大行宽才会对齐、居中和自动换行。
|
||||||
|
* 如果不设置颜色和字号,就会采用“设置剧情文本的属性”中的正文设置。
|
||||||
|
* 不设置行距就会采用字体大小的1.3倍,建议采用偶数字号和1.5倍行距。
|
||||||
|
* 多行文本不支持字体样式的设置,使用的是全塔属性中的全局字体`Verdana`,
|
||||||
|
* 如有需要,请使用“设置全局属性”指令来设置字体样式。
|
||||||
|
7. **绘制几何图形:**对应的js函数为`core.ui.fillXxx()`和`core.ui.strokeXxx()`
|
||||||
|
8. **绘制图片:**同“显示图片”指令但功能有出入,实际执行`core.drawImage('uievent',img,x,y,w,h,x1,y1,w1,h1)`
|
||||||
|
9. **绘制图标:**支持图块id和系统图标,支持伸缩和选择哪一帧。支持32×32和32×48两种尺寸,实际执行`core.drawIcon(name, id, x, y, w, h, frame)`
|
||||||
|
10. **绘制背景图:**背景色支持颜色数组,也支持类似`winskin.png`的图片名。使用图片时的不透明度以及纯色背景时的边框颜色,由“设置画布属性”指定。本指令对应的js函数为`core.ui.drawBackground(left, top, right, bottom, posInfo)`
|
||||||
|
11. **绘制和清除闪烁光标:**如题,光标的z值总是比`uievent`层大1.
|
||||||
|
12. **设置画布特效:**V2.8新增,允许给画布设置像图块一样的“虚化、色相、灰度、反色、阴影”属性。
|
||||||
|
|
||||||
|
==========================================================================================
|
||||||
|
|
||||||
|
[继续阅读下一章:个性化](personalization)
|
300
item.d.ts
vendored
Normal file
300
item.d.ts
vendored
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
interface Item<I extends AllIdsOf<'items'>> {
|
||||||
|
/**
|
||||||
|
* 道具id
|
||||||
|
*/
|
||||||
|
id: I;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具的类型
|
||||||
|
*/
|
||||||
|
cls: ItemClsOf<I>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具的名称
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具的描述
|
||||||
|
*/
|
||||||
|
text?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否在道具栏隐藏
|
||||||
|
*/
|
||||||
|
hideInToolBox: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装备信息
|
||||||
|
*/
|
||||||
|
equip: ItemClsOf<I> extends 'equips' ? Equip : never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回放使用时是否不先打开道具栏再使用
|
||||||
|
*/
|
||||||
|
hideInReplay: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 即捡即用效果
|
||||||
|
*/
|
||||||
|
itemEffect?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 即捡即用道具捡过之后的提示
|
||||||
|
*/
|
||||||
|
itemEffectTip?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用道具时执行的事件
|
||||||
|
*/
|
||||||
|
useItemEvent?: MotaEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用道具时执行的代码
|
||||||
|
*/
|
||||||
|
useItemEffect?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 能否使用道具
|
||||||
|
*/
|
||||||
|
canUseItemEffect?: string | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EquipBase {
|
||||||
|
/**
|
||||||
|
* 装备增加的数值
|
||||||
|
*/
|
||||||
|
value: Record<keyof SelectType<HeroStatus, number>, number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装备增加的百分比
|
||||||
|
*/
|
||||||
|
percentage: Record<keyof SelectType<HeroStatus, number>, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Equip extends EquipBase {
|
||||||
|
/**
|
||||||
|
* 可以装备到的装备孔
|
||||||
|
*/
|
||||||
|
type: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画信息
|
||||||
|
*/
|
||||||
|
animate: AnimationIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 穿上装备时执行的事件
|
||||||
|
*/
|
||||||
|
equipEvent?: MotaEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱下装备时执行的事件
|
||||||
|
*/
|
||||||
|
unequipEvent?: MotaEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具相关的内容
|
||||||
|
*/
|
||||||
|
interface Items {
|
||||||
|
/**
|
||||||
|
* 获得所有道具
|
||||||
|
*/
|
||||||
|
getItems(): {
|
||||||
|
[P in AllIdsOf<'items'>]: Item<P>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行即捡即用类的道具获得时的效果
|
||||||
|
* @example core.getItemEffect('redPotion', 10) // 执行获得10瓶红血的效果
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @param itemNum 道具数量,默认为1
|
||||||
|
*/
|
||||||
|
getItemEffect(itemId: AllIdsOf<'items'>, itemNum?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 即捡即用类的道具获得时的额外提示
|
||||||
|
* @example core.getItemEffectTip(redPotion) // (获得 红血瓶)',生命+100'
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @returns 图块属性itemEffectTip的内容
|
||||||
|
*/
|
||||||
|
getItemEffectTip(itemId: AllIdsOf<'items'>): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用一个道具
|
||||||
|
* @example core.useItem('pickaxe', true) // 使用破墙镐,不计入录像,无回调
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @param noRoute 是否不计入录像,快捷键使用的请填true,否则可省略
|
||||||
|
* @param callback 道具使用完毕或使用失败后的回调函数,好像没什么意义吧(
|
||||||
|
*/
|
||||||
|
useItem(
|
||||||
|
itemId: ItemIdOf<'tools' | 'constants'>,
|
||||||
|
noRoute?: boolean,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查能否使用某种道具
|
||||||
|
* @example core.canUseItem('pickaxe') // 能否使用破墙镐
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @returns true表示可以使用
|
||||||
|
*/
|
||||||
|
canUseItem(itemId: AllIdsOf<'items'>): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计某种道具的持有量
|
||||||
|
* @example core.itemCount('yellowKey') // 持有多少把黄钥匙
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @returns 该种道具的持有量,不包括已穿戴的装备
|
||||||
|
*/
|
||||||
|
itemCount(itemId: AllIdsOf<'items'>): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查主角是否持有某种道具(不包括已穿戴的装备)
|
||||||
|
* @example core.hasItem('yellowKey') // 主角是否持有黄钥匙
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @returns true表示持有
|
||||||
|
*/
|
||||||
|
hasItem(itemId: AllIdsOf<'items'>): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查主角是否穿戴着某件装备
|
||||||
|
* @example core.hasEquip('sword5') // 主角是否装备了神圣剑
|
||||||
|
* @param itemId 装备id
|
||||||
|
* @returns true表示已装备
|
||||||
|
*/
|
||||||
|
hasEquip(itemId: ItemIdOf<'equips'>): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查主角某种类型的装备目前是什么
|
||||||
|
* @example core.getEquip(1) // 主角目前装备了什么盾牌
|
||||||
|
* @param equipType 装备类型,自然数
|
||||||
|
* @returns 装备id,null表示未穿戴
|
||||||
|
*/
|
||||||
|
getEquip(equipType: number): ItemIdOf<'equips'> | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某种道具的持有量
|
||||||
|
* @example core.setItem('yellowKey', 3) // 设置黄钥匙为3把
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @param itemNum 新的持有量,可选,自然数,默认为0
|
||||||
|
*/
|
||||||
|
setItem(itemId: AllIdsOf<'items'>, itemNum?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静默增减某种道具的持有量 不会更新游戏画面或是显示提示
|
||||||
|
* @example core.addItem('yellowKey', -2) // 没收两把黄钥匙
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @param itemNum 增加量,负数表示减少
|
||||||
|
*/
|
||||||
|
addItem(itemId: AllIdsOf<'items'>, itemNum?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 使用addItem代替。
|
||||||
|
* 删除某个物品一定的数量,相当于addItem(itemId, -n);
|
||||||
|
* @param itemId 道具id
|
||||||
|
* @param itemNum 减少量,负数表示增加
|
||||||
|
*/
|
||||||
|
removeItem(itemId?: AllIdsOf<'items'>, itemNum?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型获得一个可用的装备孔
|
||||||
|
* @param equipId 道具名称
|
||||||
|
*/
|
||||||
|
getEquipTypeByName(name?: ItemIdOf<'equips'>): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判定某件装备的类型
|
||||||
|
* @example core.getEquipTypeById('shield5') // 1(盾牌)
|
||||||
|
* @param equipId 装备id
|
||||||
|
* @returns 类型编号,自然数
|
||||||
|
*/
|
||||||
|
getEquipTypeById(equipId: ItemIdOf<'equips'>): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查能否穿上某件装备
|
||||||
|
* @example core.canEquip('sword5', true) // 主角可以装备神圣剑吗,如果不能会有提示
|
||||||
|
* @param equipId 装备id
|
||||||
|
* @param hint 无法穿上时是否提示(比如是因为未持有还是别的什么原因)
|
||||||
|
* @returns true表示可以穿上,false表示无法穿上
|
||||||
|
*/
|
||||||
|
canEquip(equipId: ItemIdOf<'equips'>, hint?: boolean): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试穿上某件背包里的装备并提示
|
||||||
|
* @example core.loadEquip('sword5') // 尝试装备上背包里的神圣剑,无回调
|
||||||
|
* @param equipId 装备id
|
||||||
|
* @param callback 穿戴成功或失败后的回调函数
|
||||||
|
*/
|
||||||
|
loadEquip(equipId: ItemIdOf<'equips'>, callback?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱下某个类型的装备
|
||||||
|
* @example core.unloadEquip(1) // 卸下盾牌,无回调
|
||||||
|
* @param equipType 装备类型编号,自然数
|
||||||
|
* @param callback 卸下装备后的回调函数
|
||||||
|
*/
|
||||||
|
unloadEquip(equipType: number, callback?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两件(类型可不同)装备的优劣
|
||||||
|
* @example core.compareEquipment('sword5', 'shield5') // 比较神圣剑和神圣盾的优劣
|
||||||
|
* @param compareEquipId 装备甲的id
|
||||||
|
* @param beComparedEquipId 装备乙的id
|
||||||
|
* @returns 两装备的各属性差,甲减乙,0省略
|
||||||
|
*/
|
||||||
|
compareEquipment<F extends ItemIdOf<'equips'>>(
|
||||||
|
compareEquipId: F,
|
||||||
|
beComparedEquipId: Exclude<ItemIdOf<'equips'>, F>
|
||||||
|
): EquipBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存当前套装
|
||||||
|
* @example core.quickSaveEquip(1) // 将当前套装保存为1号套装
|
||||||
|
* @param index 套装编号,自然数
|
||||||
|
*/
|
||||||
|
quickSaveEquip(index: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快速换装
|
||||||
|
* @example core.quickLoadEquip(1) // 快速换上1号套装
|
||||||
|
* @param index 套装编号,自然数
|
||||||
|
*/
|
||||||
|
quickLoadEquip(index: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某个装备的属性并计入存档
|
||||||
|
* @example core.setEquip('sword1', 'value', 'atk', 300, '+='); // 设置铁剑的攻击力数值再加300
|
||||||
|
* @param equipId 装备id
|
||||||
|
* @param valueType 增幅类型,只能是value(数值)或percentage(百分比)
|
||||||
|
* @param name 要修改的属性名称,如atk
|
||||||
|
* @param value 要修改到的属性数值
|
||||||
|
* @param operator 操作符,如+=表示在原始值上增加
|
||||||
|
* @param prefix 独立开关前缀,一般不需要
|
||||||
|
*/
|
||||||
|
setEquip(
|
||||||
|
equipId: ItemIdOf<'equips'>,
|
||||||
|
valueType: 'value' | 'percentage',
|
||||||
|
name: keyof SelectType<HeroStatus, number>,
|
||||||
|
value: number,
|
||||||
|
operator?: MotaOperator,
|
||||||
|
prefix?: string
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 真正的穿脱装备
|
||||||
|
* @param type 装备孔
|
||||||
|
* @param loadId 装上的装备
|
||||||
|
* @param unloadId 脱下的装备
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
_realLoadEquip(
|
||||||
|
type: number,
|
||||||
|
loadId?: ItemIdOf<'equips'>,
|
||||||
|
unloadId?: ItemIdOf<'equips'>,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const items: new () => Items;
|
73
loader.d.ts
vendored
Normal file
73
loader.d.ts
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* 负责资源的加载
|
||||||
|
*/
|
||||||
|
interface Loader {
|
||||||
|
/**
|
||||||
|
* 加载一系列图片
|
||||||
|
* @param dir 图片所在目录
|
||||||
|
* @param names 图片名称列表
|
||||||
|
* @param toSave 要保存到的对象
|
||||||
|
* @param callback 加载完毕后的回调函数
|
||||||
|
*/
|
||||||
|
loadImages(
|
||||||
|
dir: string,
|
||||||
|
names: string[],
|
||||||
|
toSave: Record<string, HTMLImageElement>,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载某一张图片
|
||||||
|
* @param dir 图片所在目录
|
||||||
|
* @param imgName 图片名称
|
||||||
|
* @param callback 加载完毕的回调函数
|
||||||
|
*/
|
||||||
|
loadImage(
|
||||||
|
dir: string,
|
||||||
|
imgName: string,
|
||||||
|
callback?: (name: string, img: HTMLImageElement) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从zip中加载一系列图片
|
||||||
|
* @param url 图片所在目录
|
||||||
|
* @param names 图片名称列表
|
||||||
|
*/
|
||||||
|
loadImagesFromZip(
|
||||||
|
url: string,
|
||||||
|
names: string,
|
||||||
|
toSave: Record<string, HTMLImageElement>,
|
||||||
|
onprogress?: (loaded: number, total: number) => void,
|
||||||
|
onfinished?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载一个音乐
|
||||||
|
* @param name 要加载的音乐的名称
|
||||||
|
*/
|
||||||
|
loadOneMusic(name: BgmIds): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载一个音效
|
||||||
|
* @param name 要加载的音效的名称
|
||||||
|
*/
|
||||||
|
loadOneSound(name: SoundIds): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载一个bgm
|
||||||
|
* @param name 加载的bgm的id或名称
|
||||||
|
*/
|
||||||
|
loadBgm(name: BgmIds | NameMapIn<BgmIds>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放一个bgm的缓存
|
||||||
|
* @param name 要释放的bgm的id或名称
|
||||||
|
*/
|
||||||
|
freeBgm(name: BgmIds | NameMapIn<BgmIds>): void;
|
||||||
|
|
||||||
|
_loadMaterials_afterLoad(): void;
|
||||||
|
|
||||||
|
_loadAnimate(data: string): Animate;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const loader: new () => Loader;
|
516
loader.js
Normal file
516
loader.js
Normal file
@ -0,0 +1,516 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
loader.js:负责对资源的加载
|
||||||
|
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function loader () {
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._init = function () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
////// 设置加载进度条进度 //////
|
||||||
|
loader.prototype._setStartProgressVal = function (val) {
|
||||||
|
core.dom.startTopProgress.style.width = val + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
////// 设置加载进度条提示文字 //////
|
||||||
|
loader.prototype._setStartLoadTipText = function (text) {
|
||||||
|
core.dom.startTopLoadTips.innerText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._load = function (callback) {
|
||||||
|
if (main.useCompress) {
|
||||||
|
this._load_async(callback);
|
||||||
|
} else {
|
||||||
|
this._load_sync(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._load_sync = function (callback) {
|
||||||
|
this._loadAnimates_sync();
|
||||||
|
this._loadMusic_sync();
|
||||||
|
core.loader._loadMaterials_sync(function () {
|
||||||
|
core.loader._loadExtraImages_sync(function () {
|
||||||
|
core.loader._loadAutotiles_sync(function () {
|
||||||
|
core.loader._loadTilesets_sync(callback);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._load_async = function (callback) {
|
||||||
|
core.loader._setStartLoadTipText('正在加载资源文件...');
|
||||||
|
var all = {};
|
||||||
|
|
||||||
|
var _makeOnProgress = function (name) {
|
||||||
|
if (!all[name]) all[name] = { loaded: 0, total: 0, finished: false };
|
||||||
|
return function (loaded, total) {
|
||||||
|
all[name].loaded = loaded;
|
||||||
|
all[name].total = total;
|
||||||
|
var allLoaded = 0, allTotal = 0;
|
||||||
|
for (var one in all) {
|
||||||
|
allLoaded += all[one].loaded;
|
||||||
|
allTotal += all[one].total;
|
||||||
|
}
|
||||||
|
if (allTotal > 0) {
|
||||||
|
if (allLoaded == allTotal) {
|
||||||
|
core.loader._setStartLoadTipText("正在处理资源文件... 请稍候...");
|
||||||
|
} else {
|
||||||
|
core.loader._setStartLoadTipText('正在加载资源文件... ' +
|
||||||
|
core.formatSize(allLoaded) + " / " + core.formatSize(allTotal) +
|
||||||
|
" (" + (allLoaded / allTotal * 100).toFixed(2) + "%)");
|
||||||
|
}
|
||||||
|
core.loader._setStartProgressVal(allLoaded / allTotal * 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var _makeOnFinished = function (name) {
|
||||||
|
return function () {
|
||||||
|
setTimeout(function () {
|
||||||
|
all[name].finished = true;
|
||||||
|
for (var one in all) {
|
||||||
|
if (!all[one].finished) return;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loadAnimates_async(_makeOnProgress('animates'), _makeOnFinished('animates'));
|
||||||
|
this._loadMusic_async(_makeOnProgress('sounds'), _makeOnFinished('sounds'));
|
||||||
|
this._loadMaterials_async(_makeOnProgress('materials'), _makeOnFinished('materials'));
|
||||||
|
this._loadExtraImages_async(_makeOnProgress('images'), _makeOnFinished('images'));
|
||||||
|
this._loadAutotiles_async(_makeOnProgress('autotiles'), _makeOnFinished('autotiles'));
|
||||||
|
this._loadTilesets_async(_makeOnProgress('tilesets'), _makeOnFinished('tilesets'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- 加载资源文件 ------ //
|
||||||
|
|
||||||
|
loader.prototype._loadMaterials_sync = function (callback) {
|
||||||
|
this._setStartLoadTipText("正在加载资源文件...");
|
||||||
|
this.loadImages("materials", core.materials, core.material.images, function () {
|
||||||
|
core.loader._loadMaterials_afterLoad();
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadMaterials_async = function (onprogress, onfinished) {
|
||||||
|
this.loadImagesFromZip('project/materials/materials.h5data', core.materials, core.material.images, onprogress, function () {
|
||||||
|
core.loader._loadMaterials_afterLoad();
|
||||||
|
onfinished();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadMaterials_afterLoad = function () {
|
||||||
|
var images = core.splitImage(core.material.images['icons']);
|
||||||
|
for (var key in core.statusBar.icons) {
|
||||||
|
if (typeof core.statusBar.icons[key] == 'number') {
|
||||||
|
core.statusBar.icons[key] = images[core.statusBar.icons[key]];
|
||||||
|
if (core.statusBar.image[key] != null)
|
||||||
|
core.statusBar.image[key].src = core.statusBar.icons[key].src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 加载使用的图片 ------ //
|
||||||
|
|
||||||
|
loader.prototype._loadExtraImages_sync = function (callback) {
|
||||||
|
core.material.images.images = {};
|
||||||
|
this._setStartLoadTipText("正在加载图片文件...");
|
||||||
|
core.loadImages("images", core.images, core.material.images.images, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadExtraImages_async = function (onprogress, onfinished) {
|
||||||
|
core.material.images.images = {};
|
||||||
|
var images = core.images;
|
||||||
|
|
||||||
|
// Check .gif
|
||||||
|
var gifs = images.filter(function (name) {
|
||||||
|
return name.toLowerCase().endsWith('.gif');
|
||||||
|
});
|
||||||
|
images = images.filter(function (name) {
|
||||||
|
return !name.toLowerCase().endsWith('.gif');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadImagesFromZip('project/images/images.h5data', images, core.material.images.images, onprogress, onfinished);
|
||||||
|
// gif没有被压缩在zip中,延迟加载...
|
||||||
|
gifs.forEach(function (gif) {
|
||||||
|
this.loadImage("images", gif, function (id, image) {
|
||||||
|
if (image != null) {
|
||||||
|
core.material.images.images[gif] = image;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 加载自动元件 ------ //
|
||||||
|
|
||||||
|
loader.prototype._loadAutotiles_sync = function (callback) {
|
||||||
|
core.material.images.autotile = {};
|
||||||
|
var keys = Object.keys(core.material.icons.autotile);
|
||||||
|
var autotiles = {};
|
||||||
|
|
||||||
|
this._setStartLoadTipText("正在加载自动元件...");
|
||||||
|
this.loadImages("autotiles", keys, autotiles, function () {
|
||||||
|
core.loader._loadAutotiles_afterLoad(keys, autotiles);
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadAutotiles_async = function (onprogress, onfinished) {
|
||||||
|
core.material.images.autotile = {};
|
||||||
|
var keys = Object.keys(core.material.icons.autotile);
|
||||||
|
var autotiles = {};
|
||||||
|
|
||||||
|
this.loadImagesFromZip('project/autotiles/autotiles.h5data', keys, autotiles, onprogress, function () {
|
||||||
|
core.loader._loadAutotiles_afterLoad(keys, autotiles);
|
||||||
|
onfinished();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadAutotiles_afterLoad = function (keys, autotiles) {
|
||||||
|
// autotile需要保证顺序
|
||||||
|
keys.forEach(function (v) {
|
||||||
|
core.material.images.autotile[v] = autotiles[v];
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
core.maps._makeAutotileEdges();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 加载额外素材 ------ //
|
||||||
|
|
||||||
|
loader.prototype._loadTilesets_sync = function (callback) {
|
||||||
|
core.material.images.tilesets = {};
|
||||||
|
this._setStartLoadTipText("正在加载额外素材...");
|
||||||
|
this.loadImages("tilesets", core.tilesets, core.material.images.tilesets, function () {
|
||||||
|
core.loader._loadTilesets_afterLoad();
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadTilesets_async = function (onprogress, onfinished) {
|
||||||
|
core.material.images.tilesets = {};
|
||||||
|
this.loadImagesFromZip('project/tilesets/tilesets.h5data', core.tilesets, core.material.images.tilesets, onprogress, function () {
|
||||||
|
core.loader._loadTilesets_afterLoad();
|
||||||
|
onfinished();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadTilesets_afterLoad = function () {
|
||||||
|
// 检查宽高是32倍数,如果出错在控制台报错
|
||||||
|
for (var imgName in core.material.images.tilesets) {
|
||||||
|
var img = core.material.images.tilesets[imgName];
|
||||||
|
if (img.width % 32 != 0 || img.height % 32 != 0) {
|
||||||
|
console.warn("警告!" + imgName + "的宽或高不是32的倍数!");
|
||||||
|
}
|
||||||
|
if (img.width * img.height > 32 * 32 * 3000) {
|
||||||
|
console.warn("警告!" + imgName + "上的图块素材个数大于3000!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 实际加载一系列图片 ------ //
|
||||||
|
|
||||||
|
loader.prototype.loadImages = function (dir, names, toSave, callback) {
|
||||||
|
if (!names || names.length == 0) {
|
||||||
|
if (callback) callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var items = 0;
|
||||||
|
for (var i = 0; i < names.length; i++) {
|
||||||
|
this.loadImage(dir, names[i], function (id, image) {
|
||||||
|
core.loader._setStartLoadTipText('正在加载图片 ' + id + "...");
|
||||||
|
if (toSave[id] !== undefined) {
|
||||||
|
if (image != null)
|
||||||
|
toSave[id] = image;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toSave[id] = image;
|
||||||
|
items++;
|
||||||
|
core.loader._setStartProgressVal(items * (100 / names.length));
|
||||||
|
if (items == names.length) {
|
||||||
|
if (callback) callback();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype.loadImage = function (dir, imgName, callback) {
|
||||||
|
try {
|
||||||
|
var name = imgName;
|
||||||
|
if (name.indexOf(".") < 0)
|
||||||
|
name = name + ".png";
|
||||||
|
var image = new Image();
|
||||||
|
image.onload = function () {
|
||||||
|
image.setAttribute('_width', image.width);
|
||||||
|
image.setAttribute('_height', image.height);
|
||||||
|
callback(imgName, image);
|
||||||
|
}
|
||||||
|
image.onerror = function () {
|
||||||
|
callback(imgName, null);
|
||||||
|
}
|
||||||
|
image.src = 'project/' + dir + '/' + name + "?v=" + main.version;
|
||||||
|
if (name.endsWith('.gif'))
|
||||||
|
callback(imgName, null);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 从zip中加载一系列图片 ------ //
|
||||||
|
|
||||||
|
loader.prototype.loadImagesFromZip = function (url, names, toSave, onprogress, onfinished) {
|
||||||
|
if (!names || names.length == 0) {
|
||||||
|
if (onfinished) onfinished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.unzip(url + "?v=" + main.version, function (data) {
|
||||||
|
var cnt = 1;
|
||||||
|
names.forEach(function (name) {
|
||||||
|
var imgName = name;
|
||||||
|
if (imgName.indexOf('.') < 0) imgName += '.png';
|
||||||
|
if (imgName in data) {
|
||||||
|
var img = new Image();
|
||||||
|
var url = URL.createObjectURL(data[imgName]);
|
||||||
|
cnt++;
|
||||||
|
img.onload = function () {
|
||||||
|
cnt--;
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
img.setAttribute('_width', img.width);
|
||||||
|
img.setAttribute('_height', img.height);
|
||||||
|
if (cnt == 0 && onfinished) onfinished();
|
||||||
|
}
|
||||||
|
img.src = url;
|
||||||
|
toSave[name] = img;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cnt--;
|
||||||
|
if (cnt == 0 && onfinished) onfinished();
|
||||||
|
}, null, false, onprogress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 加载动画文件 ------ //
|
||||||
|
|
||||||
|
loader.prototype._loadAnimates_sync = function () {
|
||||||
|
this._setStartLoadTipText("正在加载动画文件...");
|
||||||
|
|
||||||
|
if (main.supportBunch) {
|
||||||
|
if (core.animates.length > 0) {
|
||||||
|
core.http('GET', '__all_animates__?v=' + main.version + '&id=' + core.animates.join(','), null, function (content) {
|
||||||
|
var u = content.split('@@@~~~###~~~@@@');
|
||||||
|
for (var i = 0; i < core.animates.length; ++i) {
|
||||||
|
if (u[i] != '') {
|
||||||
|
core.material.animates[core.animates[i]] = core.loader._loadAnimate(u[i]);
|
||||||
|
} else {
|
||||||
|
console.error('无法找到动画文件' + core.animates[i] + '!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "text/plain; charset=x-user-defined");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.animates.forEach(function (t) {
|
||||||
|
core.http('GET', 'project/animates/' + t + ".animate?v=" + main.version, null, function (content) {
|
||||||
|
core.material.animates[t] = core.loader._loadAnimate(content);
|
||||||
|
}, function (e) {
|
||||||
|
console.error(e);
|
||||||
|
core.material.animates[t] = null;
|
||||||
|
}, "text/plain; charset=x-user-defined")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadAnimates_async = function (onprogress, onfinished) {
|
||||||
|
core.unzip('project/animates/animates.h5data?v=' + main.version, function (animates) {
|
||||||
|
for (var name in animates) {
|
||||||
|
if (name.endsWith(".animate")) {
|
||||||
|
var t = name.substring(0, name.length - 8);
|
||||||
|
if (core.animates.indexOf(t) >= 0)
|
||||||
|
core.material.animates[t] = core.loader._loadAnimate(animates[name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onfinished();
|
||||||
|
}, null, true, onprogress);
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadAnimate = function (content) {
|
||||||
|
try {
|
||||||
|
content = JSON.parse(content);
|
||||||
|
var data = {};
|
||||||
|
data.ratio = content.ratio;
|
||||||
|
data.se = content.se;
|
||||||
|
data.pitch = content.pitch;
|
||||||
|
data.images = [];
|
||||||
|
content.bitmaps.forEach(function (t2) {
|
||||||
|
if (!t2) {
|
||||||
|
data.images.push(null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
var image = new Image();
|
||||||
|
image.src = t2;
|
||||||
|
data.images.push(image);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
data.images.push(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
data.frame = content.frame_max;
|
||||||
|
data.frames = [];
|
||||||
|
content.frames.forEach(function (t2) {
|
||||||
|
var info = [];
|
||||||
|
t2.forEach(function (t3) {
|
||||||
|
info.push({
|
||||||
|
'index': t3[0],
|
||||||
|
'x': t3[1],
|
||||||
|
'y': t3[2],
|
||||||
|
'zoom': t3[3],
|
||||||
|
'opacity': t3[4],
|
||||||
|
'mirror': t3[5] || 0,
|
||||||
|
'angle': t3[6] || 0,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
data.frames.push(info);
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 加载音乐和音效 ------ //
|
||||||
|
|
||||||
|
loader.prototype._loadMusic_sync = function () {
|
||||||
|
this._setStartLoadTipText("正在加载音效文件...");
|
||||||
|
core.bgms.forEach(function (t) {
|
||||||
|
core.loader.loadOneMusic(t);
|
||||||
|
});
|
||||||
|
core.sounds.forEach(function (t) {
|
||||||
|
core.loader.loadOneSound(t);
|
||||||
|
});
|
||||||
|
// 直接开始播放
|
||||||
|
core.playBgm(main.startBgm);
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadMusic_async = function (onprogress, onfinished) {
|
||||||
|
core.bgms.forEach(function (t) {
|
||||||
|
core.loader.loadOneMusic(t);
|
||||||
|
});
|
||||||
|
core.unzip('project/sounds/sounds.h5data?v=' + main.version, function (data) {
|
||||||
|
// 延迟解析
|
||||||
|
setTimeout(function () {
|
||||||
|
for (var name in data) {
|
||||||
|
if (core.sounds.indexOf(name) >= 0) {
|
||||||
|
core.loader._loadOneSound_decodeData(name, data[name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onfinished();
|
||||||
|
}, null, false, onprogress);
|
||||||
|
|
||||||
|
// 直接开始播放
|
||||||
|
core.playBgm(main.startBgm);
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype.loadOneMusic = function (name) {
|
||||||
|
var music = new Audio();
|
||||||
|
music.preload = 'none';
|
||||||
|
if (main.bgmRemote) music.src = main.bgmRemoteRoot + core.firstData.name + '/' + name;
|
||||||
|
else music.src = 'project/bgms/' + name;
|
||||||
|
music.loop = 'loop';
|
||||||
|
core.material.bgms[name] = music;
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype.loadOneSound = function (name) {
|
||||||
|
core.http('GET', 'project/sounds/' + name + "?v=" + main.version, null, function (data) {
|
||||||
|
core.loader._loadOneSound_decodeData(name, data);
|
||||||
|
}, function (e) {
|
||||||
|
console.error(e);
|
||||||
|
core.material.sounds[name] = null;
|
||||||
|
}, null, 'arraybuffer');
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._loadOneSound_decodeData = function (name, data) {
|
||||||
|
if (data instanceof Blob) {
|
||||||
|
var blobReader = new zip.BlobReader(data);
|
||||||
|
blobReader.init(function () {
|
||||||
|
blobReader.readUint8Array(0, blobReader.size, function (uint8) {
|
||||||
|
core.loader._loadOneSound_decodeData(name, uint8.buffer);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
core.musicStatus.audioContext.decodeAudioData(data, function (buffer) {
|
||||||
|
core.material.sounds[name] = buffer;
|
||||||
|
}, function (e) {
|
||||||
|
console.error(e);
|
||||||
|
core.material.sounds[name] = null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
core.material.sounds[name] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype.loadBgm = function (name) {
|
||||||
|
name = core.getMappedName(name);
|
||||||
|
if (!core.material.bgms[name]) return;
|
||||||
|
// 如果没开启音乐,则不预加载
|
||||||
|
if (!core.musicStatus.bgmStatus) return;
|
||||||
|
// 是否已经预加载过
|
||||||
|
var index = core.musicStatus.cachedBgms.indexOf(name);
|
||||||
|
if (index >= 0) {
|
||||||
|
core.musicStatus.cachedBgms.splice(index, 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 预加载BGM
|
||||||
|
this._preloadBgm(core.material.bgms[name]);
|
||||||
|
// core.material.bgms[name].load();
|
||||||
|
// 清理尾巴
|
||||||
|
if (core.musicStatus.cachedBgms.length == core.musicStatus.cachedBgmCount) {
|
||||||
|
this.freeBgm(core.musicStatus.cachedBgms.pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 移动到缓存最前方
|
||||||
|
core.musicStatus.cachedBgms.unshift(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype._preloadBgm = function (bgm) {
|
||||||
|
bgm.volume = 0;
|
||||||
|
bgm.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.prototype.freeBgm = function (name) {
|
||||||
|
name = core.getMappedName(name);
|
||||||
|
if (!core.material.bgms[name]) return;
|
||||||
|
// 从cachedBgms中删除
|
||||||
|
core.musicStatus.cachedBgms = core.musicStatus.cachedBgms.filter(function (t) {
|
||||||
|
return t != name;
|
||||||
|
});
|
||||||
|
// 清掉缓存
|
||||||
|
core.material.bgms[name].removeAttribute("src");
|
||||||
|
core.material.bgms[name].load();
|
||||||
|
core.material.bgms[name] = null;
|
||||||
|
if (name == core.musicStatus.playingBgm) {
|
||||||
|
core.musicStatus.playingBgm = null;
|
||||||
|
}
|
||||||
|
// 三秒后重新加载
|
||||||
|
setTimeout(function () {
|
||||||
|
core.loader.loadOneMusic(name);
|
||||||
|
}, 3000);
|
||||||
|
}
|
110
localSave.js
Normal file
110
localSave.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* 离线游戏使用本地存储扩展。
|
||||||
|
* 开启本拓展后,将会把所有存档存至 _saves 目录下。
|
||||||
|
* 需配合样板V2.8.2+使用
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
// 将这一行改成 false 可以禁用本拓展
|
||||||
|
var __enabled = true;
|
||||||
|
|
||||||
|
if (window.jsinterface || !window.fs || !__enabled) return;
|
||||||
|
|
||||||
|
function rewrite() {
|
||||||
|
core.utils._setLocalForage_set = function (name, str, callback) {
|
||||||
|
var data = LZString.compressToBase64(str);
|
||||||
|
core.saves.cache[name] = data;
|
||||||
|
fs.writeFile('_saves/' + name, data, 'utf-8', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.utils._getLocalForage_get = function (name, callback) {
|
||||||
|
fs.readFile('_saves/' + name, 'utf-8', function (err, data) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
callback(null, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
core.utils.decompress = function (data) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(LZString.decompressFromBase64(data))
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.utils._removeLocalForage_remove = function (name, callback) {
|
||||||
|
fs.deleteFile('_saves/' + name, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.utils.clearLocalForage = function (callback) {
|
||||||
|
fs.deleteFile('_saves', function () {
|
||||||
|
fs.mkdir('_saves', callback);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
core.utils.iterateLocalForage = function (iter, callback) {
|
||||||
|
fs.readdir('_saves', function (err, data) {
|
||||||
|
if (err) callback(err);
|
||||||
|
else {
|
||||||
|
data.forEach(function (one) {
|
||||||
|
iter(null, one, null);
|
||||||
|
});
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
core.utils.keysLocalForage = function (callback) {
|
||||||
|
fs.readdir('_saves', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.utils.lengthLocalForage = function (callback) {
|
||||||
|
fs.readdir('_saves', function (err, data) {
|
||||||
|
if (err) callback(err);
|
||||||
|
else callback(null, data.length);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var _export = function () {
|
||||||
|
var toExport = [];
|
||||||
|
|
||||||
|
localforage.iterate(function (value, key, n) {
|
||||||
|
if (value == null || !key.startsWith(core.firstData.name)) return;
|
||||||
|
value = core.decompress(value);
|
||||||
|
if (value == null) return;
|
||||||
|
var str = JSON.stringify(value).replace(/[\u007F-\uFFFF]/g, function (chr) {
|
||||||
|
return "\\u" + ("0000" + chr.charCodeAt(0).toString(16)).substr(-4)
|
||||||
|
});
|
||||||
|
str = LZString.compressToBase64(str);
|
||||||
|
toExport.push(key);
|
||||||
|
core.saves.cache[key] = str;
|
||||||
|
fs.writeFile('_saves/' + key, str, 'utf-8', function () {});
|
||||||
|
}, function () {
|
||||||
|
if (toExport.length > 0) {
|
||||||
|
alert('提示!本塔已开启存档本地化!原始存档已全部导出至 _saves/ 目录下。');
|
||||||
|
}
|
||||||
|
fs.writeFile('_saves/.exported', '1', 'utf-8', function () {});
|
||||||
|
rewrite();
|
||||||
|
core.control.getSaveIndexes(function (indexes) { core.saves.ids = indexes; });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdir('_saves', function (err) {
|
||||||
|
if (err) return;
|
||||||
|
|
||||||
|
fs.readFile('_saves/.exported', 'utf-8', function(err, data) {
|
||||||
|
if (!err && data) {
|
||||||
|
rewrite();
|
||||||
|
core.control.getSaveIndexes(function (indexes) { core.saves.ids = indexes; });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_export();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
441
personalization.md
Normal file
441
personalization.md
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
# 个性化
|
||||||
|
|
||||||
|
?> 在这一节中,让我们来了解更多对样板个性化的修改
|
||||||
|
|
||||||
|
有时候只靠样板本身可能是不够的。我们需要一些个性化、自定义的素材,道具效果,怪物属性,等等。
|
||||||
|
|
||||||
|
## 图层的说明
|
||||||
|
|
||||||
|
HTML5魔塔是使用画布(canvas)来绘制,存在若干个图层,它们之间有一个覆盖关系,后面的图层将覆盖前面的图层。
|
||||||
|
|
||||||
|
所有图层从低往高依次如下:(加[B]的代表该层是大地图,[D]代表由系统按需动态创建,z-index代表该层的纵向高度)
|
||||||
|
|
||||||
|
- bg**[B]**:背景层;绘制背景图层素材bgmap,和背景贴图 (z-index: 10)
|
||||||
|
- event**[B]**:事件层;所有事件(道具、墙壁、NPC、怪物等)都绘制在这一层进行处理 (z-index: 30)
|
||||||
|
- hero:勇士层;主要用来绘制勇士 (z-index: 40)
|
||||||
|
- event2**[B]**:事件2层;本层主要用来绘制48x32的图片素材的上半部分(避免和勇士错位) (z-index: 50)
|
||||||
|
- fg**[B]**:前景层;绘制前景图层素材fgmap,和前景贴图 (z-index: 60)
|
||||||
|
- damage**[B]**:显伤层;主要用来绘制怪物显伤和领域显伤 (z-index: 65)
|
||||||
|
- animate:动画层;主要用来绘制动画。 (z-index: 70)
|
||||||
|
- weather**[D]**:天气层;主要用来绘制天气(雨/雪/雾) (z-index: 80)
|
||||||
|
- route**[D]**:路线层;主要用来绘制勇士的行走路线图。 (z-index: 95)
|
||||||
|
- curtain:色调层;用来控制当前楼层的画面色调 (z-index: 125)
|
||||||
|
- image1\~50**[D]**:图片层;用来绘制图片等操作。(z-index: 100+code, 101~150)
|
||||||
|
- uievent**[D]**:自定义UI绘制层;用来进行自定义UI绘制等操作。(z-index:135,可以通过事件设置该值)
|
||||||
|
- ui:UI层;用来绘制一切UI窗口,如剧情文本、怪物手册、楼传器、系统菜单等等 (z-index: 140)
|
||||||
|
- data:数据层;用来绘制一些顶层的或更新比较快的数据,如左上角的提示,战斗界面中数据的变化等等。 (z-index: 170)
|
||||||
|
|
||||||
|
请注意:显示图片事件将自动创建一个图片层,z-index是100+图片编号。
|
||||||
|
|
||||||
|
而,色调层的z-index是25,ui层的z-index是140;因此,图片编号在1~24的将被色调层遮挡,25~40的将被ui层遮挡,41~50的将遮挡UI层。
|
||||||
|
|
||||||
|
uievent层为自定义UI绘制所在的层,其z值初始是135,可以通过事件设置;自定义绘制的闪烁光标所在层的z值永远比该值大1。
|
||||||
|
|
||||||
|
### 动态创建canvas
|
||||||
|
|
||||||
|
从V2.5.3开始,可以在H5样板中任意动态创建canvas并进行使用。
|
||||||
|
|
||||||
|
使用`core.createCanvas(name, x, y, w, h, z)`来动态创建一个画布。
|
||||||
|
|
||||||
|
其中name为动态canvas名称,x,y,w,h为创建的画布相对窗口左上角的像素坐标和长宽,z为画布的纵向高度。
|
||||||
|
|
||||||
|
例如:`core.createCanvas('test', 10, 20, 100, 200, 74)` 创建了一个名为test的画布,其左上角相对窗口的像素坐标为(10,20),宽100高200,纵向高度74(在动画层和天气层之间)。
|
||||||
|
|
||||||
|
该函数会返回画布的context,也可以通过 `core.dymCanvas[name]` 来获得;例如 `core.dymCanvas.test` 就是我们上面创建的画布的context,然后进行操作。
|
||||||
|
|
||||||
|
也可以简单的使用`core.fillText()`, `core.fillRect()`, `core.strokeRect()`等等对画布进行任意绘制。
|
||||||
|
|
||||||
|
``` js
|
||||||
|
core.fillText('test', '这是一段文字', 10, 30, '#FF0000', '16px Verdana'); // 绘制一段文本
|
||||||
|
```
|
||||||
|
|
||||||
|
使用 `core.deleteCanvas(name)` 删除一个动态创建的画布,例如 `core.deleteCanvas('test')`。
|
||||||
|
|
||||||
|
`core.deleteAllCanvas()`可以删除所有动态创建的画布,`core.relocateCanvas(name, x, y)`和`core.resizeCanvas(name, x, y)`可以对画布的位置和大小进行改变。
|
||||||
|
|
||||||
|
更多详细API请参见[API列表](api)。
|
||||||
|
|
||||||
|
## 单点多事件
|
||||||
|
|
||||||
|
带有独立开关的商人算是一个最为简单的单点多事件的例子了(提供在了事件编辑器左侧的“常用事件模板”),单点多事件最常用的指令就是“转变图块”(或关门)和“条件分歧”。下面以几个具体例子来进行详细说明:
|
||||||
|
1. 打怪变成门或道具、开门变成怪或道具:怪物、门、道具都是系统触发器,直接利用afterXxx事件。如:
|
||||||
|
* 战后事件:关门`yellowDoor`(原地关上了一扇黄门)
|
||||||
|
* 战后事件:转变图块为`yellowKey`(怪物掉落了黄钥匙)
|
||||||
|
* 开门后事件:转变图块为`greenSlime`或`yellowKey`(开门变成了史莱姆或黄钥匙)
|
||||||
|
* 打怪变成别的怪、开门关上别的门、门和怪来回变的,用独立开关计数。
|
||||||
|
* 如有批量需求,请使用“脚本编辑 - `afterXxx`函数或怪物的(批量)战前/战后事件。
|
||||||
|
2. 打怪/开门/捡道具后变成传送点或npc:
|
||||||
|
* 这时的传送点就不能用“楼梯、传送门”事件了,而应该用只有一条“场景切换”指令的普通事件。
|
||||||
|
* 此事件不勾选“覆盖触发器”,这样怪物、门和道具的系统触发器会触发。
|
||||||
|
* 在对应的`afterXxx`事件中“转变图块为”传送点或npc,淡入效果更佳哦。
|
||||||
|
* 请注意,因为道具是可通行的,如果勇士走到道具上,就会和npc重合。
|
||||||
|
3. (懒人的选择)利用`图块类别:x,y`和`图块ID:x,y`等值块集中处理:
|
||||||
|
* 如果您实在搞不清楚“覆盖触发器”和“通行状态”这两项,那就干脆勾选前者并把后者设为不可通行,本希望可以通行的场合就得用“无视地形移动勇士”指令前进一步。
|
||||||
|
* 用自动事件去转变图块,然后利用上述两个值块判定当前图块的状态等,再用“强制战斗”、“开门(需要钥匙)”和“增加道具”分别处理吧。
|
||||||
|
|
||||||
|
## 并行脚本:即时制的希望?
|
||||||
|
|
||||||
|
如前所述,自动事件可以让您不用考虑可能导致各值块发生变化的缘由,而是简单地在刷新状态栏时检测这些值块是否满足某些特定的关系。
|
||||||
|
|
||||||
|
然而,自动事件依然是通过插入到当前待执行指令队列的开头,或追加到队列的结尾来执行的。换言之,它占用的是事件流本身的线程资源。
|
||||||
|
|
||||||
|
那形如bgs、bgv、飘云飘雾、地图背景旋转等即使玩家挂机也会照常执行的特效,该怎么办呢?
|
||||||
|
|
||||||
|
js语言其实是没有多线程的,但我们可以写一些浏览器帧刷新时执行的脚本,也就是“并行脚本”。
|
||||||
|
|
||||||
|
并行脚本分为两种,全塔并行和楼层并行。前者在“脚本编辑—并行脚本”,后者在楼层属性。一般来说,当您有多个楼层需要执行相同的并行脚本时,建议写在前者并通过对`core.status.floorId`的范围进行判定(注意判空!)来决定具体执行的内容。
|
||||||
|
|
||||||
|
并行脚本将被系统反复执行,执行的时机是“浏览器帧刷新”,换言之相邻两次执行的间隔取决于浏览器或设备的性能,上限为60fps即每秒60次。
|
||||||
|
|
||||||
|
如果有一个bgs是1秒长的心跳声,我们把它注册到了全塔属性的音效中。假设这个bgs要被用于MT0层,那么我们在MT0层的“楼层属性——并行脚本”中写这样的代码:
|
||||||
|
|
||||||
|
``` js
|
||||||
|
if (core.hasFlag('frame')) {
|
||||||
|
// 剧情事件中将“变量:frame”设为正整数来开启bgs,设为0来关闭
|
||||||
|
core.status.hero.flags.frame %= 60000; // 防止挂机太久导致溢出
|
||||||
|
if (core.getFlag('frame', 0) % 60 === 0)
|
||||||
|
core.playSound('heartBeat.mp3'); // 每60帧即1秒播放一次,还可以指定音调。
|
||||||
|
core.status.hero.flags.frame++; // 帧数加1
|
||||||
|
} // 其他特效也是一样的用法。
|
||||||
|
```
|
||||||
|
|
||||||
|
在并行脚本(全塔或楼层)里可以使用`timestamp`作为参数,表示从游戏开始到当前总共过了多少毫秒。
|
||||||
|
|
||||||
|
## 覆盖楼传事件
|
||||||
|
|
||||||
|
对于特殊的塔,我们可以考虑修改楼传事件来完成一些特殊的要求,比如镜子可以按楼传来切换表里。
|
||||||
|
|
||||||
|
要修改楼传事件,需要进行如下两步:
|
||||||
|
|
||||||
|
1. 重写楼传的点击事件。在插件中对`core.control.useFly`进行重写。详细代码参见[重写点击楼传事件](script#重写点击楼传事件)。
|
||||||
|
2. 修改楼传的使用事件。和其他永久道具一样,在地图编辑器的图块属性中修改楼传的`useItemEvent`(或`useItemEffect`)和`canUseItemEffect`两个内容。例如:
|
||||||
|
``` js
|
||||||
|
"useItemEvent": "[...]" // 执行某段自定义事件
|
||||||
|
"canUseItemEffect": "true" // 任何时候可用
|
||||||
|
```
|
||||||
|
3. 将全塔属性里的`楼梯边楼传`的勾去掉。
|
||||||
|
|
||||||
|
除了覆盖楼传事件外,对于快捷商店、虚拟键盘等等也可以进行覆盖,只不过是仿照上述代码重写对应的函数(`openQuickShop`,`openKeyBoard`)即可。
|
||||||
|
|
||||||
|
## 自定义快捷键
|
||||||
|
|
||||||
|
如果需要绑定某个快捷键为处理一段事件,也是可行的。
|
||||||
|
|
||||||
|
要修改按键,我们可以在脚本编辑的`onKeyUp`进行处理:
|
||||||
|
|
||||||
|
我们设置一个快捷键进行绑定,比如 `Y`,其 `keycode` 是 `89` 。
|
||||||
|
(大键盘数字键 `0-9` 的 `keycode` 为 `48-57, A-Z` 键的 `keycode` 为 `65-90` ,其他键的 `keycode` 搜一下就能得到)
|
||||||
|
|
||||||
|
然后在脚本编辑的`onKeyUp`函数的`switch`中进行处理。
|
||||||
|
|
||||||
|
``` js
|
||||||
|
case 89: // 使用该按键的keyCode,比如Y键就是89
|
||||||
|
// 还可以再判定altKey是否被按下,即 if (altKey) { ...
|
||||||
|
|
||||||
|
// ... 在这里写你要执行脚本
|
||||||
|
// **强烈建议所有新增的自定义快捷键均能给个对应的道具可点击,以方便手机端的行为**
|
||||||
|
if (core.hasItem('...')) {
|
||||||
|
core.status.route.push("key:0"); // 记录按键到录像中
|
||||||
|
core.useItem('...', true); // 第二个参数true代表该次使用道具是被按键触发的,使用过程不计入录像
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
```
|
||||||
|
强烈建议所有新增的自定义非数字快捷键均给个对应的永久道具可点击,以方便手机端的行为。
|
||||||
|
|
||||||
|
使用`core.status.route.push("key:"+keyCode)`可以将这次按键记录在录像中。
|
||||||
|
|
||||||
|
!> 如果记录了按键,且使用道具的话,需要将useItem的第二个参数设为true,避免重复记录!
|
||||||
|
|
||||||
|
可以使用`altKey`来判断Alt键是否被同时按下(V2.8起,手机端的Alt键以粘滞键方式提供)。
|
||||||
|
|
||||||
|
## 左手模式
|
||||||
|
|
||||||
|
V2.7.3起,样板面向玩家提供了“左手模式”,可在ESC菜单中开启。开启后,wsad将用于勇士移动,ijkl将代替原本wsad的功能(存读档等)。
|
||||||
|
|
||||||
|
## 插件系统
|
||||||
|
|
||||||
|
在H5中,提供了“插件”系统。在V2.6中提供了一个插件下拉框,用户可以自行创建和写插件。
|
||||||
|
|
||||||
|
在插件编写的过程中,我们可以使用任何[常见API](api)里面的代码调用;也可以通过`core.insertAction`来插入自定义事件执行。
|
||||||
|
|
||||||
|
下面是一个很简单的例子,我编写一个插件函数,其效果是让勇士生命值变成原来的x倍,并令面前的图块消失。
|
||||||
|
|
||||||
|
``` js
|
||||||
|
this.myfunc = function(x) {
|
||||||
|
core.status.hero.hp *= x; // 勇士生命翻若干倍
|
||||||
|
core.insertAction([ // 自定义事件:令面前的图块消失。
|
||||||
|
{"type": "setValue", "name": "flag:x", "value": "core.nextX()"},
|
||||||
|
{"type": "setValue", "name": "flag:y", "value": "core.nextY()"},
|
||||||
|
{"type": "hide", "loc": ["flag:x", "flag:y"]}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后比如我们在某个道具的使用效果 `useItemEffect` 中写 `core.plugin.myfunc(2)` 即可调用此插件函数。也可以在战后事件或自定义脚本等位置来写。
|
||||||
|
|
||||||
|
网站上也提供了一个[插件库](https://h5mota.com/plugins/),欢迎大家把自己写的插件进行共享。
|
||||||
|
|
||||||
|
从V2.6开始,在插件中用`this.xxx`定义的函数将会被转发到core中。例如上述的`myfunc`除了`core.plugin.myfunc`外也可以直接`core.myfunc`调用。
|
||||||
|
|
||||||
|
详见[函数的转发](script#函数的转发)。
|
||||||
|
|
||||||
|
## 手机端按键模式
|
||||||
|
|
||||||
|
从V2.5.3以后,我们可以给手机端增加按键了,这样将非常有利于技能的释放。
|
||||||
|
|
||||||
|
用户在竖屏模式下点击难度标签,就会在工具栏按钮和快捷键模式之间进行切换。
|
||||||
|
|
||||||
|
切换到快捷键模式后,可以点1-7,分别等价于在电脑端按键1-7。
|
||||||
|
|
||||||
|
V2.8起,点击最后的A键则相当于电脑端按下/松开Alt键(即粘滞键),可以用来快捷换装。
|
||||||
|
|
||||||
|
可以在脚本编辑的`onKeyUp`中定义每个快捷键的使用效果,比如使用道具或释放技能等。
|
||||||
|
|
||||||
|
默认值下,1使用破,2使用炸,3使用飞,4使用其他存在的道具,5读取上一个自动存档,6读取下一个自动存档,7轻按。可以相应修改成自己的效果。
|
||||||
|
|
||||||
|
也可以替换icons.png中的对应图标,以及修改main.js中`main.statusBar.image.btn1~8`中的onclick事件来自定义按钮和对应按键。
|
||||||
|
|
||||||
|
非竖屏模式下、回放录像中、隐藏状态栏中,将不允许进行切换。
|
||||||
|
|
||||||
|
## 自绘状态栏
|
||||||
|
|
||||||
|
从V2.5.3开始允许自绘状态栏。要自绘状态栏,则应该打开全塔属性中的`statusCanvas`开关。
|
||||||
|
|
||||||
|
自绘模式下,全塔属性中的`statusCanvasRowsOnMobile`将控制竖屏模式下的状态栏行数(下面的`rows`)。
|
||||||
|
|
||||||
|
开启自绘模式后,可以在脚本编辑的`drawStatusBar`中自行进行绘制。
|
||||||
|
|
||||||
|
横屏模式下的状态栏为`129x416`(15x15则是`149x480`);竖屏模式下的状态栏为`416*(32*rows+9)`(15x15是480)。
|
||||||
|
|
||||||
|
具体可详见脚本编辑的`drawStatusBar`函数。
|
||||||
|
|
||||||
|
自绘状态栏开启后,金币图标将失去打开快捷商店的功能,您可以修改脚本编辑的 `onStatusBarCLick` 函数来适配。
|
||||||
|
|
||||||
|
## 自定义状态栏的显示项
|
||||||
|
|
||||||
|
在V2.2以后,我们可以自定义状态栏背景图(全塔属性 - 主样式)等等。
|
||||||
|
|
||||||
|
但是,如果我们还想新增其他项目的显示,比如攻速或者暴击,该怎么办?
|
||||||
|
|
||||||
|
我们可以[自绘状态栏](#自绘状态栏),或者采用下面两个方式之一来新增。
|
||||||
|
|
||||||
|
### 利用已有项目
|
||||||
|
|
||||||
|
一个最为简单的方式是,直接利用已有项目。
|
||||||
|
|
||||||
|
例如,如果本塔中没有技能栏,则可以使用技能栏所对应的显示项。
|
||||||
|
|
||||||
|
1. 覆盖project/icons.png中技能的图标
|
||||||
|
2. 打开全塔属性的enableSkill开关
|
||||||
|
3. 在脚本编辑-updateStatusBar中可以直接替换技能栏的显示内容
|
||||||
|
|
||||||
|
```
|
||||||
|
// 替换成你想显示的内容,比如你定义的一个flag:abc。
|
||||||
|
core.setStatusBarInnerHTML('skill', core.getFlag("abc", 0));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 额外新增新项目
|
||||||
|
|
||||||
|
如果是在需要给状态栏新定义项目,则需要进行如下几个操作:
|
||||||
|
|
||||||
|
1. 定义ID;比如攻速我就定义speed,暴击可以简单的定义baoji;你也可以定义其他的ID,但是不能和已有的重复。这里以speed为例。
|
||||||
|
2. 在index.html的statusBar中,进行该状态栏项的定义。仿照其他几项,插在其应当显示的位置,注意替换掉相应的ID。
|
||||||
|
``` html
|
||||||
|
<div class="status" id="speedCol">
|
||||||
|
<img id="img-speed">
|
||||||
|
<p class='statusLabel' id='speed'></p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
4. 使用便捷PS工具,打开project/icons.png,新增一行并将魔力的图标P上去;记下其索引比如37(从0开始数)。
|
||||||
|
5. 在main.js的this.statusBar中增加图片、图标和内容的定义。
|
||||||
|
``` js
|
||||||
|
this.statusBar = {
|
||||||
|
'images': {
|
||||||
|
// ...其他略
|
||||||
|
'speed': document.getElementById("img-speed"), // 图片的定义
|
||||||
|
},
|
||||||
|
'icons': {
|
||||||
|
// ...其他略
|
||||||
|
'speed': 37, // 图标的定义,这里对应的是icons.png中的索引
|
||||||
|
},
|
||||||
|
// ...其他略
|
||||||
|
'speed': document.getElementById('speed'), // 显示内容(数据)的定义
|
||||||
|
}
|
||||||
|
```
|
||||||
|
6. 显示内容的设置。在脚本编辑的updateStatusBar函数,可以对该状态栏显示内容进行设置,下面是几个例子。
|
||||||
|
``` js
|
||||||
|
// 设置其显示内容为status:speed值;需要在project/data.js中firstData的hero那里新增初始值`"speed": 0`。
|
||||||
|
core.setStatusBarInnerHTML('speed', core.getStatus('speed'));
|
||||||
|
// 设置其显示内容为flag:speed值,无需额外进行定义。
|
||||||
|
core.setStatusBarInnerHTML('speed', core.getFlag('speed', 0));
|
||||||
|
```
|
||||||
|
总的来说不建议这样做,因为`main.js`和各种html文件不在`project`文件夹,会导致随样板更新迁移接档变得困难。
|
||||||
|
|
||||||
|
## 技能塔的支持
|
||||||
|
|
||||||
|
从V2.5开始,内置了"二倍斩"技能,可以仿照其制作自己的技能。要支持技能塔,可能需要如下几个方面:
|
||||||
|
|
||||||
|
- 魔力(和上限)的添加;技能的定义
|
||||||
|
- 状态栏的显示
|
||||||
|
- 技能的触发(按键与录像问题)
|
||||||
|
- 技能的效果
|
||||||
|
|
||||||
|
### 魔力的定义添加;技能的定义
|
||||||
|
|
||||||
|
从V2.5开始,提供了 `status:mana` 选项,可以直接代表当前魔力值。可以在全塔属性勾选来启用它。
|
||||||
|
|
||||||
|
如果需要魔力上限,则可以使用 `status:manaMax` 来表示当前的魔力最大值,负数表示没有上限。
|
||||||
|
|
||||||
|
同时,我们可以使用flag:skill表示当前开启的技能编号,flag:skillName表示当前开启的技能名称。
|
||||||
|
|
||||||
|
如果flag:skill不为0,则代表当前处于某个技能开启状态,且状态栏显示flag:skillName值。伤害计算函数中只需要对flag:skill进行处理即可。
|
||||||
|
|
||||||
|
### 状态栏的显示
|
||||||
|
|
||||||
|
从V2.5开始,魔力值和技能名的状态栏项目已经被添加,可以直接使用。
|
||||||
|
|
||||||
|
在脚本编辑-updateStatusBar中,可以对状态栏显示内容进行修改。带有上限时如果一行放不下,可以想办法像生命一样拆成两行。
|
||||||
|
|
||||||
|
``` js
|
||||||
|
// 设置魔力值; status:manamax 只有在非负时才生效。
|
||||||
|
if (core.status.hero.manamax != null && core.status.hero.manamax >= 0) {
|
||||||
|
core.status.hero.mana = Math.min(core.status.hero.mana, core.status.hero.manamax);
|
||||||
|
core.setStatusBarInnerHTML('mana', core.status.hero.mana + "/" + core.status.hero.manamax);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
core.setStatusBarInnerHTML("mana", core.status.hero.mana);
|
||||||
|
}
|
||||||
|
// 设置技能栏
|
||||||
|
// 可以用flag:skill表示当前开启的技能类型,flag:skillName显示技能名
|
||||||
|
core.setStatusBarInnerHTML('skill', core.getFlag('skillName', '无'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 技能的触发
|
||||||
|
|
||||||
|
#### 使用道具作为技能
|
||||||
|
|
||||||
|
由于手机端按字母键不方便,虚拟键盘不好用,因此强烈推荐**给每个字母键技能设置一个道具,在道具栏点击使用!**
|
||||||
|
|
||||||
|
下面是个很简单的例子,要制作一个技能"二倍斩"。
|
||||||
|
|
||||||
|
我们可以设置一个道具,其cls是`constants`(永久道具),ID比如是`skill1`。
|
||||||
|
|
||||||
|
该道具的使用判定`canUseItemEffect`是`true`(表示任意时候都可使用),使用效果`useItemEffect`是:
|
||||||
|
|
||||||
|
``` js
|
||||||
|
if (core.getFlag('skill', 0)==0) { // 判断当前是否已经开了技能
|
||||||
|
if (core.getStatus('mana')>=5) { // 这里要写当前能否开技能的条件判断,比如魔力值至少要多少
|
||||||
|
core.setFlag('skill', 1); // 开技能1
|
||||||
|
core.setFlag('skillName', '二倍斩'); // 设置技能名
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
core.drawTip("魔力不足,无法开技能");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // 关闭技能
|
||||||
|
core.setFlag('skill', 0); // 关闭技能状态
|
||||||
|
core.setFlag('skillName', '无');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
简单的说,用flag:skill判断当前开启的技能,flag:skillName表示该技能名。(可在状态栏显示)
|
||||||
|
|
||||||
|
该(技能)道具任何时候都可被使用;使用时,判断当前是否开启了技能,如果开启则关闭,没开则再判断是否允许开启(魔力值够不够等)。
|
||||||
|
|
||||||
|
V2.6.6起,道具提供了`useItemEvent`项,建议使用它而不是上面的脚本。'-
|
||||||
|
|
||||||
|
#### 快捷键触发技能
|
||||||
|
|
||||||
|
在PC端,我们还可以按键触发技能。
|
||||||
|
|
||||||
|
在技能的道具定义完毕后,再将该道具绑定到一个快捷键上。有关绑定按键请参见[自定义快捷键](#自定义快捷键)。
|
||||||
|
|
||||||
|
下面是一个很简单的例子,当勇士按下 `F` 后,触发我们上面定义的二倍斩技能。
|
||||||
|
``` js
|
||||||
|
case 70: // F:开启技能“二倍斩”
|
||||||
|
// 是否拥有“二倍斩”这个技能道具
|
||||||
|
if (core.hasItem('skill1')) {
|
||||||
|
core.status.route.push("key:70");
|
||||||
|
core.useItem('skill1', true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
```
|
||||||
|
在勇士处于停止的条件下,按下 `F` 键时,判断技能的道具是否存在,如果存在再使用它。
|
||||||
|
|
||||||
|
!> 由于现在手机端存在拓展键盘,也强烈建议直接覆盖1-7的使用效果,这样手机端使用也非常方便。
|
||||||
|
|
||||||
|
### 技能的效果
|
||||||
|
|
||||||
|
最后一点就是技能的效果;其实到了这里就和RM差不多了。
|
||||||
|
|
||||||
|
技能的效果要分的话有地图类技能,战斗效果类技能,后续影响类技能什么的,这里只介绍最简单的战斗效果类技能。
|
||||||
|
|
||||||
|
其他的几类技能根据需求可能更为麻烦,有兴趣可自行进行研究。
|
||||||
|
|
||||||
|
战斗效果内技能要改两个地方:战斗伤害计算,战后扣除魔力值。
|
||||||
|
|
||||||
|
战斗伤害计算在脚本编辑的`getDamageInfo`函数,有需求直接修改这个函数即可。
|
||||||
|
|
||||||
|
战后扣除魔力值则在脚本编辑的`afterBattle`中进行编辑即可。
|
||||||
|
|
||||||
|
举个例子,我设置一个勇士的技能:二倍斩,开启技能消耗5点魔力,下一场战斗攻击力翻倍。
|
||||||
|
|
||||||
|
那么,直接在脚本编辑的`getDamageInfo`中进行判断:
|
||||||
|
|
||||||
|
``` js
|
||||||
|
if (core.getFlag('skill', 0)==1) { // 开启了技能1
|
||||||
|
hero_atk *= 2; // 计算时攻击力翻倍
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在脚本编辑的`afterBattle`中进行魔力值的扣除:
|
||||||
|
|
||||||
|
``` js
|
||||||
|
// 战后的技能处理,比如扣除魔力值
|
||||||
|
if (core.flags.statusBarItems.indexOf('enableSkill')>=0) {
|
||||||
|
// 检测当前开启的技能类型
|
||||||
|
var skill = core.getFlag('skill', 0);
|
||||||
|
if (skill==1) { // 技能1:二倍斩
|
||||||
|
core.status.hero.mana-=5; // 扣除5点魔力值
|
||||||
|
}
|
||||||
|
// 关闭技能
|
||||||
|
core.setFlag('skill', 0);
|
||||||
|
core.setFlag('skillName', '无');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!> 开启技能后,建议将全塔属性的`useLoop`置为`true`,即改用循环计算临界值,这样临界计算才不会出问题!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
通过上述这几种方式,我们就能成功的让H5支持技能啦!
|
||||||
|
|
||||||
|
## 系统使用的flag变量
|
||||||
|
|
||||||
|
众所周知,自定义flag变量都可以任意定义并取用(未定义直接取用的flag默认值为0)。
|
||||||
|
|
||||||
|
下面是一些可能会被系统设置或取用的flag变量:
|
||||||
|
|
||||||
|
- **`flag:hard`**: 当前的难度标志;此flag变量在setInitData中被定义,可以直接取用来判定当前难度分歧。上传成绩时将根据此flag来对不同难度进行排序。
|
||||||
|
- **`flag:posion`**, **`flag:weak`**, **`flag:curse`**: 中毒、衰弱、诅咒状态。
|
||||||
|
- **`flag:no_zone`**, **`flag:no_repulse`**, **`flag:no_laser`**, **`flag:no_betweenAttack`**: 是否分别免疫领域、阻击、激光、夹击效果。
|
||||||
|
- **`flag:hatred`**: 当前的仇恨数值。
|
||||||
|
- **`flag:commonTimes`**: 全局商店共用次数时的访问次数。
|
||||||
|
- **`flag:input`**: 接受用户输入的事件后,存放用户输入的结果。
|
||||||
|
- **`flag:type`**, **`flag:keycode`**, **`flag:x`**, **`flag:y`**, **`flag:px`**, **`flag:py`**: 等待用户操作后,用户的操作类型,按键keycode或点击/像素坐标。
|
||||||
|
- **`flag:skill`**, **`flag:skillName`**: 开启的技能编号和技能名。
|
||||||
|
- **`flag:saveEquips`**: 快速换装时保存的套装。
|
||||||
|
- **`flag:__visited__`**: 当前访问过的楼层。
|
||||||
|
- **`flag:__atk_buff__`**, **`flag:__def_buff__`**, **`flag:__mdef_buff__`**: 当前攻防护盾的实际计算比例加成。
|
||||||
|
- **`flag:__color__`**, **`flag:__weather__`**, **`flag:__volume__`**: 当前的画面色调、天气和音量。
|
||||||
|
- **`flag:__events__`**: 当前保存的事件列表,读档时会恢复(适用于在事件中存档)
|
||||||
|
- **`flag:textAttribute`**, **`flag:globalAttribute`**, **`flag:globalFlags`**: 当前的剧情文本属性,当前的全局属性,当前的全局开关。
|
||||||
|
- **`flag:cannotMoveDirectly`**, **`flag:__noClickMove__`**: 当前是否不允许瞬间移动,当前用户是否开启了单击瞬移。
|
||||||
|
- **`flag:hideStatusBar`**, **`flag:showToolbox`**: 是否隐藏状态栏,是否显示工具栏。
|
||||||
|
- **`flag:debug`**: 当前是否开启了调试模式。
|
||||||
|
- **`flag:__seed__`**, **`flag:__rand__`**: 伪随机数生成种子和当前的状态
|
||||||
|
|
||||||
|
==========================================================================================
|
||||||
|
|
||||||
|
[继续阅读脚本](script)
|
32
plugin.d.ts
vendored
Normal file
32
plugin.d.ts
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// 这里包含所有插件导出的函数及变量声明,声明的函数会在类型标注中标注到core上
|
||||||
|
|
||||||
|
type Ref<T> = {
|
||||||
|
value: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DragFn = (x: number, y: number, e: MouseEvent | TouchEvent) => void;
|
||||||
|
|
||||||
|
type CanParseCss = keyof {
|
||||||
|
[P in keyof CSSStyleDeclaration as CSSStyleDeclaration[P] extends string
|
||||||
|
? P extends string
|
||||||
|
? P
|
||||||
|
: never
|
||||||
|
: never]: CSSStyleDeclaration[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PluginDeclaration {}
|
||||||
|
|
||||||
|
type Forward<T> = {
|
||||||
|
[K in keyof T as T[K] extends Function
|
||||||
|
? K extends `_${string}`
|
||||||
|
? never
|
||||||
|
: K
|
||||||
|
: never]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ForwardKeys<T> = keyof Forward<T>;
|
||||||
|
|
||||||
|
declare const Mota: import('../index.d.ts').IMota;
|
||||||
|
interface Window {
|
||||||
|
Mota: import('../index.d.ts').IMota;
|
||||||
|
}
|
14397
plugins.js
Normal file
14397
plugins.js
Normal file
File diff suppressed because one or more lines are too long
739
script.md
Normal file
739
script.md
Normal file
@ -0,0 +1,739 @@
|
|||||||
|
# 脚本
|
||||||
|
|
||||||
|
?> 在这一节中,让我们来了解如何使用控制台和使用脚本!
|
||||||
|
|
||||||
|
在V2.6版本中,基本对整个项目代码进行了重写,更加方便造塔者的使用。
|
||||||
|
|
||||||
|
* [脚本教程视频](https://www.bilibili.com/video/BV1uL411J7yZ?share_source=copy_web)
|
||||||
|
|
||||||
|
## 控制台的使用
|
||||||
|
|
||||||
|
在Chrome浏览器中,按(Ctrl+Shift+I)可打开控制台。
|
||||||
|
|
||||||
|
![](img/console.jpg)
|
||||||
|
|
||||||
|
控制台中有很多的标签,最常用的是`Console`, `Sources`和`Elements`。
|
||||||
|
|
||||||
|
有关更详尽的控制台使用可自行搜索[Chrome开发者工具](https://www.baidu.com/s?wd=chrome%20%E5%BC%80%E5%8F%91%E8%80%85%E5%B7%A5%E5%85%B7)了解更多。
|
||||||
|
|
||||||
|
### Console:命令行
|
||||||
|
|
||||||
|
Console页为命令行。可以在这里输入一些命令进行调试。
|
||||||
|
|
||||||
|
比如,进入游戏后,输入`core.status.hero.atk`即可获得勇士的当前攻击力数值。`core.status.hero.atk=100`可以设置攻击力为100。
|
||||||
|
|
||||||
|
更多的API可参见[附录:API列表](#附录:API列表)。
|
||||||
|
|
||||||
|
除此以外,游戏中的报错等信息也是可以在Console中进行查看的。
|
||||||
|
|
||||||
|
![](img/console1.jpg)
|
||||||
|
|
||||||
|
### Sources:断点调试
|
||||||
|
|
||||||
|
Sources页可以查看JS源代码,并进行断点调试等。
|
||||||
|
|
||||||
|
例如,如果相对脚本编辑中的伤害计算函数进行断点调试:
|
||||||
|
1. 在左边找到`project/functions.js`,单击打开文件
|
||||||
|
2. 并找到对应的行(可以Ctrl+F搜索),比如搜索`getDamageInfo`
|
||||||
|
3. 在行号上点一下打断点,会出现一个蓝色标签
|
||||||
|
|
||||||
|
之后,当代码运行到你的断点处时,将自动停止运行。
|
||||||
|
|
||||||
|
![](img/sources.jpg)
|
||||||
|
|
||||||
|
可以将鼠标移动到变量上,将弹窗形式显示这个变量的各项数值,从而查看变量值是否符合预期。
|
||||||
|
|
||||||
|
图中红色框内有几个按钮,从左到右分别是:**继续执行**,**执行到下一行**,**进入当前函数**,**跳出当前函数**,**单步执行**。
|
||||||
|
|
||||||
|
通过这几个按钮,可以一行一行的对代码进行执行,执行过程中能不断查看各个变量的数值变化,从而定位问题所在。
|
||||||
|
|
||||||
|
红圈下方是Call Stack,即当前的函数调用链(从哪些地方调用过来的)。
|
||||||
|
|
||||||
|
Sources还有更多有趣的功能,在此不做介绍,有兴趣的可自行网上搜索了解。
|
||||||
|
|
||||||
|
### Elements:网页元素查看
|
||||||
|
|
||||||
|
Elements页可以查看网页的源代码,调整css布局等。
|
||||||
|
|
||||||
|
![](img/elements.jpg)
|
||||||
|
|
||||||
|
不过对魔塔样板来说,最重要的是红圈中的按钮。点击此按钮可以进入**手机模式**。
|
||||||
|
|
||||||
|
手机模式下,左边可以对屏幕分辨率进行调整和模拟。
|
||||||
|
|
||||||
|
这可以很有效的帮我们进行测试样板在手机端的表现。
|
||||||
|
|
||||||
|
## 整体项目架构
|
||||||
|
|
||||||
|
``` text
|
||||||
|
├── /_server/ # 为可视化地图编辑器提供一些支持的目录
|
||||||
|
├── /libs/ # ---- 系统库目录 ----
|
||||||
|
│ ├─ /thirdparty/ # 游戏所用到的第三方库文件
|
||||||
|
│ ├─ actions.js # 用户交互处理
|
||||||
|
│ ├─ core.js # 系统核心文件(游戏入口,接口&转发)
|
||||||
|
│ ├─ control.js # 游戏逻辑控制
|
||||||
|
│ ├─ data.js # 全塔属性等
|
||||||
|
│ ├─ enemys.js # 怪物相关处理
|
||||||
|
│ ├─ events.js # 各个事件的执行
|
||||||
|
│ ├─ icons.js # 图标和素材
|
||||||
|
│ ├─ items.js # 道具效果
|
||||||
|
│ ├─ loader.js # 各个资源加载
|
||||||
|
│ ├─ maps.js # 地图数据和绘制
|
||||||
|
│ ├─ ui.js # UI窗口绘制
|
||||||
|
│ └─ utils.js # 工具类函数
|
||||||
|
├── /project/ # ---- 项目目录 ----
|
||||||
|
│ ├─ /animates/ # 动画目录
|
||||||
|
│ ├─ /floors/ # 楼层文件
|
||||||
|
│ ├─ /images/ # 图片素材
|
||||||
|
│ ├─ /sounds/ # bgm和音效
|
||||||
|
│ ├─ data.js # 全塔属性
|
||||||
|
│ ├─ enemys.js # 怪物属性
|
||||||
|
│ ├─ events.js # 公共事件
|
||||||
|
│ ├─ functions.js # 脚本编辑
|
||||||
|
│ ├─ icons.js # 素材和ID的对应关系定义
|
||||||
|
│ ├─ items.js # 道具的定义和效果
|
||||||
|
│ ├─ maps.js # 地图和数字的对应关系
|
||||||
|
│ └─ plugins.js # 自定义插件
|
||||||
|
├── /常用工具/ # 辅助造塔的小工具
|
||||||
|
├── editor.html # 地图编辑器
|
||||||
|
├── editor-mobile.html # 手机版的地图编辑器
|
||||||
|
├── index.html # 主程序,游戏的入口
|
||||||
|
├── main.js # JS程序的入口,将动态对所需JS进行加载
|
||||||
|
├── style.css # 游戏所需要用到的样式表
|
||||||
|
└── 启动服务.exe # 一个本地的HTTP服务器,通过它来运行游戏
|
||||||
|
```
|
||||||
|
|
||||||
|
`_server`为**地图编辑器目录**,里面存放了地图编辑器相关的各项内容。
|
||||||
|
|
||||||
|
`libs`为**系统库目录**,里面存放了各个系统核心函数。
|
||||||
|
|
||||||
|
从V2.6开始,请勿直接修改libs下的代码,如有需要修改系统库函数请尝试在插件中[复写函数](#复写函数)。
|
||||||
|
|
||||||
|
`project`为**项目目录**,你所造的塔的数据全部存放在project下。在不同样板之间接档也是直接迁移project目录即可。
|
||||||
|
|
||||||
|
## 函数的转发
|
||||||
|
|
||||||
|
在本样板中,`core.js`里面基本是没有定义什么函数的,所有的游戏内函数都在其他几个文件中实现。
|
||||||
|
|
||||||
|
例如,常见的获得某个变量值`getFlag`是定义在`control.js`中的:
|
||||||
|
|
||||||
|
```js
|
||||||
|
////// 获得某个自定义变量或flag //////
|
||||||
|
control.prototype.getFlag = function(name, defaultValue) {
|
||||||
|
if (!core.status.hero) return defaultValue;
|
||||||
|
var value = core.status.hero.flags[name];
|
||||||
|
return value != null ? value : defaultValue;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
也就是,我们可以通过`core.control.getFlag(name, value)`来调用此函数。
|
||||||
|
|
||||||
|
但是这样会十分不便,我们希望能直接调用`core.getFlag(name, value)`,而不需要中间的control。
|
||||||
|
|
||||||
|
为了达到这个目的,样板设置了**函数转发**,即**将其他文件中定义的函数,转发到core中执行**。
|
||||||
|
|
||||||
|
上述`getFlag`代码的转发实际上是增加了如下函数:
|
||||||
|
|
||||||
|
```js
|
||||||
|
////// getFlag函数的转发 //////
|
||||||
|
core.getFlag = function (name, defaultValue) {
|
||||||
|
return core.control.getFlag(name, defaultValue);
|
||||||
|
}
|
||||||
|
// 转发后,即可通过 core.getFlag() 来实际调用 core.control.getFlag()
|
||||||
|
```
|
||||||
|
|
||||||
|
转发是自动完成的,其满足如下两条规则:
|
||||||
|
- **在libs中其他文件定义的函数,如果不以下划线`_`开头,就会进行转发。**
|
||||||
|
- **如果core中已经存在同名函数,则会在控制台中打出一条报错信息,并不转发该函数。**
|
||||||
|
|
||||||
|
具体函数的转发实现代码可参见`core.js`的`_forwardFunc`函数。
|
||||||
|
|
||||||
|
!> 除此以外,插件中以`this.xxx`来定义的函数也会被转发!
|
||||||
|
|
||||||
|
例如,你可以直接调用`core.drawLight()`来实际调用插件中的`core.plugin.drawLight`。
|
||||||
|
|
||||||
|
## 插件编写
|
||||||
|
|
||||||
|
插件编写是H5魔塔的一个重大特点,从V2.0.1引入,并逐渐发扬光大。
|
||||||
|
|
||||||
|
对于有一定脚本经验的人来说,可以编写插件来实现各种各样的功能,包括且不仅限于拓展功能的实现,系统代码的复写等等。
|
||||||
|
|
||||||
|
在V2.5.5以前,插件位置都在脚本编辑中;从V2.6开始则迁移到了新的下拉框中,并进行了切分。
|
||||||
|
|
||||||
|
你也可以创建自己的插件。
|
||||||
|
|
||||||
|
![](img/plugin.jpg)
|
||||||
|
|
||||||
|
新的插件切分和原来的单插件使用方法完全一致,单纯进行了切分而已。可参见已有的`init`和`drawLight`的样例。
|
||||||
|
|
||||||
|
拆分的意义主要是将各个可能的功能独立出来,避免单个框内内容太长,过大和混杂等。
|
||||||
|
|
||||||
|
在V2.6中,应当每个独立的额外功能实现都新建一个自己的插件,这样也方便进行拓展,例如打包迁移到别的塔上,或发布在网页插件库中。
|
||||||
|
|
||||||
|
另外一点需要注意的是,所有插件的初始化都会在系统资源加载之前,此时图片等资源尚未进行加载。
|
||||||
|
|
||||||
|
在所有资源加载完毕时,将会执行init插件中的_afterLoadResources函数,可以在这里对资源进行一些操作,比如切分图片等。
|
||||||
|
|
||||||
|
```js
|
||||||
|
function () {
|
||||||
|
console.log("插件编写测试");
|
||||||
|
|
||||||
|
// 可以写一些直接执行的代码
|
||||||
|
// 在这里写的代码将会在【资源加载前】被执行,此时图片等资源尚未被加载。
|
||||||
|
// 请勿在这里对包括bgm,图片等资源进行操作。
|
||||||
|
|
||||||
|
|
||||||
|
this._afterLoadResources = function () {
|
||||||
|
// 本函数将在所有资源加载完毕后,游戏开启前被执行
|
||||||
|
// 可以在这个函数里面对资源进行一些操作,比如切分图片等。
|
||||||
|
|
||||||
|
// 这是一个将assets.png拆分成若干个32x32像素的小图片并保存的样例。
|
||||||
|
// var arr = core.splitImage("assets.png", 32, 32);
|
||||||
|
// for (var i = 0; i < arr.length; i++) {
|
||||||
|
// core.material.images.images["asset"+i+".png"] = arr[i];
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可以在任何地方(如afterXXX或自定义脚本事件)调用函数,方法为 core.plugin.xxx();
|
||||||
|
// 从V2.6开始,插件中用this.XXX方式定义的函数也会被转发到core中,详见文档-脚本-函数的转发。
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
网站上提供了一个插件库,[https://h5mota.com/plugins/](https://h5mota.com/plugins/),上面有一些大家分享的插件,可供使用。
|
||||||
|
|
||||||
|
可以查看附录中的[API列表](api)来查看所有的系统API内容。
|
||||||
|
|
||||||
|
## register系列函数
|
||||||
|
|
||||||
|
在API列表中,有一系列的函数以`register`开头;这一系列函数允许你将一部分自定义的代码注入到样板中,从而可以监听某一系列行为并进行处理。
|
||||||
|
|
||||||
|
下面会对这系列函数进行逐个介绍。
|
||||||
|
|
||||||
|
### registerAction
|
||||||
|
|
||||||
|
```
|
||||||
|
registerAction: fn(action: string, name: string, func: string|fn(params: ?), priority?: number)
|
||||||
|
此函数将注册一个用户交互行为。
|
||||||
|
action: 要注册的交互类型,如 ondown, onup, keyDown 等等。
|
||||||
|
name: 你的自定义名称,可被注销使用;同名重复注册将后者覆盖前者。
|
||||||
|
func: 执行函数。
|
||||||
|
如果func返回true,则不会再继续执行其他的交互函数;否则会继续执行其他的交互函数。
|
||||||
|
priority: 优先级;优先级高的将会被执行。此项可不填,默认为0
|
||||||
|
```
|
||||||
|
|
||||||
|
`registerAction`为register系列最常用的功能之一,它允许你任意监听用户的交互事件,包括且不仅限于按键、点击、拖动、长按,等等。
|
||||||
|
|
||||||
|
下面是一个例子:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 当flag:abc是true时,点击屏幕左上角可以使用道具破墙镐
|
||||||
|
// 注入一个 ondown 事件,名称为 my_pickaxe
|
||||||
|
core.registerAction('ondown', 'my_pickaxe', function (x, y, px, py) {
|
||||||
|
// 如果当前正在执行某个事件,则忽略之。
|
||||||
|
if (core.status.lockControl) return false;
|
||||||
|
// 如果勇士正在行走中,则忽略之。
|
||||||
|
if (core.isMoving()) return false;
|
||||||
|
// x, y 为点击坐标;px, py 为点击的像素坐标
|
||||||
|
// 检查flag是否为true,且点击了(0,0)点
|
||||||
|
if (core.hasFlag('abc') && x == 0 && y == 0) {
|
||||||
|
// 检查能否使用破墙镐
|
||||||
|
if (core.canUseItem('pickaxe')) {
|
||||||
|
core.useItem('pickaxe');
|
||||||
|
// 返回true将拦截其他交互函数对于点击的处理
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 返回false后将会继续执行其他的交互函数(例如寻路)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 优先级设置为100,比系统优先级高,因此将会优先执行此注入的项目。
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// 当你不再需要上述监听时,可以通过下面这一句取消注入。
|
||||||
|
// core.unregisterActon('ondown', 'my_pickaxe');
|
||||||
|
```
|
||||||
|
|
||||||
|
下面是另一个例子:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 假设我们通过事件流做了一个商店,希望能通过”长按空格“和“长按屏幕”进行连续确认购买
|
||||||
|
// 此时,需要使用一个flag,表示是否在商店中;例如 flag:inShop
|
||||||
|
|
||||||
|
// 注入一个keyDown(按下)事件;请注意,当按下按键且不释放时,会连续触发keyDown。
|
||||||
|
core.registerAction('keyDown', 'my_shop', function (keycode) {
|
||||||
|
// 检查是否确实在该商店事件中;进该商店前需要将flag:inShop设为true,退出后需要设为false
|
||||||
|
if (!core.hasFlag('inShop')) return false;
|
||||||
|
// 检查当前事件是一个”显示选择项“
|
||||||
|
if (core.status.event.id != 'action' || core.status.event.data.type != 'choices') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 获得当前“显示选择项”的各个选项
|
||||||
|
var choices = core.status.event.data.current.choices;
|
||||||
|
// 获得当前选项第一项在屏幕上的y坐标
|
||||||
|
var topIndex = core.actions._getChoicesTopIndex(choices.length);
|
||||||
|
// 检查按键的键值,是否是空格或者回车
|
||||||
|
if (keycode == 13 || keycode == 32) {
|
||||||
|
// 如果是,则视为直接点击屏幕上的选项
|
||||||
|
// core.status.event.selection为当前光标选择项;第一项为0
|
||||||
|
// 点击的x为HSIZE,即屏幕左右居中;y为topIndex + core.status.event.selection为该选择项
|
||||||
|
core.actions._clickAction(core.actions.HSIZE, topIndex + core.status.event.selection);
|
||||||
|
// 不再执行其他的交互函数
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 否则,继续执行其他的交互函数(如上下键光标选择)
|
||||||
|
return false;
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
|
||||||
|
// 还需要注册一个keyUp(放开)事件,拦截空格和回车
|
||||||
|
// 这是因为,按一下空格会先触发keyDown再触发keyUp;这里不拦截的话会购买两次。
|
||||||
|
var _my_shop_up = function (keycode) {
|
||||||
|
if (!core.hasFlag('inShop')) return false;
|
||||||
|
if (core.status.event.id != 'action' || core.status.event.data.type != 'choices') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 检查是否是空格或者回车,如果是则拦截操作。
|
||||||
|
if (keycode == 13 || keycode == 32) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
// 这里可以传入前面定义的函数体。
|
||||||
|
core.registerAction('keyUp', 'my_shop', _my_shop_up, 100);
|
||||||
|
|
||||||
|
|
||||||
|
// 注册一个长按事件,允许长按连续购买
|
||||||
|
this._my_shop_longClick = function (x, y, px, py) {
|
||||||
|
if (!core.hasFlag('inShop')) return false;
|
||||||
|
if (core.status.event.id != 'action' || core.status.event.data.type != 'choices') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 直接视为点击了此位置
|
||||||
|
core.actions._clickAction(x, y, px, py);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 如果是插件中使用`this.xxx`定义的函数,也可以直接传入函数名。
|
||||||
|
core.registerAction('longClick', 'my_shop', '_my_shop_longClick', 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
简单地说,`registerAction`这个函数**允许你在任何时候监听和拦截任何用户交互行为**。
|
||||||
|
|
||||||
|
目前,`registerAction`支持监听和拦截如下交互:
|
||||||
|
|
||||||
|
- `keyDown`: 当一个键被按下时
|
||||||
|
- 对应的函数参数:`function (keycode)`,为此时按下键的键值
|
||||||
|
- 请注意:如果长按键不放开的话,会连续触发`keyDown`事件
|
||||||
|
- `keyUp`: 当一个键被放开时
|
||||||
|
- 对应的函数参数:`function (keycode, altKey, fromReplay)`,为此时放开键的键值、当前alt是否被按下、是否是录像播放中模拟的按键
|
||||||
|
- `onKeyDown`: 当一个键被按下时
|
||||||
|
- 对应的函数参数:`function (e)`,为此时按键的信息。这里的`e`是一个`KeyboardEvent`。
|
||||||
|
- 请注意:如果长按键不放开的话,会连续触发`onKeyDown`事件。
|
||||||
|
- 和上述`keyDown`的主要区别就是参数为原始的`KeyboardEvent`;仍然推荐直接使用`keyDown`。
|
||||||
|
- `onKeyUp`: 当一个键被放开时
|
||||||
|
- 对应的函数参数:`function (e)`,为此时按键的信息。这里的`e`是一个`KeyboardEvent`。
|
||||||
|
- 和上述`keyUp`的主要区别就是参数为原始的`KeyboardEvent`;仍然推荐直接使用`keyUp`。
|
||||||
|
- `ondown`: 当屏幕被鼠标或手指按下时
|
||||||
|
- 对应的函数参数:`function (x, y, px, py)`,为此时按下时的格子坐标和像素坐标。
|
||||||
|
- `onmove`: 当屏幕被鼠标滑动,或手指拖动时
|
||||||
|
- 对应的函数参数:`function (x, y, px, py)`,为此时滑动或拖动时的格子坐标和像素坐标。
|
||||||
|
- 如果是鼠标,只要在屏幕上滑动就会触发`onmove`,无需处于点击状态(也就是悬浮也是可以的)。
|
||||||
|
- 如果是触摸屏,则只有手指按下滑动时才会触发`onmove`(并不存在什么悬浮的说法)。
|
||||||
|
- `onup`: 当屏幕被鼠标或手指放开时
|
||||||
|
- 对应的函数参数:`function (x, y, px, py)`,为此时放开时的格子坐标和像素坐标。
|
||||||
|
- `onclick` 【已废弃】
|
||||||
|
- 从V2.8.2起,此交互已被废弃,注册一个`onclick`会被直接转发至`ondown`。
|
||||||
|
- `onmousewheel`: 当鼠标滚轮滚动时
|
||||||
|
- 对应的函数参数:`function (direct)`,为此时滚轮方向。向下滚动是-1,向上滚动是1。
|
||||||
|
- 目前在楼传、怪物手册、存读档、浏览地图等多个地方绑定了鼠标滚轮事件。
|
||||||
|
- `keyDownCtrl`: 当Ctrl键处于被长按不放时
|
||||||
|
- 对应的函数参数:`function ()`,即无参数。
|
||||||
|
- 目前主要用于跳过剧情对话。
|
||||||
|
- `longClick`: 当鼠标或手指长按屏幕时
|
||||||
|
- 对应的函数参数:`function (x, y, px, py)`,为长按屏幕对应的格子坐标和像素坐标
|
||||||
|
- 目前主要用于虚拟键盘,和跳过剧情对话。
|
||||||
|
- `onStatusBarClick`: 当点击状态栏时
|
||||||
|
- 对应的函数参数:`function (px, py, vertical)`,为状态栏点击的像素坐标。
|
||||||
|
- 如果参数`vertical`不为`null`,表示该状态栏点击是录像播放时触发的,其值为录像记录时的横竖屏状态。
|
||||||
|
|
||||||
|
### registerAnimationFrame
|
||||||
|
|
||||||
|
```
|
||||||
|
registerAnimationFrame: fn(name: string, needPlaying: bool, func?: fn(timestamp: number))
|
||||||
|
注册一个 animationFrame
|
||||||
|
name: 名称,可用来作为注销使用
|
||||||
|
needPlaying: 是否只在游戏运行时才执行(在标题界面不执行)
|
||||||
|
func: 要执行的函数,或插件中的函数名;可接受timestamp(从页面加载完毕到当前所经过的时间)作为参数
|
||||||
|
```
|
||||||
|
|
||||||
|
`registerAnimationFrame`为其次常用的功能,可以用于需要反复执行的脚本,常用于动画之类。
|
||||||
|
|
||||||
|
目前,样板的全局动画(即怪物的帧振动)、人物行走动画、普通动画、天气效果、游戏计时、以及楼层并行事件等,都是通过`registerAnimationFrame`进行实现的。
|
||||||
|
|
||||||
|
通过`registerAnimationFrame`注册的函数会平均每16.6ms执行一次(视浏览器的性能而定,例如手机上可能执行频率会变慢)。可以通过函数的`timestamp`参数来获得从页面加载完毕到当前所经过的毫秒数(从而可以进一步控制执行频率,如每500ms才执行一次)。
|
||||||
|
|
||||||
|
**推荐所有的需要反复执行的脚本(如动画相关)都通过此函数来注册,而不是使用`setInterval`,以获得更好的性能。**
|
||||||
|
|
||||||
|
下面是“人物血量动态变化”的插件的例子。
|
||||||
|
|
||||||
|
```js
|
||||||
|
var speed = 0.05; // 动态血量变化速度,越大越快。
|
||||||
|
var _currentHp = null; // 当前正在变化的hp值
|
||||||
|
var _lastStatus = null; // 上次人物的属性对象
|
||||||
|
|
||||||
|
// 注册一个
|
||||||
|
core.registerAnimationFrame('dynamicHp', true, function (timestamp) {
|
||||||
|
// 检查人物所指向的对象是否发生了变化
|
||||||
|
// 在例如读档后,core.status.hero被重置了,此时需要将_currentHp重置为正确的生命值。
|
||||||
|
if (_lastStatus != core.status.hero) {
|
||||||
|
_lastStatus = core.status.hero;
|
||||||
|
_currentHp = core.status.hero.hp;
|
||||||
|
}
|
||||||
|
// 如果显示的hp值和当前实际hp值不同
|
||||||
|
if (core.status.hero.hp != _currentHp) {
|
||||||
|
// 计算hp差值,并获得下个血量变化值。
|
||||||
|
var dis = (_currentHp - core.status.hero.hp) * speed;
|
||||||
|
if (Math.abs(dis) < 2) {
|
||||||
|
// 如果很接近了,直接设为实际hp值。
|
||||||
|
_currentHp = core.status.hero.hp;
|
||||||
|
} else {
|
||||||
|
// 否则,改变显示值使其更接近。
|
||||||
|
_currentHp -= dis;
|
||||||
|
}
|
||||||
|
// 然后设置状态栏中生命的显示项。
|
||||||
|
core.setStatusBarInnerHTML('hp', _currentHp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### registerReplayAction
|
||||||
|
|
||||||
|
```
|
||||||
|
registerReplayAction: fn(name: string, func: fn(action?: string) -> bool)
|
||||||
|
注册一个录像行为
|
||||||
|
name: 自定义名称,可用于注销使用
|
||||||
|
func: 具体执行录像的函数,可为一个函数或插件中的函数名;
|
||||||
|
需要接受一个action参数,代表录像回放时的下一个操作
|
||||||
|
func返回true代表成功处理了此录像行为,false代表没有处理此录像行为。
|
||||||
|
```
|
||||||
|
|
||||||
|
`registerReplayAction`允许你自己定义和注册一个录像行为。
|
||||||
|
|
||||||
|
在游戏时,你的所有操作会被记录为一个数组`core.status.route`,如按上就是`up`,使用破墙镐就是`item:pickaxe`,瞬移到(1,2)就是`move:1:2`,等等。
|
||||||
|
|
||||||
|
在录像播放时,会依次从数组中取出一个个的录像操作,并模拟执行对应的操作。这里对于每个录像操作,都是靠`registerReplayAction`进行注册的。
|
||||||
|
|
||||||
|
下面是一个例子:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 当flag:abc为true时,点击屏幕(0,0),(0,1),(0,2),(0,3)会分别执行不同的公共事件。
|
||||||
|
|
||||||
|
// 先通过registerAction注册屏幕点击事件
|
||||||
|
core.registerAction('onclick', 'commonEvent', function (x, y, px, py) {
|
||||||
|
// 检查是否是自由状态、是否在移动,是否存在flag。
|
||||||
|
if (core.status.lockControl || core.isMoving() || !core.hasFlag('abc')) return false;
|
||||||
|
// 公共事件名
|
||||||
|
var eventName = null;
|
||||||
|
if (x == 0 && y == 0) {
|
||||||
|
eventName = "公共事件0";
|
||||||
|
} else if (x == 0 && y == 1) {
|
||||||
|
eventName = "公共事件1";
|
||||||
|
} else if (x == 0 && y == 2) {
|
||||||
|
eventName = "公共事件2";
|
||||||
|
} // ... 可以继续写下去。
|
||||||
|
|
||||||
|
// 如果存在需要执行的公共事件
|
||||||
|
if (eventName != null) {
|
||||||
|
// 重要!往录像中写入一个自定义的`commonEvent:`项,记录执行的公共事件名称。
|
||||||
|
// 由于录像编码时只支持部分ascii码,而eventName作为公共事件名可能带有中文,
|
||||||
|
// 因此需要使用 core.encodeBase64() 对eventName进行编码
|
||||||
|
core.status.route.push("commonEvent:" + core.encodeBase64(eventName));
|
||||||
|
// 插入公共事件
|
||||||
|
core.insertCommonEvent(eventName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注册一个录像处理行为来处理上述的`commonEvent:`项。
|
||||||
|
core.registerReplayAction('commonEvent', function (action) {
|
||||||
|
// 参数action为此时录像播放时执行到的项目。
|
||||||
|
// 如果不是`commonEvent:`,则不应该被此函数处理。
|
||||||
|
if (action.indexOf('commonEvent:') !== 0) return false;
|
||||||
|
// 二次检查flag:abc是否存在
|
||||||
|
if (!core.hasFlag('abc')) {
|
||||||
|
core.control._replay_error(action);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 如果是我们的录像行为,获得具体的公共事件名。
|
||||||
|
// 上面使用encodeBase64()编码了录像,现在同样需要解码。
|
||||||
|
var eventName = core.decodeBase64(action.substring(12));
|
||||||
|
// 由于录像播放中存在二次记录,还需要将action写入到当前播放过程中的录像中。
|
||||||
|
// 这样录像播放完毕后再次直接重头播放不会出现问题。
|
||||||
|
core.status.route.push(action);
|
||||||
|
// 执行该公共事件。
|
||||||
|
core.insertCommonEvent(eventName);
|
||||||
|
// 继续播放录像。
|
||||||
|
core.replay();
|
||||||
|
// 返回true表示我们已经成功处理了此录像行为。
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### registerWeather
|
||||||
|
|
||||||
|
```
|
||||||
|
registerWeather: fn(name: string, initFunc: fn(level: number), frameFunc?: fn(timestamp: number, level: number))
|
||||||
|
注册一个天气
|
||||||
|
name: 要注册的天气名
|
||||||
|
initFunc: 当切换到此天气时的初始化;接受level(天气等级)为参数;可用于创建多个节点(如初始化雪花)
|
||||||
|
frameFunc: 每帧的天气效果变化;可接受timestamp(从页面加载完毕到当前所经过的时间)和level(天气等级)作为参数
|
||||||
|
天气应当仅在weather层进行绘制,推荐使用core.animateFrame.weather.nodes用于节点信息。
|
||||||
|
```
|
||||||
|
|
||||||
|
`registerWeather`允许你注册一个天气。
|
||||||
|
|
||||||
|
在游戏时,楼层属性中可以设置天气如 `["snow", 5]`,或者脚本 `core.setWeather("snow", 5)` 来切换天气。
|
||||||
|
|
||||||
|
下面是一个例子:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 注册一个”血“天气,每200ms就随机在界面上的绘制红色斑点
|
||||||
|
core.registerWeather('blood', function (level) {
|
||||||
|
// 切换到此天气时应当执行的脚本吗,如播放一个音效
|
||||||
|
core.playSound('blood.mp3');
|
||||||
|
}, function (timestamp, level) {
|
||||||
|
// 我们希望每200ms就界面上随机绘制 level^2 个红点,半径在0~32像素之间
|
||||||
|
|
||||||
|
// 检查是否经过了200ms
|
||||||
|
if (timestamp - core.animateFrame.weather.time < 200) return;
|
||||||
|
// 当且仅当在weather层上绘制
|
||||||
|
core.clearMap('weather');
|
||||||
|
for (var i = 0; i < level * level; ++i) {
|
||||||
|
// 随机界面中的一个点,半径在0~32之间
|
||||||
|
var px = Math.random() * core.__PIXELS__;
|
||||||
|
var py = Math.random() * core.__PIXELS__;
|
||||||
|
var r = Math.random() * 32;
|
||||||
|
core.fillCircle('weather', px, py, r, 'red');
|
||||||
|
}
|
||||||
|
// 设置本次天气调用的时间
|
||||||
|
core.animateFrame.weather.time = timestamp;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
值得注意的是,天气当且仅当在`weather`层进行绘制,推荐使用或设置`core.animateFrame.weather.time`作为上次天气调用的时间避免太过于频繁的调用。
|
||||||
|
|
||||||
|
推荐使用`core.animateFrame.weather.nodes`来存储天气的节点,这样会在取消天气时自动被移除。
|
||||||
|
|
||||||
|
样板的云`cloud`和雾`fog`均由多个图片叠加移动实现;如果你想实现类似效果,可直接使用`core.control.__animateFrame_weather_image`作为`frameFunc`,详见样板的云雾实现。
|
||||||
|
|
||||||
|
另外注意的是,注册的天气无法在事件编辑器的下拉框中选择;你可以选择脚本调用`core.setWeather`,或者修改`_server/MotaAction.g4`中的`Weather_List`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
Weather_List
|
||||||
|
: '无'|'雨'|'雪'|'晴'|'雾'|'云'
|
||||||
|
/*Weather_List ['null','rain','snow','sun','fog','cloud']*/;
|
||||||
|
```
|
||||||
|
|
||||||
|
### registerSystemEvent
|
||||||
|
|
||||||
|
```
|
||||||
|
registerSystemEvent: fn(type: string, func: fn(data?: ?, callback?: fn()))
|
||||||
|
注册一个系统事件
|
||||||
|
type: 事件名
|
||||||
|
func: 为事件的处理函数,可接受(data,callback)参数
|
||||||
|
```
|
||||||
|
|
||||||
|
`registerSystemEvent`允许你注册一个系统事件。
|
||||||
|
|
||||||
|
在图块属性中,存在一个`trigger`选项,可以设置图块的触发器;当玩家碰触到对应的图块时,会根据对应图块的触发器来执行对应系统事件,即`registerSystemEvent`所注册的。
|
||||||
|
|
||||||
|
这里的`data`为触发该图块时的`block`信息,和`core.getBlock()`的返回值相同。`callback`为执行完毕时的回调。
|
||||||
|
|
||||||
|
下面是一个例子:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 假设存在一个图块,当触碰时会触发一个公共事件,且需要图块坐标作为公共事件参数。
|
||||||
|
// 首先需要将该图块的触发器设置为`custom`。
|
||||||
|
|
||||||
|
// 注册一个`custom`的系统事件;当角色碰触到触发器为`custom`的图块时会被执行。
|
||||||
|
core.registerSystemEvent("custom", function (data, callback) {
|
||||||
|
// 这里的`data`为碰触到的图块信息。
|
||||||
|
console.log(data);
|
||||||
|
// 插入一个公共事件(如“图块触碰”),把图块坐标作为公共事件参数传入。
|
||||||
|
core.insertCommonEvent("图块触碰", /*args*/ [data.x, data.y], data.x, data.y);
|
||||||
|
if (callback) callback();
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`registerSystemEvent`系列最大的好处是,他是和“图块”相关而不是和“点”相关的。也就是说,可以任意通过`setBlock`之类的函数移动图块,不会影响到事件的触发(而这个是靠红点所无法完成的)。
|
||||||
|
|
||||||
|
也可以通过`registerSystemEvent`来拦截默认的系统事件,例如 `core.registerSystemEvent("battle", ...)` 可以拦截当遇到没有覆盖触发器的怪物的系统处理(即不再直接战斗)。
|
||||||
|
|
||||||
|
### registerEvent
|
||||||
|
|
||||||
|
```
|
||||||
|
registerEvent: fn(type: string, func: fn(data: ?, x?: number, y?: number, prefix?: string))
|
||||||
|
注册一个自定义事件
|
||||||
|
type: 事件类型
|
||||||
|
func: 事件的处理函数,可接受(data, x, y, prefix)参数
|
||||||
|
data为事件内容,x和y为当前点坐标(可为null),prefix为当前点前缀
|
||||||
|
```
|
||||||
|
|
||||||
|
`registerEvent`允许你注册一个自定义事件,即事件流中 type:xxx 的行为。
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 注册一个type:abc事件
|
||||||
|
core.registerEvent("abc", function (data, x, y, prefix) {
|
||||||
|
// 直接打出当前事件信息
|
||||||
|
console.log(data.name);
|
||||||
|
core.doAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行一个abc事件
|
||||||
|
// 控制台会打出 "hahaha"
|
||||||
|
// core.insertAction([{"type": "abc", "name": "hahaha"}]);
|
||||||
|
```
|
||||||
|
|
||||||
|
此函数一般不是很常用,但是配合“编辑器修改”(即给事件编辑器修改或者增加事件图块)会有奇效。
|
||||||
|
|
||||||
|
具体例子可参见[修改编辑器](editor)中的`demo - 增加新语句图块`。
|
||||||
|
|
||||||
|
### registerResize
|
||||||
|
|
||||||
|
```
|
||||||
|
registerResize: fn(name: string, func: fn(obj: ?))
|
||||||
|
注册一个resize函数
|
||||||
|
name: 名称,可供注销使用
|
||||||
|
func: 可以是一个函数,或者是插件中的函数名;可以接受obj参数,详见resize函数。
|
||||||
|
```
|
||||||
|
|
||||||
|
`registerResize`允许你重写一个屏幕重定位函数。
|
||||||
|
|
||||||
|
此函数会在游戏开始前,以及每次屏幕大小发生改变时调用。系统通过此函数来对游戏界面进行调整,如横竖屏自适应等。
|
||||||
|
|
||||||
|
此函数会被较少用到。
|
||||||
|
|
||||||
|
## 复写函数
|
||||||
|
|
||||||
|
样板的功能毕竟是写死的,有时候我们也需要修改样板的一些行为。
|
||||||
|
|
||||||
|
在V2.6以前,需要直接打开libs目录下的对应文件并进行修改。但是开libs下的文件就会出现各种问题:
|
||||||
|
|
||||||
|
- 不容易记得自己修改过什么,而且如果改错了很麻烦
|
||||||
|
- 例如,直接修改了某函数加了新功能,结果过段时间发现不需要,想删掉,但是这时候已经很难找到自己改过了什么了。
|
||||||
|
- 或者,如果代码改错了,不断往上面打补丁,也只会使得libs越来越乱,最后连自己做过什么都不记得。
|
||||||
|
- 不容易随着新样板接档进行迁移
|
||||||
|
- 不方便能整理成新的插件在别的塔使用(总不能让别的塔也去修改libs吧)
|
||||||
|
- ……
|
||||||
|
|
||||||
|
好消息是,从V2.6开始,我们再也不需要开文件了,而是可以直接在插件中对原始函数进行复写。
|
||||||
|
|
||||||
|
函数复写的好处如下:
|
||||||
|
|
||||||
|
- 不会影响系统原有代码。
|
||||||
|
- 即使写错了或不需要了,也只用把插件中的函数注释或删除即可,不会对原来的系统代码产生任何影响。
|
||||||
|
- 清晰明了。很容易方便知道自己修改过什么,尤其是可以和系统原有代码进行对比。
|
||||||
|
- 方便整理成新的插件,给其他的塔使用。
|
||||||
|
|
||||||
|
一般而言,复写规则如下:
|
||||||
|
|
||||||
|
**对xxx文件中的yyy函数进行复写,规则是`core.xxx.yyy = function (参数列表) { ... }`。**
|
||||||
|
|
||||||
|
但是,对于`register`系列函数是无效的,例如直接复写`core.control._animationFrame_globalAnimate`函数是没有效果的。对于这种情况引入的函数,需要注册同名函数,可参见最下面的样例。
|
||||||
|
|
||||||
|
下面是几个例子,从简单到复杂。
|
||||||
|
|
||||||
|
### 重写怪物手册的背景图绘制,使用winskin而不是默认的黑色
|
||||||
|
|
||||||
|
直接重写怪物手册的背景图绘制,使用`core.drawBackground`来用winskin绘制一个背景图。
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 重写ui.js中的_drawBook_drawBackground函数
|
||||||
|
core.ui._drawBook_drawBackground = function () {
|
||||||
|
// core.__PIXELS__为定义的一个宏,对于13x13的值是416,对于15x15的值是480
|
||||||
|
core.drawBackground(0, 0, core.__PIXELS__, core.__PIXELS__);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重写点击楼传事件
|
||||||
|
|
||||||
|
重写点击楼传事件,使得点击楼传按钮时能使用一个道具(比如item:fly)。
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 重写events.js的useFly函数,即点击楼传按钮时的事件
|
||||||
|
core.events.useFly = function (fromUserAction) {
|
||||||
|
if (core.isMoving()) {
|
||||||
|
core.drawTip("请先停止勇士行动");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (core.status.lockControl || core.status.event.id != null) return;
|
||||||
|
|
||||||
|
if (core.canUseItem('fly')) core.useItem('fly');
|
||||||
|
else core.drawTip("当前无法使用"+core.material.items.fly.name);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
其他的几个按钮,如快捷商店`openQuickShop`,虚拟键盘`openKeyBoard`的重写也几乎完全一样。
|
||||||
|
|
||||||
|
### 关门时播放一个动画
|
||||||
|
|
||||||
|
关门是在`events.js`中的`closeDoor`函数,因此需要对其进行重写。
|
||||||
|
|
||||||
|
然而,我们只需要在这个函数执行之前插一句播放动画,所以并不需要重写整个函数,而是直接插入一行就行。
|
||||||
|
|
||||||
|
```js
|
||||||
|
var closeDoor = core.events.closeDoor; // 先把原始函数用一个变量记录下来
|
||||||
|
core.events.closeDoor = function (x, y, id, callback) {
|
||||||
|
core.drawAnimate('closeDoor', x, y); // 播放 closeDoor 这个动画
|
||||||
|
return closeDoor(x, y, id, callback); // 直接调用原始函数
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 每次跳跃角色前播放一个音效
|
||||||
|
|
||||||
|
跳跃角色在`events.js`的`jumpHero`函数,因此需要对其进行重写。
|
||||||
|
|
||||||
|
由于只需要额外在函数执行前增加一句音效播放,所以直接插入一行即可。
|
||||||
|
|
||||||
|
但是需要注意的是,`jumpHero`中使用了`this._jumpHero_doJump()`,因此使用函数时需要用`call`或者`apply`来告知this是什么。
|
||||||
|
|
||||||
|
```js
|
||||||
|
var jumpHero = core.events.jumpHero; // 先把原始函数用一个变量记录下来
|
||||||
|
core.events.jumpHero = function (ex, ey, time, callback) {
|
||||||
|
core.playSound("jump.mp3"); // 播放一个音效
|
||||||
|
return jumpHero.call(core.events, ex, ey, time, callback); // 需要使用`call`来告知this是core.events
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
详见[call和apply的用法](https://www.jianshu.com/p/80ea0d1c04f8)。
|
||||||
|
|
||||||
|
### 复写全局动画绘制函数
|
||||||
|
|
||||||
|
全局动画绘制在`control.js`的`_animationFrame_globalAnimate`函数。
|
||||||
|
|
||||||
|
注意到此函数是由`registerAnimationFrame`注册的,因此直接复写是无效的。
|
||||||
|
|
||||||
|
其在control.js的注册的定义如下:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 注册全局动画函数
|
||||||
|
this.registerAnimationFrame("globalAnimate", true, this._animationFrame_globalAnimate);
|
||||||
|
```
|
||||||
|
|
||||||
|
因此,可以在插件中自行注册一个**同名**的函数来覆盖原始的内容。
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 插件中复写全局动画绘制函数
|
||||||
|
this.myGlobalAnimate = function (timestamp) {
|
||||||
|
// ...... 实际复写的函数内容
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册同名(globalAnimate)函数来覆盖系统原始内容
|
||||||
|
core.registerAnimationFrame("globalAnimate", true, "myGlobalAnimate");
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
==========================================================================================
|
||||||
|
|
||||||
|
[继续阅读下一章:修改编辑器](editor)
|
||||||
|
|
||||||
|
|
1
search.min.js
vendored
Normal file
1
search.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
!function(){"use strict";function e(e){var n={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return String(e).replace(/[&<>"'\/]/g,function(e){return n[e]})}function n(e){var n=[];return h.dom.findAll("a:not([data-nosearch])").map(function(t){var o=t.href,i=t.getAttribute("href"),r=e.parse(o).path;r&&-1===n.indexOf(r)&&!Docsify.util.isAbsolutePath(i)&&n.push(r)}),n}function t(e){localStorage.setItem("docsify.search.expires",Date.now()+e),localStorage.setItem("docsify.search.index",JSON.stringify(g))}function o(e,n,t,o){void 0===n&&(n="");var i,r=window.marked.lexer(n),a=window.Docsify.slugify,s={};return r.forEach(function(n){if("heading"===n.type&&n.depth<=o)i=t.toURL(e,{id:a(n.text)}),s[i]={slug:i,title:n.text,body:""};else{if(!i)return;s[i]?s[i].body?s[i].body+="\n"+(n.text||""):s[i].body=n.text:s[i]={slug:i,title:"",body:""}}}),a.clear(),s}function i(n){var t=[],o=[];Object.keys(g).forEach(function(e){o=o.concat(Object.keys(g[e]).map(function(n){return g[e][n]}))}),n=n.trim();var i=n.split(/[\s\-\,\\\/]+/);1!==i.length&&(i=[].concat(n,i));for(var r=0;r<o.length;r++)!function(n){var r=o[n],a=!1,s="",c=r.title&&r.title.trim(),l=r.body&&r.body.trim(),f=r.slug||"";if(c&&l&&(i.forEach(function(n,t){var o=new RegExp(n,"gi"),i=-1,r=-1;if(i=c&&c.search(o),r=l&&l.search(o),i<0&&r<0)a=!1;else{a=!0,r<0&&(r=0);var f=0,d=0;f=r<11?0:r-10,d=0===f?70:r+n.length+60,d>l.length&&(d=l.length);var p="..."+e(l).substring(f,d).replace(o,'<em class="search-keyword">'+n+"</em>")+"...";s+=p}}),a)){var d={title:e(c),content:s,url:f};t.push(d)}}(r);return t}function r(e,i){h=Docsify;var r="auto"===e.paths,a=localStorage.getItem("docsify.search.expires")<Date.now();if(g=JSON.parse(localStorage.getItem("docsify.search.index")),a)g={};else if(!r)return;var s=r?n(i.router):e.paths,c=s.length,l=0;s.forEach(function(n){if(g[n])return l++;h.get(i.router.getFile(n)).then(function(r){g[n]=o(n,r,i.router,e.depth),c===++l&&t(e.maxAge)})})}function a(){Docsify.dom.style("\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 7px;\n line-height: 22px;\n font-size: 14px;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}")}function s(e,n){void 0===n&&(n="");var t='<input type="search" value="'+n+'" /><div class="results-panel"></div></div>',o=Docsify.dom.create("div",t),i=Docsify.dom.find("aside");Docsify.dom.toggleClass(o,"search"),Docsify.dom.before(i,o)}function c(e){var n=Docsify.dom.find("div.search"),t=Docsify.dom.find(n,".results-panel");if(!e)return t.classList.remove("show"),void(t.innerHTML="");var o=i(e),r="";o.forEach(function(e){r+='<div class="matching-post">\n<a href="'+e.url+'"> \n<h2>'+e.title+"</h2>\n<p>"+e.content+"</p>\n</a>\n</div>"}),t.classList.add("show"),t.innerHTML=r||'<p class="empty">'+y+"</p>"}function l(){var e,n=Docsify.dom.find("div.search"),t=Docsify.dom.find(n,"input");Docsify.dom.on(n,"click",function(e){return"A"!==e.target.tagName&&e.stopPropagation()}),Docsify.dom.on(t,"input",function(n){clearTimeout(e),e=setTimeout(function(e){return c(n.target.value.trim())},100)})}function f(e,n){var t=Docsify.dom.getNode('.search input[type="search"]');if(t)if("string"==typeof e)t.placeholder=e;else{var o=Object.keys(e).filter(function(e){return n.indexOf(e)>-1})[0];t.placeholder=e[o]}}function d(e,n){if("string"==typeof e)y=e;else{var t=Object.keys(e).filter(function(e){return n.indexOf(e)>-1})[0];y=e[t]}}function p(e,n){var t=n.router.parse().query.s;a(),s(e,t),l(),t&&setTimeout(function(e){return c(t)},500)}function u(e,n){f(e.placeholder,n.route.path),d(e.noData,n.route.path)}var h,g={},y="",m={placeholder:"Type to search",noData:"No Results!",paths:"auto",depth:2,maxAge:864e5},v=function(e,n){var t=Docsify.util,o=n.config.search||m;Array.isArray(o)?m.paths=o:"object"==typeof o&&(m.paths=Array.isArray(o.paths)?o.paths:"auto",m.maxAge=t.isPrimitive(o.maxAge)?o.maxAge:m.maxAge,m.placeholder=o.placeholder||m.placeholder,m.noData=o.noData||m.noData,m.depth=o.depth||m.depth);var i="auto"===m.paths;e.mounted(function(e){p(m,n),!i&&r(m,n)}),e.doneEach(function(e){u(m,n),i&&r(m,n)})};$docsify.plugins=[].concat(v,$docsify.plugins)}();
|
99
source.d.ts
vendored
Normal file
99
source.d.ts
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* 图块类型
|
||||||
|
*/
|
||||||
|
type Cls =
|
||||||
|
| 'autotile'
|
||||||
|
| 'animates'
|
||||||
|
| 'enemys'
|
||||||
|
| 'items'
|
||||||
|
| 'npcs'
|
||||||
|
| 'terrains'
|
||||||
|
| 'enemy48'
|
||||||
|
| 'npc48'
|
||||||
|
| 'tileset';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的可动画图块类型
|
||||||
|
*/
|
||||||
|
type AnimatableCls = Exclude<Cls, 'items' | 'terrains' | 'tileset'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 道具类型
|
||||||
|
*/
|
||||||
|
type ItemCls = 'tools' | 'items' | 'equips' | 'constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的道具id
|
||||||
|
*/
|
||||||
|
type AllIds = keyof IdToNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的道具数字
|
||||||
|
*/
|
||||||
|
type AllNumbers = keyof NumberToId | 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 某种类型的图块的id
|
||||||
|
*/
|
||||||
|
type AllIdsOf<T extends Cls> = keyof {
|
||||||
|
[P in keyof IdToCls as IdToCls[P] extends T ? P : never]: P;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 某种类型的道具的id
|
||||||
|
*/
|
||||||
|
type ItemIdOf<T extends ItemCls> = keyof {
|
||||||
|
[P in keyof ItemDeclaration as ItemDeclaration[P] extends T ? P : never]: P;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 某个道具的类型
|
||||||
|
*/
|
||||||
|
type ItemClsOf<T extends AllIdsOf<'items'>> = ItemDeclaration[T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取某个图块的类型
|
||||||
|
*/
|
||||||
|
type ClsOf<T extends AllIds> = IdToCls[T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 某种类型的图块数字
|
||||||
|
*/
|
||||||
|
type AllNumbersOf<T extends Cls> = IdToNumber[AllIdsOf<T>];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选取在一段字符串中的映射名称
|
||||||
|
*/
|
||||||
|
type NameMapIn<T extends string> = keyof {
|
||||||
|
[P in keyof NameMap as NameMap[P] extends T ? P : never]: NameMap[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的怪物id
|
||||||
|
*/
|
||||||
|
type EnemyIds = AllIdsOf<'enemys' | 'enemy48'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 各种图块的动画数量
|
||||||
|
*/
|
||||||
|
interface FrameNumbers {
|
||||||
|
autotile: 4;
|
||||||
|
animates: 4;
|
||||||
|
enemys: 2;
|
||||||
|
items: 1;
|
||||||
|
npcs: 2;
|
||||||
|
terrains: 1;
|
||||||
|
enemy48: 4;
|
||||||
|
npc48: 4;
|
||||||
|
tileset: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画帧数
|
||||||
|
*/
|
||||||
|
type FrameOf<T extends Cls> = FrameNumbers[T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的文件名
|
||||||
|
*/
|
||||||
|
type SourceIds = ImageIds | AnimationIds | SoundIds | BgmIds | FontIds;
|
129
start.md
Normal file
129
start.md
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# 快速上手
|
||||||
|
|
||||||
|
?> 在这一节中,将详细介绍做一部塔的流程。现在,让我们来做一部单层塔!
|
||||||
|
|
||||||
|
* [新版视频教程](https://www.bilibili.com/video/BV1SB4y1p7bg?share_source=copy_web)
|
||||||
|
* [脚本教程](https://www.bilibili.com/video/BV1uL411J7yZ?share_source=copy_web)
|
||||||
|
|
||||||
|
## 前置需求
|
||||||
|
你需要满足如下条件才能进行制作:[样板下载地址](https://github.com/ckcz123/mota-js/releases)。
|
||||||
|
1. 操作系统:
|
||||||
|
- Win8或更高版本,Win7则需要安装 .Net Framework
|
||||||
|
4.0(能打开根目录的“启动服务.exe”即可)
|
||||||
|
- 其他电脑则需安装[python
|
||||||
|
3.9.6](https://www.python.org/downloads/)或更高版本,能运行根目录的server.py即可(Windows也可以这样做)。
|
||||||
|
- 安卓手机需要安装[HTML5安卓造塔器](http://h5mota.com/games/_client/H5motaMaker.apk),推荐搭配ES文件浏览器。
|
||||||
|
2. [谷歌chrome浏览器](http://google.cn/chrome)或win10自带的新版Edge浏览器:其他浏览器可能导致本地服务闪退。
|
||||||
|
3. (强烈推荐)[VScode](http://code.visualstudio.com/download):最适合HTML5项目的文本编辑器,能进行跨文件的正则搜索和替换,也能完整发挥根目录的runtime.d.ts文件的作用。
|
||||||
|
|
||||||
|
只要满足了上述条件,你就可以开始做自己的塔啦!
|
||||||
|
|
||||||
|
## 启动HTTP服务
|
||||||
|
|
||||||
|
与编辑器闭源的RPG Maker MV/MZ不同,本样板对文件的绝大部分修改是通过网页编辑器经由本地HTTP服务完成的,这样做也有助于编辑器跨平台并最大限度地复用运行时代码,还可以让玩家在在线游戏时查看游戏工程。
|
||||||
|
|
||||||
|
在根目录下有一个“启动服务.exe”,运行之。(非Windows电脑则需使用命令行运行`server.py`,安卓手机则使用造塔器)
|
||||||
|
|
||||||
|
!> 使用python启动服务的话,整个塔的绝对路径必须是全英文!
|
||||||
|
|
||||||
|
![image](img/server.jpg)
|
||||||
|
|
||||||
|
* 启动游戏:打开[http://127.0.0.1:1055/index.html](http://127.0.0.1:1055/index.html),同时开启多个启动服务则1056、1057顺延(下同)。你能在里面看到现在游戏的效果。
|
||||||
|
* 启动编辑器:打开[http://127.0.0.1:1055/editor.html](http://127.0.0.1:1055/editor.html)(竖屏则为editor-mobile.html),这是您整个制作流程的核心页面
|
||||||
|
|
||||||
|
以下为Windows专用的一些辅助工具(位于“辅助工具”文件夹),由C#编写:
|
||||||
|
|
||||||
|
* 便捷PS工具:能方便地替换和新增32×32、32×48素材。V2.8.1对该工具进行了大幅强化,您甚至可以指定32×16的格子尺寸,作为前述两种尺寸的过渡操作。
|
||||||
|
* 地图生成器:识别RPG Maker魔塔的地图截图,生成HTML5魔塔的地图数据。
|
||||||
|
* 怪物数据导出:从RPG Maker XP 1.03游戏导出怪物数据,用于HTML5魔塔或使用Excel查看。
|
||||||
|
* RM动画导出:从RPG Maker XP 1.03游戏导出动画,用于HTML5魔塔。
|
||||||
|
* JS代码压缩:对js代码(限es5)和音像素材(背景音乐除外)进行压缩,从而减小文件体积,加快在线游戏的加载。一般无需手动运行,发布后我们的服务器会处理的。
|
||||||
|
* 动画编辑器:编辑`project\animates`文件夹中的动画文件,或利用图片制作全新的动画。
|
||||||
|
* 伤害和临界值计算器、帮助文档:如题,后者会打开本文档。
|
||||||
|
|
||||||
|
!> **整个造塔过程中,启动服务必须全程处于开启状态!切不可手滑关闭,否则做的都是无用功!**
|
||||||
|
|
||||||
|
![image](img/V2_8server.jpg)
|
||||||
|
|
||||||
|
V2.7.x中,“地图生成器”因为图片文件被拆分到两个文件夹的缘故而无法再使用,该问题已在V2.8中修复,同时该工具更名为“截图识别器”。
|
||||||
|
|
||||||
|
V2.8中,“额外素材合并”不再在本地进行而是改在作品发布时在服务器端用python实现,并把实现原理改为“将未用到的图块透明化后,全图裁剪到可能的最小尺寸”,以避免图块id错乱的问题。
|
||||||
|
|
||||||
|
## 编辑器页面的结构
|
||||||
|
|
||||||
|
![image](img/editor.jpg)
|
||||||
|
|
||||||
|
如上图,编辑器页面的结构分为三大部分。左边叫**数据区**,中央叫**地图区**,右侧叫**素材区**,竖屏状态下同时只能显示其中一个,需要经常来回切换。
|
||||||
|
|
||||||
|
请尤其注意中央下方的下拉框,您可以随时按下Z、X、…、句号键(字母键第三行)让数据区在这些模式之间切换。更多键鼠快捷操作请按下H键查看,这里列出一部分:
|
||||||
|
1. **Alt+0~9、0~9:**给素材区的图块绑定数字快捷键,并使用。(您也可以用中央下方的“最近/最常使用图块”和置顶来代替)
|
||||||
|
2. **WASD、或单击/长按四个箭头按钮:**滚动大地图,还可以单击“大地图”按钮观看全景。
|
||||||
|
3. **Ctrl+W/A/S/Z/X/C/V/Y:**关闭、全选、保存、撤销、剪切、复制、粘贴、重做绘图等操作。
|
||||||
|
4. **PageUp/PageDown或滚轮:**切换楼层。
|
||||||
|
5. **ESC、Delete:**取消选中并保存、删除选中点的图块和事件。(需要保存时“保存地图”按钮也会变色提示)
|
||||||
|
6. **地图上单击、双击、右击:**地图选点、选中该点的素材并自动定位到素材区、弹出菜单(您可以进行出生点、机关门、上下楼的快速绑定等操作)
|
||||||
|
7. **地图上左键或右键拖动:**交换两个点的图块和事件、框选一些点供Ctrl+X/C/V剪切复制。
|
||||||
|
8. **素材区最右侧的tileset区域左键拖动:**框选一批素材,供在地图区单击批量绘制或左键拖动平铺。(右击或触屏点击则是手动输入所需的宽高)
|
||||||
|
9. **事件编辑器中Ctrl+S/Z/X/C/V/Y、右击、双击等:**执行相应操作,如双击可以进入多行编辑/绘制预览/弹窗选文件/地图选点,地图选点时右击可以选多个点。
|
||||||
|
|
||||||
|
## 快速上手
|
||||||
|
|
||||||
|
针对红海塔作者,这里给出一个极简版的造塔流程,您可以据此造出一座没有任何事件(包括但不限于难度分歧、老人、木牌、商人和商店等)的塔:
|
||||||
|
1. 编辑勇士的出生点和初始属性:
|
||||||
|
1. 滚轮切换到主塔0层,右击地图任意位置,绑定出生点为此点(会有一个大大的白色S字母显示出来)。
|
||||||
|
2. 按下B键切换到“全塔属性”,填写`core.firstData.hero`中勇士的各项初始属性,以及一些全局的数值(如四种血瓶和三种宝石的效果、破甲反击净化的比例等,注意“唯一英文标识符”name一定要修改!)。
|
||||||
|
3. 在数据区使用滚轮向下翻(您可以随时折叠全塔属性的几大部分),按需编辑下面的大量勾选框(主要就是状态栏的那些显示项)。
|
||||||
|
2. 从素材区选择各种各样的非NPC图块绘制在地图上,如门、怪物、道具、楼梯、路障、箭头、踩灯、箱子等。每当选中一个素材时,数据区就进入了“图块属性”模式,您可以去填写道具的一些说明、以及修改其他一些图块的可通行性等。注意滑冰(触发器为ski)要画在背景层。如果您需要制作机关门,请简单地将机关门和守卫(不支持阻击怪和炸弹)画在地图上,再右击机关门快速绑定即可。(看到机关门左下角出现橙色小方块、守卫们左下角出现黄色小方块即说明绑定成功)
|
||||||
|
3. 如果您需要制作多个楼层,只需按下Z键将数据区切换到“地图编辑”模式,然后“新建空白地图”即可,不同楼层之间简单地通过楼梯来往返,您可以将楼梯画在地图上再右击快速绑定即可。(看到楼梯左下角出现绿色小方块即说明绑定成功)各个楼层的属性可以通过按下V键将数据区切换到“楼层属性”模式来填写,如能否使用楼传、是否为地下层、画面色调、宝石血瓶倍率等。
|
||||||
|
4. 从素材区选择您所使用的各种怪物,在数据区填写它们的各项属性,其中“特殊属性”是通过多选框来编辑的。
|
||||||
|
5. 游戏胜利的触发:滚轮切换到样板1层,单击地图上的公主,按下Ctrl+C复制。滚轮切换回您的boss层,(记得给boss设置不可被炸哦)单击boss身后的任何一个空格子,按下Ctrl+V粘贴即可。这样玩家触碰到公主游戏就会胜利。
|
||||||
|
|
||||||
|
## 控制台调试
|
||||||
|
|
||||||
|
![image](img/console2.jpg)
|
||||||
|
|
||||||
|
HTML5项目都是可以进行控制台调试的,下面以edge浏览器为例介绍其部分操作:
|
||||||
|
|
||||||
|
首先按下F12键(部分键盘没有此键或需与Fn键一起按)或Ctrl+Shift+I,打开开发人员界面。
|
||||||
|
|
||||||
|
可以看到它分为“元素”、“控制台”、“调试程序”、“性能”等多个部分:
|
||||||
|
1. **元素:**您可以在这里对游戏和编辑器的各HTML和css元素进行查看和临时的修改,譬如您想观察游戏在竖屏的表现,只需将窗口拉到瘦高。
|
||||||
|
2. **性能:**您可以在这里对游戏的任何一段脚本进行性能分析,观察其中各行的执行频率和耗时,从而确定优化的方向。
|
||||||
|
3. **调试程序:**您可以在这里查看游戏的源码,包括project文件夹的functions.js和plugin.js(脚本编辑和插件编写)以及整个libs文件夹,并进行断点调试。
|
||||||
|
4. **控制台:**最常使用的部分,当编辑器或游戏打不开、卡死、或者不按您的预想运作时您就需要查看这里的报错信息。这里也是各种API输入的地方,譬如上图中您可以看到全部的插件函数。
|
||||||
|
|
||||||
|
在控制台中,我们可以输入一些命令对游戏进行调试,常见的命令有:
|
||||||
|
|
||||||
|
- `core.status.floorId` 获得当前层的floorId。
|
||||||
|
- `core.status.thisMap` 获得当前地图信息。例如`core.status.thisMap.blocks`可以获得当前层所有图块。
|
||||||
|
- `core.floors` 获得所有楼层的初始信息。例如`core.floors[core.status.floorId].events`可以获得当前层所有事件。
|
||||||
|
- `core.status.hero` (或简写为hero)获得当前勇士状态信息。例如`core.status.hero.atk`就是当前勇士的攻击力数值。
|
||||||
|
- `core.material.enemys` 获得所有怪物信息。例如`core.material.enemys.greenSlime`就是获得绿色史莱姆的属性数据。
|
||||||
|
- `core.material.items` 获得所有道具的信息。例如`core.material.items.pickaxe`就是获得破墙镐的信息。
|
||||||
|
- `core.debug()` 开启调试模式;此模式下可以按住Ctrl键进行穿墙。
|
||||||
|
- `core.updateStatusBar()` 立刻更新状态栏、地图显伤并触发自动事件。
|
||||||
|
- `core.setStatus('atk', 1000)` 直接设置勇士的某项属性。本句等价于 `core.status.hero.atk = 1000`。
|
||||||
|
- `core.getStatus('atk')` 返回勇士当前某项属性数值。本句等价于 `core.status.hero.atk`。
|
||||||
|
- `core.setItem('pickaxe', 10)` 直接设置勇士某个道具的个数。这里可以需要写道具的ID。
|
||||||
|
- `core.getItem('pickaxe', 2)` 令勇士获得两个破墙镐。
|
||||||
|
- `core.itemCount('pickaxe')` 返回勇士某个道具的个数。
|
||||||
|
- `core.hasItem('pickaxe')` 返回勇士是否拥有某个道具。等价于`core.itemCount('pickaxe')>0`。
|
||||||
|
- `core.getEquip(0)` 返回0号装备类型(武器)的当前装备的itemId,不存在则返回`null`
|
||||||
|
- `core.hasEquip('sword1')` 返回某个装备当前是否处于被装备状态
|
||||||
|
- `core.setFlag('xxx', 1)` 设置某个flag/自定义变量的值,可以设为`null`表示删除。
|
||||||
|
- `core.getFlag('xxx', 10)` 获得某个flag/自定义变量的值;如果该项不存在(未被定义),则返回第二个参数的值。
|
||||||
|
- `core.hasFlag('xxx')` 返回是否存在某个变量且不为0。等价于`!!core.getFlag('xxx', 0)`。
|
||||||
|
- `core.removeFlag('xxx')` 删除某个flag/自定义变量
|
||||||
|
- `core.insertAction(list)` 执行一段自定义事件。比如 `core.insertAction(["剧情文本"])` 将执行一个剧情文本显示事件。
|
||||||
|
- `core.changeFloor('MT2', 'downFloor')` 立刻执行楼层切换到MT2层的下楼点位置。
|
||||||
|
- `core.changeFloor('MT5', null, {'x': 4, 'y': 7})` 立刻切换楼层到MT5层的(4,7)点。
|
||||||
|
- `core.getBlock(3, 5, 'MT1')` 获得当前地图上某一个块的信息。第三个参数为floorId,可省略表示当前楼层。
|
||||||
|
- `core.getBlockId(3, 5, 'MT1')` 获得当前地图上某一个点的图块ID。第三个参数为floorId,可省略表示当前楼层。
|
||||||
|
- `core.resetMap()` 重置当前层地图。
|
||||||
|
- ……
|
||||||
|
|
||||||
|
更多关于控制台调试和脚本的信息可参见[脚本](script)和[API列表](api)。
|
||||||
|
|
||||||
|
==========================================================================================
|
||||||
|
|
||||||
|
[继续阅读下一章:元件说明](element)
|
974
status.d.ts
vendored
Normal file
974
status.d.ts
vendored
Normal file
@ -0,0 +1,974 @@
|
|||||||
|
/**
|
||||||
|
* 怪物buff缓存
|
||||||
|
*/
|
||||||
|
interface EnemyBuffCache {
|
||||||
|
/**
|
||||||
|
* 生命值提升量
|
||||||
|
*/
|
||||||
|
hp_buff: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 攻击提升量
|
||||||
|
*/
|
||||||
|
atk_buff: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防御提升量
|
||||||
|
*/
|
||||||
|
def_buff: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支援信息
|
||||||
|
*/
|
||||||
|
guards: [number, number, string][];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CheckBlockStatus {
|
||||||
|
/**
|
||||||
|
* 捕捉信息
|
||||||
|
*/
|
||||||
|
ambush: Record<LocString, [number, number, string, Dir]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阻击信息
|
||||||
|
*/
|
||||||
|
repulse: Record<LocString, [number, number, string, Dir]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每点的伤害,小于等于0会不显示
|
||||||
|
*/
|
||||||
|
damage: Record<LocString, number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要重算
|
||||||
|
*/
|
||||||
|
needCache: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每点的伤害类型
|
||||||
|
*/
|
||||||
|
type: Record<LocString, Record<string, number>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存信息,是每个怪物受到的光环加成
|
||||||
|
*/
|
||||||
|
cache: Record<string, DeepReadonly<EnemyBuffCache>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 光环信息
|
||||||
|
*/
|
||||||
|
halo: Record<LocString, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DamageStatus {
|
||||||
|
/**
|
||||||
|
* v2优化下当前的偏移横坐标,单位格子
|
||||||
|
*/
|
||||||
|
posX: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v2优化下当前的偏移纵坐标,单位格子
|
||||||
|
*/
|
||||||
|
posY: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示的伤害信息
|
||||||
|
*/
|
||||||
|
data: DamageStatusData[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地图伤害或其它在地图上显示的文字
|
||||||
|
*/
|
||||||
|
extraData: DamageStatusExtraData[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不同方向伤害不同的信息
|
||||||
|
*/
|
||||||
|
dir: DamageDirData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DamageStatusData {
|
||||||
|
/**
|
||||||
|
* 显示的文字
|
||||||
|
*/
|
||||||
|
text: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示横坐标,单位像素
|
||||||
|
*/
|
||||||
|
px: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示纵坐标,单位像素
|
||||||
|
*/
|
||||||
|
py: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文字的颜色
|
||||||
|
*/
|
||||||
|
color: Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DamageStatusExtraData extends DamageStatusData {
|
||||||
|
/**
|
||||||
|
* 文字的不透明度
|
||||||
|
*/
|
||||||
|
alpha: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DamageDirData {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
dir: Dir;
|
||||||
|
color: Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AutomaticRouteStatus {
|
||||||
|
/**
|
||||||
|
* 勇士是否正在移动
|
||||||
|
*/
|
||||||
|
autoHeroMove: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不太清楚用处
|
||||||
|
*/
|
||||||
|
autoStep: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动寻路中已经走过的步数
|
||||||
|
*/
|
||||||
|
movedStep: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动寻路的总步数
|
||||||
|
*/
|
||||||
|
destStep: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动寻路的目标横坐标
|
||||||
|
*/
|
||||||
|
destX: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动寻路的目标纵坐标
|
||||||
|
*/
|
||||||
|
destY: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动寻路绘图时的偏移横坐标,单位像素
|
||||||
|
*/
|
||||||
|
offsetX: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动寻路绘图时的偏移纵坐标,单位像素
|
||||||
|
*/
|
||||||
|
offsetY: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动寻路的路线
|
||||||
|
*/
|
||||||
|
autoStepRoutes: AutoStep[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 剩下的自动寻路路线
|
||||||
|
*/
|
||||||
|
moveStepBeforeStop: AutoStep[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上一步的勇士方向
|
||||||
|
*/
|
||||||
|
lastDirection: Dir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 光标界面(按下E时的界面)的光标横坐标
|
||||||
|
*/
|
||||||
|
cursorX: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 光标界面(按下E时的界面)的光标纵坐标
|
||||||
|
*/
|
||||||
|
cursorY: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否瞬间移动
|
||||||
|
*/
|
||||||
|
moveDirectly: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AutoStep {
|
||||||
|
/**
|
||||||
|
* 当前步的步数
|
||||||
|
*/
|
||||||
|
step: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前步走向的方向
|
||||||
|
*/
|
||||||
|
direction: Dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReplaySaveBase {
|
||||||
|
/**
|
||||||
|
* 录像播放时,剩下的要播放的录像内容
|
||||||
|
*/
|
||||||
|
toReplay: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录像播放时,录像的完整信息
|
||||||
|
*/
|
||||||
|
totalList: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不太清楚用处,应该是与录像回退有关的
|
||||||
|
*/
|
||||||
|
steps: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReplayStatus extends ReplaySaveBase {
|
||||||
|
/**
|
||||||
|
* 当前是否正在播放录像,同core.isReplaying()
|
||||||
|
*/
|
||||||
|
replaying: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前录像有没有暂停
|
||||||
|
*/
|
||||||
|
pausing: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否正在某段动画中
|
||||||
|
*/
|
||||||
|
animate: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录像播放是否失败
|
||||||
|
*/
|
||||||
|
failed: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前的录像播放速度
|
||||||
|
*/
|
||||||
|
speed: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录像的回退信息
|
||||||
|
*/
|
||||||
|
save: ReplaySave[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReplaySave {
|
||||||
|
/**
|
||||||
|
* 回退的存档信息
|
||||||
|
*/
|
||||||
|
data: Save;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回退的录像信息
|
||||||
|
*/
|
||||||
|
replay: ReplaySaveBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TextAttribute {
|
||||||
|
/**
|
||||||
|
* 文本框的位置
|
||||||
|
*/
|
||||||
|
position: TextPosition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本的左右对齐方式
|
||||||
|
*/
|
||||||
|
align: 'left' | 'center' | 'right';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 偏移像素
|
||||||
|
*/
|
||||||
|
offset: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标题颜色
|
||||||
|
*/
|
||||||
|
title: RGBArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 背景颜色
|
||||||
|
*/
|
||||||
|
background: RGBArray | ImageIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文字颜色
|
||||||
|
*/
|
||||||
|
text: RGBArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标题字体大小
|
||||||
|
*/
|
||||||
|
titlefont: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正文字体大小
|
||||||
|
*/
|
||||||
|
textfont: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否加粗
|
||||||
|
*/
|
||||||
|
bold: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打字机时间,每隔多少毫秒显示一个字
|
||||||
|
*/
|
||||||
|
time: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符间距
|
||||||
|
*/
|
||||||
|
letterSpacing: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 淡入淡出时间
|
||||||
|
*/
|
||||||
|
animateTime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行距
|
||||||
|
*/
|
||||||
|
lineHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StatusStyle {
|
||||||
|
/**
|
||||||
|
* 游戏的边框颜色
|
||||||
|
*/
|
||||||
|
borderColor: Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态栏文字的颜色
|
||||||
|
*/
|
||||||
|
statusBarColor: Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 楼层切换样式,css字符串
|
||||||
|
*/
|
||||||
|
floorChangingStyle: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局字体
|
||||||
|
*/
|
||||||
|
font: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GlobalAttribute extends StatusStyle {
|
||||||
|
/**
|
||||||
|
* 装备栏名称
|
||||||
|
*/
|
||||||
|
equipName: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FloorAnimate {
|
||||||
|
/**
|
||||||
|
* 图片的目标画布
|
||||||
|
*/
|
||||||
|
canvas: 'bg' | 'fg';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片的名称
|
||||||
|
*/
|
||||||
|
name: ImageIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制横坐标
|
||||||
|
*/
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制纵坐标
|
||||||
|
*/
|
||||||
|
y: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 裁剪横坐标
|
||||||
|
*/
|
||||||
|
sx?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 裁剪纵坐标
|
||||||
|
*/
|
||||||
|
sy?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 裁剪宽度
|
||||||
|
*/
|
||||||
|
w?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 裁剪高度
|
||||||
|
*/
|
||||||
|
h?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制帧数
|
||||||
|
*/
|
||||||
|
frame?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片翻转
|
||||||
|
*/
|
||||||
|
reverse?: ':x' | ':y' | ':o';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用
|
||||||
|
*/
|
||||||
|
disable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BoxAnimate {
|
||||||
|
/**
|
||||||
|
* 动画的帧数
|
||||||
|
*/
|
||||||
|
animate: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 背景的高度
|
||||||
|
*/
|
||||||
|
bgHeight: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 背景的宽度
|
||||||
|
*/
|
||||||
|
bgWidth: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 背景的左上角横坐标
|
||||||
|
*/
|
||||||
|
bgx: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 背景的左上角纵坐标
|
||||||
|
*/
|
||||||
|
bgy: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画图片的高度
|
||||||
|
*/
|
||||||
|
height: 32 | 48;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片信息
|
||||||
|
*/
|
||||||
|
img: HTMLImageElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个图块的图片在其素材图片的纵坐标
|
||||||
|
*/
|
||||||
|
pos: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图块的横坐标
|
||||||
|
*/
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图块的纵坐标
|
||||||
|
*/
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BigImageBoxAnimate {
|
||||||
|
/**
|
||||||
|
* 大图片的贴图信息
|
||||||
|
*/
|
||||||
|
bigImage: HTMLImageElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贴图的朝向
|
||||||
|
*/
|
||||||
|
face: Dir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中心横坐标
|
||||||
|
*/
|
||||||
|
centerX: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中心纵坐标
|
||||||
|
*/
|
||||||
|
centerY: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大宽度
|
||||||
|
*/
|
||||||
|
max_width: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制到的画布
|
||||||
|
*/
|
||||||
|
ctx: CtxRefer;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnimateObj {
|
||||||
|
/**
|
||||||
|
* 动画名称
|
||||||
|
*/
|
||||||
|
name: AnimationIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画的唯一标识符
|
||||||
|
*/
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画信息
|
||||||
|
*/
|
||||||
|
animate: Animate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中心横坐标
|
||||||
|
*/
|
||||||
|
centerX: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中心纵坐标
|
||||||
|
*/
|
||||||
|
centerY: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前帧数
|
||||||
|
*/
|
||||||
|
index: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调函数
|
||||||
|
*/
|
||||||
|
callback: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionsPreview {
|
||||||
|
/**
|
||||||
|
* 大地图中当前是否正在拖拽
|
||||||
|
*/
|
||||||
|
dragging: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大地图中是否允许拖拽
|
||||||
|
*/
|
||||||
|
enabled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大地图中当前是否已经按下了鼠标
|
||||||
|
*/
|
||||||
|
prepareDragging: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前鼠标的横坐标
|
||||||
|
*/
|
||||||
|
px: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前鼠标的纵坐标
|
||||||
|
*/
|
||||||
|
py: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RouteFolding {
|
||||||
|
/**
|
||||||
|
* 录像折叠信息中的勇士信息
|
||||||
|
*/
|
||||||
|
hero: Omit<SelectType<HeroStatus, number>, 'steps'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 折叠的长度
|
||||||
|
*/
|
||||||
|
length: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始游戏状态
|
||||||
|
*/
|
||||||
|
interface InitGameStatus {
|
||||||
|
/**
|
||||||
|
* 是否开始了游戏
|
||||||
|
*/
|
||||||
|
played: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏是否结束
|
||||||
|
*/
|
||||||
|
gameOver: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有楼层的地图信息
|
||||||
|
*/
|
||||||
|
maps: {
|
||||||
|
[P in FloorIds]: Floor<P>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 背景图块
|
||||||
|
*/
|
||||||
|
bgmaps: Record<FloorIds, number[][]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前景图块
|
||||||
|
*/
|
||||||
|
fgmaps: Record<FloorIds, number[][]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以坐标列举的图块
|
||||||
|
*/
|
||||||
|
mapBlockObjs: Record<FloorIds, Record<LocString, Block>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 伤害显示信息
|
||||||
|
*/
|
||||||
|
damage: DamageStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否锁定了用户控制
|
||||||
|
*/
|
||||||
|
lockControl: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士移动状态,每个数字干啥的自己去libs翻,这东西太复杂了,不过应该不会有人用这个东西吧(
|
||||||
|
*/
|
||||||
|
heroMoving: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士是否停下了
|
||||||
|
*/
|
||||||
|
heroStop: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动寻路状态
|
||||||
|
*/
|
||||||
|
automaticRoute: DeepReadonly<AutomaticRouteStatus>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按键按下的时间,用于判定双击
|
||||||
|
*/
|
||||||
|
downTime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ctrl键是否倍按下
|
||||||
|
*/
|
||||||
|
ctrlDown: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前录像信息
|
||||||
|
*/
|
||||||
|
route: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前的回放状态
|
||||||
|
*/
|
||||||
|
replay: DeepReadonly<ReplayStatus>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前的所有全局商店
|
||||||
|
*/
|
||||||
|
shops: Record<string, ShopEventOf<keyof ShopEventMap>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前的事件状态,样板最大的败笔之一,离谱到逆天
|
||||||
|
*/
|
||||||
|
event: EventStatusOf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前的所有自动事件
|
||||||
|
*/
|
||||||
|
autoEvents: DeepReadonly<AutoEvent[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前的全局剧情文本设置
|
||||||
|
*/
|
||||||
|
textAttribute: TextAttribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部分全局属性,会跟随存档
|
||||||
|
*/
|
||||||
|
globalAttribute: GlobalAttribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 色调的颜色
|
||||||
|
*/
|
||||||
|
curtainColor: Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局动画对象
|
||||||
|
*/
|
||||||
|
globalAnimateObjs: Block<
|
||||||
|
IdToNumber[AllIdsOf<Exclude<AnimatableCls, 'autotile'>>]
|
||||||
|
>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 楼层贴图
|
||||||
|
*/
|
||||||
|
floorAnimateObjs: FloorAnimate[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的BoxAnimate信息
|
||||||
|
*/
|
||||||
|
boxAnimateObjs: (BoxAnimate | BigImageBoxAnimate)[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的自动元件动画
|
||||||
|
*/
|
||||||
|
autotileAnimateObjs: Block<IdToNumber[AllIdsOf<'autotile'>]>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局动画状态,每经过一个全局动画时间便加一
|
||||||
|
*/
|
||||||
|
globalAnimateStatus: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有绘制的动画
|
||||||
|
*/
|
||||||
|
animateObjs: AnimateObj[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前难度
|
||||||
|
*/
|
||||||
|
hard: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的中心
|
||||||
|
*/
|
||||||
|
heroCenter: Record<'px' | 'py', number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前按下的按键
|
||||||
|
*/
|
||||||
|
holdingKeys: number[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id转数字
|
||||||
|
*/
|
||||||
|
id2number: IdToNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字转图块
|
||||||
|
*/
|
||||||
|
number2block: {
|
||||||
|
[P in keyof NumberToId]: Block<P>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大地图中的拖拽处理
|
||||||
|
*/
|
||||||
|
preview: ActionsPreview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录像折叠信息
|
||||||
|
*/
|
||||||
|
routeFolding: Record<`${LocString},${FirstCharOf<Dir>}`, RouteFolding>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行时的游戏状态
|
||||||
|
*/
|
||||||
|
interface GameStatus extends InitGameStatus {
|
||||||
|
/**
|
||||||
|
* 当前层的floorId
|
||||||
|
*/
|
||||||
|
floorId: FloorIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得当前楼层信息,等价于core.status.maps[core.status.floorId]
|
||||||
|
*/
|
||||||
|
thisMap: Floor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地图伤害
|
||||||
|
*/
|
||||||
|
checkBlock: Readonly<CheckBlockStatus>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前勇士状态信息。例如core.status.hero.atk就是当前勇士的攻击力数值
|
||||||
|
*/
|
||||||
|
hero: HeroStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Follower {
|
||||||
|
/**
|
||||||
|
* 跟随者的图片id
|
||||||
|
*/
|
||||||
|
name: ImageIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HeroStatistics {
|
||||||
|
/**
|
||||||
|
* 击败的怪物数量
|
||||||
|
*/
|
||||||
|
battle: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由于战斗损失的生命值
|
||||||
|
*/
|
||||||
|
battleDamage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前游戏时间
|
||||||
|
*/
|
||||||
|
currentTime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得的总经验值
|
||||||
|
*/
|
||||||
|
exp: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由于地图伤害损失的生命值
|
||||||
|
*/
|
||||||
|
extraDamage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总共损失的生命值
|
||||||
|
*/
|
||||||
|
hp: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由于瞬移少走的步数
|
||||||
|
*/
|
||||||
|
ignoreSteps: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总共获得的金币数
|
||||||
|
*/
|
||||||
|
money: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 瞬移次数
|
||||||
|
*/
|
||||||
|
moveDirectly: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中毒损失的生命值
|
||||||
|
*/
|
||||||
|
poisonDamage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本次游戏的开始时间
|
||||||
|
*/
|
||||||
|
startTime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏总时间
|
||||||
|
*/
|
||||||
|
totalTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士状态
|
||||||
|
*/
|
||||||
|
interface HeroStatus {
|
||||||
|
/**
|
||||||
|
* 勇士停止时及对话框中是否启用帧动画
|
||||||
|
*/
|
||||||
|
animate: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士生命值
|
||||||
|
*/
|
||||||
|
hp: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士生命回复
|
||||||
|
*/
|
||||||
|
hpmax: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的攻击
|
||||||
|
*/
|
||||||
|
atk: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的防御
|
||||||
|
*/
|
||||||
|
def: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的魔防
|
||||||
|
*/
|
||||||
|
mdef: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的等级
|
||||||
|
*/
|
||||||
|
lv: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的经验
|
||||||
|
*/
|
||||||
|
exp: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的金币
|
||||||
|
*/
|
||||||
|
money: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的额外攻击
|
||||||
|
*/
|
||||||
|
mana: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的魔法上限
|
||||||
|
*/
|
||||||
|
manamax: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的名称
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士移动过的步数
|
||||||
|
*/
|
||||||
|
steps: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的图片
|
||||||
|
*/
|
||||||
|
image: ImageIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前勇士的装备
|
||||||
|
*/
|
||||||
|
equipment: ItemIdOf<'equips'>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士当前的位置
|
||||||
|
*/
|
||||||
|
loc: DiredLoc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前的变量
|
||||||
|
*/
|
||||||
|
flags: Flags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士的跟随者
|
||||||
|
*/
|
||||||
|
followers: Follower[];
|
||||||
|
|
||||||
|
statistics: HeroStatistics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士拥有的道具
|
||||||
|
*/
|
||||||
|
items: {
|
||||||
|
[P in Exclude<ItemCls, 'items'>]: Record<ItemIdOf<P>, number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士学习的特技
|
||||||
|
*/
|
||||||
|
special: {
|
||||||
|
num: number[];
|
||||||
|
last: number[];
|
||||||
|
[k: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
floorId?: FloorIds;
|
||||||
|
}
|
864
ui-editor.md
Normal file
864
ui-editor.md
Normal file
@ -0,0 +1,864 @@
|
|||||||
|
# UI编辑器
|
||||||
|
|
||||||
|
## 基础操作
|
||||||
|
|
||||||
|
打开编辑界面后即可编辑,通过添加新操作可以向ui中添加新的内容。点击一个操作后可以选中该操作,被选中的操作将呈现黄色,可按住ctrl多选。此时点击鼠标右键,可以打开右键菜单,包括在上方插入、复制等操作。执行在上方或下方插入时,会在鼠标点击的操作上方或下方插入,执行复制或剪切时会复制或剪切所有被选中的操作,被剪切的操作将会呈现灰色。当ui编辑到一半时,可以点击保存按钮进行保存,当ui未保存时,保存按钮应当显示为黄色,点击保存后,如果保存成功,按钮会短暂变成绿色。当ui编辑完成后,可以点击预览按钮从头开始预览ui,确认符合预期后,可以点击编译按钮将操作编译为js代码,然后将该代码按照编译完成的注释操作,将代码放入插件中,在需要的地方插入即可。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 设置过渡不会立刻生效,会在下一个动画帧中生效,因此设置之后可能需要大概20~50ms的等待操作
|
||||||
|
- 画布无法更改名称
|
||||||
|
- 对于绘制成段文本,如果设置了打字机速度,那么两个该操作不能同时进行
|
||||||
|
- 临时禁用只对预览有效,编译时被临时禁用的操作仍会被编译
|
||||||
|
- 遇到问题或bug、建议等请在造塔群及时反馈
|
||||||
|
|
||||||
|
## 编译
|
||||||
|
|
||||||
|
该操作会将当前编辑器显示的所有操作编译为js代码。编译完成后,应当放到插件中一个形式为`this.xxx = function () { ui内容 }`的函数中,在需要显示该ui时,只需执行代码`core.xxx()`
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 插件中
|
||||||
|
this.myUi = function() {
|
||||||
|
// 请复制以下内容至插件中以使用
|
||||||
|
// 使用案例:
|
||||||
|
/*
|
||||||
|
插件中:
|
||||||
|
this.showMyUi = function () {
|
||||||
|
函数内容
|
||||||
|
}
|
||||||
|
调用时:
|
||||||
|
core.showMyUi();
|
||||||
|
*/
|
||||||
|
var _sprite_0 = new Sprite(100, 100, 100, 100, 200, 'game', '_sprite_0');
|
||||||
|
_sprite_0.setCss('display: none;');
|
||||||
|
|
||||||
|
action_0_6();
|
||||||
|
|
||||||
|
function action_0_6() {
|
||||||
|
_sprite_0.setCss('display: block;');
|
||||||
|
_sprite_0.move(100, 100);
|
||||||
|
_sprite_0.resize(100, 100);
|
||||||
|
_sprite_0.canvas.style.zIndex = '200';
|
||||||
|
core.setAlpha(_sprite_0.context, 0.5);
|
||||||
|
core.fillEllipse(_sprite_0.context, 50, 50, 30, 50, 0, '');
|
||||||
|
var ctx = _sprite_0.context;
|
||||||
|
ctx.moveTo(0, 20);
|
||||||
|
ctx.bezierCurveTo(10, 10, 50, 50, 50, 100);
|
||||||
|
ctx.strokeStyle = '#fff';
|
||||||
|
ctx.lineWidth = 5;
|
||||||
|
ctx.stoke();
|
||||||
|
_sprite_0.context.globalCompositeOperation = 'difference';
|
||||||
|
core.fillRect(_sprite_0.context, 0, 0, 100, 100, '');
|
||||||
|
setTimeout(action_7_8, 1000);
|
||||||
|
}
|
||||||
|
function action_7_8() {
|
||||||
|
var res = _sprite_0.canvas.style.transition;
|
||||||
|
if (res !== '') res += ', ';
|
||||||
|
res += 'left 300ms ease-out 0ms';
|
||||||
|
_sprite_0.canvas.style.transition = res;
|
||||||
|
setTimeout(action_9_9, 50);
|
||||||
|
}
|
||||||
|
function action_9_9() {
|
||||||
|
_sprite_0.resize(100, 100, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要显示ui时
|
||||||
|
core.myUi();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预览
|
||||||
|
|
||||||
|
预览分为两种,一种是编辑时的即时预览,这种预览会显示所有的画布,并无视等待和删除,确保你能够看到所有的绘制内容,如果你想要隐藏某个操作,可以打开该操作的详细信息,然后将临时禁用勾上,这样就能隐藏该操作了。
|
||||||
|
|
||||||
|
第二种是点击预览按钮时发生的,这种预览会尽可能地还原编译后的执行结果,让你能够完整地预览ui。
|
||||||
|
|
||||||
|
## 保存
|
||||||
|
|
||||||
|
显而易见,该操作可以保存现在正在编辑的ui。注意,ui会保存在样板根目录下的_ui文件夹,以base64的形式进行保存。当你编辑了ui时,保存按钮会呈现黄色,说明此时你还没有保存,此时切出编辑页面会弹出提示。点击保存后,会出现短暂的保存中字样(保存快的话可能看不清),如果保存成功,会出现成功字样,并会将背景短暂地改成绿色
|
||||||
|
|
||||||
|
## 临时禁用
|
||||||
|
|
||||||
|
该操作可以让你能够禁用某个操作,让你在不删除操作的情况下看到删除之后的效果。该操作只对预览有效,这意味着编译仍会编译被禁用的操作。
|
||||||
|
|
||||||
|
## 编辑
|
||||||
|
|
||||||
|
### 创建画布
|
||||||
|
|
||||||
|
| 事件名称 | 创建画布 |
|
||||||
|
| --- | --- |
|
||||||
|
| 事件原形 | 创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深) |
|
||||||
|
| 功能描述 | 创建一个新的画布 |
|
||||||
|
| 输入参数 1 | 名称:画布的名称 |
|
||||||
|
| 输入参数 2 | 横坐标:画布的起点横坐标(左上角) |
|
||||||
|
| 输入参数 3 | 纵坐标:画布的起点纵坐标(左上角) |
|
||||||
|
| 输入参数 4 | 宽度:画布的宽度 |
|
||||||
|
| 输入参数 5 | 高度:画布的高度 |
|
||||||
|
| 输入参数 6 | 纵深:画布的显示优先级,数值越大优先级越高 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 创建画布 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
……
|
||||||
|
```
|
||||||
|
|
||||||
|
### 等待
|
||||||
|
|
||||||
|
| 事件名称 | 等待 |
|
||||||
|
| --- | --- |
|
||||||
|
| 事件原形 | 等待(毫秒数) |
|
||||||
|
| 功能描述 | 等待一定时间后再执行后续操作 |
|
||||||
|
| 输入参数 1 | 毫秒数:等待的时间长度 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 等待 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
等待(毫秒数);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置过渡
|
||||||
|
|
||||||
|
| 事件名称 | 设置过渡 |
|
||||||
|
| --- | --- |
|
||||||
|
| 事件原形 | 设置过渡(作用画布, 作用效果, 过渡时间, 过渡方式, 延迟) |
|
||||||
|
| 功能描述 | 指定画布前两个命令之间的渐变式变化 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 作用效果:可用于transition的style |
|
||||||
|
| 输入参数 3 | 过渡时间:变化的时间长度 |
|
||||||
|
| 输入参数 4 | 过渡方式:过渡方式,包括ease-out ease-in ease-in-out linear bezier-curve()等 |
|
||||||
|
| 输入参数 5 | 延迟:当指定属性变化后,多长时间后开始过渡 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
| 常见作用效果 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| left | x轴方向的位置 |
|
||||||
|
| top | y轴方向的位置 |
|
||||||
|
| opacity | 不透明度 |
|
||||||
|
| background-color | 背景色 |
|
||||||
|
| transform | 2D和3D变换 |
|
||||||
|
| filter | 滤镜 |
|
||||||
|
| border | 边框 |
|
||||||
|
|
||||||
|
| 过渡方式 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| ease-out | 先快后慢 |
|
||||||
|
| ease-in | 先慢后快 |
|
||||||
|
| ease-in-out | 慢-快-慢 |
|
||||||
|
| linear | 线性变化(匀速) |
|
||||||
|
| bezier-curve() | 指定按照贝塞尔曲线的形式变化,1表示到达终点,0表示起点 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 设置过渡 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
设置过渡(作用画布, 作用效果, 过渡时间, 过渡方式, 延迟);
|
||||||
|
移动画布(名称, 横坐标, 纵坐标);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 删除画布
|
||||||
|
|
||||||
|
| 事件名称 | 删除画布 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 删除画布(作用画布) |
|
||||||
|
| 功能描述 | 删除一个已经存在的画布 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 画布已经存在 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 删除画布 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
删除画布(名称);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 移动画布
|
||||||
|
|
||||||
|
| 事件名称 | 移动画布 |
|
||||||
|
| --- | --- |
|
||||||
|
| 事件原形 | 移动画布(作用画布, 横坐标, 纵坐标, 移动模式) |
|
||||||
|
| 功能描述 | 移动指定画布一定指定距离 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 横坐标:画布的起点横坐标(左上角) |
|
||||||
|
| 输入参数 3 | 纵坐标:画布的起点纵坐标(左上角) |
|
||||||
|
| 输入参数 4 | 移动模式:是,则为相对坐标模式,否则为绝对坐标模式 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
| 移动模式 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| 绝对坐标 | 横纵坐标为画布移动后左上角的坐标位置 |
|
||||||
|
| 相对坐标 | 横纵坐标为画布在横向与纵向上移动的距离值 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 移动画布 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
移动画布(名称, 横坐标, 纵坐标,false);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 缩放画布
|
||||||
|
|
||||||
|
| 事件名称 | 缩放画布 |
|
||||||
|
| --- | --- |
|
||||||
|
| 事件原形 | 缩放画布(作用画布, 宽度, 高度, 修改样式) |
|
||||||
|
| 功能描述 | 调整画布至指定尺寸大小 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 宽度:画布新的宽度尺寸 |
|
||||||
|
| 输入参数 3 | 高度:画布新的高度尺寸 |
|
||||||
|
| 输入参数 4 | 修改样式:如果是,那么只会修改css的长宽,导致模糊,如果不是,那么会修改画布的长宽,清晰,但会清空已绘制内容 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 缩放画布 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
缩放画布(名称, 宽度, 高度, false);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 旋转画布
|
||||||
|
|
||||||
|
| 事件名称 | 旋转画布 |
|
||||||
|
| --- | --- |
|
||||||
|
| 事件原形 | 旋转画布(作用画布, 中心横坐标, 中心纵坐标, 角度) |
|
||||||
|
| 功能描述 | 指定画布以指定的中心点旋转一定的角度 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 中心横坐标:旋转中心的横坐标 |
|
||||||
|
| 输入参数 3 | 中心纵坐标:旋转中心的纵坐标 |
|
||||||
|
| 输入参数 4 | 角度:旋转的角度,正数顺时针旋转,负数逆时针旋转 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 旋转画布 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
旋转画布(名称, 中心横坐标, 中心纵坐标, 角度);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 擦除画布
|
||||||
|
|
||||||
|
| 事件名称 | 擦除画布 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 擦除画布(作用画布, 横坐标, 纵坐标, 宽度, 高度) |
|
||||||
|
| 功能描述 | 擦除画布指定区域内的所有内容 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 横坐标:区域的起点横坐标(左上角) |
|
||||||
|
| 输入参数 3 | 纵坐标:区域的起点纵坐标(左上角) |
|
||||||
|
| 输入参数 4 | 宽度:区域的宽度 |
|
||||||
|
| 输入参数 5 | 高度:区域的高度 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
如果不填的话,默认擦除全部
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 擦除画布 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
擦除画布(名称, 横坐标, 纵坐标, 宽度, 高度);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置CSS效果
|
||||||
|
|
||||||
|
| 事件名称 | 设置CSS效果 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 设置CSS效果(作用画布, CSS效果) |
|
||||||
|
| 功能描述 | 修改指定画布的样式 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | CSS效果:编辑框内输入自定义的CSS效果 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
| 常用CSS效果 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| opacity | 不透明度,一个浮点型数字,在0~1区间内 |
|
||||||
|
| background-color | 背景色,可以使用`#rgba #rrggbbaa #rgb #rrggbb rgb(r,g,b) rgba(r,g,b,a) 英文单词`共7种形式 |
|
||||||
|
| background-image | 背景图片,可使用`url(project/images/image.png)`的形式 |
|
||||||
|
| box-shadow | 元素阴影,形式为 `x偏移 y偏移 阴影大小 颜色, x偏移 y偏移 阴影大小 颜色` |
|
||||||
|
| transform | 元素的2D和3D转换,用法过多,请百度搜索或在mdn搜索 |
|
||||||
|
| filter | 元素滤镜,同上 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 设置CSS效果 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
设置CSS效果(名称, CSS效果);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置混合方式
|
||||||
|
|
||||||
|
| 事件名称 | 设置混合方式 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 设置混合方式(作用画布, 混合方式) |
|
||||||
|
| 功能描述 | 设置新绘制的内容与已绘制的内容间的叠加方式 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 混合方式:设置画布的混合方式 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
混合方式说明:包括`color color-burn color-dodge copy darken destination-atop destination-in destination-out destination-over difference exclusion hard-light hue lighten lighter luminosity multiply overlay saturation screen soft-light source-atop source-in source-out source-over xor`共26种,默认为source-over,其余效果请自行百度或尝试
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 设置混合方式 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
设置混合方式(名称, 混合方式);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置绘制滤镜
|
||||||
|
|
||||||
|
| 事件名称 | 设置绘制滤镜 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 设置绘制滤镜(作用画布, 滤镜) |
|
||||||
|
| 功能描述 | 设置在绘制时的滤镜,不影响已绘制内容 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 滤镜:编辑框内输入自定义的滤镜 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
| 常见滤镜 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| blur | 高斯模糊,用法示例:blur(5px) |
|
||||||
|
| brightness | 亮度,100%为原始亮度,用法示例:brightness(120%) |
|
||||||
|
| contrast | 对比度,100%为原始对比度,用法示例:contrast(80%) |
|
||||||
|
| grayscale | 灰度,0%为原始灰度,设为100%将会变成黑白,用法示例:grayscale(75%) |
|
||||||
|
| opacity | 不透明度,100%为原始不透明度,设为0%将会完全透明,用法示例:opacity(80%) |
|
||||||
|
|
||||||
|
滤镜填写示例:`blur(5px)brightness(120%)grayscale(50%)`,注意会完全覆盖之前的效果,也就是说之前的效果会全部失效
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 设置绘制滤镜 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
设置绘制滤镜(名称, 滤镜);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置阴影
|
||||||
|
|
||||||
|
| 事件名称 | 设置阴影 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 设置阴影(作用画布, 阴影模糊, 阴影颜色, 阴影横坐标, 阴影纵坐标) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 阴影模糊:阴影的高斯模糊,与设置绘制滤镜的blur相同 |
|
||||||
|
| 输入参数 3 | 阴影颜色:与设置css效果中对background-color的描述相同 |
|
||||||
|
| 输入参数 4 | 阴影横坐标:阴影偏移绘制位置的横坐标 |
|
||||||
|
| 输入参数 5 | 阴影纵坐标:阴影偏移绘制位置的纵坐标 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 设置阴影 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
设置阴影(名称, 阴影模糊, 阴影颜色, 宽度, 高度);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置文本属性
|
||||||
|
|
||||||
|
| 事件名称 | 设置文本属性 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 设置文本属性(作用画布, 文本方向, 文本对齐, 文本基线) |
|
||||||
|
| 功能描述 | 设置文本方向、文本对齐、文本基线 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 文本方向:文字阅读的方向 |
|
||||||
|
| 输入参数 3 | 文本对齐:文本水平对齐的方向 |
|
||||||
|
| 输入参数 4 | 文本基线:文本竖直对齐的方向 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
| 文本方向 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| ltr | 从左往右 |
|
||||||
|
| rtl | 从右往左 |
|
||||||
|
|
||||||
|
| 文本对齐 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| left | 左对齐 |
|
||||||
|
| center | 居中对齐 |
|
||||||
|
| right | 右对齐 |
|
||||||
|
|
||||||
|
| 文本基线 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| bottom | 底部对齐 |
|
||||||
|
| middle | 居中对齐 |
|
||||||
|
| top | 顶部对齐 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 设置文本信息 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
设置文本属性(名称, 文本方向, 文本对齐, 文本基线);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置不透明度
|
||||||
|
|
||||||
|
| 事件名称 | 设置不透明度 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 设置不透明度(作用画布, 文本不透明度) |
|
||||||
|
| 功能描述 | 设置画布之后绘制的不透明度 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 不透明度:要设置到的不透明度 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
该操作对已绘制内容没有影响,如果想要对已绘制内容也产生影响,请使用设置css
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 设置不透明度 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
设置不透明度(名称, 不透明度);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 保存画布属性
|
||||||
|
|
||||||
|
| 事件名称 | 保存画布属性 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 保存画布属性(作用画布) |
|
||||||
|
| 功能描述 | 保存画布属性,注意之会保存之前设置的属性,如文本属性,不会保存绘制内容 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 保存画布属性 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
保存画布属性(名称);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 回退画布属性
|
||||||
|
|
||||||
|
| 事件名称 | 回退画布属性 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 回退画布属性(作用画布) |
|
||||||
|
| 功能描述 | 回退画布属性,注意只会回退画布的属性,如文本属性,不会回退绘制内容 |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 回退画布属性 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
回退画布属性(名称);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制直线
|
||||||
|
|
||||||
|
| 事件名称 | 绘制直线 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制直线(作用画布, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 连线宽度, 连线颜色) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 起点横坐标: |
|
||||||
|
| 输入参数 3 | 起点纵坐标: |
|
||||||
|
| 输入参数 4 | 终点横坐标: |
|
||||||
|
| 输入参数 4 | 终点纵坐标: |
|
||||||
|
| 输入参数 7 | 连线宽度: |
|
||||||
|
| 输入参数 8 | 连线颜色: |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制直线 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制直线(名称, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 连线宽度, 连线颜色);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制弧线
|
||||||
|
|
||||||
|
| 事件名称 | 绘制弧线 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制弧线(作用画布, 中心横坐标, 中心纵坐标, 半径, 起始弧度, 终止弧度, 是否为边框, 线条宽度, 颜色) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 横坐标 |
|
||||||
|
| 输入参数 3 | 纵坐标 |
|
||||||
|
| 输入参数 4 | 半径 |
|
||||||
|
| 输入参数 4 | 起始弧度 |
|
||||||
|
| 输入参数 4 | 终止弧度 |
|
||||||
|
| 输入参数 6 | 是否为边框 |
|
||||||
|
| 输入参数 7 | 线条宽度 |
|
||||||
|
| 输入参数 8 | 颜色 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制弧线 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制弧线(名称, 中心横坐标, 中心纵坐标, 半径, 起始弧度, 终止弧度, 是否为边框, 线条宽度, 颜色);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制圆
|
||||||
|
|
||||||
|
| 事件名称 | 绘制圆 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制圆(作用画布, 中心横坐标, 中心纵坐标, 半径, 是否为边框, 线条宽度, 颜色) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 横坐标 |
|
||||||
|
| 输入参数 3 | 纵坐标 |
|
||||||
|
| 输入参数 4 | 半径 |
|
||||||
|
| 输入参数 6 | 是否为边框 |
|
||||||
|
| 输入参数 7 | 线条宽度 |
|
||||||
|
| 输入参数 8 | 颜色 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制圆 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制圆(名称, 中心横坐标, 中心纵坐标, 半径, 是否为边框, 线条宽度, 颜色);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制矩形
|
||||||
|
|
||||||
|
| 事件名称 | 绘制矩形 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制矩形(作用画布, 横坐标, 纵坐标, 宽度, 高度, 是否为边框, 线条宽度, 颜色) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 横坐标 |
|
||||||
|
| 输入参数 3 | 纵坐标 |
|
||||||
|
| 输入参数 4 | 宽度 |
|
||||||
|
| 输入参数 5 | 高度 |
|
||||||
|
| 输入参数 6 | 是否为边框 |
|
||||||
|
| 输入参数 7 | 线条宽度 |
|
||||||
|
| 输入参数 8 | 颜色 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制矩形 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制矩形(名称, 横坐标, 纵坐标, 宽度, 高度, 是否为边框, 线条宽度, 颜色);
|
||||||
|
```
|
||||||
|
### 绘制圆角矩形
|
||||||
|
|
||||||
|
| 事件名称 | 绘制圆角矩形 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制圆角矩形(作用画布, 横坐标, 纵坐标, 宽度, 高度, 圆角半径, 是否为边框, 线条宽度, 颜色) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 横坐标 |
|
||||||
|
| 输入参数 3 | 纵坐标 |
|
||||||
|
| 输入参数 4 | 宽度 |
|
||||||
|
| 输入参数 5 | 高度 |
|
||||||
|
| 输入参数 5 | 圆角半径 |
|
||||||
|
| 输入参数 6 | 是否为边框 |
|
||||||
|
| 输入参数 7 | 线条宽度 |
|
||||||
|
| 输入参数 8 | 颜色 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制圆角矩形 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制圆角矩形(名称, 横坐标, 纵坐标, 宽度, 高度, 圆角半径, 是否为边框, 线条宽度, 颜色);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制多边形
|
||||||
|
|
||||||
|
| 事件名称 | 绘制多边形 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制多边形(作用画布, 是否为边框, 线条宽度, 颜色) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 是否为边框 |
|
||||||
|
| 输入参数 3 | 线条宽度 |
|
||||||
|
| 输入参数 4 | 颜色 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制多边形 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制多边形(名称, 是否为边框, 线条宽度, 颜色);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制椭圆
|
||||||
|
|
||||||
|
| 事件名称 | 绘制椭圆 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制椭圆(作用画布, 中心横坐标, 中心纵坐标, 半长轴, 半短轴, 旋转角度, 是否为边框, 线条宽度, 颜色) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 中心横坐标 |
|
||||||
|
| 输入参数 3 | 中心纵坐标 |
|
||||||
|
| 输入参数 4 | 半长轴 |
|
||||||
|
| 输入参数 5 | 半短轴 |
|
||||||
|
| 输入参数 6 | 旋转角度 |
|
||||||
|
| 输入参数 7 | 是否为边框 |
|
||||||
|
| 输入参数 8 | 线条宽度 |
|
||||||
|
| 输入参数 9 | 颜色 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制椭圆 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制椭圆(名称, 中心横坐标, 中心纵坐标, 半长轴, 半短轴, 旋转角度, 是否为边框, 线条宽度, 颜色)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制箭头
|
||||||
|
|
||||||
|
| 事件名称 | 绘制箭头 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制箭头(作用画布, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 连线宽度, 连线颜色) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 起点横坐标 |
|
||||||
|
| 输入参数 3 | 起点纵坐标 |
|
||||||
|
| 输入参数 4 | 终点横坐标 |
|
||||||
|
| 输入参数 5 | 终点纵坐标 |
|
||||||
|
| 输入参数 6 | 连线宽度 |
|
||||||
|
| 输入参数 7 | 连线颜色 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制箭头 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制箭头(名称, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 连线宽度, 连线颜色);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制贝塞尔曲线
|
||||||
|
|
||||||
|
| 事件名称 | 绘制贝塞尔曲线 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制贝塞尔曲线(作用画布, 控制点1横坐标, 控制点1纵坐标, 控制点2横坐标, 控制点2纵坐标, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 线条宽度, 颜色) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 控制点1横坐标 |
|
||||||
|
| 输入参数 3 | 控制点1纵坐标 |
|
||||||
|
| 输入参数 4 | 控制点2横坐标 |
|
||||||
|
| 输入参数 5 | 控制点2纵坐标 |
|
||||||
|
| 输入参数 6 | 起点横坐标 |
|
||||||
|
| 输入参数 7 | 起点纵坐标 |
|
||||||
|
| 输入参数 8 | 终点横坐标 |
|
||||||
|
| 输入参数 9 | 终点纵坐标 |
|
||||||
|
| 输入参数 10 | 线条宽度 |
|
||||||
|
| 输入参数 11 | 颜色 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制贝塞尔曲线 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制贝塞尔曲线(名称, 控制点1横坐标, 控制点1纵坐标, 控制点2横坐标, 控制点2纵坐标, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 线条宽度, 颜色);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制图片
|
||||||
|
|
||||||
|
| 事件名称 | 绘制图片 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制图片(作用画布, 图片, 裁切点横坐标, 裁切点纵坐标, 裁切宽度, 裁切高度, 绘制点横坐标, 绘制点纵坐标, 绘制宽度, 绘制高度) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 图片 |
|
||||||
|
| 输入参数 3 | 裁切点横坐标 |
|
||||||
|
| 输入参数 4 | 裁切点纵坐标 |
|
||||||
|
| 输入参数 5 | 裁切宽度 |
|
||||||
|
| 输入参数 6 | 裁切高度 |
|
||||||
|
| 输入参数 7 | 绘制点横坐标 |
|
||||||
|
| 输入参数 8 | 绘制点纵坐标 |
|
||||||
|
| 输入参数 9 | 绘制宽度 |
|
||||||
|
| 输入参数 10 | 绘制高度 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制图片 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制图片(名称, 图片, 裁切点横坐标, 裁切点纵坐标, 裁切宽度, 裁切高度, 绘制点横坐标, 绘制点纵坐标, 绘制宽度, 绘制高度);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制图标
|
||||||
|
|
||||||
|
| 事件名称 | 绘制图标 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制图标(作用画布, 图标id, 横坐标, 纵坐标, 帧数) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 图标id |
|
||||||
|
| 输入参数 3 | 横坐标 |
|
||||||
|
| 输入参数 4 | 纵坐标 |
|
||||||
|
| 输入参数 5 | 宽度 |
|
||||||
|
| 输入参数 6 | 高度 |
|
||||||
|
| 输入参数 7 | 帧数:绘制图标的第几帧 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制图标 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制图标(名称, 图标id, 横坐标, 纵坐标, 帧数);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制窗口皮肤
|
||||||
|
|
||||||
|
| 事件名称 | 绘制窗口皮肤 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制窗口皮肤(作用画布, 皮肤背景, 横坐标, 纵坐标, 宽度, 高度) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 皮肤背景:需为已注册的图片 |
|
||||||
|
| 输入参数 3 | 横坐标 |
|
||||||
|
| 输入参数 4 | 纵坐标 |
|
||||||
|
| 输入参数 5 | 宽度 |
|
||||||
|
| 输入参数 6 | 高度 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制窗口皮肤 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制窗口皮肤(名称, 皮肤背景, 横坐标, 纵坐标, 宽度, 高度);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制文本
|
||||||
|
|
||||||
|
| 事件名称 | 绘制文本 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制文本(作用画布, 文字, 横坐标, 纵坐标, 添加描边, 斜体, 字体, 字体大小, 字体粗细, 字体颜色, 描边颜色, 最大宽度) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 文本 |
|
||||||
|
| 输入参数 3 | 横坐标 |
|
||||||
|
| 输入参数 4 | 纵坐标 |
|
||||||
|
| 输入参数 5 | 添加描边 |
|
||||||
|
| 输入参数 6 | 斜体 |
|
||||||
|
| 输入参数 7 | 字体 |
|
||||||
|
| 输入参数 8 | 字体大小 |
|
||||||
|
| 输入参数 9 | 字体粗细 |
|
||||||
|
| 输入参数 10 | 字体颜色 |
|
||||||
|
| 输入参数 11 | 描边颜色 |
|
||||||
|
| 输入参数 12 | 最大宽度 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制文本 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制文本(名称, 文字, 横坐标, 纵坐标, 添加描边, 斜体, 字体, 字体大小, 字体粗细, 字体颜色, 描边颜色, 最大宽度);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘制成段文本
|
||||||
|
|
||||||
|
| 事件名称 | 绘制成段文本 |
|
||||||
|
| --- | ----------- |
|
||||||
|
| 事件原形 | 绘制成段文本(作用画布, 文本, 横坐标, 纵坐标, 宽度, 颜色, 对齐, 字体大小, 行间距, 打字机时间间隔, 字体, 字符间距, 加粗, 斜体) |
|
||||||
|
| 功能描述 | |
|
||||||
|
| 输入参数 1 | 作用画布:画布的名称 |
|
||||||
|
| 输入参数 2 | 文本 |
|
||||||
|
| 输入参数 3 | 横坐标 |
|
||||||
|
| 输入参数 4 | 纵坐标 |
|
||||||
|
| 输入参数 5 | 宽度 |
|
||||||
|
| 输入参数 6 | 颜色 |
|
||||||
|
| 输入参数 7 | 对齐 |
|
||||||
|
| 输入参数 8 | 字体大小 |
|
||||||
|
| 输入参数 9 | 行间距 |
|
||||||
|
| 输入参数 10 | 打字机时间间隔 |
|
||||||
|
| 输入参数 11 | 字体 |
|
||||||
|
| 输入参数 12 | 字符间距 |
|
||||||
|
| 输入参数 13 | 加粗 |
|
||||||
|
| 输入参数 14 | 斜体 |
|
||||||
|
| 输出参数 | 无 |
|
||||||
|
| 返回值 | 无 |
|
||||||
|
| 先决条件 | 无 |
|
||||||
|
| 调用函数 | 无 |
|
||||||
|
|
||||||
|
例:
|
||||||
|
```js
|
||||||
|
/* 这是UI编辑器中 绘制成段文本 的参考例程 */
|
||||||
|
创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深);
|
||||||
|
绘制成段文本(名称, 文本, 横坐标, 纵坐标, 宽度, 颜色, 对齐, 字体大小, 行间距, 打字机时间间隔, 字体, 字符间距, 加粗, 斜体);
|
||||||
|
```
|
||||||
|
|
||||||
|
==========================================================================================
|
||||||
|
|
||||||
|
[继续阅读下一章:API](api)
|
831
ui.d.ts
vendored
Normal file
831
ui.d.ts
vendored
Normal file
@ -0,0 +1,831 @@
|
|||||||
|
/**
|
||||||
|
* 可以设置成的画布填充描边样式
|
||||||
|
*/
|
||||||
|
type CanvasStyle = string | CanvasGradient | CanvasPattern;
|
||||||
|
|
||||||
|
type ImageSource =
|
||||||
|
| CanvasImageSource
|
||||||
|
| ImageIds
|
||||||
|
| `${ImageIds}${ImageReverse}`
|
||||||
|
| NameMapIn<ImageIds>
|
||||||
|
| `${NameMapIn<ImageIds>}${ImageReverse}`;
|
||||||
|
|
||||||
|
interface BackgroundPosInfo {
|
||||||
|
/**
|
||||||
|
* 横坐标
|
||||||
|
*/
|
||||||
|
px: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纵坐标
|
||||||
|
*/
|
||||||
|
py: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否没有位置
|
||||||
|
*/
|
||||||
|
noPeak: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 横坐标偏移值
|
||||||
|
*/
|
||||||
|
xoffset: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纵坐标偏移值
|
||||||
|
*/
|
||||||
|
yoffset: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画布,默认是ui
|
||||||
|
*/
|
||||||
|
ctx: CtxRefer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 箭头指向是朝上还是朝下
|
||||||
|
*/
|
||||||
|
position: 'up' | 'bottom';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TextContentConfig {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大宽度
|
||||||
|
*/
|
||||||
|
maxWidth: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 颜色,不影响\r
|
||||||
|
*/
|
||||||
|
color: Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对齐方式
|
||||||
|
*/
|
||||||
|
align: 'left' | 'center' | 'right';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字体大小
|
||||||
|
*/
|
||||||
|
fontSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行高
|
||||||
|
*/
|
||||||
|
lineHeight: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打字机间隔
|
||||||
|
*/
|
||||||
|
time: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字体名
|
||||||
|
*/
|
||||||
|
font: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符间距
|
||||||
|
*/
|
||||||
|
letterSpacing: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否加粗
|
||||||
|
*/
|
||||||
|
bold: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否斜体
|
||||||
|
*/
|
||||||
|
italic: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TextContentBlock {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
line: number;
|
||||||
|
marginLeft: number;
|
||||||
|
marginTop: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReturnedTextContentConfig extends TextContentConfig {
|
||||||
|
right: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认字体
|
||||||
|
*/
|
||||||
|
defaultFont: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前绘制的文字索引
|
||||||
|
*/
|
||||||
|
index: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前颜色
|
||||||
|
*/
|
||||||
|
currcolor: Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前字体
|
||||||
|
*/
|
||||||
|
currfont: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每一行的间距
|
||||||
|
*/
|
||||||
|
lineMargin: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每一行间距的一半
|
||||||
|
*/
|
||||||
|
topMargin: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 横坐标偏移量
|
||||||
|
*/
|
||||||
|
offsetX: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纵坐标偏移量
|
||||||
|
*/
|
||||||
|
offsetY: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前行数
|
||||||
|
*/
|
||||||
|
line: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有的文字
|
||||||
|
*/
|
||||||
|
blocks: TextContentBlock[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是高清画布
|
||||||
|
*/
|
||||||
|
isHD: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这一行的最大高度
|
||||||
|
*/
|
||||||
|
lineMaxHeight: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是强制换行
|
||||||
|
*/
|
||||||
|
forceChangeLine: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TextBoxConfig {
|
||||||
|
/**
|
||||||
|
* 画布
|
||||||
|
*/
|
||||||
|
ctx: CtxRefer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框位置
|
||||||
|
*/
|
||||||
|
pos: TextBoxPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否一次性全部显示
|
||||||
|
*/
|
||||||
|
showAll: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否异步显示
|
||||||
|
*/
|
||||||
|
async: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI窗口的绘制,如对话框、怪物手册、楼传器、存读档界面等
|
||||||
|
*/
|
||||||
|
interface Ui {
|
||||||
|
/**
|
||||||
|
* ui数据
|
||||||
|
*/
|
||||||
|
uidata: UiData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据画布名找到一个画布的context;支持系统画布和自定义画布。如果不存在画布返回null。
|
||||||
|
* 也可以传画布的context自身,则返回自己。
|
||||||
|
*/
|
||||||
|
getContextByName(canvas: CtxRefer): CanvasRenderingContext2D | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空某个画布图层
|
||||||
|
* name为画布名,可以是系统画布之一,也可以是任意自定义动态创建的画布名;还可以直接传画布的context本身。(下同)
|
||||||
|
* 如果name也可以是'all',若为all则为清空所有系统画布。
|
||||||
|
*/
|
||||||
|
clearMap(
|
||||||
|
name: CtxRefer,
|
||||||
|
x?: number,
|
||||||
|
y?: number,
|
||||||
|
w?: number,
|
||||||
|
h?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个画布上绘制一段文字
|
||||||
|
* @param text 要绘制的文本
|
||||||
|
* @param style 绘制的样式
|
||||||
|
* @param font 绘制的字体
|
||||||
|
* @param maxWidth 文字整体的最大宽度,如果超过会自动缩小文字使其宽度小于这个值
|
||||||
|
*/
|
||||||
|
fillText(
|
||||||
|
name: CtxRefer,
|
||||||
|
text: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
font?: string,
|
||||||
|
maxWidth?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据最大宽度自动缩小字体
|
||||||
|
* @param name 画布
|
||||||
|
* @param text 文字
|
||||||
|
* @param maxWidth 最大和宽度
|
||||||
|
* @param font 字体
|
||||||
|
*/
|
||||||
|
setFontForMaxWidth(
|
||||||
|
name: CtxRefer,
|
||||||
|
text: string,
|
||||||
|
maxWidth: number,
|
||||||
|
font?: string
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个画布上绘制一个描边文字
|
||||||
|
* @param text 要绘制的文本
|
||||||
|
* @param style 绘制的样式
|
||||||
|
* @param strokeStyle 绘制的描边颜色
|
||||||
|
* @param font 绘制的字体
|
||||||
|
* @param lineWidth 描边的线宽
|
||||||
|
*/
|
||||||
|
fillBoldText(
|
||||||
|
name: CtxRefer,
|
||||||
|
text: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
strokeStyle?: CanvasStyle,
|
||||||
|
font?: string,
|
||||||
|
maxWidth?: number,
|
||||||
|
lineWidth?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制一个矩形
|
||||||
|
* @param style 绘制的样式
|
||||||
|
* @param angle 旋转角度,弧度制
|
||||||
|
*/
|
||||||
|
fillRect(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
angle?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制一个矩形的边框
|
||||||
|
* @param style 绘制的样式
|
||||||
|
* @param angle 旋转角度,单位弧度
|
||||||
|
*/
|
||||||
|
strokeRect(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
lineWidth?: number,
|
||||||
|
angle?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个圆角矩形
|
||||||
|
*/
|
||||||
|
fillRoundRect(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
radius: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
angle?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个圆角矩形的边框
|
||||||
|
*/
|
||||||
|
strokeRoundRect(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
radius: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
lineWidth?: number,
|
||||||
|
angle?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个多边形
|
||||||
|
*/
|
||||||
|
fillPolygon(
|
||||||
|
name: CtxRefer,
|
||||||
|
nodes?: [x: number, y: number][],
|
||||||
|
style?: CanvasStyle
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个多边形的边框
|
||||||
|
*/
|
||||||
|
strokePolygon(
|
||||||
|
name: CtxRefer,
|
||||||
|
nodes?: [x: number, y: number][],
|
||||||
|
style?: CanvasStyle,
|
||||||
|
lineWidth?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个椭圆
|
||||||
|
* @param a 横轴长度的一半
|
||||||
|
* @param b 纵轴长度的一半
|
||||||
|
* @param angle 旋转角度
|
||||||
|
*/
|
||||||
|
fillEllipse(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
a: number,
|
||||||
|
b: number,
|
||||||
|
angle?: number,
|
||||||
|
style?: CanvasStyle
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个圆
|
||||||
|
*/
|
||||||
|
fillCircle(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
r: number,
|
||||||
|
style?: CanvasStyle
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个椭圆的边框
|
||||||
|
* @param a 横轴长度的一半
|
||||||
|
* @param b 纵轴长度的一半
|
||||||
|
* @param angle 旋转角度
|
||||||
|
*/
|
||||||
|
strokeEllipse(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
a: number,
|
||||||
|
b: number,
|
||||||
|
angle?: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
lineWidth?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个圆的边框
|
||||||
|
*/
|
||||||
|
strokeCircle(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
r: any,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
lineWidth?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个扇形
|
||||||
|
*/
|
||||||
|
fillArc(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
r: number,
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
style?: CanvasStyle
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一段弧
|
||||||
|
*/
|
||||||
|
strokeArc(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
r: number,
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
lineWidth?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一条线
|
||||||
|
*/
|
||||||
|
drawLine(
|
||||||
|
name: CtxRefer,
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
x2: number,
|
||||||
|
y2: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
lineWidth?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个箭头
|
||||||
|
*/
|
||||||
|
drawArrow(
|
||||||
|
name: CtxRefer,
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
x2: number,
|
||||||
|
y2: number,
|
||||||
|
style?: CanvasStyle,
|
||||||
|
lineWidth?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某个canvas的文字字体
|
||||||
|
*/
|
||||||
|
setFont(name: CtxRefer, font: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某个canvas的线宽度
|
||||||
|
*/
|
||||||
|
setLineWidth(name: CtxRefer, lineWidth: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存某个canvas状态
|
||||||
|
*/
|
||||||
|
saveCanvas(name: CtxRefer): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回退某个canvas状态
|
||||||
|
*/
|
||||||
|
loadCanvas(name: CtxRefer): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某个canvas的绘制不透明度
|
||||||
|
* @returns 之前画布的不透明度
|
||||||
|
*/
|
||||||
|
setAlpha(name: CtxRefer, alpha: number): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置画布元素的不透明度
|
||||||
|
*/
|
||||||
|
setOpacity(name: CtxRefer, opacity: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某个canvas的滤镜
|
||||||
|
*/
|
||||||
|
setFilter(name: CtxRefer, filter?: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某个canvas的填充样式
|
||||||
|
*/
|
||||||
|
setFillStyle(name: CtxRefer, style: CanvasStyle): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某个canvas描边样式
|
||||||
|
*/
|
||||||
|
setStrokeStyle(name: CtxRefer, style: CanvasStyle): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某个canvas的文字左右对齐方式
|
||||||
|
*/
|
||||||
|
setTextAlign(name: CtxRefer, align: CanvasTextAlign): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某个canvas的文字上下对齐方式
|
||||||
|
*/
|
||||||
|
setTextBaseline(name: CtxRefer, baseline: CanvasTextBaseline): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算某段文字的宽度,注意该函数会设置画布的字体
|
||||||
|
*/
|
||||||
|
calWidth(name: CtxRefer, text: string, font?: string): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串自动换行的分割
|
||||||
|
*/
|
||||||
|
splitLines(
|
||||||
|
name: CtxRefer,
|
||||||
|
text: string,
|
||||||
|
maxWidth?: number,
|
||||||
|
font?: string
|
||||||
|
): string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制图片
|
||||||
|
* @param dx 绘制的横坐标
|
||||||
|
* @param dy 绘制的纵坐标
|
||||||
|
*/
|
||||||
|
drawImage(name: CtxRefer, image: ImageSource, dx: number, dy: number): void;
|
||||||
|
/**
|
||||||
|
* 绘制图片
|
||||||
|
* @param dx 绘制的横坐标
|
||||||
|
* @param dy 绘制的纵坐标
|
||||||
|
* @param dw 绘制的宽度
|
||||||
|
* @param dh 绘制的高度
|
||||||
|
*/
|
||||||
|
drawImage(
|
||||||
|
name: CtxRefer,
|
||||||
|
image: ImageSource,
|
||||||
|
dx: number,
|
||||||
|
dy: number,
|
||||||
|
dw: number,
|
||||||
|
dh: number
|
||||||
|
): void;
|
||||||
|
/**
|
||||||
|
* 绘制图片
|
||||||
|
* @param sx 裁剪的横坐标
|
||||||
|
* @param sy 裁剪的纵坐标
|
||||||
|
* @param sw 裁剪的宽度
|
||||||
|
* @param sh 裁剪的高度
|
||||||
|
* @param dx 绘制的横坐标
|
||||||
|
* @param dy 绘制的纵坐标
|
||||||
|
* @param dw 绘制的宽度
|
||||||
|
* @param dh 绘制的高度
|
||||||
|
*/
|
||||||
|
drawImage(
|
||||||
|
name: CtxRefer,
|
||||||
|
image: ImageSource,
|
||||||
|
sx: number,
|
||||||
|
sy: number,
|
||||||
|
sw: number,
|
||||||
|
sh: number,
|
||||||
|
dx: number,
|
||||||
|
dy: number,
|
||||||
|
dw: number,
|
||||||
|
dh: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在某个canvas上绘制一个图标
|
||||||
|
* @param frame 图标的第几帧
|
||||||
|
*/
|
||||||
|
drawIcon(
|
||||||
|
name: CtxRefer,
|
||||||
|
id: AllIds,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
w?: number,
|
||||||
|
h?: number,
|
||||||
|
frame?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束一切事件和绘制,关闭UI窗口,返回游戏进程
|
||||||
|
*/
|
||||||
|
closePanel(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空UI层内容
|
||||||
|
*/
|
||||||
|
clearUI(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 左上角绘制一段提示
|
||||||
|
* @param text 要提示的文字内容
|
||||||
|
* @param id 要绘制的图标ID
|
||||||
|
* @param frame 要绘制图标的第几帧
|
||||||
|
*/
|
||||||
|
drawTip(text: string, id?: AllIds, frame?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地图中间绘制一段文字
|
||||||
|
*/
|
||||||
|
drawText(contents: string, callback?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自绘选择光标
|
||||||
|
*/
|
||||||
|
drawUIEventSelector(
|
||||||
|
code: number,
|
||||||
|
background: RGBArray | ImageIds,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
w: number,
|
||||||
|
h: number,
|
||||||
|
z?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除一个或多个选择光标
|
||||||
|
* @param code 要清除的选择光标,不填表示清除所有
|
||||||
|
*/
|
||||||
|
clearUIEventSelector(code?: number | number[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制WindowSkin
|
||||||
|
* @param direction 指向箭头的方向
|
||||||
|
*/
|
||||||
|
drawWindowSkin(
|
||||||
|
background: any,
|
||||||
|
ctx: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
w: number,
|
||||||
|
h: number,
|
||||||
|
direction?: 'up' | 'down',
|
||||||
|
px?: number,
|
||||||
|
py?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制一个背景图,可绘制winskin或纯色背景;支持小箭头绘制
|
||||||
|
*/
|
||||||
|
drawBackground(
|
||||||
|
left: string,
|
||||||
|
top: string,
|
||||||
|
right: string,
|
||||||
|
bottom: string,
|
||||||
|
posInfo?: Partial<BackgroundPosInfo>
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制一段文字到某个画布上面
|
||||||
|
* @param ctx 要绘制到的画布
|
||||||
|
* @param content 要绘制的内容;转义字符只允许保留 \n, \r[...], \i[...], \c[...], \d, \e
|
||||||
|
* @param config 绘制配置项
|
||||||
|
* @returns 绘制信息
|
||||||
|
*/
|
||||||
|
drawTextContent(
|
||||||
|
ctx: CtxRefer,
|
||||||
|
content: string,
|
||||||
|
config: Partial<TextContentConfig>
|
||||||
|
): ReturnedTextContentConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得某段文字的预计绘制高度
|
||||||
|
*/
|
||||||
|
getTextContentHeight(
|
||||||
|
content: string,
|
||||||
|
config: Partial<TextContentConfig>
|
||||||
|
): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制一个对话框
|
||||||
|
*/
|
||||||
|
drawTextBox(content: string, config?: TextBoxConfig): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制滚动字幕
|
||||||
|
*/
|
||||||
|
drawScrollText(
|
||||||
|
content: string,
|
||||||
|
time?: number,
|
||||||
|
lineHeight?: number,
|
||||||
|
callback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本图片化
|
||||||
|
*/
|
||||||
|
textImage(content: string, lineHeight?: number): HTMLCanvasElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制一个选项界面
|
||||||
|
*/
|
||||||
|
drawChoices(
|
||||||
|
content: string,
|
||||||
|
choices: string[],
|
||||||
|
width?: number,
|
||||||
|
ctx?: CtxRefer
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制一个确认框
|
||||||
|
*/
|
||||||
|
drawConfirmBox(
|
||||||
|
text: string,
|
||||||
|
yesCallback?: () => void,
|
||||||
|
noCallback?: () => void,
|
||||||
|
ctx?: CtxRefer
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制等待界面
|
||||||
|
*/
|
||||||
|
drawWaiting(text: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制分页
|
||||||
|
*/
|
||||||
|
drawPagination(page: number, totalPage: number, y?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制怪物手册
|
||||||
|
*/
|
||||||
|
drawBook(index: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制楼层传送器
|
||||||
|
*/
|
||||||
|
drawFly(page: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得所有应该在道具栏显示的某个类型道具
|
||||||
|
*/
|
||||||
|
getToolboxItems<T extends Exclude<ItemCls, 'items'>>(cls: T): ItemIdOf<T>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态创建一个画布
|
||||||
|
* @param name 画布名称,如果已存在则会直接取用当前存在的
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
* @param zIndex 纵深
|
||||||
|
* @param nonAntiAliasing 是否取消抗锯齿
|
||||||
|
*/
|
||||||
|
createCanvas(
|
||||||
|
name: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
zIndex?: number,
|
||||||
|
nonAntiAliasing?: boolean
|
||||||
|
): CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新定位一个自定义画布
|
||||||
|
*/
|
||||||
|
relocateCanvas(
|
||||||
|
name: CtxRefer,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
useDelta?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置一个自定义画布的旋转角度
|
||||||
|
*/
|
||||||
|
rotateCanvas(
|
||||||
|
name: CtxRefer,
|
||||||
|
angle: number,
|
||||||
|
centerX?: number,
|
||||||
|
centerY?: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新设置一个自定义画布的大小
|
||||||
|
* @param styleOnly 是否只修改style,而不修改元素上的长宽,如果是true,会出现模糊现象
|
||||||
|
* @param isTempCanvas 是否是临时画布,如果填true,会将临时画布修改为高清画布
|
||||||
|
*/
|
||||||
|
resizeCanvas(
|
||||||
|
name: CtxRefer,
|
||||||
|
x?: number,
|
||||||
|
y?: number,
|
||||||
|
styleOnly?: boolean,
|
||||||
|
isTempCanvas?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一个自定义画布
|
||||||
|
*/
|
||||||
|
deleteCanvas(name: string | ((name: string) => boolean)): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有的自定义画布
|
||||||
|
*/
|
||||||
|
deleteAllCanvas(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制浏览地图
|
||||||
|
*/
|
||||||
|
_drawViewMaps(): void;
|
||||||
|
|
||||||
|
_drawReplay(): void;
|
||||||
|
|
||||||
|
_drawStatistics(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const ui: new () => Ui;
|
912
util.d.ts
vendored
Normal file
912
util.d.ts
vendored
Normal file
@ -0,0 +1,912 @@
|
|||||||
|
/** 工具类 主要用来进行一些辅助函数的计算 */
|
||||||
|
interface Utils {
|
||||||
|
/**
|
||||||
|
* 四个方向的坐标增量
|
||||||
|
*/
|
||||||
|
readonly scan: DeepReadonly<Scan>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 八个方向的坐标增量
|
||||||
|
*/
|
||||||
|
readonly scan2: DeepReadonly<Scan2>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将一段文字中的${}(表达式)进行替换。很多情况下可以用模板字符串替代
|
||||||
|
* @example
|
||||||
|
* // 把主角的生命值和持有的黄钥匙数量代入这句话
|
||||||
|
* core.replaceText('衬衫的价格是${status:hp}镑${item:yellowKey}便士。');
|
||||||
|
* @param text 模板字符串,可以使用${}计算js表达式
|
||||||
|
* @param prefix 独立开关前缀
|
||||||
|
* @returns 替换完毕后的字符串
|
||||||
|
*/
|
||||||
|
replaceText(text: string, prefix?: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对一个表达式中的特殊规则进行替换,如status:xxx等。
|
||||||
|
* 其中变量和全局存储会替换中文冒号,其余的不会替换
|
||||||
|
* @example
|
||||||
|
* // 把这两个冒号表达式替换为core.getStatus('hp')和core.itemCount('yellowKey')这样的函数调用
|
||||||
|
* core.replaceValue('衬衫的价格是${status:hp}镑${item:yellowKey}便士。');
|
||||||
|
* @param value 模板字符串,注意独立开关不会被替换
|
||||||
|
* @returns 替换完毕后的字符串
|
||||||
|
*/
|
||||||
|
replaceValue(value: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算一个表达式的值,支持status:xxx等的计算。
|
||||||
|
* @example core.calValue('status:hp + status:def'); // 计算主角的生命值加防御力
|
||||||
|
* @param value 待求值的表达式
|
||||||
|
* @param prefix 独立开关前缀,一般可省略
|
||||||
|
* @returns 求出的值
|
||||||
|
*/
|
||||||
|
calValue(value: string | Function, prefix?: string): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 将b(可以是另一个数组)插入数组a的开头,用Array.unshift就行
|
||||||
|
* @example core.unshift(todo, {type: 'unfollow'}); // 在事件指令数组todo的开头插入“取消所有跟随者”指令
|
||||||
|
* @param a 原数组
|
||||||
|
* @param b 待插入的新首项或前缀数组
|
||||||
|
* @returns 插入完毕后的新数组,它是改变原数组a本身得到的
|
||||||
|
*/
|
||||||
|
unshift<A extends any[], B extends any[]>(a: A, b: B): [...B, ...A];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 将b(可以是另一个数组)插入数组a的末尾,用Array.push就行
|
||||||
|
* @example core.push(todo, {type: 'unfollow'}); // 在事件指令数组todo的末尾插入“取消所有跟随者”指令
|
||||||
|
* @param a 原数组
|
||||||
|
* @param b 待插入的新末项或后缀数组
|
||||||
|
* @returns 插入完毕后的新数组,它是改变原数组a本身得到的
|
||||||
|
*/
|
||||||
|
push<A extends any[], B extends any[]>(a: A, b: B): [...A, ...B];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解压缩一个数据,我也不知道这个解压的是什么
|
||||||
|
* @param 要解压的内容,字符串
|
||||||
|
*/
|
||||||
|
decompress(value: string): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* //@deprecated
|
||||||
|
* 设置本地存储
|
||||||
|
* @param key 本地存储的名称
|
||||||
|
* @param value 本地存储的值,不填代表删除
|
||||||
|
*/
|
||||||
|
setLocalStorage(key: string, value?: any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* //@deprecated
|
||||||
|
* 获得本地存储
|
||||||
|
* @param key 获取的本地存储的名称
|
||||||
|
* @param defaultValue 当不存在的时候的默认值
|
||||||
|
*/
|
||||||
|
getLocalStorage<T>(key: string, defaultValue?: T): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 移除本地存储
|
||||||
|
* @param key 要移除的本地存储的值
|
||||||
|
*/
|
||||||
|
removeLocalStorage(key: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步写入localforage
|
||||||
|
* @param key 写入的键
|
||||||
|
* @param value 写入的值
|
||||||
|
* @param successCallback 写入成功的回调函数
|
||||||
|
* @param errorCallback 写入出错的回调函数
|
||||||
|
*/
|
||||||
|
setLocalForage(
|
||||||
|
key: string,
|
||||||
|
value?: any,
|
||||||
|
successCallback?: () => void,
|
||||||
|
errorCallback?: (err: Error) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从localforage读出一段数据
|
||||||
|
*/
|
||||||
|
getLocalForage<T>(
|
||||||
|
key: string,
|
||||||
|
defaultValue?: T,
|
||||||
|
successCallback?: (data: T) => void,
|
||||||
|
errorCallback?: (err: Error) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除localforage的数据
|
||||||
|
*/
|
||||||
|
removeLocalForage(
|
||||||
|
key: string,
|
||||||
|
successCallback?: () => void,
|
||||||
|
errorCallback?: (err: Error) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除localforage所有的数据
|
||||||
|
* @param callback 清除完毕的回调函数
|
||||||
|
*/
|
||||||
|
clearLocalForage(callback?: (err?: Error) => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 迭代localforage的数据
|
||||||
|
* @param iteratee 迭代器
|
||||||
|
* @param callback 迭代完毕的回调函数
|
||||||
|
*/
|
||||||
|
iterateLocalForage<T, U>(
|
||||||
|
iteratee: (value: T, key: string, iterationNumber: number) => U,
|
||||||
|
callback?: (err: any, result: U) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取localforage数据的所有的键
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
keysLocalForage(callback?: (err: any, keys: string[]) => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取localforage数据的数据量
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
lengthLocalForage(
|
||||||
|
callback?: (err: any, numberOfKeys: number) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 设置一个全局存储,适用于global:xxx,录像播放时将忽略此函数。
|
||||||
|
* @example core.setBlobal('一周目已通关', true); // 设置全局存储“一周目已通关”为true,方便二周目游戏中的新要素。
|
||||||
|
* @param key 全局变量名称,支持中文
|
||||||
|
* @param value 全局变量的新值,不填或null表示清除此全局存储
|
||||||
|
*/
|
||||||
|
setGlobal(key: string, value?: any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 读取一个全局存储,适用于global:xxx,支持录像。
|
||||||
|
* @example if (core.getGlobal('一周目已通关', false) === true) core.getItem('dagger'); // 二周目游戏进行到此处时会获得一把屠龙匕首
|
||||||
|
* @param key 全局变量名称,支持中文
|
||||||
|
* @param defaultValue 可选,当此全局变量不存在或值为null、undefined时,用此值代替
|
||||||
|
* @returns 全局变量的值
|
||||||
|
*/
|
||||||
|
getGlobal<T>(key: string, defaultValue?: T): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深拷贝一个对象(函数将原样返回)
|
||||||
|
* @example core.clone(core.status.hero, (name, value) => (name == 'items' || typeof value == 'number'), false); // 深拷贝主角的属性和道具
|
||||||
|
* @param data 待拷贝对象
|
||||||
|
* @param filter 过滤器,可选,表示data为数组或对象时拷贝哪些项或属性,true表示拷贝
|
||||||
|
* @param recursion 过滤器是否递归,可选。true表示过滤器也被递归
|
||||||
|
* @returns 拷贝的结果,注意函数将原样返回
|
||||||
|
*/
|
||||||
|
clone<T>(
|
||||||
|
data: T,
|
||||||
|
filter?: (name: string, value: any) => boolean,
|
||||||
|
recursion?: boolean
|
||||||
|
): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深拷贝一个1D或2D的数组
|
||||||
|
* @param data 要拷贝的数据
|
||||||
|
*/
|
||||||
|
cloneArray<T extends any[]>(data: T): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等比例切分一张图片
|
||||||
|
* @example core.splitImage(core.material.images.images['npc48.png'], 32, 48); // 把npc48.png切分成若干32×48px的小人
|
||||||
|
* @param image 图片名(支持映射前的中文名)或图片对象(参见上面的例子),获取不到时返回[]
|
||||||
|
* @param width 子图的宽度,单位为像素。原图总宽度必须是其倍数,不填视为32
|
||||||
|
* @param height 子图的高度,单位为像素。原图总高度必须是其倍数,不填视为正方形
|
||||||
|
* @returns 子图组成的数组,在原图中呈先行后列,从左到右、从上到下排列。
|
||||||
|
*/
|
||||||
|
splitImage(
|
||||||
|
image: NameMapIn<ImageIds> | ImageIds | HTMLImageElement,
|
||||||
|
width?: number,
|
||||||
|
height?: number
|
||||||
|
): HTMLImageElement[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期为字符串
|
||||||
|
* @param date 时间,不填代表当前时间
|
||||||
|
* @returns 格式: yyyy-mm-dd hh:mm:ss
|
||||||
|
*/
|
||||||
|
formatDate(date?: Date): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期为最简字符串
|
||||||
|
* @param date 时间,不填代表当前时间
|
||||||
|
* @returns 格式: yyyymmddhhmmss
|
||||||
|
*/
|
||||||
|
formatDate2(date?: Date): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化时间
|
||||||
|
* @param time 时间
|
||||||
|
* @returns 格式: hh:mm:ss
|
||||||
|
*/
|
||||||
|
formatTime(time: number): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 设置成两位数显示,请使用setDigits代替
|
||||||
|
*/
|
||||||
|
setTwoDigits(x: number): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置一个数为n位数显示
|
||||||
|
* @param x 要设置的数
|
||||||
|
* @param n 设置成的位数
|
||||||
|
*/
|
||||||
|
setDigits(x: number, n: number): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文件大小
|
||||||
|
* @param size 大小,字节数
|
||||||
|
* @returns 格式为xx.xxB KB MB
|
||||||
|
*/
|
||||||
|
formatSize(size: number): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大数字格式化,单位为10000的倍数(w,e,z,j,g),末尾四舍五入
|
||||||
|
* @example core.formatBigNumber(123456789); // "12346w"
|
||||||
|
* @param x 原数字
|
||||||
|
* @param onMap 显示的字符数
|
||||||
|
* @returns 格式化结果
|
||||||
|
*/
|
||||||
|
formatBigNumber<T extends string>(x: T, onMap?: number): T;
|
||||||
|
formatBigNumber(x: number | string, onMap?: number | boolean): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 变速移动,完全可以用mutate-animate代替
|
||||||
|
* @param mode 缓动模式
|
||||||
|
*/
|
||||||
|
applyEasing(mode?: EaseMode): (x: number) => number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 颜色数组转十六进制
|
||||||
|
* @example core.arrayToRGB([102, 204, 255]); // "#66ccff",加载画面的宣传色
|
||||||
|
* @param color 一行三列的数组,各元素必须为不大于255的自然数
|
||||||
|
* @returns 该颜色的十六进制表示,使用小写字母
|
||||||
|
*/
|
||||||
|
arrayToRGB(color: RGBArray): _RGBA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 颜色数组转字符串
|
||||||
|
* @example core.arrayToRGBA([102, 204, 255]); // "rgba(102,204,255,1)"
|
||||||
|
* @param color 一行三列或一行四列的数组,前三个元素必须为不大于255的自然数。
|
||||||
|
* 第四个元素(如果有)必须为0或不大于1的数字,第四个元素不填视为1
|
||||||
|
* @returns 该颜色的字符串表示
|
||||||
|
*/
|
||||||
|
arrayToRGBA(color: Color): _RGBA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录像一压,其结果会被再次base64压缩
|
||||||
|
* @example core.encodeRoute(core.status.route); // 一压当前录像
|
||||||
|
* @param route 原始录像,自定义内容(不予压缩,原样写入)必须由0-9A-Za-z和下划线、冒号组成,
|
||||||
|
* 所以中文和数组需要用JSON.stringify预处理再base64压缩才能交由一压
|
||||||
|
* @returns 一压的结果
|
||||||
|
*/
|
||||||
|
encodeRoute(route: string[]): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录像解压的最后一步,即一压的逆过程
|
||||||
|
* @example core.decodeRoute(core.encodeRoute(core.status.route)); // 一压当前录像再解压-_-|
|
||||||
|
* @param route 录像解压倒数第二步的结果,即一压的结果
|
||||||
|
* @returns 原始录像
|
||||||
|
*/
|
||||||
|
decodeRoute(route: string): string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个值是否不为null,undefined和NaN
|
||||||
|
* @example core.isset(0/0); // false,因为0/0等于NaN
|
||||||
|
* @param v 待测值
|
||||||
|
* @returns false表示待测值为null、undefined、NaN或未填写,true表示为其他值
|
||||||
|
*/
|
||||||
|
isset(v?: any): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判定一个数组是否为另一个数组的前缀,用于录像接续播放
|
||||||
|
* @example core.subarray(['ad', '米库', '小精灵', '小破草', '小艾'], ['ad', '米库', '小精灵']); // ['小破草', '小艾']
|
||||||
|
* @param a 可能的母数组,不填或比b短将返回null
|
||||||
|
* @param b 可能的前缀,不填或比a长将返回null
|
||||||
|
* @returns 如果b不是a的前缀将返回null,否则将返回a去掉此前缀后的剩余数组
|
||||||
|
*/
|
||||||
|
subarray(a: any[], b: any[]): any[] | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 判定array是不是一个数组,以及element是否在该数组中。使用Array.includes代替
|
||||||
|
* @param array 可能的数组,不为数组或不填将导致返回值为false
|
||||||
|
* @param element 待查找的元素
|
||||||
|
* @returns 如果array为数组且具有element这项,就返回true,否则返回false
|
||||||
|
*/
|
||||||
|
inArray(array?: any, element?: any): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将x限定在[a,b]区间内,注意a和b可交换
|
||||||
|
* @example core.clamp(1200, 1, 1000); // 1000
|
||||||
|
* @param x 原始值,!x为true时x一律视为0
|
||||||
|
* @param a 下限值,大于b将导致与b交换
|
||||||
|
* @param b 上限值,小于a将导致与a交换
|
||||||
|
*/
|
||||||
|
clamp(x: number, a: number, b: number): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问浏览器cookie
|
||||||
|
*/
|
||||||
|
getCookie(name: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 已失效(大概
|
||||||
|
* 填写非自绘状态栏
|
||||||
|
* @example
|
||||||
|
* // 更新状态栏中的主角生命,使用加载画面的宣传色
|
||||||
|
* core.setStatusBarInnerHTML('hp', core.status.hero.hp, 'color: #66CCFF');
|
||||||
|
* @param name 状态栏项的名称,如'hp', 'atk', 'def'等。必须是core.statusBar中的一个合法项
|
||||||
|
* @param value 要填写的内容,大数字会被格式化为至多6个字符,无中文的内容会被自动设为斜体
|
||||||
|
* @param css 额外的css样式,可选。如更改颜色等
|
||||||
|
*/
|
||||||
|
setStatusBarInnerHTML(
|
||||||
|
name: string,
|
||||||
|
value: string | number,
|
||||||
|
css?: string
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 求字符串的国标码字节数,也可用于等宽字体下文本的宽度测算。请注意样板的默认字体Verdana不是等宽字体
|
||||||
|
* @example core.strlen('无敌ad'); // 6
|
||||||
|
* @param str 待测字符串
|
||||||
|
* @returns 字符串的国标码字节数,每个汉字为2,每个ASCII字符为1
|
||||||
|
*/
|
||||||
|
strlen(str: string): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算应当转向某个方向
|
||||||
|
* @param turn 转向的方向
|
||||||
|
* @param direction 当前方向,不填视为当前方向
|
||||||
|
*/
|
||||||
|
turnDirection(turn: HeroTurnDir, direction?: Dir): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通配符匹配,用于搜索图块等批量处理。
|
||||||
|
* @example core.playSound(core.matchWildcard('*Key', itemId) ? 'item.mp3' : 'door.mp3'); // 判断捡到的是钥匙还是别的道具,从而播放不同的音效
|
||||||
|
* @param pattern 模式串,每个星号表示任意多个(0个起)字符
|
||||||
|
* @param string 待测串
|
||||||
|
* @returns true表示匹配成功,false表示匹配失败
|
||||||
|
*/
|
||||||
|
matchWildcard(pattern: string, string: string): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否满足正则表达式,一般可以直接用/RegExp/.test(str)代替
|
||||||
|
* @param pattern 正则表达式
|
||||||
|
* @param string 要匹配的字符串
|
||||||
|
*/
|
||||||
|
matchRegex(pattern: string, string: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base64加密
|
||||||
|
* @example
|
||||||
|
* core.encodeBase64('If you found this note in a small wooden box with a heart on it');
|
||||||
|
* // "SWYgeW91IGZvdW5kIHRoaXMgbm90ZSBpbiBhIHNtYWxsIHdvb2RlbiBib3ggd2l0aCBhIGhlYXJ0IG9uIGl0"
|
||||||
|
* @param str 明文
|
||||||
|
* @returns 密文
|
||||||
|
*/
|
||||||
|
encodeBase64(str: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base64解密
|
||||||
|
* @example
|
||||||
|
* core.decodeBase64('SWYgeW91IGZvdW5kIHRoaXMgbm90ZSBpbiBhIHNtYWxsIHdvb2RlbiBib3ggd2l0aCBhIGhlYXJ0IG9uIGl0');
|
||||||
|
* // "If you found this note in a small wooden box with a heart on it"
|
||||||
|
* @param str 密文
|
||||||
|
* @returns 明文
|
||||||
|
*/
|
||||||
|
decodeBase64(str: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不支持SL的随机数
|
||||||
|
* @exmaple 1 + core.rand(6); // 随机生成一个小于7的正整数,模拟骰子的效果
|
||||||
|
* @param num 填正数表示生成小于num的随机自然数,否则生成小于1的随机正数
|
||||||
|
* @returns 随机数,即使读档也不会改变结果
|
||||||
|
*/
|
||||||
|
rand(num?: number): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支持SL的随机数,并计入录像
|
||||||
|
* @exmaple 1 + core.rand2(6); // 随机生成一个小于7的正整数,模拟骰子的效果
|
||||||
|
* @param num 正整数,0或不填会被视为2147483648
|
||||||
|
* @returns 属于 [0, num) 的随机数
|
||||||
|
*/
|
||||||
|
rand2(num?: number): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试请求读取一个本地文件内容 [异步]
|
||||||
|
* @param success 成功后的回调
|
||||||
|
* @param error 失败后的回调
|
||||||
|
* @param accept input元素的accept属性
|
||||||
|
* @param readType 不设置则以文本读取,否则以DataUrl形式读取
|
||||||
|
*/
|
||||||
|
readFile(
|
||||||
|
success: (obj: any) => void,
|
||||||
|
error: () => void,
|
||||||
|
accept: string,
|
||||||
|
readType: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件读取完毕后的内容处理 [异步]
|
||||||
|
* @param content 读取的内容
|
||||||
|
*/
|
||||||
|
readFileContent(content: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹窗请求下载一个文本文件
|
||||||
|
* @example core.download('route.txt', core.status.route); // 弹窗请求下载录像
|
||||||
|
* @param filename 文件名
|
||||||
|
* @param content 文件内容
|
||||||
|
*/
|
||||||
|
download(filename: string, content: string | string[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试复制一段文本到剪切板
|
||||||
|
* @param data 赋值的东西
|
||||||
|
*/
|
||||||
|
copy(data: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示确认框,类似core.drawConfirmBox()
|
||||||
|
* @example core.myconfirm('重启游戏?', core.restart); // 弹窗询问玩家是否重启游戏
|
||||||
|
* @param hint 弹窗的内容
|
||||||
|
* @param yesCallback 确定后的回调函数
|
||||||
|
* @param noCallback 取消后的回调函数
|
||||||
|
*/
|
||||||
|
myconfirm(
|
||||||
|
hint: string,
|
||||||
|
yesCallback: () => void,
|
||||||
|
noCallback?: () => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 让用户输入一段文字
|
||||||
|
*/
|
||||||
|
myprompt(
|
||||||
|
hint: string,
|
||||||
|
value: string,
|
||||||
|
callback?: (data?: string) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 动画显示某对象,有vue了,你还用这个?Transition组件和css的transition比这个强得多
|
||||||
|
*/
|
||||||
|
showWithAnimate(
|
||||||
|
obj?: HTMLElement,
|
||||||
|
speed?: number,
|
||||||
|
callback?: () => any
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 动画使某对象消失
|
||||||
|
*/
|
||||||
|
hideWithAnimate(
|
||||||
|
obj?: HTMLElement,
|
||||||
|
speed?: number,
|
||||||
|
callback?: () => any
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得浏览器唯一的guid
|
||||||
|
*/
|
||||||
|
getGuid(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个对象的哈希值
|
||||||
|
* @param obj 要获取的对象
|
||||||
|
*/
|
||||||
|
hashCode(obj: any): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判定深层相等, 会逐层比较每个元素
|
||||||
|
* @example core.same(['1', 2], ['1', 2]); // true
|
||||||
|
*/
|
||||||
|
same(a: any, b: any): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解压一段内容
|
||||||
|
*/
|
||||||
|
unzip(
|
||||||
|
blobOrUrl: string | Blob,
|
||||||
|
success?: (data: any) => void,
|
||||||
|
error?: (error: string) => void,
|
||||||
|
convertToText?: boolean,
|
||||||
|
onprogress?: (loaded: number, total: number) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送一个HTTP请求 [异步]
|
||||||
|
* @param type 请求类型
|
||||||
|
* @param url 目标地址
|
||||||
|
* @param formData 如果是POST请求则为表单数据
|
||||||
|
* @param success 成功后的回调
|
||||||
|
* @param error 失败后的回调
|
||||||
|
*/
|
||||||
|
http(
|
||||||
|
type: 'GET' | 'POST',
|
||||||
|
url: string,
|
||||||
|
formData?: FormData,
|
||||||
|
success?: (res: any) => void,
|
||||||
|
error?: (err: string) => void,
|
||||||
|
mimeType?: string,
|
||||||
|
responseType?: XMLHttpRequestResponseType,
|
||||||
|
onProgress?: (loaded: number, total: number) => void
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const utils: new () => Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APP接口,使用前应先判断是否存在
|
||||||
|
*/
|
||||||
|
interface JSInterface {
|
||||||
|
/** 强制横屏 */
|
||||||
|
requireLandscape(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const jsinterface: JSInterface;
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
readonly jsinterface: JSInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动的方向
|
||||||
|
*/
|
||||||
|
type Step = Move | 'backward';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 坐标字符串
|
||||||
|
*/
|
||||||
|
type LocString = `${number},${number}`;
|
||||||
|
|
||||||
|
type _RGBA =
|
||||||
|
| `rgb(${number},${number},${number})`
|
||||||
|
| `rgba(${number},${number},${number},${number})`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RGBA颜色数组
|
||||||
|
*/
|
||||||
|
type RGBArray = [number, number, number, number?];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 样板的颜色字符串
|
||||||
|
*/
|
||||||
|
type Color = `#${string}` | _RGBA | RGBArray | 'transparent';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 四个方向
|
||||||
|
*/
|
||||||
|
type Dir = 'up' | 'down' | 'left' | 'right';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 八个方向
|
||||||
|
*/
|
||||||
|
type Dir2 = Dir | 'leftup' | 'rightup' | 'leftdown' | 'rightdown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转向的方向
|
||||||
|
*/
|
||||||
|
type TurnDir = Dir | ':left' | ':right' | ':back';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士转向
|
||||||
|
*/
|
||||||
|
type HeroTurnDir = TurnDir | ':hero' | ':backhero';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框的位置
|
||||||
|
*/
|
||||||
|
type TextPosition = 'up' | 'center' | 'down';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动的方向
|
||||||
|
*/
|
||||||
|
type Move = 'forward' | Dir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓动模式,不过在高级动画插件面前不堪一击(
|
||||||
|
*/
|
||||||
|
type EaseMode = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件执行的操作符\
|
||||||
|
* += 增加并赋值\
|
||||||
|
* -= 减少并赋值\
|
||||||
|
* *= 相乘并赋值\
|
||||||
|
* /= 相除并赋值\
|
||||||
|
* //= 除以并取商\
|
||||||
|
* **= 取幂\
|
||||||
|
* %= 取余\
|
||||||
|
* min= 取二者的最小值\
|
||||||
|
* max= 取二者的最大值\
|
||||||
|
* 其它的任意字符串都是赋值
|
||||||
|
*/
|
||||||
|
type MotaOperator =
|
||||||
|
| '+='
|
||||||
|
| '-='
|
||||||
|
| '*='
|
||||||
|
| '/='
|
||||||
|
| '//='
|
||||||
|
| '**='
|
||||||
|
| '%='
|
||||||
|
| 'min='
|
||||||
|
| 'max='
|
||||||
|
| '=';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位置数组
|
||||||
|
*/
|
||||||
|
type LocArr = [x: number, y: number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位置
|
||||||
|
*/
|
||||||
|
interface Loc {
|
||||||
|
/**
|
||||||
|
* 横坐标
|
||||||
|
*/
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纵坐标
|
||||||
|
*/
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带方向的位置
|
||||||
|
*/
|
||||||
|
interface DiredLoc extends Loc {
|
||||||
|
/**
|
||||||
|
* 方向
|
||||||
|
*/
|
||||||
|
direction: Dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompressedStep {
|
||||||
|
/**
|
||||||
|
* 移动方向
|
||||||
|
*/
|
||||||
|
direction: Dir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向该方向移动的步数
|
||||||
|
*/
|
||||||
|
step: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 四个方向的坐标增量
|
||||||
|
*/
|
||||||
|
type Scan = {
|
||||||
|
[D in Dir]: Loc;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 八个方向的坐标增量
|
||||||
|
*/
|
||||||
|
type Scan2 = {
|
||||||
|
[D in Dir2]: Loc;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片翻转
|
||||||
|
*/
|
||||||
|
type ImageReverse = ':o' | ':x' | ':y';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框的箭头方向
|
||||||
|
*/
|
||||||
|
type TextBoxDir = 'up' | 'down';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框的位置
|
||||||
|
*/
|
||||||
|
type TextBoxPos =
|
||||||
|
| `${TextBoxDir},hero`
|
||||||
|
| `${TextBoxDir},${number},${number}`
|
||||||
|
| TextPosition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画布信息
|
||||||
|
*/
|
||||||
|
type CtxRefer = string | CanvasRenderingContext2D | HTMLCanvasElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发器类型
|
||||||
|
*/
|
||||||
|
type MotaTrigger =
|
||||||
|
| 'battle'
|
||||||
|
| 'pusBox'
|
||||||
|
| 'openDoor'
|
||||||
|
| 'ski'
|
||||||
|
| 'custom'
|
||||||
|
| 'getItem'
|
||||||
|
| 'changeFloor'
|
||||||
|
| 'null';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换楼层的目标坐标
|
||||||
|
*/
|
||||||
|
type FloorChangeStair =
|
||||||
|
| 'upFloor'
|
||||||
|
| 'downFloor'
|
||||||
|
| ':symmetry'
|
||||||
|
| ':symmetry_x'
|
||||||
|
| ':symmetry_y'
|
||||||
|
| 'flyPoint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件值的前缀
|
||||||
|
*/
|
||||||
|
type EventValuePreffix =
|
||||||
|
| 'status'
|
||||||
|
| 'flag'
|
||||||
|
| 'item'
|
||||||
|
| 'buff'
|
||||||
|
| 'switch'
|
||||||
|
| 'temp'
|
||||||
|
| 'global';
|
||||||
|
|
||||||
|
interface Animate {
|
||||||
|
/**
|
||||||
|
* 动画的帧数s
|
||||||
|
*/
|
||||||
|
frame: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每帧的信息
|
||||||
|
*/
|
||||||
|
frames: FrameObj[][];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片信息
|
||||||
|
*/
|
||||||
|
images: HTMLImageElement[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩放信息
|
||||||
|
*/
|
||||||
|
ratio: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音效
|
||||||
|
*/
|
||||||
|
se: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Save = DeepReadonly<{
|
||||||
|
/**
|
||||||
|
* 存档所在的楼层id
|
||||||
|
*/
|
||||||
|
floorId: FloorIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存档的勇士信息
|
||||||
|
*/
|
||||||
|
hero: HeroStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 难度信息
|
||||||
|
*/
|
||||||
|
hard: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存档的地图信息,已经过压缩处理
|
||||||
|
*/
|
||||||
|
maps: Record<string, ResolvedFloor>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录像信息
|
||||||
|
*/
|
||||||
|
route: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存档的全局变量信息
|
||||||
|
*/
|
||||||
|
values: CoreValues;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏版本
|
||||||
|
*/
|
||||||
|
version: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器唯一guid
|
||||||
|
*/
|
||||||
|
guid: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存档时间
|
||||||
|
*/
|
||||||
|
time: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type ValueType = number | string | boolean | undefined | null | bigint | symbol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度只读一个对象,使其所有属性都只读
|
||||||
|
*/
|
||||||
|
type DeepReadonly<T> = {
|
||||||
|
readonly [P in keyof T]: T[P] extends ValueType ? T[P] : DeepReadonly<T[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度可选一个对象,使其所有属性都可选
|
||||||
|
*/
|
||||||
|
type DeepPartial<T> = {
|
||||||
|
[P in keyof T]?: T[P] extends ValueType ? T[P] : DeepPartial<T[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度必选一个对象,使其所有属性都必选
|
||||||
|
*/
|
||||||
|
type DeepRequired<T> = {
|
||||||
|
[P in keyof T]-?: T[P] extends ValueType ? T[P] : DeepRequired<T[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使一个对象的所有属性可写
|
||||||
|
*/
|
||||||
|
type Writable<T> = {
|
||||||
|
-readonly [P in keyof T]: T[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度可写一个对象,使其所有属性都可写
|
||||||
|
*/
|
||||||
|
type DeepWritable<T> = {
|
||||||
|
-readonly [P in keyof T]: T[P] extends ValueType
|
||||||
|
? T[P]
|
||||||
|
: DeepWritable<T[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从一个对象中选择类型是目标类型的属性
|
||||||
|
*/
|
||||||
|
type SelectType<R, T> = {
|
||||||
|
[P in keyof R as R[P] extends T ? P : never]: R[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从一个对象中选择类型是目标属性的键名
|
||||||
|
*/
|
||||||
|
type SelectKey<R, T> = keyof SelectType<R, T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一段字符串的第一个字符
|
||||||
|
*/
|
||||||
|
type FirstCharOf<T extends string> = T extends `${infer F}${infer A}`
|
||||||
|
? F
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非对象属性
|
||||||
|
*/
|
||||||
|
type NonObject = number | string | boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个对象的非对象值
|
||||||
|
*/
|
||||||
|
type NonObjectOf<T> = SelectType<T, NonObject>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以一个字符串结尾
|
||||||
|
*/
|
||||||
|
type EndsWith<T extends string> = `${string}${T}`;
|
||||||
|
|
||||||
|
type KeyExcludesUnderline<T> = Exclude<keyof T, `_${string}`>;
|
||||||
|
|
||||||
|
type ValueOf<T> = T[keyof T];
|
Loading…
Reference in New Issue
Block a user