Merge branch 'libccy:PR-Branch' into PR-Branch

This commit is contained in:
157 2024-01-20 09:53:28 +08:00 committed by GitHub
commit 29d6f111c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 398 additions and 295 deletions

View File

@ -1,3 +1,9 @@
noname-server.exe的源码见以下仓库
https://github.com/nonameShijian/noname-server
---
在线试玩:
https://spmario233.github.io/noname/index.html (图片素材加载速度较慢,不推荐)

View File

@ -272,7 +272,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
game.cardsGotoOrdering(cards);
const color=event.result.card.name=='wuxie'?'black':'red';
if(get.color(cards,false)!=color){
player.tempBanSkill('jsrgwentian');
player.tempBanSkill('jsrgwentian','roundStart');
}
}
}
@ -284,12 +284,12 @@ game.import('character',function(lib,game,ui,get,ai,_status){
usable:1,
filter(event,player){
const zhu=get.zhu(player);
if(!zhu||!zhu.isZhu2()) return false;
return !player.isZhu2();
if(!zhu||!zhu.isZhu2()||!zhu.countCards('h')) return false;
return !player.isZhu2()&&player.countCards('h');
},
async content(event,trigger,player){
player.chooseToDebate(game.filterPlayer(current=>{
return current==player||current.isZhu2();
return (current==player||current.isZhu2())&&current.countCards('h');
})).set('callback',async event=>{
const result=event.debateResult;
if(result.bool&&result.opinion){
@ -574,7 +574,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
if(!get.event('goon')) return 0;
return -get.attitude(get.player(),target);
}).set('goon',player.countCards('hs',['shan','caochuan'])||player.getHp()>=3);
if(!result.bool) return event.finish();
if(!result.bool) return;
const {targets}=result,target=targets[0];
player.logSkill('jsrgyoujin',target);
const {result:result2}=await player.chooseToCompare(target).set('small',true);
@ -701,8 +701,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
return p+c;
},0)<-4).set('logSkill',['jsrglonglin',trigger.player]);
if(result.bool){
trigger.targets.length=0;
trigger.all_excluded=true;
trigger.excluded.addArray(trigger.targets);
game.asyncDelayx();
if(trigger.player.canUse(juedou,player)){
const {result}=await trigger.player.chooseBool(`是否视为对${get.translation(player)}使用一张【决斗】?`).set('choice',get.effect(player,juedou,trigger.player,trigger.player)>=0);
@ -880,7 +879,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
]);
next.set('prompt','鹰眎:点击将牌移动到牌堆顶或牌堆底');
next.processAI=list=>{
const cards=list[0][1],player=_status.event.player;
const cards=list[1][1],player=_status.event.player;
const top=[];
const judges=player.getCards('j');
let stopped=false;
@ -977,6 +976,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
global:['gainAfter','equipAfter','addJudgeAfter','loseAsyncAfter','addToExpansionAfter'],
},
filter(event,player){
if(player.isHealthy()) return false;
const evt=event.getl(player);
return evt&&evt.es&&evt.es.length>0;
},
@ -1011,7 +1011,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
charlotte:true,
forced:true,
silent:true,
async content(event,trigger,player){
content(){
trigger.player.addGaintag(trigger.cards,'jsrgtuigu');
trigger.player.addTempSkill('jsrgtuigu_blocked',{player:'phaseAfter'});
}
@ -1213,6 +1213,9 @@ game.import('character',function(lib,game,ui,get,ai,_status){
enable:'chooseToUse',
viewAs:{name:'lebu'},
position:'hes',
viewAsFilter(player){
return player.countCards('hes');
},
filterCard(card,player){
return get.color(card)=='red'&&get.type2(card)!='trick';
},
@ -1506,22 +1509,29 @@ game.import('character',function(lib,game,ui,get,ai,_status){
effect:{
audio:'jsrgdanxin',
trigger:{
player:['loseAfter','gainAfter'],
global:'gainAfter',
},
filter(event,player){
if(event.getParent(2).name!='tuixinzhifu') return false;
const card=event.getParent(3).card;
const level=event.player!=player?1:2;
if(event.player!=player&&event.getParent(level).name!='tuixinzhifu') return false;
if(event.player==player&&event.getParent(level).name!='tuixinzhifu') return false;
const card=event.getParent(level+1).card;
return card&&card.storage&&card.storage.jsrgdanxin;
},
forced:true,
popup:false,
charlotte:true,
async content(event,trigger,player){
const {targets}=trigger.getParent(3);
const level=trigger.player!=player?1:2;
const {targets}=trigger.getParent(level+1);
await player.showCards(trigger.cards);
if(trigger.cards.some(card=>get.suit(card)=='heart')){
await get.owner(trigger.cards.find(card=>get.suit(card)=='heart')).recover();
const owners=trigger.cards.filter((card=>get.suit(card)=='heart')).map(card=>get.owner(card)).toUniqued();
for(const owner of owners){
if(owner&&owner.isIn()) await owner.recover();
}
}
if(trigger.player==player) return;
player.addTempSkill('jsrgdanxin_distance');
if(!player.storage.jsrgdanxin_distance) player.storage.jsrgdanxin_distance={};
const id=targets[0].playerid;
@ -1570,7 +1580,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
},true).set('ai',target=>{
const player=get.player();
const att=get.attitude(player,target);
const delta=get.value(target.getCards('e'),player)-get.value(player.getCards('e'),player);
let delta=get.value(target.getCards('e'),player)-get.value(player.getCards('e'),player);
if(att>0){
if(delta<0) delta+=att/3;
}
@ -1754,7 +1764,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
async content(event,trigger,player){
const target=event.target;
const targets=game.filterPlayer(current=>target.inRange(current)&&current!=player).sortBySeat(player);
if(!targets.length) return event.finish();
if(!targets.length) return;
while(targets.length){
const current=targets.shift();
if(current.countCards('he')) await current.chooseToDiscard('驰应:请弃置一张牌','he',true);
@ -1777,7 +1787,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
const targets=game.filterPlayer(current=>target.inRange(current)&&current!=player);
let eff=0;
for(const targetx of targets){
const effx=get.effect(targetx,{name:'guohe_copy2'},player,target);
let effx=get.effect(targetx,{name:'guohe_copy2'},player,target);
if(get.attitude(player,targetx)<0) effx/=2;
eff+=effx;
}
@ -8200,7 +8210,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
jsrg_zhugeliang:'梦诸葛亮',
jsrg_zhugeliang_prefix:'梦',
jsrgwentian:'问天',
jsrgwentian_info:'①你可以将牌堆顶的牌当【无懈可击】/【火攻】使用,若此牌不为黑色/红色,〖问天〗于本回合失效。②每回合限一次。你的一个阶段开始时,你可以观看牌堆顶的五张牌,然后将其中一张牌交给一名其他角色,将其余牌以任意顺序置于牌堆顶或牌堆底。',
jsrgwentian_info:'①你可以将牌堆顶的牌当【无懈可击】/【火攻】使用,若此牌不为黑色/红色,〖问天〗于本失效。②每回合限一次。你的一个阶段开始时,你可以观看牌堆顶的五张牌,然后将其中一张牌交给一名其他角色,将其余牌以任意顺序置于牌堆顶或牌堆底。',
jsrgchushi:'出师',
jsrgchushi_info:'出牌阶段限一次。若你不为主公你可以与主公议事。若结果为红色你与其各摸一张牌若你与其手牌数之和小于7重复此流程黑色当你于本轮内造成属性伤害时此伤害+1。',
jsrgyinlve:'隐略',

View File

@ -954,7 +954,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){
forced:true,
content:function(){
'step 0'
player.addTempSkill('olsilv'+(trigger.getg?'gain':'lose'));
player.addTempSkill('olsilv_'+(trigger.getg?'gain':'lose'));
if(!trigger.visible){
var cards,name=player.storage.ollianju;
if(trigger.getg) cards=trigger.getg(player).filter(card=>card.name==name);

View File

@ -1,5 +1,5 @@
window.noname_asset_list=[
'v1.10.6',
'v1.10.6.1',
/*audio start*/
'audio/background/aozhan_chaoming.mp3',
'audio/background/aozhan_online.mp3',
@ -7237,30 +7237,41 @@ window.noname_asset_list=[
'image/character/jsp_liubei.jpg',
'image/character/jsp_zhaoyun.jpg',
'image/character/jsrg_caocao.jpg',
'image/character/jsrg_caofang.jpg',
'image/character/jsrg_chendeng.jpg',
'image/character/jsrg_chunyuqiong.jpg',
'image/character/jsrg_dongbai.jpg',
'image/character/jsrg_fanjiangzhangda.jpg',
'image/character/jsrg_gaoxiang.jpg',
'image/character/jsrg_guanyu.jpg',
'image/character/jsrg_guojia.jpg',
'image/character/jsrg_guoxun.jpg',
'image/character/jsrg_guozhao.jpg',
'image/character/jsrg_hansui.jpg',
'image/character/jsrg_hejin.jpg',
'image/character/jsrg_huangfusong.jpg',
'image/character/jsrg_huangzhong.jpg',
'image/character/jsrg_jiangwei.jpg',
'image/character/jsrg_kongrong.jpg',
'image/character/jsrg_liubei.jpg',
'image/character/jsrg_liuhong.jpg',
'image/character/jsrg_liuyan.jpg',
'image/character/jsrg_liuyong.jpg',
'image/character/jsrg_lougui.jpg',
'image/character/jsrg_luxun.jpg',
'image/character/jsrg_lvbu.jpg',
'image/character/jsrg_machao.jpg',
'image/character/jsrg_nanhualaoxian.jpg',
'image/character/jsrg_pangtong.jpg',
'image/character/jsrg_qiaoxuan.jpg',
'image/character/jsrg_simayi.jpg',
'image/character/jsrg_sunce.jpg',
'image/character/jsrg_sunjian.jpg',
'image/character/jsrg_sunjun.jpg',
'image/character/jsrg_sunlubansunluyu.jpg',
'image/character/jsrg_sunshangxiang.jpg',
'image/character/jsrg_wangyun.jpg',
'image/character/jsrg_weiwenzhugezhi.jpg',
'image/character/jsrg_xiahouen.jpg',
'image/character/jsrg_xiahourong.jpg',
'image/character/jsrg_xugong.jpg',
@ -7272,7 +7283,10 @@ window.noname_asset_list=[
'image/character/jsrg_zhanghe.jpg',
'image/character/jsrg_zhangliao.jpg',
'image/character/jsrg_zhangren.jpg',
'image/character/jsrg_zhangxuan.jpg',
'image/character/jsrg_zhaoyun.jpg',
'image/character/jsrg_zhenji.jpg',
'image/character/jsrg_zhugeliang.jpg',
'image/character/jsrg_zhujun.jpg',
'image/character/jsrg_zoushi.jpg',
'image/character/jun_caocao.jpg',

View File

@ -1,137 +1,56 @@
window.noname_update={
version:'1.10.6',
update:'1.10.5',
version:'1.10.6.1',
update:'1.10.6',
changeLog:[
'整合@nonameShijian @mengxinzxz @PZ157 @Ansolve @Rintim @S-N-O-R-L-A-X @universe-st @copcap @kuangshen04 的Pull Request',
'拆分game.js优化代码逻辑与可读性',
'孙策(十周年斗地主)、乐小乔、手杀陈珪、手杀胡班、OL界凌统、OL界曹彰、OL界简雍、OL谋姜维、谋诸葛亮、谋关羽、曹宇、星董卓、曹宪、新杀谋鲁肃、新杀谋周瑜',
'整合@mengxinzxz @PZ157 @universe-st @Ansolve @Rintim @nonameShijian @copcap @kuangshen04 的Pull Request',
'《江山如故·合》武将包',
'其他AI优化与bug修复',
],
files:[
'card/extra.js',
'card/gujian.js',
'card/guozhan.js',
'card/gwent.js',
'card/hearth.js',
'card/huanlekapai.js',
'card/mtg.js',
'card/sp.js',
'card/standard.js',
'card/swd.js',
'card/yingbian.js',
'card/yongjian.js',
'card/yunchou.js',
'card/zhenfa.js',
'card/zhulu.js',
'character/clan.js',
'character/collab.js',
'character/ddd.js',
'character/diy.js',
'character/extra.js',
'character/gujian.js',
'character/gwent.js',
'character/hearth.js',
'character/huicui.js',
'character/jiange.js',
'character/jsrg.js',
'character/mobile.js',
'character/mtg.js',
'character/offline.js',
'character/old.js',
'character/onlyOL.js',
'character/ow.js',
'character/rank.js',
'character/refresh.js',
'character/sb.js',
'character/shenhua.js',
'character/shiji.js',
'character/sp.js',
'character/sp2.js',
'character/standard.js',
'character/swd.js',
'character/tw.js',
'character/xiake.js',
'character/xianding.js',
'character/xianjian.js',
'character/xinghuoliaoyuan.js',
'character/yijiang.js',
'character/yingbian.js',
'character/yxs.js',
'extension/boss/extension.js',
'game/codemirror.js',
'game/config.js',
'game/core-js-bundle.js',
'game/entry.js',
'game/game.js',
'game/package.js',
'game/source.js',
'mode/boss.js',
'mode/brawl.js',
'mode/chess.js',
'mode/doudizhu.js',
'mode/guozhan.js',
'mode/identity.js',
'mode/realtime.js',
'mode/single.js',
'mode/stone.js',
'mode/tafang.js',
'mode/versus.js',
'noname.js',
'noname/ai/basic.js',
'noname/ai/index.js',
'noname/game/index.js',
'noname/game/promises.js',
'noname/game/dynamic-style/index.js',
'noname/get/index.js',
'noname/get/is.js',
'noname/gnc/index.js',
'noname/gnc/is.js',
'noname/init/cordova.js',
'noname/init/import.js',
'noname/init/index.js',
'noname/init/node.js',
'noname/init/onload.js',
'noname/init/polyfill.js',
'noname/library/index.js',
'noname/library/path.js',
'noname/library/announce/index.d.ts',
'noname/library/announce/index.js',
'noname/library/channel/index.js',
'noname/library/element/button.js',
'noname/library/element/card.js',
'noname/library/element/client.js',
'noname/library/element/content.js',
'noname/library/element/contents.js',
'noname/library/element/control.js',
'noname/library/element/dialog.js',
'noname/library/element/gameEvent.js',
'noname/library/element/gameEventPromise.js',
'noname/library/element/index.js',
'noname/library/element/nodeWS.js',
'noname/library/element/player.js',
'noname/library/element/vcard.js',
'noname/library/experimental/index.js',
'noname/library/experimental/symbol.js',
'noname/library/init/index.js',
'noname/library/init/promises.js',
'noname/status/index.js',
'noname/ui/index.js',
'noname/util/browser.js',
'noname/util/config.js',
'noname/util/index.js',
'noname/util/mutex.js',
'noname/util/struct/index.js',
'noname/util/struct/interface/index.d.ts',
'noname/util/struct/interface/promise-error-handler.d.ts',
'noname/util/struct/promise-error-handler/chrome.js',
'noname/util/struct/promise-error-handler/firefox.js',
'noname/util/struct/promise-error-handler/index.js',
'noname/util/struct/promise-error-handler/unknown.js',
]
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -18,118 +18,130 @@
</script>
<script>
if (location.href.startsWith('http') && typeof window.initReadWriteFunction != 'function' && !window.require && !window.__dirname) {
window.initReadWriteFunction = function(game) {
/*game.download = function() {
// 暂不实现
};*/
game.createDir = function (dir, success = () => {}, error = () => {}) {
fetch(`./createDir?dir=${dir}`)
window.initReadWriteFunction = async function(game) {
return new Promise((resolve, reject) => {
fetch(`./readFile?fileName=noname.js`)
.then(response => {
return response.json();
})
.then(result => {
if (result && result.success) success();
else error();
if (result && result.success) callback();
else reject(result.errorMsg)
})
.catch(error);
};
.catch(reject);
game.readFile = function (fileName, callback = () => {}, error = () => {}) {
fetch(`./readFile?fileName=${fileName}`)
.then(response => {
return response.json();
})
.then(result => {
if (result && result.success) callback(new Uint8Array(result.data).buffer);
else error(result && result.errorMsg);
})
.catch(error);
};
game.readFileAsText = function (fileName, callback = () => {}, error = () => {}) {
fetch(`./readFileAsText?fileName=${fileName}`)
.then(response => {
return response.json();
})
.then(result => {
if (result && result.success) callback(result.data);
else error(result && result.errorMsg);
})
.catch(error);
};
game.writeFile = function (data, path, name, callback = () => { }) {
game.ensureDirectory(path, function () {
if (Object.prototype.toString.call(data) == '[object File]') {
const fileReader = new FileReader();
fileReader.onload = function (e) {
game.writeFile(e.target.result, path, name, callback);
};
fileReader.readAsArrayBuffer(data, "UTF-8");
}
else {
let filePath = path;
if (path.endsWith('/')) {
filePath += name;
} else if (path == "") {
filePath += name;
} else {
filePath += '/' + name;
}
fetch(`./writeFile`, {
method: 'post',
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
data: typeof data == 'string' ? data : Array.prototype.slice.call(new Uint8Array(data)),
path: filePath
function callback() {
game.createDir = function (dir, success = () => { }, error = () => { }) {
fetch(`./createDir?dir=${dir}`)
.then(response => {
return response.json();
})
})
.then(response => {
return response.json();
})
.then(result => {
if (result && result.success) {
callback();
} else {
callback(result.errorMsg);
.then(result => {
if (result && result.success) success();
else error();
})
.catch(error);
};
game.readFile = function (fileName, callback = () => { }, error = () => { }) {
fetch(`./readFile?fileName=${fileName}`)
.then(response => {
return response.json();
})
.then(result => {
if (result && result.success) callback(new Uint8Array(result.data).buffer);
else error(result && result.errorMsg);
})
.catch(error);
};
game.readFileAsText = function (fileName, callback = () => { }, error = () => { }) {
fetch(`./readFileAsText?fileName=${fileName}`)
.then(response => {
return response.json();
})
.then(result => {
if (result && result.success) callback(result.data);
else error(result && result.errorMsg);
})
.catch(error);
};
game.writeFile = function (data, path, name, callback = () => { }) {
game.ensureDirectory(path, function () {
if (Object.prototype.toString.call(data) == '[object File]') {
const fileReader = new FileReader();
fileReader.onload = function (e) {
game.writeFile(e.target.result, path, name, callback);
};
fileReader.readAsArrayBuffer(data, "UTF-8");
}
else {
let filePath = path;
if (path.endsWith('/')) {
filePath += name;
} else if (path == "") {
filePath += name;
} else {
filePath += '/' + name;
}
fetch(`./writeFile`, {
method: 'post',
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
data: typeof data == 'string' ? data : Array.prototype.slice.call(new Uint8Array(data)),
path: filePath
})
})
.then(response => {
return response.json();
})
.then(result => {
if (result && result.success) {
callback();
} else {
callback(result.errorMsg);
}
});
}
});
}
});
};
};
game.removeFile = function (fileName, callback = () => {}) {
fetch(`./removeFile?fileName=${fileName}`)
.then(response => {
return response.json();
})
.then(result => {
callback(result.errorMsg);
})
.catch(error);
};
game.removeFile = function (fileName, callback = () => { }) {
fetch(`./removeFile?fileName=${fileName}`)
.then(response => {
return response.json();
})
.then(result => {
callback(result.errorMsg);
})
.catch(error);
};
game.getFileList = function (dir, callback = () => {}) {
fetch(`./getFileList?dir=${dir}`)
.then(response => {
return response.json();
})
.then(result => {
if (result && result.success) {
callback(result.data.dirs, result.data.files);
game.getFileList = function (dir, callback = () => { }) {
fetch(`./getFileList?dir=${dir}`)
.then(response => {
return response.json();
})
.then(result => {
if (result && result.success) {
callback(result.data.dirs, result.data.files);
}
});
};
game.ensureDirectory = function (list, callback = () => { }, file = false) {
let pathArray = typeof list == "string" ? list.split("/") : list;
if (file) {
pathArray = pathArray.slice(0, -1);
}
});
};
game.createDir(pathArray.join("/"), callback, console.error);
};
game.ensureDirectory = function (list, callback = () => {}, file = false) {
let pathArray = typeof list == "string" ? list.split("/") : list;
if (file) {
pathArray = pathArray.slice(0, -1);
resolve();
}
game.createDir(pathArray.join("/"), callback, console.error);
};
});
};
}
</script>

View File

@ -8818,7 +8818,7 @@ game.import('mode',function(lib,game,ui,get,ai,_status){
}
function isDefined(opd) {
if(opd!=undefined){
if (opd.get||opd.set||opd.writable!=true||opd.configurable!=true||opd.enumerable!=true){
if (opd.get||opd.set||opd.writable!=true||opd.configurable!=true){
return true;
}
}

View File

@ -68,8 +68,8 @@ declare interface Window {
get: Get;
ai: AI;
}
initReadWriteFunction?(game: Game): void;
/** 为其他自定义平台提供文件读写函数赋值的一种方式 */
initReadWriteFunction?(game: Game): Promise<void>;
bannedKeyWords: string[];
}

View File

@ -1364,19 +1364,21 @@ export class Game extends Uninstantable {
else if (!path.startsWith('db:')) path = `audio/${path}`;
if (!lib.config.repeat_audio && _status.skillaudio.includes(path)) return;
}
_status.skillaudio.add(path);
game.addVideo('playAudio', null, path);
setTimeout(() => _status.skillaudio.remove(path), 1000);
const audio = document.createElement('audio');
audio.autoplay = true;
audio.volume = lib.config.volumn_audio / 8;
audio.addEventListener('ended', () => audio.remove());
audio.onerror = event => {
//Some browsers do not support "autoplay", so "oncanplay" listening has been added
audio.oncanplay = () => Promise.resolve(audio.play()).catch(() => void 0);
audio.onplay = () => {
_status.skillaudio.add(path);
setTimeout(() => _status.skillaudio.remove(path), 1000);
game.addVideo("playAudio", null, path);
};
audio.onended = (event) => audio.remove();
audio.onerror = (event) => {
audio.remove();
if (onError) onError(event);
};
//Some browsers do not support "autoplay", so "oncanplay" listening has been added
audio.oncanplay = () => Promise.resolve(audio.play()).catch(() => void 0);
new Promise((resolve, reject) => {
if (path.startsWith('db:')) game.getDB('image', path.slice(3)).then(octetStream => resolve(get.objectURL(octetStream)), reject);
else if (lib.path.extname(path)) resolve(`${lib.assetURL}${path}`);
@ -4213,6 +4215,7 @@ export class Game extends Uninstantable {
* @param { string } skill
* @param { Player } player
* @param { GameEventPromise } event
* @returns { GameEventPromise }
*/
static createTrigger(name, skill, player, event) {
let info = get.info(skill);
@ -4227,6 +4230,7 @@ export class Game extends Uninstantable {
next.includeOut = true;
next._trigger = event;
next.setContent('createTrigger');
return next;
}
/**
* @legacy Use {@link lib.element.GameEvent.constructor} instead.
@ -5362,10 +5366,11 @@ export class Game extends Uninstantable {
* @param { GameEventPromise } [belongAsyncEvent]
*/
static async loop(belongAsyncEvent) {
if (!game.belongAsyncEventList) game.belongAsyncEventList = [];
if (belongAsyncEvent) {
game.belongAsyncEvent = belongAsyncEvent;
} else if (game.belongAsyncEvent) {
return game.loop(game.belongAsyncEvent);
game.belongAsyncEventList.push(belongAsyncEvent);
} else if (game.belongAsyncEventList.length) {
belongAsyncEvent = game.belongAsyncEventList.at(-1);
}
while (true) {
let event = (belongAsyncEvent && belongAsyncEvent.parent == _status.event) ? belongAsyncEvent : _status.event;
@ -5443,8 +5448,8 @@ export class Game extends Uninstantable {
event.parent._result = event.result;
}
_status.event = event.parent;
if (game.belongAsyncEvent == event) {
delete game.belongAsyncEvent;
if (game.belongAsyncEventList.includes(event)) {
game.belongAsyncEventList.remove(event);
}
_resolve();
// 此时应该退出了
@ -5453,8 +5458,8 @@ export class Game extends Uninstantable {
}
}
else {
if (game.belongAsyncEvent == event) {
delete game.belongAsyncEvent;
if (game.belongAsyncEventList.includes(event)) {
game.belongAsyncEventList.remove(event);
}
return _resolve();
}

View File

@ -1367,11 +1367,13 @@ export class Get extends Uninstantable {
}
static infoFuncOL(info) {
var func;
const str = info.slice(13).trim();
try {
eval('func=(' + info.slice(13) + ');');
}
catch (e) {
return function () { };
if (str.startsWith("function") || str.startsWith("(")) eval(`func=(${str});`);
else eval(`func=(function ${str});`);
} catch (e) {
console.error(`${e} in \n${str}`);
return function () {};
}
if (Array.isArray(func)) {
func = get.filter.apply(this, get.parsedResult(func));

View File

@ -17,7 +17,7 @@ export async function cordovaReady() {
}
});
document.addEventListener("resume", () => {
if (ui.backgroundMusic) ui.backgroundMusic.play();
if (ui.backgroundMusic && !isNaN(ui.backgroundMusic.duration)) ui.backgroundMusic.play();
});
document.addEventListener("backbutton", function () {
if (ui.arena && ui.arena.classList.contains('menupaused')) {

View File

@ -106,7 +106,7 @@ export async function boot() {
else if (!Reflect.has(lib, 'device')) {
Reflect.set(lib, 'path', (await import('../library/path.js')).default);
//为其他自定义平台提供文件读写函数赋值的一种方式。
//但这种方式只修改game的文件读写函数。
//但这种方式只允许修改game的文件读写函数。
if (typeof window.initReadWriteFunction == 'function') {
const g = {};
const ReadWriteFunctionName = ['download', 'readFile', 'readFileAsText', 'writeFile', 'removeFile', 'getFileList', 'ensureDirectory', 'createDir'];
@ -123,7 +123,9 @@ export async function boot() {
});
});
// @ts-ignore
window.initReadWriteFunction(g);
await window.initReadWriteFunction(g).catch(e => {
console.error('文件读写函数初始化失败:', e);
});
}
window.onbeforeunload = function () {
if (config.get('confirm_exit') && !_status.reloading) {
@ -861,7 +863,8 @@ async function setOnError() {
}
}
//解析parsex里的content fun内容(通常是技能content)
else if (err && err.stack && err.stack.split('\n')[1].trim().startsWith('at Object.eval [as content]')) {
// @ts-ignore
else if (err && err.stack && ['at Object.eval [as content]', 'at Proxy.content'].some(str => err.stack.split('\n')[1].trim().startsWith(str))) {
const codes = _status.event.content;
if (typeof codes == 'function') {
const lines = codes.toString().split("\n");

View File

@ -393,6 +393,14 @@ export const Content = {
event.swapped = true;
}
"step 5";
if (get.itemtype(result) == 'cards') {
for (let card of result){
if (card.willBeDestroyed('discardPile', player, event)) {
card.selfDestroy(event);
}
}
}
"step 6";
//if(player.isMin() || player.countCards('e',{subtype:get.subtype(card)})){
if (player.isMin() || !player.canEquip(card)) {
event.finish();
@ -411,7 +419,7 @@ export const Content = {
game.addVideo('equip', player, get.cardInfo(card));
if (event.log != false) game.log(player, '装备了', card);
if (event.updatePile) game.updateRoundNumber();
"step 6";
"step 7";
var info = get.info(card, false);
if (info.onEquip && (!info.filterEquip || info.filterEquip(card, player))) {
if (Array.isArray(info.onEquip)) {
@ -2002,48 +2010,57 @@ export const Content = {
event.callback();
}
},
arrangeTrigger: function () {
'step 0';
event.doing = event.doingList[0];
if (event.doing && event.doing.todoList.length) return;
if (event.doingList.length) {
event.doingList.shift();
return event.redo();
arrangeTrigger: async function (event,trigger,player) {
while(event.doingList.length>0){
event.doing = event.doingList.shift();
while(true){
if (trigger.filterStop && trigger.filterStop()) return;
const usableSkills = event.doing.todoList.filter(info => {
if (!lib.filter.filterTrigger(trigger, info.player, event.triggername, info.skill)) return false;
return lib.skill.global.includes(info.skill) || info.player.hasSkill(info.skill, true);
});
if (usableSkills.length == 0){
break;
}
else {
event.doing.todoList = event.doing.todoList.filter(i => i.priority <= usableSkills[0].priority);
//firstDo时机和lastDo时机不进行技能优先级选择
if (get.itemtype(event.doing.player) !== 'player'){
event.current = usableSkills[0];
}
else {
event.choice = usableSkills.filter(n => n.priority == usableSkills[0].priority);
//现在只要找到一个同优先度技能为silent 便优先执行该技能
const silentSkill = event.choice.find(item => {
const skillInfo = lib.skill[item.skill];
return (skillInfo && skillInfo.silent);
})
if (silentSkill){
event.current = silentSkill;
}
else {
const currentChoice = event.choice[0];
if (event.choice.length == 1) {
event.current = currentChoice;
}
else{
const currentPlayer = currentChoice.player , skillsToChoose = event.choice.map(i => i.skill);
const next = currentPlayer.chooseControl(skillsToChoose);
next.set('prompt', '选择下一个触发的技能');
next.set('forceDie', true);
next.set('arrangeSkill', true);
next.set('includeOut', true);
const {result} = await next;
event.current = event.doing.todoList.find(info => info.skill == result.control);
}
}
}
event.doing.doneList.push(event.current);
event.doing.todoList.remove(event.current);
await game.createTrigger(event.triggername, event.current.skill, event.current.player, trigger);
}
}
}
event.finish();
'step 1';
if (trigger.filterStop && trigger.filterStop()) return event.finish();
event.current = event.doing.todoList.find(info => lib.filter.filterTrigger(trigger, info.player, event.triggername, info.skill));
if (!event.current) {
event.doing.todoList = [];
return event.goto(0);
}
event.doing.todoList = event.doing.todoList.filter(i => i.priority <= event.current.priority);
const directUse = info => lib.skill[info.skill].silent || !lib.translate[info.skill];//是否不触发同顺序选择
if (directUse(event.current)) return event.goto(4);
event.choice = event.doing.todoList.filter(info => {
if (!lib.filter.filterTrigger(trigger, info.player, event.triggername, info.skill)) return false;
if (directUse(info)) return false;
if (event.current.player !== info.player) return false;
return lib.skill.global.includes(info.skill) || event.current.player.hasSkill(info.skill, true);
});
event.choice = event.choice.filter(n=>n.priority == event.choice[0].priority);
if (event.choice.length < 2) return event.goto(4);
'step 2';
const next = event.choice[0].player.chooseControl(event.choice.map(i => i.skill));
next.set('prompt', '选择下一个触发的技能');
next.set('forceDie', true);
next.set('arrangeSkill', true);
next.set('includeOut', true);
'step 3';
if (result.control) event.current = event.doing.todoList.find(info => info.skill == result.control && info.player == event.choice[0].player);
'step 4';
if (!event.current || !event.doing.todoList.includes(event.current)) return;
event.doing.doneList.push(event.current);
event.doing.todoList.remove(event.current);
game.createTrigger(event.triggername, event.current.skill, event.current.player, trigger);
event.goto(0);
},
createTrigger: function () {
"step 0";
@ -8167,7 +8184,12 @@ export const Content = {
cards[0].classList.add('fakejudge');
cards[0].node.background.innerHTML = lib.translate[cards[0].viewAs + '_bg'] || get.translation(cards[0].viewAs)[0];
}
game.log(player, '被贴上了<span class="yellowtext">' + get.translation(cards[0].viewAs) + '</span>', cards, '');
if(lib.card[viewAs].blankCard){
game.log(player, '被扣置了<span class="yellowtext">' + get.translation(cards[0].viewAs) + '</span>');
}
else {
game.log(player, '被贴上了<span class="yellowtext">' + get.translation(cards[0].viewAs) + '</span>', cards, '');
}
}
else {
cards[0].classList.remove('fakejudge');

View File

@ -9050,10 +9050,9 @@ class Create extends Uninstantable {
ui.backgroundMusic.autoplay = true;
ui.backgroundMusic.addEventListener('ended', game.playBackgroundMusic);
ui.window.appendChild(ui.backgroundMusic);
ui.window.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', function playMusic() {
ui.window.removeEventListener(lib.config.touchscreen ? 'touchend' : 'click', playMusic, false);
if (!ui.backgroundMusic.played.length && lib.config.background_music != 'music_off') ui.backgroundMusic.play();
}, false);
ui.window.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', () => {
if (!ui.backgroundMusic.played.length && lib.config.background_music != 'music_off' && !isNaN(ui.backgroundMusic.duration)) ui.backgroundMusic.play();
}, {once:true});
if (lib.config.cursor_style == 'pointer') {
ui.window.classList.add('nopointer');
}
@ -13419,12 +13418,14 @@ class Click extends Uninstantable {
// 有bug先用旧版
if (lib.config.background_speak && e !== 'init') {
let audio, skillnode = this;
const playedAudios = [];
(function play() {
if (!skillnode.audioList || !skillnode.audioList.length) {
skillnode.audioList = game.parseSkillAudio(skillnode.link, playername);
if (!skillnode.audioList.length) return;
if (!skillnode.audioList.length||skillnode.audioList.length==playedAudios.length) return;
}
audio = skillnode.audioList.shift();
playedAudios.push(audio);
game.playAudio(audio, play);
})();
}
@ -13634,12 +13635,14 @@ class Click extends Uninstantable {
// 有bug先用旧版
if (lib.config.background_speak && e !== 'init') {
let audio, skillnode = this;
const playedAudios = [];
(function play() {
if (!skillnode.audioList || !skillnode.audioList.length) {
skillnode.audioList = game.parseSkillAudio(skillnode.link, playername);
if (!skillnode.audioList.length) return;
if (!skillnode.audioList.length||skillnode.audioList.length==playedAudios.length) return;
}
audio = skillnode.audioList.shift();
playedAudios.push(audio);
game.playAudio(audio, play);
})();
}
@ -14142,6 +14145,10 @@ export class UI extends Uninstantable {
* @type { HTMLDivElement }
*/
static pause;
/**
* @type { HTMLAudioElement }
*/
static backgroundMusic;
static refresh(node) {
void window.getComputedStyle(node, null).getPropertyValue("opacity");
}

View File

@ -1,36 +1,49 @@
/**
* 关于`Google Chrome`的异步错误处理
*
* `Chrome`所用的`v8`引擎为`Error`提供了特有的报错栈堆处理函数用于用户自定义报错栈堆的内容
* 由于`v8`提供的`Error.prepareStackTrace(error, structuredStackTrace)`接口存在一些限制导致不适合无名杀
*
* 我们用到了`Error.prepareStackTrace(error, structuredStackTrace)`这个函数这个函数的信息可参考[这里](https://v8.dev/docs/stack-trace-api#customizing-stack-traces)
* 故我们直接解析`Error`的栈堆信息来获取相关内容
*
* 该函数提供了结构化的栈堆信息很幸运的是这个结构化的栈堆能直接告诉我们报错的文件以及位置故我们使用该函数让异步报错能直接定位原始位置
* ~~`Chrome`所用的`v8`引擎为`Error`提供了特有的报错栈堆处理接口用于用户自定义报错栈堆的内容~~
*
* ~~我们用到了`Error.prepareStackTrace(error, structuredStackTrace)`这个接口这个接口的信息可参考[这里](https://v8.dev/docs/stack-trace-api#customizing-stack-traces)~~
*
* ~~该接口提供了结构化的栈堆信息很幸运的是这个结构化的栈堆能直接告诉我们报错的文件以及位置故我们使用该接口让异步报错能直接定位原始位置~~
*
* @implements {PromiseErrorHandler}
*/
export class ChromePromiseErrorHandler {
/**
* 用于临时记录报错信息的列表通过`Error.prepareStackTrace`更新该列表
* ~~用于临时记录报错信息的列表通过`Error.prepareStackTrace`更新该列表~~
*
* @type {[Error, NodeJS.CallSite[]][]}
* 现在用于存储报错过的错误信息
*
* @type {Error[]}
*/
#errorList;
/**
* @type {typeof Error.prepareStackTrace}
*
* @deprecated
*/
#originErrorPrepareStackTrace;
#_originErrorPrepareStackTrace;
/**
* 初始化`Error.prepareStackTrace`将该值赋值成我们需要的函数
* 判断是否是v8错误栈堆用到的正则
*/
#STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
/**
* ~~初始化`Error.prepareStackTrace`将该值赋值成我们需要的函数~~
*
* 未防止本来Error.prepareStackTrace便存在赋值的行为我们将原始值存储并在需要的函数中调用
* ~~未防止本来Error.prepareStackTrace便存在赋值的行为我们将原始值存储并在需要的函数中调用~~
*
* > 这或许就是本体扩展化的第一步小声
* 初始化存储报错信息的列表
*/
onLoad() {
/*
this.#errorList = [];
this.#originErrorPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = (error, stackTraces) => {
@ -42,13 +55,19 @@ export class ChromePromiseErrorHandler {
this.#errorList.push([error, stackTraces]);
return result;
};
*/
this.#errorList = [];
}
/**
* 将原来可能的`Error.prepareStackTrace`赋值回去
* ~~将原来可能的`Error.prepareStackTrace`赋值回去~~
*
* @deprecated
*/
onUnload() {
/*
Error.prepareStackTrace = this.#originErrorPrepareStackTrace;
*/
}
/**
@ -60,6 +79,65 @@ export class ChromePromiseErrorHandler {
*/
onHandle(event) {
event.promise.catch((error) => {
// 如果`error`是个错误,则继续处理
if (error instanceof Error) {
// 如果已经处理过该错误,则不再处理
if (this.#errorList.includes(error)) return;
this.#errorList.push(error);
// 如果`error`拥有字符串形式的报错栈堆且报错栈堆确实符合v8的stack
if (typeof error.stack === 'string' && this.#STACK_REGEXP.test(error.stack)) {
// 获取符合栈堆信息的字符串,一般来说就是从第二行开始的所有行
// 为了处理eval的情况故必须获取完行数
let lines = error.stack.split('\n').filter((line) =>
this.#STACK_REGEXP.test(line));
// 提供类型信息防止vscode报错
/**
* @type {string | undefined}
*/
let fileName = void 0;
/**
* @type {number | undefined}
*/
let line = void 0;
/**
* @type {number | undefined}
*/
let column = void 0;
// 从第一条开始遍历一直遍历到不存在eval的位置
for (let currentLine = 0; currentLine < lines.length; ++currentLine) {
if (/\(eval /.test(lines[currentLine])) continue;
let formatedLine = lines[currentLine].replace(/^\s+/, '').replace(/\(eval code/g, '(').replace(/^.*?\s+/, '');
const location = formatedLine.match(/ (\(.+\)$)/);
if (location) formatedLine = formatedLine.replace(location[0], '');
const locationParts = extractLocation(location ? location[1] : formatedLine);
fileName = ['eval', '<anonymous>'].includes(locationParts[0]) ? void 0 : locationParts[0];
line = Number(locationParts[1]);
column = Number(locationParts[2]);
break;
}
// @ts-ignore
window.onerror(error.message, fileName, line, column, error);
}
// 反之我们只能不考虑报错文件信息直接调用onerror
else {
// @ts-ignore
let [_, src = void 0, line = void 0, column = void 0] = /at\s+.*\s+\((.*):(\d*):(\d*)\)/i.exec(error.stack.split('\n')[1])
if (typeof line == 'string') line = Number(line);
if (typeof column == 'string') column = Number(column);
// @ts-ignore
window.onerror(error.message, src, line, column, error);
}
}
/*
console.error(error)
const result = this.#errorList.find(savedError => savedError[0] === error);
if (result) {
@ -72,17 +150,42 @@ export class ChromePromiseErrorHandler {
result[0]
);
}
*/
});
}
/**
* 正式报错时便不再需要报错信息了故直接清空列表释放内存
* ~~正式报错时便不再需要报错信息了故直接清空列表释放内存~~
*
* @deprecated
*/
onErrorPrepare() {
/*
this.#errorList.length = 0;
*/
}
}
/**
* 简易的解析报错栈堆位置信息的函数
*
* @param {string} urlLike
* @returns {string[]}
*/
export function extractLocation(urlLike) {
// 不存在地址信息的字符串
if (!/:/.test(urlLike)) {
return [urlLike];
}
// 捕获位置用到的正则
const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
const parts = regExp.exec(urlLike.replace(/[()]/g, ''));
// @ts-ignore
return [parts[1], parts[2] || void 0, parts[3] || void 0];
}
/**
* @typedef {import('../interface/promise-error-handler').PromiseErrorHandler} PromiseErrorHandler
*/