更新内容
v1.10.17版本更新内容
※彻底取消file协议的支持,以及客户端或浏览器必须启用ServiceWorker,新版本最低要求为chrome 91或ios15
※从此版本开始,最低支持的安卓客户端为:由理版(v1.9.4),兼容版(v1.8.4),诗笺版(v1.6.7),增强版(v1.3.2),兼容版因技术问题暂时不进行更新,由理版需要卸载重装。这些APP均已强制使用HTTPS协议进行游戏以及签名验证,此举将不再能使用旧版本无名杀进行游戏。最低支持的Windows客户端为:诗笺版(v1.7.4)和新由理版客户端(没有版本号)。另外,ios端将只能使用网页端进行游戏且其余版本的无名杀APP均不为官方发布,且其内容无法保证,请注意甄别!
※我们继续和一些优秀且具有开源精神的代码编写者保持着积极合作。在这一版本中,我们通过接收GitHub的Pull Request,整合了 @rintim @mengxinzxz @PZ157 @Curpond @zhichaoxi2006 @xizifu @Bryant-F @kuangshen04 @Iking123 @Icelotusflower @weeeeeesterly @1039727228 @Spmario233 等13位其他代码贡献者编写的代码。
※正式允许使用import关键字来导入css,json,typescript和vue文件了
※针对早已修改的get.stringify
,将character
文件夹和mode/guozhan.js
中所有可以省略的: function
给删去,减小文件大小,详见PR2204和PR2212
※添加新武将OL韩馥、OL牛辅、成公英、星法正、传械马钧、奇巧马钧、OL董翓、新武将手杀SP甘夫人、谋郭嘉、谋张辽、SP曹操、威张辽、新杀袁胤、司马师、马钧、裴秀、幻刘封、莫琼树、无名专属·诗笺、OL南华老仙(三服老仙糖丸了)、幻曹昂、乐周瑜、谋邓艾、十周年李丰、卫青、OL武安国、OL谋公孙瓒、【线下·汉末风云】武将包、庞宏、吕据、OL郭照、星丁奉、OL薛灵芸、OL谋沮授、OL界廖化、OL谋黄月英、OL谋赵云、OL谋张飞、威孙权、OL谋张绣、龙起襄樊庞德、战神吕布、OL秦朗、势太史慈、OL刘璋、孙霸、神庞统、SP刘备、OL袁涣、手杀谋郭淮、族吴懿、幻黄盖、幻丁尚涴、手杀薛综、新服SP马超一号、新服SP马超二号、星文丑、【老友季】三个、武陆抗、势董昭、年兽、十二生肖、TW司马师、韩氏五虎、食岑昏、新张翼、威吕布、抢红包年兽、十二生肖、外服谋诸葛亮、外服谋曹丕
※将Key武将包中的“由依”重命名为“芳冈由依”
※单机模式下点将单挑添加玩家控制双方角色选项
※cardPrompt
支持传入第二个player
参数,详见PR2229
※为changeSkills
、addSkillLog
函数添加popup
参数以实现获得/失去技能时的popup
功能,详见PR2207
※删除未使用的技能_save
,将唯一用到的content
流程置入lib.element.content
(从lib.skill._save.content
到lib.element.content._save
),详见PR2229
※取消国战武将的体力限制
※补充部分技能的cost选择和content执行分离
※补充响应卡牌的chooseToRespond事件的respondTo属性
※修复tip和记牌器开关不生效的bug
※修改记牌器的样式且支持联机
※修复lib.element.player.$uninit
不能清除角色翻面、横置和tip显示的bug
※技能的usable支持函数写法(skill.usable(skill, player)
)
※Player.countSkill
支持返回更多技能本回合的使用次数
※3D武将解禁,线下卡牌包联机默认关闭
※现在每次启动都会检测并导入根目录的noname.config.txt配置文件了
※添加dedent.js(MIT),用于处理模板字符串的诱导缩进问题
※修复Chrome 123版本新增的import-with语法会在无名杀报错的问题
※添加部分Vite项目的特殊的查询参数功能
※新增经Mod检测的弃牌方法Player.modedDiscard(令玩家弃置其区域内一些能被弃置的牌)
※Get.cardPile
、Get.cardPile2
、Get.discardPile
功能拓展,可从牌堆顶或底部或随机开始遍历
※修复报错弹窗不准确的问题
※指示线优化(可从选项 - 外观 - 指示线调整配置)
※修复乱斗自定义场景装备牌和判定牌失效bug
※修复chooseUseTarget
不能使用自定义ai的问题
※加强身份局候选武将数功能
※现在联机模式也可以自定义各身份候选武将数了
※修复安卓端无法使用game.download
函数在线下载文件的问题
※Player.setAvatar
适配皮肤
※修复千里走单骑因打断arrangeTrigger事件,可能导致事件内的chooseControl没有result的问题
※修复历史记录栏单击后显示的技能详细中的技能名,仅会截取技能名的前两个字符的问题
※Player.markAuto
无第二个传参时将自动刷新标记(mark/unmark)
※Player.unmarkAuto
支持移除单个元素,并在没有长度时对此技能执行unmark,但仍然限制对应storage必须是数组以保证兼容性
※请所有开启[加强主公]的玩家重新开关一次此功能(关闭再开启),以保证其能够正常生效!
※修改get.skillInfoTranslation
,为其添加保底机制,避免报错
※修改_wuxie
用于在联机模式下令客机接收onChooseToUse
的相关赋值
※修改get.bottomCards
不再支持get.bottomCards(0)
的写法
※扩展衍生牌bug修复
※修复拼点event.small
不生效的问题
※新增AI.guessTargetPoints
方法
※现在GameEvent.addTrigger
会跑技能的getIndex
※菜单增加内核查看和切换功能
※其他bug修复、AI优化、台词调整、素材补充、姓名适配和补充函数注释。
扩展适配
修改了以下函数的扩展需要进行适配:
- game.check
- game.uncheck
- lib.element.player.$uninit
- lib.element.player.init
- lib.element.player.$update
- lib.element.content.die
- lib.element.player/content.draw/gainPlayerCard/chooseToGive
- lib.skill._save.content
- game.trySkillAnimate
- lib.element.content.chooseButtonOL
新增或修改的函数用法以及接口
- get.strNumber
/**
* 返回数字在扑克牌中的表示形式
* @param { number } num
* @param { boolean } [forced] 未获取点数字母对应元素时,若此参数不为false,则返回字符串格式
* @returns { string }
*/
strNumber(num, forced) {
if (typeof num !== "number") return;
let result = lib.numstrList.get(num);
if (result === undefined && forced !== false) result = num.toString();
return result;
}
- get.numString
/**
* 返回扑克牌中的表示形式对应的数字
* @param { string } str
* @param { boolean } [forced] 未获取字母点数对应元素时,若此参数不为false,则返回数字格式
* @returns { number }
*/
numString(str, forced) {
if (typeof str !== "string") return;
let result = lib.numstrList.entries().reduce((map, list) => {
map[list[1]] = list[0];
return map;
}, {})[str];
if (result === undefined && forced !== false) result = parseInt(str);
return result;
}
- usable(skill, player)
添加技能usable的函数使用方法(同卡牌usable使用方法),以步骘【定叛】(部分)为例
dingpan: {
// 其他代码省略
usable(skill, player) {
let num, mode = get.mode();
if (mode == "identity" || mode == "doudizhu") {
if (mode == "identity" && _status.mode == "purple") num = player.getEnemies().length;
else num = get.population("fan");
} else if (mode == "versus") {
if (!_status.mode || _status.mode != "two") num = player.getEnemies().length;
else {
const target = game.findPlayer(x => {
return !game.hasPlayer(y => {
return x != y && y.getFriends().length > x.getFriends().length;
});
});
num = target ? target.getFriends(true).length : 1;
}
} else {
num = 1;
}
return num;
},
}
- player.countSkill支持返回更多技能本回合的使用次数
/**
* @returns { number }
*/
countSkill(skill) {
const info = lib.skill[skill];
let num = 0;
if (!info) {
console.warn("“" + skill + "”为无效技能ID!");
return 0;
}
if (info.usable !== undefined && this.hasSkill("counttrigger") && this.storage.counttrigger) {
num = this.storage.counttrigger[skill];
if (typeof num === "number") return num;
}
num = this.getStat("skill")[skill];
if (typeof num === "number") return num;
return this.getHistory("useSkill", evt => {
return evt.skill === skill;
}).length;
}
- 新增Player.modedDiscard,用法同Player.discard,也触发discard事件,但不弃置不能弃置的牌
// 弃置target的所有红色牌
const cards = target.getDiscardableCards(player, "he", card => {
return get.color(card) === "red";
});
if (cards.length) await target.discard(cards, player);
// 可以改写为:
const cards = target.getCards("he", card => {
return get.color(card) === "red";
});
await target.modedDiscard(cards, player);
受Mod保护的牌不会被弃置且会告知对应Mod技能
可以传参false取消技能告知,或传参"logSkill"令对应技能在拦截卡牌时触发
存在区别的地方
/* 从target能被弃置的手牌中随机弃置两张 */
const cards = target.getDiscardableCards(target, "h");
if (cards.length) await target.discard(cards.randomGets(2));
/* 从target的手牌中随机弃置两张 */
const cards = target.getCards("h");
await target.modedDiscard(cards.randomGets(2), player);
由于Player.discard为强制弃牌,将第一段代码改为const cards = target.getCards("h");并不能实现第二段代码可能少弃甚至不弃牌的效果
Get.cardPile
、Get.cardPile2
、Get.discardPile
功能拓展
效果:试从指定区域获得一张牌
第一个参数 name:{function|string|object|true} 牌的筛选条件或名字,true为任意一张牌
第二个参数 position:{string|boolean|undefined} 筛选区域,默认牌堆+弃牌堆:
"cardPile":仅牌堆;
"discardPile":仅弃牌堆;
"filed":牌堆+弃牌堆+场上
若为true且name为{string|object}类型,则在筛选区域内没有找到卡牌时创建一张name条件的牌
第三个参数 start:{string|undefined} 遍历方式。默认置为"top"
"top":从牌堆和弃牌堆顶自顶向下遍历
"bottom":从牌堆和弃牌堆自底向上遍历
"random":随机位置遍历
// 新增start参数,可为“top”,“bottom”,“random”,代表从顶部、底部、随机获取,默认为顶部
get.cardPile(name, position, start)
- 添加部分Vite项目的特殊的查询参数功能(需要启用service worker)
raw: 返回资源的原始内容字符串
import string from './noname.js?raw';
// 打印该文件的字符串形式
console.log(string);
worker和sharedworker: 返回一个 Web Worker 或 Shared Worker 构造函数
// 普通worker
import myWorker from 'url?worker';
new myWorker();
// 普通sharedworker
import myWorker2 from 'url?sharedworker';
new myWorker2();
// 模块worker
import myWorker3 from 'url?worker&module';
new myWorker3();
// 模块sharedworker
mport myWorker4 from 'url?sharedworker&module';
new myWorker4();
url: 返回资源的 URL 而不是文件内容
import logoUrl from 'logo.png?url';
img.src = logoUrl;
- 支持直接通过import导入css,json,typescript,vue文件
css: 无返回值,将css直接嵌入到html中
import './a/b.css';
await import('./a/b.css');
注: 在chrome 123中全面支持的import-with导入css: 返回CSSStyleSheet
import sheet from './a/b.css' with { type: "css" };
const { default: sheet } = await import("./a/b.css", { with: { type: "css" } });
json: 将json文件的数据转换为js的json数据
import json from './package.json'
const { default: json } = await import('./package.json');
注: 在chrome 123中全面支持的import-with导入json: 返回对应的json数据
import json from './package.json' with { type: "json" };
const { default: json } = await import('./package.json', { with: { type: "json"} });
typescript: 返回编译后的js,同样的,在电脑端可以导入一个node的原生模块(js文件中也可用)
import xxx from './a/b.ts';
const { default: xxx } = await import('./a/b.ts');
import fs from 'node:fs';
const { default: fs } = await import('node:fs');
vue: 同vue项目的使用方法,vue文件中目前只支持使用原生js,ts和原生css
<template>
<Hello />
</template>
<script setup lang="ts">
import Hello from './Hello.vue';
// 或
const { default: Hello} = await import('./Hello.vue');
</script>
get.cards
、get.bottomCards
、player.getTopCards
等方法不再支持num参数小于等于0的情况
/* 此前执行以下情况等均会获取相应牌堆首张牌的数组(虽然没有实际应用) ,这与新武将乐周瑜的初始手牌数可为0冲突*/
/* 从牌堆顶摸牌 */
const cards = get.cards(-1);
/* 从牌堆底摸牌 */
const cards = get.bottomCards(0);
/* 从斗地主智斗模式的底牌库中摸牌 */
const cards = player.getTopCards(-2);
- 为
get.skillInfoTranslation
添加保底检测
在某些情况如DIY张绣百鸣初始化技能时,部分扩展的技能翻译存在为最终返回值为undefined的情况,主要是动态翻译(一般没人会在lib.translate[技能名 + "_info"]也返回不为字符串的类型吧)如:
dynamicTranslate: {
jineng(player) {
if (player.storage.jineng == 1) return '出牌阶段,你可以摸一张牌。';
else if (player.storage.jineng == 2) return '出牌阶段,你可以摸两张牌。';
else if (player.storage.jineng == 3) return '出牌阶段,你可以摸三张牌。';
}
}
现在对原来的返回值进行一步类型检查的保底检测,不为字符串则于控制台反馈
Player.markAuto
无第二个参数时支持依据各类型的this.storage[name]对技能name标记进行this.markSkill(name)或this.unmarkSkill(name)操作了
/* 此前执行以下语句均无效果 */
if (typeof player.storage.skill_id1 === "string") player.markAuto("skill_id1");
if (typeof player.storage.skill_id2 === "boolean") player.markAuto("skill_id2");
Player.unmarkAuto
第二个参数即使不为数组,亦可将其作为元素加入this.storage[name]内了(当然this.storage[name]须为数组)AI.getTargetPoints
获取viewer视角下target手牌的点数、最大值和最小值
target(必需): { Player } target 目标
viewer: { Player | true } 视角,true则透视
cards: { function (Card): boolean | Card[] } 枚举的卡牌或卡牌筛选条件
access: { string } [access] Cache存取,默认"11"。第一位为"1"存入,第二位为"1"读取
right: { number } 最大值限制,默认13
left: { number } 最小值限制,默认1
返回值{ nums: number[], max: number, min: number }
ai.getTargetPoints(target, viewer, cards, access ,right, left);
- 为
damage
属性添加无视护甲参数nohujia
示例(以XXX
的部分代码为例)
//牢写法-对自己造成1点无视护甲的伤害
lib.skill['XXX'] = {
content() {
player.damage();
},
ai: {
nohujia: true,
skillTagFilter(player) {
return get.event().getParent('XXX').player === player;
},
},
};
//新写法①
lib.skill['XXX'] = {
content() {
player.damage('nohujia');
},
};
//新写法②
lib.skill['XXX'] = {
content() {
player.damage().nohujia = true;
},
};
- 为
lib.element.player.getRoundHistory
的filter
参数添加默认的lib.filter.all
第二个filer参数不存在或不为函数则默认lib.filter.all
,不作使用介绍 - 为
lib.element.player.draw/gainPlayerCard/chooseToGive
添加默认gaintag
属性
//以后将支持直接为这三个函数获得的牌添加gaintag
//lib.element.player.draw
player.draw().gaintag.add('wusheng');
//lib.element.player.gainPlayerCard
player.gainPlayerCard(target,'h',true).gaintag.add('wusheng');
//lib.element.player.chooseToGive
player.chooseToGive(target,'h',true).gaintag.add('wusheng');
- 拓充翻页功能
添加lib.element.dialog
方法addPagination
添加一系列翻页方法,翻页方法变量定义详见文件node_modules\@types\noname-typings\Pagination.d.ts
翻页方法具体实现详见文件noname\util\pagination.js
以下为界左慈【化身】代码,仅展示使用了翻页方法的部分
(目前【化身】dialog代码得到进一步优化,本实例着重于方法介绍,需要技能本身的请进入character
文件夹查询rehuashen
代码)
//界左慈化身
rehuashen: {
content() {
"step 0";
//...代码省略
var dialog = (event.dialog = ui.create.dialog(get.prompt("rehuashen"), [cards, (item, type, position, noclick, node) => lib.skill.rehuashen.$createButton(item, type, position, noclick, node)]));
event.dialog.videoId = event.videoId;
var buttons = dialog.content.querySelector(".buttons");
var array = dialog.buttons.filter(item => !item.classList.contains("nodisplay") && item.style.display !== "none");
var groups = array
.map(i => get.character(i.link).group)
.unique()
.sort((a, b) => {
const getNum = g => (lib.group.includes(g) ? lib.group.indexOf(g) : lib.group.length);
return getNum(a) - getNum(b);
});
if (groups.length > 1) {
event.dialog.classList.add("fullheight");
event.dialog.addPagination({
data: array,
totalPageCount: groups.length,
container: dialog.content,
insertAfter: buttons,
onPageChange(state) {
const { pageNumber, data } = state;
data.forEach(item => {
const group = get.character(item.link).group;
item.classList[groups.indexOf(group) + 1 === pageNumber ? "remove" : "add"]("nodisplay");
});
},
pageLimitForCN: ["上一势力", "下一势力"],
pageNumberForCN: groups.map(i => get.plainText(lib.translate[i + "2"] || lib.translate[i] || "无").slice(0, 1)),
changePageEvent: "click",
});
}
//...代码省略
"step 2";
if (result.bool && event.control != "弃置化身") {
event.card = result.links[0];
var func = function (card, id) {
var dialog = get.idDialog(id);
if (dialog) {
//禁止翻页
var paginationInstance = dialog.paginationMap?.get(event.dialog.content.querySelector(".buttons"));
if (paginationInstance?.state) paginationInstance.state.pageRefuseChanged = true;
for (var i = 0; i < dialog.buttons.length; i++) {
if (dialog.buttons[i].link == card) {
dialog.buttons[i].classList.add("selectedx");
} else {
dialog.buttons[i].classList.add("unselectable");
}
}
}
};
if (player.isOnline2()) {
player.send(func, event.card, event.videoId);
} else if (event.isMine()) {
func(event.card, event.videoId);
}
var list = player.storage.rehuashen.map[event.card].slice(0);
list.push("返回");
player
.chooseControl(list)
.set("choice", event.aiChoice)
.set("ai", function () {
return _status.event.choice;
});
} else {
lib.skill.rehuashen.removeHuashen(player, result.links.slice(0));
lib.skill.rehuashen.addHuashens(player, result.links.length);
}
"step 3";
if (result.control == "返回") {
var func = function (id) {
var dialog = get.idDialog(id);
if (dialog) {
//允许翻页
var paginationInstance = dialog.paginationMap?.get(event.dialog.content.querySelector(".buttons"));
if (paginationInstance?.state) paginationInstance.state.pageRefuseChanged = false;
for (var i = 0; i < dialog.buttons.length; i++) {
dialog.buttons[i].classList.remove("selectedx");
dialog.buttons[i].classList.remove("unselectable");
}
}
};
if (player.isOnline2()) {
player.send(func, event.videoId);
} else if (event.isMine()) {
func(event.videoId);
}
event._result = { control: "更换技能" };
event.goto(1);
return;
}
//...代码省略
},
},