diff --git a/card/gujian.js b/card/gujian.js index ab5f139a9..484341589 100644 --- a/card/gujian.js +++ b/card/gujian.js @@ -1,1935 +1,1935 @@ -'use strict'; -game.import('card',function(lib,game,ui,get,ai,_status){ - return { - name:'gujian', - card:{ - luyugeng:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('luyugeng'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.luyugeng=card; - target.storage.luyugeng_markcount=3; - target.addSkill('luyugeng'); - }, - ai:{ - order:2, - value:4, - result:{ - target:1 - } - } - }, - jinlianzhu:{ - type:'trick', - fullskin:true, - filterTarget:true, - global:'g_jinlianzhu', - content:function(){ - var evt=event.getParent(3)._trigger; - evt.cancel() - if(evt.source){ - evt.source.draw(); - } - }, - ai:{ - order:1, - value:[5,1], - useful:[6,1], - result:{ - target:function(player,target){ - var evt=_status.event.getTrigger(); - var eff=get.damageEffect(target,evt.source,target,evt.nature); - if(eff>0) return -1; - if(eff<0) return 2; - return 0; - } - } - } - }, - chunbing:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('chunbing'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.chunbing=card; - target.storage.chunbing_markcount=5; - target.addSkill('chunbing'); - }, - ai:{ - order:2, - value:4, - result:{ - target:function(player,target){ - var num=target.needsToDiscard(); - if(num){ - if(target==player&&num>1){ - return num; - } - return Math.sqrt(num); - } - return 0; - } - } - } - }, - gudonggeng:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('gudonggeng'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.gudonggeng=card; - target.storage.gudonggeng_markcount=3; - target.addSkill('gudonggeng'); - }, - ai:{ - order:2, - value:4, - result:{ - target:function(player,target){ - if(player==target&&!player.hasShan()) return 2; - return 1/Math.max(1,target.hp); - } - } - } - }, - liyutang:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('liyutang'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.liyutang=card; - target.storage.liyutang_markcount=2; - target.addSkill('liyutang'); - }, - ai:{ - order:2, - value:4, - result:{ - target:function(player,target){ - if(player==target&&target.isMinHp()) return 2; - if(target.isMinHp()) return 1.5; - return 1/Math.max(1,target.hp); - } - } - } - }, - mizhilianou:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('mizhilianou'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.mizhilianou=card; - target.storage.mizhilianou_markcount=4; - target.addSkill('mizhilianou'); - }, - ai:{ - order:2, - value:4, - result:{ - target:function(player,target){ - if(target==player){ - if(target.countCards('he',{suit:'heart'})){ - if(target.isDamaged()) return 1.5; - } - else{ - return 0.2; - } - } - else if(target.isDamaged()){ - return 1; - } - return 0.5; - } - } - } - }, - xiajiao:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('xiajiao'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.xiajiao=card; - target.storage.xiajiao_markcount=3; - target.addSkill('xiajiao'); - target.addTempSkill('xiajiao3'); - }, - ai:{ - order:2, - value:5, - result:{ - target:1 - } - } - }, - tanhuadong:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('tanhuadong'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.tanhuadong=card; - target.storage.tanhuadong_markcount=3; - target.addSkill('tanhuadong'); - }, - ai:{ - order:2, - value:5, - result:{ - target:1 - } - } - }, - mapodoufu:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('mapodoufu'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.mapodoufu=card; - target.storage.mapodoufu_markcount=2; - target.addSkill('mapodoufu'); - }, - ai:{ - order:2, - value:5, - result:{ - target:function(player,target){ - return player==target?2:1; - } - } - } - }, - qingtuan:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('qingtuan'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.qingtuan=card; - target.storage.qingtuan_markcount=2; - target.addSkill('qingtuan'); - }, - ai:{ - order:4, - value:4, - result:{ - target:function(player,target){ - if(target==player){ - if(target.hasSha()) return 2; - } - else{ - var nh=target.countCards('h'); - if(nh>=3) return 1; - if(target.hasSha()) return 1; - if(nh&&Math.random()<0.5) return 1; - } - return player.needsToDiscard()?0.2:0; - } - } - } - }, - yougeng:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('yougeng'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.yougeng=card; - target.storage.yougeng_markcount=2; - target.addSkill('yougeng'); - }, - ai:{ - order:2, - value:4, - result:{ - target:function(player,target){ - if(target.isHealthy()) return player.needsToDiscard()?0.1:0; - if(target.isMinHp()) return 1.5; - return 1/Math.max(1,target.hp); - } - } - } - }, - molicha:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('molicha'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.molicha=card; - target.storage.molicha_markcount=4; - target.addSkill('molicha'); - }, - ai:{ - order:2, - value:4, - result:{ - target:1 - } - } - }, - yuanbaorou:{ - fullskin:true, - type:'food', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('yuanbaorou'); - }, - //range:{global:1}, - content:function(){ - target.$gain2(cards); - target.storage.yuanbaorou=card; - target.storage.yuanbaorou_markcount=4; - target.addSkill('yuanbaorou'); - }, - ai:{ - order:2, - value:4, - result:{ - target:function(player,target){ - if(target==player){ - if(target.hasSha()) return 2; - } - else{ - var nh=target.countCards('h'); - if(nh>=3) return 1; - if(target.hasSha()) return 1; - if(nh&&Math.random()<0.5) return 1; - } - return player.needsToDiscard()?0.2:0; - } - } - } - }, - heilonglinpian:{ - fullskin:true, - type:'trick', - enable:true, - toself:true, - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - modTarget:true, - content:function(){ - target.changeHujia(); - target.addTempSkill('heilonglinpian',{player:'phaseBegin'}); - }, - ai:{ - value:[6,1], - useful:1, - order:2, - result:{ - target:1 - } - } - }, - mutoumianju:{ - fullskin:true, - type:'equip', - subtype:'equip2', - skills:['mutoumianju_skill'], - ai:{ - equipValue:4 - } - }, - yuheng:{ - fullskin:true, - type:'equip', - subtype:'equip5', - nopower:true, - nomod:true, - unique:true, - skills:['yuheng_skill'], - ai:{ - equipValue:6 - } - }, - yuheng_plus:{ - fullskin:true, - type:'equip', - subtype:'equip5', - nopower:true, - unique:true, - nomod:true, - epic:true, - cardimage:'yuheng', - skills:['yuheng_plus_skill'], - ai:{ - equipValue:7 - } - }, - yuheng_pro:{ - fullskin:true, - type:'equip', - subtype:'equip5', - nopower:true, - unique:true, - nomod:true, - legend:true, - cardimage:'yuheng', - skills:['yuheng_pro_skill'], - ai:{ - equipValue:8 - } - }, - shatang:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:true, - cardcolor:'red', - cardnature:'fire', - content:function(){ - 'step 0' - target.damage('fire'); - 'step 1' - target.changeHujia(); - }, - ai:{ - value:[4,1], - useful:2, - order:2, - result:{ - target:function(player,target){ - if(target.hasSkillTag('nofire')) return 1.5; - if(target.hasSkillTag('maixie_hp')) return 0; - if(target.hp==1) return -1; - return -1/Math.sqrt(target.hp+1); - } - }, - tag:{ - damage:1, - fireDamage:1, - natureDamage:1 - } - } - }, - shujinsan:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:function(card,player,target){ - return target.countCards('he')>0; - }, - content:function(){ - 'step 0' - target.chooseToDiscard('he',[1,target.countCards('he')],'弃置任意张牌并摸等量的牌').ai=function(card){ - return 6-get.value(card); - } - 'step 1' - if(result.bool){ - target.draw(result.cards.length); - } - }, - ai:{ - order:1.5, - value:[4,1], - tag:{ - norepeat:1 - }, - result:{ - target:function(player,target){ - if(target==player){ - var cards=player.getCards('he'); - var num=-1; - for(var i=0;i2){ - if(player.needsToDiscard()) return 1/target.hp; - return 0; - } - if(target.hp>0){ - return 2/target.hp; - } - return 0; - } - } - } - }, - yunvyuanshen:{ - fullskin:true, - type:'basic', - enable:true, - logv:false, - filterTarget:function(card,player,target){ - return !target.hasSkill('yunvyuanshen_skill'); - }, - content:function(){ - target.storage.yunvyuanshen_skill=game.createCard('yunvyuanshen'); - target.addSkill('yunvyuanshen_skill'); - if(cards&&cards.length){ - card=cards[0]; - } - if(target==targets[0]&&card.clone&&(card.clone.parentNode==player.parentNode||card.clone.parentNode==ui.arena)){ - card.clone.moveDelete(target); - game.addVideo('gain2',target,get.cardsInfo([card])); - } - }, - ai:{ - basic:{ - value:9, - useful:4, - value:7 - }, - order:2, - result:{ - target:function(player,target){ - return 1/Math.sqrt(1+target.hp); - }, - }, - } - }, - bingpotong:{ - fullskin:true, - type:'jiguan', - enable:true, - wuxieable:true, - filterTarget:function(card,player,target){ - return target.countCards('h')>0; - }, - selectTarget:[1,3], - content:function(){ - "step 0" - if(target.countCards('h')==0||player.countCards('h')==0){ - event.finish(); - return; - } - player.chooseCard('请展示一张手牌',true).set('ai',function(){ - var num=0; - var rand=_status.event.rand; - if(get.color(card)=='red'){ - if(rand) num-=6; - } - else{ - if(!rand) num-=6; - } - var value=get.value(card); - if(value>=8) return -100; - return num-value; - }).set('rand', Math.random()<0.5).prompt2='若与'+get.translation(target)+'展示的牌相同,你弃置展示的牌,'+get.translation(target)+'失去一点体力'; - "step 1" - event.card1=result.cards[0]; - target.chooseCard('请展示一张手牌',true).set('ai',function(card){ - var num=0; - var rand=_status.event.rand; - if(get.color(card)=='red'){ - if(rand) num-=6; - } - else{ - if(!rand) num-=6; - } - var value=get.value(card); - if(value>=8) return -100; - return num-value; - }).set('rand', Math.random()<0.5).prompt2='若与'+get.translation(player)+'展示的牌相同,'+get.translation(player)+'弃置展示的牌,你失去一点体力'; - "step 2" - event.card2=result.cards[0]; - ui.arena.classList.add('thrownhighlight'); - game.addVideo('thrownhighlight1'); - player.$compare(event.card1,target,event.card2); - game.delay(4); - "step 3" - game.log(player,'展示了',event.card1); - game.log(target,'展示了',event.card2); - if(get.color(event.card2)==get.color(event.card1)){ - player.discard(event.card1).animate=false; - target.$gain2(event.card2); - var clone=event.card1.clone; - if(clone){ - clone.style.transition='all 0.5s'; - clone.style.transform='scale(1.2)'; - clone.delete(); - game.addVideo('deletenode',player,get.cardsInfo([clone])); - } - target.loseHp(); - event.finish(); - event.parent.cancelled=true; - } - else{ - player.$gain2(event.card1); - target.$gain2(event.card2); - game.delay(); - } - ui.arena.classList.remove('thrownhighlight'); - game.addVideo('thrownhighlight2'); - "step 4" - // if(cards&&cards.length){ - // player.gain(cards,'gain2'); - // target.addTempSkill('bingpotong'); - // } - }, - ai:{ - basic:{ - order:2, - value:[5,1], - useful:1, - }, - result:{ - player:function(player,target){ - if(player.countCards('h')<=Math.min(5,Math.max(2,player.hp))&&_status.event.name=='chooseToUse'){ - if(typeof _status.event.filterCard=='function'&& - _status.event.filterCard({name:'bingpotong'})){ - return -10; - } - if(_status.event.skill){ - var viewAs=get.info(_status.event.skill).viewAs; - if(viewAs=='bingpotong') return -10; - if(viewAs&&viewAs.name=='bingpotong') return -10; - } - } - return 0; - }, - target:function(player,target){ - if(player.countCards('h')<=1) return 0; - return -1.5; - } - }, - tag:{ - loseHp:1 - } - } - }, - feibiao:{ - type:'jiguan', - enable:true, - fullskin:true, - wuxieable:true, - outrange:{globalFrom:2}, - filterTarget:function(card,player,target){ - return target!=player; - }, - content:function(){ - "step 0" - if(!target.countCards('h',{color:'black'})){ - target.loseHp(); - event.finish(); - } - else{ - target.chooseToDiscard({color:'black'},'弃置一张黑色手牌或受流失一点体力').ai=function(card){ - return 8-get.value(card); - }; - } - "step 1" - if(!result.bool){ - target.loseHp(); - } - }, - ai:{ - basic:{ - order:9, - value:3, - useful:1, - }, - result:{ - target:-2 - }, - tag:{ - discard:1, - loseHp:1 - } - } - }, - longxugou:{ - type:'jiguan', - enable:true, - fullskin:true, - wuxieable:true, - filterTarget:function(card,player,target){ - return target!=player&&target.countGainableCards(player,'e'); - }, - content:function(){ - 'step 0' - var es=target.getGainableCards(player,'e') - if(es.length){ - player.choosePlayerCard('e',target,true).set('es',es).set('filterButton',function(button){ - return _status.event.es.contains(button.link); - }); - } - else{ - event.finish(); - } - 'step 1' - if(result.bool){ - target.$give(result.links[0],player); - target.lose(result.links[0],ui.special); - event.card=result.links[0]; - game.delay(); - } - else{ - event.finish(); - } - 'step 2' - if(event.card&&get.position(event.card)=='s'){ - player.equip(event.card); - } - }, - ai:{ - basic:{ - order:9, - value:6, - useful:4, - }, - result:{ - target:-1 - }, - tag:{ - loseCard:1, - gain:1, - } - } - }, - qiankunbiao:{ - type:'jiguan', - enable:true, - fullskin:true, - wuxieable:true, - filterTarget:function(card,player,target){ - return target!=player&&target.countCards('he')>0; - }, - changeTarget:function(player,targets){ - game.filterPlayer(function(current){ - return get.distance(targets[0],current,'pure')==1&¤t.countCards('he'); - },targets); - }, - content:function(){ - var he=target.getCards('he'); - if(he.length){ - target.discard(he.randomGet()).delay=false; - } - }, - contentAfter:function(){ - game.delay(0.5); - }, - ai:{ - order:7, - tag:{ - loseCard:1, - discard:1, - }, - wuxie:function(){ - return 0; - }, - result:{ - player:function(player,target){ - return game.countPlayer(function(current){ - if(current==target||(get.distance(target,current,'pure')==1&¤t.countCards('he'))){ - return -get.sgn(get.attitude(player,current)); - } - }); - } - } - } - }, - shenhuofeiya:{ - type:'jiguan', - enable:true, - fullskin:true, - wuxieable:true, - filterTarget:function(card,player,target){ - return target!=player; - }, - changeTarget:function(player,targets){ - game.filterPlayer(function(current){ - return get.distance(targets[0],current,'pure')==1; - },targets); - }, - cardcolor:'red', - cardnature:'fire', - content:function(){ - "step 0" - var next=target.chooseToRespond({name:'shan'}); - next.ai=function(card){ - if(get.damageEffect(target,player,target,'fire')>=0) return 0; - if(player.hasSkillTag('notricksource')) return 0; - if(target.hasSkillTag('notrick')) return 0; - if(target.hasSkillTag('noShan')){ - return -1; - } - return 11-get.value(card); - }; - next.autochoose=lib.filter.autoRespondShan; - "step 1" - if(result.bool==false){ - target.damage('fire'); - } - }, - ai:{ - wuxie:function(target,card,player,viewer){ - if(get.attitude(viewer,target)>0&&target.countCards('h','shan')){ - if(!target.countCards('h')||target.hp==1||Math.random()<0.7) return 0; - } - if(get.attitude(viewer,target)<=0){ - return 0; - } - }, - order:7, - tag:{ - respond:1, - respondShan:1, - damage:1, - natureDamage:1, - fireDamage:1, - multitarget:1, - multineg:1, - }, - result:{ - player:function(player,target){ - return game.countPlayer(function(current){ - if(current==target||(get.distance(target,current,'pure')==1)){ - return get.sgn(get.effect(current,{name:'chiyuxi'},player,player)); - } - }); - } - } - } - }, - // wenhuangsan:{ - // type:'jiguan', - // enable:true, - // fullskin:true, - // }, - // tuhunsha:{ - // type:'jiguan', - // enable:true, - // fullskin:true, - // }, - // shenhuofeiya:{ - // type:'jiguan', - // enable:true, - // fullskin:true, - // }, - mianlijinzhen:{ - type:'jiguan', - enable:true, - fullskin:true, - filterTarget:function(card,player,target){ - return target.hp>=player.hp; - }, - content:function(){ - 'step 0' - target.draw(); - 'step 1' - target.loseHp(); - }, - ai:{ - order:2, - value:[5,1], - useful:[4,1], - result:{ - target:-1.5 - }, - tag:{ - // damage:1 - } - } - }, - // longxugou:{ - // type:'jiguan', - // enable:true, - // fullskin:true, - // }, - liutouge:{ - type:'jiguan', - enable:true, - fullskin:true, - filterTarget:true, - wuxieable:true, - content:function(){ - if(player.getEnemies().contains(target)){ - target.getDebuff(); - } - else{ - target.getBuff(); - } - }, - ai:{ - order:4, - value:5, - result:{ - player:function(player,target){ - if(get.attitude(player,target)==0) return 0; - return 1; - } - } - } - }, - liufengsan:{ - type:'trick', - enable:true, - fullskin:true, - filterTarget:true, - content:function(){ - var list=[]; - for(var i=0;i<2;i++){ - list.push(game.createCard('shan')); - } - target.gain(list,'gain2'); - }, - ai:{ - order:4.5, - value:[5,1], - tag:{ - gain:1, - norepeat:1 - }, - result:{ - target:function(player,target){ - if(target==player){ - if(!target.hasShan()) return 2; - var num=target.needsToDiscard(2); - if(num==0) return 1.5; - if(num==1) return 1; - return 0.5; - } - else{ - switch(target.countCards('h')){ - case 0:return 2; - case 1:return 1.5; - case 2:return 1; - default:return 0.5; - } - } - } - } - } - }, - shihuifen:{ - type:'trick', - fullskin:true, - filterTarget:true, - global:'g_shihuifen', - content:function(){ - 'step 0' - var next=_status.currentPhase.chooseToRespond({name:'shan'}); - next.set('prompt2','否则本回合无法对其他角色使用卡牌'); - 'step 1' - if(!result.bool){ - _status.currentPhase.addTempSkill('shihuifen','phaseUseAfter'); - } - }, - ai:{ - order:1, - value:[5,1], - useful:[5,1], - tag:{ - respond:1, - respondShan:1, - }, - result:{ - target:function(player,target){ - if(target.countCards('h')>=3||target.needsToDiscard()) return -1.5; - return 0; - } - } - } - }, - }, - skill:{ - ziyangdan:{ - trigger:{player:'phaseBegin'}, - silent:true, - init:function(player){ - player.storage.ziyangdan=3; - }, - onremove:true, - content:function(){ - if(player.hujia>0){ - player.changeHujia(-1); - } - player.storage.ziyangdan--; - if(player.hujia==0||player.storage.ziyangdan==0){ - player.removeSkill('ziyangdan'); - } - }, - ai:{ - threaten:0.8 - } - }, - luyugeng:{ - mark:'card', - enable:'phaseUse', - usable:1, - nopop:true, - filterCard:{type:'basic'}, - filter:function(event,player){ - return player.countCards('h',{type:'basic'}); - }, - intro:{ - content:function(storage,player){ - return '出牌阶段限一次,你可以弃置一张基本牌并发现一张牌,持续两回合(剩余'+player.storage.luyugeng_markcount+'回合)' - } - }, - content:function(){ - player.discoverCard(); - }, - group:'luyugeng_count', - subSkill:{ - count:{ - trigger:{player:'phaseAfter'}, - forced:true, - popup:false, - content:function(){ - player.storage.luyugeng_markcount--; - if(player.storage.luyugeng_markcount==0){ - delete player.storage.luyugeng; - delete player.storage.luyugeng_markcount; - player.removeSkill('luyugeng'); - } - else{ - player.updateMarks(); - } - }, - } - } - }, - xiajiao:{ - mark:'card', - trigger:{player:['phaseUseBefore','phaseEnd']}, - forced:true, - popup:false, - nopop:true, - filter:function(event,player){ - return !player.hasSkill('xiajiao3'); - }, - intro:{ - content:function(storage,player){ - return '你在摸牌阶段额外摸一张牌,然后弃置一张牌(剩余'+player.storage.xiajiao_markcount+'回合)' - } - }, - content:function(){ - player.storage.xiajiao_markcount--; - if(player.storage.xiajiao_markcount==0){ - delete player.storage.xiajiao; - delete player.storage.xiajiao_markcount; - player.removeSkill('xiajiao'); - } - else{ - player.updateMarks(); - } - player.addTempSkill('xiajiao3'); - }, - group:'xiajiao_draw', - subSkill:{ - draw:{ - trigger:{player:'phaseDrawBegin'}, - forced:true, - content:function(){ - trigger.num++; - player.addTempSkill('xiajiao2'); - } - } - } - }, - xiajiao2:{ - trigger:{player:'phaseDrawAfter'}, - silent:true, - content:function(){ - player.chooseToDiscard('he',true); - } - }, - xiajiao3:{}, - mizhilianou:{ - mark:'card', - trigger:{player:'phaseAfter'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '你可以将一张红桃牌当作桃使用(剩余'+player.storage.mizhilianou_markcount+'回合)' - } - }, - content:function(){ - player.storage.mizhilianou_markcount--; - if(player.storage.mizhilianou_markcount==0){ - delete player.storage.mizhilianou; - delete player.storage.mizhilianou_markcount; - player.removeSkill('mizhilianou'); - } - else{ - player.updateMarks(); - } - }, - group:'mizhilianou_use', - subSkill:{ - use:{ - enable:'chooseToUse', - filterCard:{suit:'heart'}, - position:'he', - viewAs:{name:'tao'}, - viewAsFilter:function(player){ - return player.countCards('he',{suit:'heart'})>0; - }, - prompt:'将一张红桃牌当桃使用', - check:function(card){return 10-get.value(card)}, - ai:{ - skillTagFilter:function(player){ - return player.countCards('he',{suit:'heart'})>0; - }, - save:true, - respondTao:true, - } - } - } - }, - chunbing:{ - mark:'card', - trigger:{player:'phaseAfter'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '你的手牌上限+1(剩余'+player.storage.chunbing_markcount+'回合)' - } - }, - mod:{ - maxHandcard:function(player,num){ - return num+1; - } - }, - content:function(){ - player.storage.chunbing_markcount--; - if(player.storage.chunbing_markcount==0){ - delete player.storage.chunbing; - delete player.storage.chunbing_markcount; - player.removeSkill('chunbing'); - } - else{ - player.updateMarks(); - } - }, - }, - gudonggeng:{ - mark:'card', - trigger:{player:'phaseBegin'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '当你下一次受到杀造成的伤害时,令伤害-1(剩余'+player.storage.gudonggeng_markcount+'回合)' - } - }, - content:function(){ - player.storage.gudonggeng_markcount--; - if(player.storage.gudonggeng_markcount==0){ - delete player.storage.gudonggeng; - delete player.storage.gudonggeng_markcount; - player.removeSkill('gudonggeng'); - } - else{ - player.updateMarks(); - } - }, - group:'gudonggeng_damage', - subSkill:{ - damage:{ - trigger:{player:'damageBegin'}, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&event.num>0; - }, - forced:true, - content:function(){ - trigger.num--; - delete player.storage.gudonggeng; - delete player.storage.gudonggeng_markcount; - player.removeSkill('gudonggeng'); - } - } - }, - ai:{ - effect:{ - target:function(card,player,target){ - if(card.name=='sha'&&get.attitude(player,target)<0) return 0.5; - } - } - } - }, - qingtuan:{ - mark:'card', - trigger:{player:'phaseAfter'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '你在回合内使用首张杀时摸一张牌(剩余'+player.storage.qingtuan_markcount+'回合)' - } - }, - content:function(){ - player.storage.qingtuan_markcount--; - if(player.storage.qingtuan_markcount==0){ - delete player.storage.qingtuan; - delete player.storage.qingtuan_markcount; - player.removeSkill('qingtuan'); - } - else{ - player.updateMarks(); - } - }, - group:'qingtuan_draw', - subSkill:{ - draw:{ - trigger:{player:'useCard'}, - filter:function(event,player){ - return event.card.name=='sha'&&_status.currentPhase==player; - }, - usable:1, - forced:true, - content:function(){ - player.draw(); - } - } - } - }, - liyutang:{ - mark:'card', - trigger:{player:'phaseEnd'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '结束阶段,若你的体力值为全场最少或之一,你获得一点护甲(剩余'+player.storage.liyutang_markcount+'回合)' - } - }, - content:function(){ - if(player.isMinHp()){ - player.logSkill('liyutang'); - player.changeHujia(); - } - player.storage.liyutang_markcount--; - if(player.storage.liyutang_markcount==0){ - delete player.storage.liyutang; - delete player.storage.liyutang_markcount; - player.removeSkill('liyutang'); - } - else{ - player.updateMarks(); - } - }, - }, - yougeng:{ - mark:'card', - trigger:{player:'phaseBegin'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '准备阶段,若你的体力值为全场最少或之一,你回复一点体力(剩余'+player.storage.yougeng_markcount+'回合)' - } - }, - content:function(){ - if(player.isDamaged()&&player.isMinHp()){ - player.logSkill('yougeng'); - player.recover(); - } - player.storage.yougeng_markcount--; - if(player.storage.yougeng_markcount==0){ - delete player.storage.yougeng; - delete player.storage.yougeng_markcount; - player.removeSkill('yougeng'); - } - else{ - player.updateMarks(); - } - }, - }, - molicha:{ - mark:'card', - trigger:{player:'phaseAfter'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '你不能成为其他角色的黑色牌的目标(剩余'+player.storage.molicha_markcount+'回合)' - } - }, - mod:{ - targetEnabled:function(card,player,target){ - if(player!=target&&get.color(card)=='black'){ - return false; - } - } - }, - content:function(){ - player.storage.molicha_markcount--; - if(player.storage.molicha_markcount==0){ - delete player.storage.molicha; - delete player.storage.molicha_markcount; - player.removeSkill('molicha'); - player.logSkill('molicha'); - } - else{ - player.updateMarks(); - } - } - }, - yuanbaorou:{ - mark:'card', - trigger:{player:'phaseAfter'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '你在出牌阶段可以额外使用一张杀(剩余'+player.storage.yuanbaorou_markcount+'回合)' - } - }, - mod:{ - cardUsable:function(card,player,num){ - if(card.name=='sha') return num+1; - } - }, - content:function(){ - player.storage.yuanbaorou_markcount--; - if(player.storage.yuanbaorou_markcount==0){ - delete player.storage.yuanbaorou; - delete player.storage.yuanbaorou_markcount; - player.removeSkill('yuanbaorou'); - } - else{ - player.updateMarks(); - } - }, - }, - tanhuadong:{ - mark:'card', - trigger:{player:'phaseEnd'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '出牌阶段结束时,你摸一张牌(剩余'+player.storage.tanhuadong_markcount+'回合)' - } - }, - content:function(){ - player.storage.tanhuadong_markcount--; - if(player.storage.tanhuadong_markcount==0){ - delete player.storage.tanhuadong; - delete player.storage.tanhuadong_markcount; - player.removeSkill('tanhuadong'); - } - else{ - player.updateMarks(); - } - }, - group:'tanhuadong_draw', - subSkill:{ - draw:{ - trigger:{player:'phaseUseEnd'}, - forced:true, - content:function(){ - player.draw(); - } - } - } - }, - mapodoufu:{ - mark:'card', - trigger:{player:'phaseEnd'}, - forced:true, - popup:false, - nopop:true, - intro:{ - content:function(storage,player){ - return '结束阶段,你随机弃置一名随机敌人的一张随机牌(剩余'+player.storage.mapodoufu_markcount+'回合)' - } - }, - content:function(){ - var list=player.getEnemies(); - for(var i=0;i=3){ - card.init([card.suit,card.number,'yuheng_plus',card.nature]); - player.addTempSkill('yuheng_plus_temp'); - } - } - }, - ai:{ - order:9, - result:{ - player:1 - } - } - }, - yuheng_plus_temp:{}, - yuheng_plus_skill:{ - enable:'phaseUse', - usable:1, - filterCard:{color:'black'}, - check:function(card){ - return 8-get.value(card); - }, - filter:function(event,player){ - // if(player.hasSkill('yuheng_plus_temp')) return false; - if(!player.countCards('h',{color:'black'})) return false; - var enemies=player.getEnemies(); - for(var i=0;i=7){ - card.init([card.suit,card.number,'yuheng_pro',card.nature]); - } - } - }, - ai:{ - order:9, - result:{ - player:1 - } - } - }, - yuheng_pro_skill:{ - enable:'phaseUse', - filterCard:{color:'black'}, - check:function(card){ - return 8-get.value(card); - }, - filter:function(event,player){ - if(!player.countCards('h',{color:'black'})) return false; - var enemies=player.getEnemies(); - for(var i=0;i=3){ - card.init([card.suit,card.number,'yuheng_plus',card.nature]); - player.addTempSkill('yuheng_plus_temp'); - } - } - }, - ai:{ - order:9, - result:{ - player:1 - } - } - }, - shihuifen:{ - mark:true, - intro:{ - content:'使用卡牌无法指定其他角色为目标' - }, - mod:{ - playerEnabled:function(card,player,target){ - if(player!=target) return false; - } - } - }, - g_shihuifen:{ - trigger:{global:'phaseUseBegin'}, - direct:true, - filter:function(event,player){ - if(event.player.hasSkill('shihuifen')) return false; - if(event.player==player) return false; - if(!lib.filter.targetEnabled({name:'shihuifen'},player,event.player)) return false; - return player.hasCard('shihuifen')||player.hasSkillTag('shihuifen'); - }, - content:function(){ - player.chooseToUse(get.prompt('shihuifen',trigger.player).replace(/发动/,'使用'),function(card,player){ - if(card.name!='shihuifen') return false; - return lib.filter.cardEnabled(card,player,'forceEnable'); - },trigger.player,-1).targetRequired=true; - } - }, - g_jinlianzhu:{ - trigger:{global:'damageBefore'}, - direct:true, - filter:function(event,player){ - if(!lib.filter.targetEnabled({name:'jinlianzhu'},player,event.player)) return false; - return player.hasCard('jinlianzhu'); - }, - content:function(){ - player.chooseToUse(get.prompt('jinlianzhu',trigger.player).replace(/发动/,'使用'),function(card,player){ - if(card.name!='jinlianzhu') return false; - return lib.filter.cardEnabled(card,player,'forceEnable'); - },trigger.player,-1).targetRequired=true; - } - }, - }, - cardType:{ - food:0.3 - }, - translate:{ - jinlianzhu:'金莲珠', - jinlianzhu_info:'对一名即将受到伤害的角色使用,防止此伤害,并令伤害来源摸一张牌', - shihuifen:'石灰粉', - shihuifen_info:'在一名其他角色的出牌阶段开始时对其使用,目标需打出一张闪,否则此阶段使用卡牌无法指定其他角色为目标', - liufengsan:'流风散', - liufengsan_info:'出牌阶段对一名角色使用,目标获得两张闪', - liutouge:'六骰格', - liutouge_info:'出牌阶段对一名角色使用,若目标是敌人,对目标施加一个随机的负面效果;否则对目标施加一个随机的正面效果', - longxugou:'龙须钩', - longxugou_info:'出牌阶段对一名装备区内有牌的其他角色使用,获得其装备区内的一张牌并装备之', - mianlijinzhen:'棉里针', - mianlijinzhen_info:'出牌阶段对一名体力值不小于你的角色使用,目标摸一张牌然后失去一点体力', - shenhuofeiya:'神火飞鸦', - shenhuofeiya_info:'出牌阶段对一名其他角色和其相邻角色使用,目标需打出一张闪,否则受到一点火属性伤害', - // tuhunsha:'土魂砂', - // tuhunsha_info:'土魂砂', - // wenhuangsan:'瘟癀伞', - // wenhuangsan_info:'瘟癀伞', - qiankunbiao:'乾坤镖', - qiankunbiao_info:'随机弃置一名其他角色和其相邻角色的一张牌', - - bingpotong:'天女散花', - bingpotong_ab:'散花', - bingpotong_info:'出牌阶段对至多3名角色使用,你与每个目标依次同时展示一张手牌,若颜色相同,你弃置展示的手牌,目标失去一点体力并终止结算', - feibiao:'飞镖', - feibiao_info:'出牌阶段,对一名距离1以外的角色使用,令其弃置一张黑色手牌或流失一点体力', - - dinvxuanshuang:'帝女玄霜', - dinvxuanshuang_skill:'帝女玄霜', - dinvxuanshuang_info:'对一名濒死状态的角色使用,目标回复一点体力,然后可以弃置任意张牌并摸等量的牌', - yunvyuanshen:'玉女元参', - yunvyuanshen_skill:'玉女元参', - yunvyuanshen_info:'出牌阶段对一名角色使用,目标在下一次进入濒死状态时回复一点体力', - ziyangdan:'紫阳丹', - ziyangdan_info:'出牌阶段对一名角色使用,目标获得3点护甲,此后每个准备阶段失去1点护甲,直到首次失去所有护甲或累计以此法失去3点护甲', - yuheng:'玉衡', - yuheng_plus:'玉衡', - yuheng_pro:'玉衡', - yuheng_skill:'玉衡', - yuheng_plus_skill:'玉衡', - yuheng_pro_skill:'玉衡', - yuheng_info:'出牌阶段限一次,若敌方角色有黑桃手牌,你可以弃置一张黑桃手牌,然后获得一名随机敌方角色的一张随机黑桃手牌(此牌在本局游戏中第三次和第七次发动效果后,分别自动获得一次强化)', - yuheng_plus_info:'由普通玉衡强化得到,将玉衡技能描述中的“弃置一张黑桃手牌”改为“弃置一张黑色手牌”', - yuheng_pro_info:'由普通玉衡二次强化得到,将玉横技能描述中的“弃置一张黑桃手牌”改为“弃置一张黑色手牌”,并去掉使用次数限制', - yuheng_skill_info:'出牌阶段限一次,若敌方角色有黑桃手牌,你可以弃置一张黑桃手牌,然后获得一名随机敌方角色的一张随机黑桃手牌', - yuheng_plus_skill_info:'出牌阶段限一次,若敌方角色有黑桃手牌,你可以弃置一张黑色手牌,然后获得一名随机敌方角色的一张随机黑桃手牌', - yuheng_pro_skill_info:'出牌阶段限,若敌方角色有黑桃手牌,你可以弃置一张黑色手牌,然后获得一名随机敌方角色的一张随机黑桃手牌', - shujinsan:'舒筋散', - shujinsan_info:'出牌阶段对任意角色使用,目标可弃置任意张牌,并摸等量的牌', - mutoumianju:'木头面具', - mutoumianju_info:'出牌阶段限一次,你可以将一张手牌当作杀使用', - mutoumianju_skill:'木杀', - mutoumianju_skill_info:'出牌阶段限一次,你可以将一张手牌当作杀使用', - heilonglinpian:'黑龙鳞片', - heilonglinpian_info:'出牌阶段对自己使用,获得一点护甲,直到下一回合开始,你的防御距离+1', - shatang:'沙棠', - shatang_info:'出牌阶段对一名角色使用,对目标造成一点火焰伤害,然后目标获得一点护甲', - - food:'食物', - chunbing:'春饼', - chunbing_info:'你的手牌上限+1,持续五回合', - gudonggeng:'骨董羹', - gudonggeng_info:'当你下一次受到杀造成的伤害时,令伤害-1,持续三回合', - yougeng:'酉羹', - yougeng_info:'准备阶段,若你的体力值为全场最少或之一,你回复一点体力,持续两回合', - liyutang:'鲤鱼汤', - liyutang_info:'结束阶段,若你的体力值为全场最少或之一,你获得一点护甲,持续两回合', - mizhilianou:'蜜汁藕', - mizhilianou_info:'你可以将一张红桃牌当作桃使用,持续四回合', - xiajiao:'虾饺', - xiajiao_info:'你在摸牌阶段额外摸一张牌,然后弃置一张牌,持续三回合', - tanhuadong:'昙花冻', - tanhuadong_info:'出牌阶段结束时,你摸一张牌,持续三回合', - qingtuan:'青团', - qingtuan_info:'你在回合内使用首张杀时摸一张牌,持续两回合', - luyugeng:'鲈鱼羹', - luyugeng_info:'出牌阶段限一次,你可以弃置一张基本牌并发现一张牌,持续三回合', - yuanbaorou:'元宝肉', - yuanbaorou_info:'你在出牌阶段可以额外使用一张杀,持续四回合', - molicha:'茉莉茶', - molicha_info:'你不能成为其他角色的黑色牌的目标,持续四回合', - mapodoufu:'麻婆豆腐', - mapodoufu_info:'结束阶段,你弃置一名随机敌人的一张随机牌,持续两回合', - }, - list:[ - ['spade',2,'tanhuadong'], - ['club',1,'molicha'], - ['club',3,'chunbing'], - ['heart',12,'yougeng'], - ['heart',8,'gudonggeng'], - ['heart',1,'liyutang'], - ['diamond',4,'mizhilianou'], - ['diamond',6,'xiajiao'], - ['spade',3,'qingtuan'], - ['club',11,'luyugeng'], - ['heart',4,'mapodoufu'], - ['spade',8,'yuanbaorou'], - - ['spade',7,'yuheng'], - ['club',4,'mutoumianju'], - ['spade',2,'heilonglinpian'], - ['spade',1,'mianlijinzhen'], - ['heart',13,'yunvyuanshen'], - - ['club',8,'feibiao','poison'], - ['diamond',9,'feibiao','poison'], - - ['spade',3,'bingpotong','poison'], - ['club',12,'bingpotong','poison'], - - ['club',5,'shihuifen'], - ['club',1,'shihuifen'], - ['spade',13,'shihuifen'], - - ['diamond',6,'shujinsan'], - ['spade',2,'shujinsan'], - - ['diamond',6,'ziyangdan'], - ['heart',1,'ziyangdan'], - - // ['diamond',7,'dinvxuanshuang'], - ['heart',9,'dinvxuanshuang'], - - ['spade',9,'qiankunbiao'], - ['club',13,'qiankunbiao'], - - ['diamond',9,'shenhuofeiya'], - ['spade',7,'longxugou'], - - ['heart',9,'jinlianzhu'], - ['spade',7,'jinlianzhu'], - - ['heart',6,'liutouge'], - ['club',6,'liutouge'], - - ['club',6,'liufengsan'], - ['club',3,'liufengsan'], - - ['heart',13,'shatang','fire'] - ] - }; -}); +'use strict'; +game.import('card',function(lib,game,ui,get,ai,_status){ + return { + name:'gujian', + card:{ + luyugeng:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('luyugeng'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.luyugeng=card; + target.storage.luyugeng_markcount=3; + target.addSkill('luyugeng'); + }, + ai:{ + order:2, + value:4, + result:{ + target:1 + } + } + }, + jinlianzhu:{ + type:'trick', + fullskin:true, + filterTarget:true, + global:'g_jinlianzhu', + content:function(){ + var evt=event.getParent(3)._trigger; + evt.cancel() + if(evt.source){ + evt.source.draw(); + } + }, + ai:{ + order:1, + value:[5,1], + useful:[6,1], + result:{ + target:function(player,target){ + var evt=_status.event.getTrigger(); + var eff=get.damageEffect(target,evt.source,target,evt.nature); + if(eff>0) return -1; + if(eff<0) return 2; + return 0; + } + } + } + }, + chunbing:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('chunbing'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.chunbing=card; + target.storage.chunbing_markcount=5; + target.addSkill('chunbing'); + }, + ai:{ + order:2, + value:4, + result:{ + target:function(player,target){ + var num=target.needsToDiscard(); + if(num){ + if(target==player&&num>1){ + return num; + } + return Math.sqrt(num); + } + return 0; + } + } + } + }, + gudonggeng:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('gudonggeng'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.gudonggeng=card; + target.storage.gudonggeng_markcount=3; + target.addSkill('gudonggeng'); + }, + ai:{ + order:2, + value:4, + result:{ + target:function(player,target){ + if(player==target&&!player.hasShan()) return 2; + return 1/Math.max(1,target.hp); + } + } + } + }, + liyutang:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('liyutang'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.liyutang=card; + target.storage.liyutang_markcount=2; + target.addSkill('liyutang'); + }, + ai:{ + order:2, + value:4, + result:{ + target:function(player,target){ + if(player==target&&target.isMinHp()) return 2; + if(target.isMinHp()) return 1.5; + return 1/Math.max(1,target.hp); + } + } + } + }, + mizhilianou:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('mizhilianou'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.mizhilianou=card; + target.storage.mizhilianou_markcount=4; + target.addSkill('mizhilianou'); + }, + ai:{ + order:2, + value:4, + result:{ + target:function(player,target){ + if(target==player){ + if(target.countCards('he',{suit:'heart'})){ + if(target.isDamaged()) return 1.5; + } + else{ + return 0.2; + } + } + else if(target.isDamaged()){ + return 1; + } + return 0.5; + } + } + } + }, + xiajiao:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('xiajiao'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.xiajiao=card; + target.storage.xiajiao_markcount=3; + target.addSkill('xiajiao'); + target.addTempSkill('xiajiao3'); + }, + ai:{ + order:2, + value:5, + result:{ + target:1 + } + } + }, + tanhuadong:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('tanhuadong'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.tanhuadong=card; + target.storage.tanhuadong_markcount=3; + target.addSkill('tanhuadong'); + }, + ai:{ + order:2, + value:5, + result:{ + target:1 + } + } + }, + mapodoufu:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('mapodoufu'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.mapodoufu=card; + target.storage.mapodoufu_markcount=2; + target.addSkill('mapodoufu'); + }, + ai:{ + order:2, + value:5, + result:{ + target:function(player,target){ + return player==target?2:1; + } + } + } + }, + qingtuan:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('qingtuan'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.qingtuan=card; + target.storage.qingtuan_markcount=2; + target.addSkill('qingtuan'); + }, + ai:{ + order:4, + value:4, + result:{ + target:function(player,target){ + if(target==player){ + if(target.hasSha()) return 2; + } + else{ + var nh=target.countCards('h'); + if(nh>=3) return 1; + if(target.hasSha()) return 1; + if(nh&&Math.random()<0.5) return 1; + } + return player.needsToDiscard()?0.2:0; + } + } + } + }, + yougeng:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('yougeng'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.yougeng=card; + target.storage.yougeng_markcount=2; + target.addSkill('yougeng'); + }, + ai:{ + order:2, + value:4, + result:{ + target:function(player,target){ + if(target.isHealthy()) return player.needsToDiscard()?0.1:0; + if(target.isMinHp()) return 1.5; + return 1/Math.max(1,target.hp); + } + } + } + }, + molicha:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('molicha'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.molicha=card; + target.storage.molicha_markcount=4; + target.addSkill('molicha'); + }, + ai:{ + order:2, + value:4, + result:{ + target:1 + } + } + }, + yuanbaorou:{ + fullskin:true, + type:'food', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('yuanbaorou'); + }, + //range:{global:1}, + content:function(){ + target.$gain2(cards); + target.storage.yuanbaorou=card; + target.storage.yuanbaorou_markcount=4; + target.addSkill('yuanbaorou'); + }, + ai:{ + order:2, + value:4, + result:{ + target:function(player,target){ + if(target==player){ + if(target.hasSha()) return 2; + } + else{ + var nh=target.countCards('h'); + if(nh>=3) return 1; + if(target.hasSha()) return 1; + if(nh&&Math.random()<0.5) return 1; + } + return player.needsToDiscard()?0.2:0; + } + } + } + }, + heilonglinpian:{ + fullskin:true, + type:'trick', + enable:true, + toself:true, + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + modTarget:true, + content:function(){ + target.changeHujia(); + target.addTempSkill('heilonglinpian',{player:'phaseBegin'}); + }, + ai:{ + value:[6,1], + useful:1, + order:2, + result:{ + target:1 + } + } + }, + mutoumianju:{ + fullskin:true, + type:'equip', + subtype:'equip2', + skills:['mutoumianju_skill'], + ai:{ + equipValue:4 + } + }, + yuheng:{ + fullskin:true, + type:'equip', + subtype:'equip5', + nopower:true, + nomod:true, + unique:true, + skills:['yuheng_skill'], + ai:{ + equipValue:6 + } + }, + yuheng_plus:{ + fullskin:true, + type:'equip', + subtype:'equip5', + nopower:true, + unique:true, + nomod:true, + epic:true, + cardimage:'yuheng', + skills:['yuheng_plus_skill'], + ai:{ + equipValue:7 + } + }, + yuheng_pro:{ + fullskin:true, + type:'equip', + subtype:'equip5', + nopower:true, + unique:true, + nomod:true, + legend:true, + cardimage:'yuheng', + skills:['yuheng_pro_skill'], + ai:{ + equipValue:8 + } + }, + shatang:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:true, + cardcolor:'red', + cardnature:'fire', + content:function(){ + 'step 0' + target.damage('fire'); + 'step 1' + target.changeHujia(); + }, + ai:{ + value:[4,1], + useful:2, + order:2, + result:{ + target:function(player,target){ + if(target.hasSkillTag('nofire')) return 1.5; + if(target.hasSkillTag('maixie_hp')) return 0; + if(target.hp==1) return -1; + return -1/Math.sqrt(target.hp+1); + } + }, + tag:{ + damage:1, + fireDamage:1, + natureDamage:1 + } + } + }, + shujinsan:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:function(card,player,target){ + return target.countCards('he')>0; + }, + content:function(){ + 'step 0' + target.chooseToDiscard('he',[1,target.countCards('he')],'弃置任意张牌并摸等量的牌').ai=function(card){ + return 6-get.value(card); + } + 'step 1' + if(result.bool){ + target.draw(result.cards.length); + } + }, + ai:{ + order:1.5, + value:[4,1], + tag:{ + norepeat:1 + }, + result:{ + target:function(player,target){ + if(target==player){ + var cards=player.getCards('he'); + var num=-1; + for(var i=0;i2){ + if(player.needsToDiscard()) return 1/target.hp; + return 0; + } + if(target.hp>0){ + return 2/target.hp; + } + return 0; + } + } + } + }, + yunvyuanshen:{ + fullskin:true, + type:'basic', + enable:true, + logv:false, + filterTarget:function(card,player,target){ + return !target.hasSkill('yunvyuanshen_skill'); + }, + content:function(){ + target.storage.yunvyuanshen_skill=game.createCard('yunvyuanshen'); + target.addSkill('yunvyuanshen_skill'); + if(cards&&cards.length){ + card=cards[0]; + } + if(target==targets[0]&&card.clone&&(card.clone.parentNode==player.parentNode||card.clone.parentNode==ui.arena)){ + card.clone.moveDelete(target); + game.addVideo('gain2',target,get.cardsInfo([card])); + } + }, + ai:{ + basic:{ + value:9, + useful:4, + value:7 + }, + order:2, + result:{ + target:function(player,target){ + return 1/Math.sqrt(1+target.hp); + }, + }, + } + }, + bingpotong:{ + fullskin:true, + type:'jiguan', + enable:true, + wuxieable:true, + filterTarget:function(card,player,target){ + return target.countCards('h')>0; + }, + selectTarget:[1,3], + content:function(){ + "step 0" + if(target.countCards('h')==0||player.countCards('h')==0){ + event.finish(); + return; + } + player.chooseCard('请展示一张手牌',true).set('ai',function(){ + var num=0; + var rand=_status.event.rand; + if(get.color(card)=='red'){ + if(rand) num-=6; + } + else{ + if(!rand) num-=6; + } + var value=get.value(card); + if(value>=8) return -100; + return num-value; + }).set('rand', Math.random()<0.5).prompt2='若与'+get.translation(target)+'展示的牌相同,你弃置展示的牌,'+get.translation(target)+'失去一点体力'; + "step 1" + event.card1=result.cards[0]; + target.chooseCard('请展示一张手牌',true).set('ai',function(card){ + var num=0; + var rand=_status.event.rand; + if(get.color(card)=='red'){ + if(rand) num-=6; + } + else{ + if(!rand) num-=6; + } + var value=get.value(card); + if(value>=8) return -100; + return num-value; + }).set('rand', Math.random()<0.5).prompt2='若与'+get.translation(player)+'展示的牌相同,'+get.translation(player)+'弃置展示的牌,你失去一点体力'; + "step 2" + event.card2=result.cards[0]; + ui.arena.classList.add('thrownhighlight'); + game.addVideo('thrownhighlight1'); + player.$compare(event.card1,target,event.card2); + game.delay(4); + "step 3" + game.log(player,'展示了',event.card1); + game.log(target,'展示了',event.card2); + if(get.color(event.card2)==get.color(event.card1)){ + player.discard(event.card1).animate=false; + target.$gain2(event.card2); + var clone=event.card1.clone; + if(clone){ + clone.style.transition='all 0.5s'; + clone.style.transform='scale(1.2)'; + clone.delete(); + game.addVideo('deletenode',player,get.cardsInfo([clone])); + } + target.loseHp(); + event.finish(); + event.parent.cancelled=true; + } + else{ + player.$gain2(event.card1); + target.$gain2(event.card2); + game.delay(); + } + ui.arena.classList.remove('thrownhighlight'); + game.addVideo('thrownhighlight2'); + "step 4" + // if(cards&&cards.length){ + // player.gain(cards,'gain2'); + // target.addTempSkill('bingpotong'); + // } + }, + ai:{ + basic:{ + order:2, + value:[5,1], + useful:1, + }, + result:{ + player:function(player,target){ + if(player.countCards('h')<=Math.min(5,Math.max(2,player.hp))&&_status.event.name=='chooseToUse'){ + if(typeof _status.event.filterCard=='function'&& + _status.event.filterCard({name:'bingpotong'})){ + return -10; + } + if(_status.event.skill){ + var viewAs=get.info(_status.event.skill).viewAs; + if(viewAs=='bingpotong') return -10; + if(viewAs&&viewAs.name=='bingpotong') return -10; + } + } + return 0; + }, + target:function(player,target){ + if(player.countCards('h')<=1) return 0; + return -1.5; + } + }, + tag:{ + loseHp:1 + } + } + }, + feibiao:{ + type:'jiguan', + enable:true, + fullskin:true, + wuxieable:true, + outrange:{globalFrom:2}, + filterTarget:function(card,player,target){ + return target!=player; + }, + content:function(){ + "step 0" + if(!target.countCards('h',{color:'black'})){ + target.loseHp(); + event.finish(); + } + else{ + target.chooseToDiscard({color:'black'},'弃置一张黑色手牌或受流失一点体力').ai=function(card){ + return 8-get.value(card); + }; + } + "step 1" + if(!result.bool){ + target.loseHp(); + } + }, + ai:{ + basic:{ + order:9, + value:3, + useful:1, + }, + result:{ + target:-2 + }, + tag:{ + discard:1, + loseHp:1 + } + } + }, + longxugou:{ + type:'jiguan', + enable:true, + fullskin:true, + wuxieable:true, + filterTarget:function(card,player,target){ + return target!=player&&target.countGainableCards(player,'e'); + }, + content:function(){ + 'step 0' + var es=target.getGainableCards(player,'e') + if(es.length){ + player.choosePlayerCard('e',target,true).set('es',es).set('filterButton',function(button){ + return _status.event.es.contains(button.link); + }); + } + else{ + event.finish(); + } + 'step 1' + if(result.bool){ + target.$give(result.links[0],player); + target.lose(result.links[0],ui.special); + event.card=result.links[0]; + game.delay(); + } + else{ + event.finish(); + } + 'step 2' + if(event.card&&get.position(event.card)=='s'){ + player.equip(event.card); + } + }, + ai:{ + basic:{ + order:9, + value:6, + useful:4, + }, + result:{ + target:-1 + }, + tag:{ + loseCard:1, + gain:1, + } + } + }, + qiankunbiao:{ + type:'jiguan', + enable:true, + fullskin:true, + wuxieable:true, + filterTarget:function(card,player,target){ + return target!=player&&target.countCards('he')>0; + }, + changeTarget:function(player,targets){ + game.filterPlayer(function(current){ + return get.distance(targets[0],current,'pure')==1&¤t.countCards('he'); + },targets); + }, + content:function(){ + var he=target.getCards('he'); + if(he.length){ + target.discard(he.randomGet()).delay=false; + } + }, + contentAfter:function(){ + game.delay(0.5); + }, + ai:{ + order:7, + tag:{ + loseCard:1, + discard:1, + }, + wuxie:function(){ + return 0; + }, + result:{ + player:function(player,target){ + return game.countPlayer(function(current){ + if(current==target||(get.distance(target,current,'pure')==1&¤t.countCards('he'))){ + return -get.sgn(get.attitude(player,current)); + } + }); + } + } + } + }, + shenhuofeiya:{ + type:'jiguan', + enable:true, + fullskin:true, + wuxieable:true, + filterTarget:function(card,player,target){ + return target!=player; + }, + changeTarget:function(player,targets){ + game.filterPlayer(function(current){ + return get.distance(targets[0],current,'pure')==1; + },targets); + }, + cardcolor:'red', + cardnature:'fire', + content:function(){ + "step 0" + var next=target.chooseToRespond({name:'shan'}); + next.ai=function(card){ + if(get.damageEffect(target,player,target,'fire')>=0) return 0; + if(player.hasSkillTag('notricksource')) return 0; + if(target.hasSkillTag('notrick')) return 0; + if(target.hasSkillTag('noShan')){ + return -1; + } + return 11-get.value(card); + }; + next.autochoose=lib.filter.autoRespondShan; + "step 1" + if(result.bool==false){ + target.damage('fire'); + } + }, + ai:{ + wuxie:function(target,card,player,viewer){ + if(get.attitude(viewer,target)>0&&target.countCards('h','shan')){ + if(!target.countCards('h')||target.hp==1||Math.random()<0.7) return 0; + } + if(get.attitude(viewer,target)<=0){ + return 0; + } + }, + order:7, + tag:{ + respond:1, + respondShan:1, + damage:1, + natureDamage:1, + fireDamage:1, + multitarget:1, + multineg:1, + }, + result:{ + player:function(player,target){ + return game.countPlayer(function(current){ + if(current==target||(get.distance(target,current,'pure')==1)){ + return get.sgn(get.effect(current,{name:'chiyuxi'},player,player)); + } + }); + } + } + } + }, + // wenhuangsan:{ + // type:'jiguan', + // enable:true, + // fullskin:true, + // }, + // tuhunsha:{ + // type:'jiguan', + // enable:true, + // fullskin:true, + // }, + // shenhuofeiya:{ + // type:'jiguan', + // enable:true, + // fullskin:true, + // }, + mianlijinzhen:{ + type:'jiguan', + enable:true, + fullskin:true, + filterTarget:function(card,player,target){ + return target.hp>=player.hp; + }, + content:function(){ + 'step 0' + target.draw(); + 'step 1' + target.loseHp(); + }, + ai:{ + order:2, + value:[5,1], + useful:[4,1], + result:{ + target:-1.5 + }, + tag:{ + // damage:1 + } + } + }, + // longxugou:{ + // type:'jiguan', + // enable:true, + // fullskin:true, + // }, + liutouge:{ + type:'jiguan', + enable:true, + fullskin:true, + filterTarget:true, + wuxieable:true, + content:function(){ + if(player.getEnemies().contains(target)){ + target.getDebuff(); + } + else{ + target.getBuff(); + } + }, + ai:{ + order:4, + value:5, + result:{ + player:function(player,target){ + if(get.attitude(player,target)==0) return 0; + return 1; + } + } + } + }, + liufengsan:{ + type:'trick', + enable:true, + fullskin:true, + filterTarget:true, + content:function(){ + var list=[]; + for(var i=0;i<2;i++){ + list.push(game.createCard('shan')); + } + target.gain(list,'gain2'); + }, + ai:{ + order:4.5, + value:[5,1], + tag:{ + gain:1, + norepeat:1 + }, + result:{ + target:function(player,target){ + if(target==player){ + if(!target.hasShan()) return 2; + var num=target.needsToDiscard(2); + if(num==0) return 1.5; + if(num==1) return 1; + return 0.5; + } + else{ + switch(target.countCards('h')){ + case 0:return 2; + case 1:return 1.5; + case 2:return 1; + default:return 0.5; + } + } + } + } + } + }, + shihuifen:{ + type:'trick', + fullskin:true, + filterTarget:true, + global:'g_shihuifen', + content:function(){ + 'step 0' + var next=_status.currentPhase.chooseToRespond({name:'shan'}); + next.set('prompt2','否则本回合无法对其他角色使用卡牌'); + 'step 1' + if(!result.bool){ + _status.currentPhase.addTempSkill('shihuifen','phaseUseAfter'); + } + }, + ai:{ + order:1, + value:[5,1], + useful:[5,1], + tag:{ + respond:1, + respondShan:1, + }, + result:{ + target:function(player,target){ + if(target.countCards('h')>=3||target.needsToDiscard()) return -1.5; + return 0; + } + } + } + }, + }, + skill:{ + ziyangdan:{ + trigger:{player:'phaseBegin'}, + silent:true, + init:function(player){ + player.storage.ziyangdan=3; + }, + onremove:true, + content:function(){ + if(player.hujia>0){ + player.changeHujia(-1); + } + player.storage.ziyangdan--; + if(player.hujia==0||player.storage.ziyangdan==0){ + player.removeSkill('ziyangdan'); + } + }, + ai:{ + threaten:0.8 + } + }, + luyugeng:{ + mark:'card', + enable:'phaseUse', + usable:1, + nopop:true, + filterCard:{type:'basic'}, + filter:function(event,player){ + return player.countCards('h',{type:'basic'}); + }, + intro:{ + content:function(storage,player){ + return '出牌阶段限一次,你可以弃置一张基本牌并发现一张牌,持续两回合(剩余'+player.storage.luyugeng_markcount+'回合)' + } + }, + content:function(){ + player.discoverCard(); + }, + group:'luyugeng_count', + subSkill:{ + count:{ + trigger:{player:'phaseAfter'}, + forced:true, + popup:false, + content:function(){ + player.storage.luyugeng_markcount--; + if(player.storage.luyugeng_markcount==0){ + delete player.storage.luyugeng; + delete player.storage.luyugeng_markcount; + player.removeSkill('luyugeng'); + } + else{ + player.updateMarks(); + } + }, + } + } + }, + xiajiao:{ + mark:'card', + trigger:{player:['phaseUseBefore','phaseEnd']}, + forced:true, + popup:false, + nopop:true, + filter:function(event,player){ + return !player.hasSkill('xiajiao3'); + }, + intro:{ + content:function(storage,player){ + return '你在摸牌阶段额外摸一张牌,然后弃置一张牌(剩余'+player.storage.xiajiao_markcount+'回合)' + } + }, + content:function(){ + player.storage.xiajiao_markcount--; + if(player.storage.xiajiao_markcount==0){ + delete player.storage.xiajiao; + delete player.storage.xiajiao_markcount; + player.removeSkill('xiajiao'); + } + else{ + player.updateMarks(); + } + player.addTempSkill('xiajiao3'); + }, + group:'xiajiao_draw', + subSkill:{ + draw:{ + trigger:{player:'phaseDrawBegin'}, + forced:true, + content:function(){ + trigger.num++; + player.addTempSkill('xiajiao2'); + } + } + } + }, + xiajiao2:{ + trigger:{player:'phaseDrawAfter'}, + silent:true, + content:function(){ + player.chooseToDiscard('he',true); + } + }, + xiajiao3:{}, + mizhilianou:{ + mark:'card', + trigger:{player:'phaseAfter'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '你可以将一张红桃牌当作桃使用(剩余'+player.storage.mizhilianou_markcount+'回合)' + } + }, + content:function(){ + player.storage.mizhilianou_markcount--; + if(player.storage.mizhilianou_markcount==0){ + delete player.storage.mizhilianou; + delete player.storage.mizhilianou_markcount; + player.removeSkill('mizhilianou'); + } + else{ + player.updateMarks(); + } + }, + group:'mizhilianou_use', + subSkill:{ + use:{ + enable:'chooseToUse', + filterCard:{suit:'heart'}, + position:'he', + viewAs:{name:'tao'}, + viewAsFilter:function(player){ + return player.countCards('he',{suit:'heart'})>0; + }, + prompt:'将一张红桃牌当桃使用', + check:function(card){return 10-get.value(card)}, + ai:{ + skillTagFilter:function(player){ + return player.countCards('he',{suit:'heart'})>0; + }, + save:true, + respondTao:true, + } + } + } + }, + chunbing:{ + mark:'card', + trigger:{player:'phaseAfter'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '你的手牌上限+1(剩余'+player.storage.chunbing_markcount+'回合)' + } + }, + mod:{ + maxHandcard:function(player,num){ + return num+1; + } + }, + content:function(){ + player.storage.chunbing_markcount--; + if(player.storage.chunbing_markcount==0){ + delete player.storage.chunbing; + delete player.storage.chunbing_markcount; + player.removeSkill('chunbing'); + } + else{ + player.updateMarks(); + } + }, + }, + gudonggeng:{ + mark:'card', + trigger:{player:'phaseBegin'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '当你下一次受到杀造成的伤害时,令伤害-1(剩余'+player.storage.gudonggeng_markcount+'回合)' + } + }, + content:function(){ + player.storage.gudonggeng_markcount--; + if(player.storage.gudonggeng_markcount==0){ + delete player.storage.gudonggeng; + delete player.storage.gudonggeng_markcount; + player.removeSkill('gudonggeng'); + } + else{ + player.updateMarks(); + } + }, + group:'gudonggeng_damage', + subSkill:{ + damage:{ + trigger:{player:'damageBegin'}, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&event.num>0; + }, + forced:true, + content:function(){ + trigger.num--; + delete player.storage.gudonggeng; + delete player.storage.gudonggeng_markcount; + player.removeSkill('gudonggeng'); + } + } + }, + ai:{ + effect:{ + target:function(card,player,target){ + if(card.name=='sha'&&get.attitude(player,target)<0) return 0.5; + } + } + } + }, + qingtuan:{ + mark:'card', + trigger:{player:'phaseAfter'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '你在回合内使用首张杀时摸一张牌(剩余'+player.storage.qingtuan_markcount+'回合)' + } + }, + content:function(){ + player.storage.qingtuan_markcount--; + if(player.storage.qingtuan_markcount==0){ + delete player.storage.qingtuan; + delete player.storage.qingtuan_markcount; + player.removeSkill('qingtuan'); + } + else{ + player.updateMarks(); + } + }, + group:'qingtuan_draw', + subSkill:{ + draw:{ + trigger:{player:'useCard'}, + filter:function(event,player){ + return event.card.name=='sha'&&_status.currentPhase==player; + }, + usable:1, + forced:true, + content:function(){ + player.draw(); + } + } + } + }, + liyutang:{ + mark:'card', + trigger:{player:'phaseEnd'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '结束阶段,若你的体力值为全场最少或之一,你获得一点护甲(剩余'+player.storage.liyutang_markcount+'回合)' + } + }, + content:function(){ + if(player.isMinHp()){ + player.logSkill('liyutang'); + player.changeHujia(); + } + player.storage.liyutang_markcount--; + if(player.storage.liyutang_markcount==0){ + delete player.storage.liyutang; + delete player.storage.liyutang_markcount; + player.removeSkill('liyutang'); + } + else{ + player.updateMarks(); + } + }, + }, + yougeng:{ + mark:'card', + trigger:{player:'phaseBegin'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '准备阶段,若你的体力值为全场最少或之一,你回复一点体力(剩余'+player.storage.yougeng_markcount+'回合)' + } + }, + content:function(){ + if(player.isDamaged()&&player.isMinHp()){ + player.logSkill('yougeng'); + player.recover(); + } + player.storage.yougeng_markcount--; + if(player.storage.yougeng_markcount==0){ + delete player.storage.yougeng; + delete player.storage.yougeng_markcount; + player.removeSkill('yougeng'); + } + else{ + player.updateMarks(); + } + }, + }, + molicha:{ + mark:'card', + trigger:{player:'phaseAfter'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '你不能成为其他角色的黑色牌的目标(剩余'+player.storage.molicha_markcount+'回合)' + } + }, + mod:{ + targetEnabled:function(card,player,target){ + if(player!=target&&get.color(card)=='black'){ + return false; + } + } + }, + content:function(){ + player.storage.molicha_markcount--; + if(player.storage.molicha_markcount==0){ + delete player.storage.molicha; + delete player.storage.molicha_markcount; + player.removeSkill('molicha'); + player.logSkill('molicha'); + } + else{ + player.updateMarks(); + } + } + }, + yuanbaorou:{ + mark:'card', + trigger:{player:'phaseAfter'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '你在出牌阶段可以额外使用一张杀(剩余'+player.storage.yuanbaorou_markcount+'回合)' + } + }, + mod:{ + cardUsable:function(card,player,num){ + if(card.name=='sha') return num+1; + } + }, + content:function(){ + player.storage.yuanbaorou_markcount--; + if(player.storage.yuanbaorou_markcount==0){ + delete player.storage.yuanbaorou; + delete player.storage.yuanbaorou_markcount; + player.removeSkill('yuanbaorou'); + } + else{ + player.updateMarks(); + } + }, + }, + tanhuadong:{ + mark:'card', + trigger:{player:'phaseEnd'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '出牌阶段结束时,你摸一张牌(剩余'+player.storage.tanhuadong_markcount+'回合)' + } + }, + content:function(){ + player.storage.tanhuadong_markcount--; + if(player.storage.tanhuadong_markcount==0){ + delete player.storage.tanhuadong; + delete player.storage.tanhuadong_markcount; + player.removeSkill('tanhuadong'); + } + else{ + player.updateMarks(); + } + }, + group:'tanhuadong_draw', + subSkill:{ + draw:{ + trigger:{player:'phaseUseEnd'}, + forced:true, + content:function(){ + player.draw(); + } + } + } + }, + mapodoufu:{ + mark:'card', + trigger:{player:'phaseEnd'}, + forced:true, + popup:false, + nopop:true, + intro:{ + content:function(storage,player){ + return '结束阶段,你随机弃置一名随机敌人的一张随机牌(剩余'+player.storage.mapodoufu_markcount+'回合)' + } + }, + content:function(){ + var list=player.getEnemies(); + for(var i=0;i=3){ + card.init([card.suit,card.number,'yuheng_plus',card.nature]); + player.addTempSkill('yuheng_plus_temp'); + } + } + }, + ai:{ + order:9, + result:{ + player:1 + } + } + }, + yuheng_plus_temp:{}, + yuheng_plus_skill:{ + enable:'phaseUse', + usable:1, + filterCard:{color:'black'}, + check:function(card){ + return 8-get.value(card); + }, + filter:function(event,player){ + // if(player.hasSkill('yuheng_plus_temp')) return false; + if(!player.countCards('h',{color:'black'})) return false; + var enemies=player.getEnemies(); + for(var i=0;i=7){ + card.init([card.suit,card.number,'yuheng_pro',card.nature]); + } + } + }, + ai:{ + order:9, + result:{ + player:1 + } + } + }, + yuheng_pro_skill:{ + enable:'phaseUse', + filterCard:{color:'black'}, + check:function(card){ + return 8-get.value(card); + }, + filter:function(event,player){ + if(!player.countCards('h',{color:'black'})) return false; + var enemies=player.getEnemies(); + for(var i=0;i=3){ + card.init([card.suit,card.number,'yuheng_plus',card.nature]); + player.addTempSkill('yuheng_plus_temp'); + } + } + }, + ai:{ + order:9, + result:{ + player:1 + } + } + }, + shihuifen:{ + mark:true, + intro:{ + content:'使用卡牌无法指定其他角色为目标' + }, + mod:{ + playerEnabled:function(card,player,target){ + if(player!=target) return false; + } + } + }, + g_shihuifen:{ + trigger:{global:'phaseUseBegin'}, + direct:true, + filter:function(event,player){ + if(event.player.hasSkill('shihuifen')) return false; + if(event.player==player) return false; + if(!lib.filter.targetEnabled({name:'shihuifen'},player,event.player)) return false; + return player.hasCard('shihuifen')||player.hasSkillTag('shihuifen'); + }, + content:function(){ + player.chooseToUse(get.prompt('shihuifen',trigger.player).replace(/发动/,'使用'),function(card,player){ + if(card.name!='shihuifen') return false; + return lib.filter.cardEnabled(card,player,'forceEnable'); + },trigger.player,-1).targetRequired=true; + } + }, + g_jinlianzhu:{ + trigger:{global:'damageBefore'}, + direct:true, + filter:function(event,player){ + if(!lib.filter.targetEnabled({name:'jinlianzhu'},player,event.player)) return false; + return player.hasCard('jinlianzhu'); + }, + content:function(){ + player.chooseToUse(get.prompt('jinlianzhu',trigger.player).replace(/发动/,'使用'),function(card,player){ + if(card.name!='jinlianzhu') return false; + return lib.filter.cardEnabled(card,player,'forceEnable'); + },trigger.player,-1).targetRequired=true; + } + }, + }, + cardType:{ + food:0.3 + }, + translate:{ + jinlianzhu:'金莲珠', + jinlianzhu_info:'对一名即将受到伤害的角色使用,防止此伤害,并令伤害来源摸一张牌', + shihuifen:'石灰粉', + shihuifen_info:'在一名其他角色的出牌阶段开始时对其使用,目标需打出一张闪,否则此阶段使用卡牌无法指定其他角色为目标', + liufengsan:'流风散', + liufengsan_info:'出牌阶段对一名角色使用,目标获得两张闪', + liutouge:'六骰格', + liutouge_info:'出牌阶段对一名角色使用,若目标是敌人,对目标施加一个随机的负面效果;否则对目标施加一个随机的正面效果', + longxugou:'龙须钩', + longxugou_info:'出牌阶段对一名装备区内有牌的其他角色使用,获得其装备区内的一张牌并装备之', + mianlijinzhen:'棉里针', + mianlijinzhen_info:'出牌阶段对一名体力值不小于你的角色使用,目标摸一张牌然后失去一点体力', + shenhuofeiya:'神火飞鸦', + shenhuofeiya_info:'出牌阶段对一名其他角色和其相邻角色使用,目标需打出一张闪,否则受到一点火属性伤害', + // tuhunsha:'土魂砂', + // tuhunsha_info:'土魂砂', + // wenhuangsan:'瘟癀伞', + // wenhuangsan_info:'瘟癀伞', + qiankunbiao:'乾坤镖', + qiankunbiao_info:'随机弃置一名其他角色和其相邻角色的一张牌', + + bingpotong:'天女散花', + bingpotong_ab:'散花', + bingpotong_info:'出牌阶段对至多3名角色使用,你与每个目标依次同时展示一张手牌,若颜色相同,你弃置展示的手牌,目标失去一点体力并终止结算', + feibiao:'飞镖', + feibiao_info:'出牌阶段,对一名距离1以外的角色使用,令其弃置一张黑色手牌或流失一点体力', + + dinvxuanshuang:'帝女玄霜', + dinvxuanshuang_skill:'帝女玄霜', + dinvxuanshuang_info:'对一名濒死状态的角色使用,目标回复一点体力,然后可以弃置任意张牌并摸等量的牌', + yunvyuanshen:'玉女元参', + yunvyuanshen_skill:'玉女元参', + yunvyuanshen_info:'出牌阶段对一名角色使用,目标在下一次进入濒死状态时回复一点体力', + ziyangdan:'紫阳丹', + ziyangdan_info:'出牌阶段对一名角色使用,目标获得3点护甲,此后每个准备阶段失去1点护甲,直到首次失去所有护甲或累计以此法失去3点护甲', + yuheng:'玉衡', + yuheng_plus:'玉衡', + yuheng_pro:'玉衡', + yuheng_skill:'玉衡', + yuheng_plus_skill:'玉衡', + yuheng_pro_skill:'玉衡', + yuheng_info:'出牌阶段限一次,若敌方角色有黑桃手牌,你可以弃置一张黑桃手牌,然后获得一名随机敌方角色的一张随机黑桃手牌(此牌在本局游戏中第三次和第七次发动效果后,分别自动获得一次强化)', + yuheng_plus_info:'由普通玉衡强化得到,将玉衡技能描述中的“弃置一张黑桃手牌”改为“弃置一张黑色手牌”', + yuheng_pro_info:'由普通玉衡二次强化得到,将玉横技能描述中的“弃置一张黑桃手牌”改为“弃置一张黑色手牌”,并去掉使用次数限制', + yuheng_skill_info:'出牌阶段限一次,若敌方角色有黑桃手牌,你可以弃置一张黑桃手牌,然后获得一名随机敌方角色的一张随机黑桃手牌', + yuheng_plus_skill_info:'出牌阶段限一次,若敌方角色有黑桃手牌,你可以弃置一张黑色手牌,然后获得一名随机敌方角色的一张随机黑桃手牌', + yuheng_pro_skill_info:'出牌阶段限,若敌方角色有黑桃手牌,你可以弃置一张黑色手牌,然后获得一名随机敌方角色的一张随机黑桃手牌', + shujinsan:'舒筋散', + shujinsan_info:'出牌阶段对任意角色使用,目标可弃置任意张牌,并摸等量的牌', + mutoumianju:'木头面具', + mutoumianju_info:'出牌阶段限一次,你可以将一张手牌当作杀使用', + mutoumianju_skill:'木杀', + mutoumianju_skill_info:'出牌阶段限一次,你可以将一张手牌当作杀使用', + heilonglinpian:'黑龙鳞片', + heilonglinpian_info:'出牌阶段对自己使用,获得一点护甲,直到下一回合开始,你的防御距离+1', + shatang:'沙棠', + shatang_info:'出牌阶段对一名角色使用,对目标造成一点火焰伤害,然后目标获得一点护甲', + + food:'食物', + chunbing:'春饼', + chunbing_info:'你的手牌上限+1,持续五回合', + gudonggeng:'骨董羹', + gudonggeng_info:'当你下一次受到杀造成的伤害时,令伤害-1,持续三回合', + yougeng:'酉羹', + yougeng_info:'准备阶段,若你的体力值为全场最少或之一,你回复一点体力,持续两回合', + liyutang:'鲤鱼汤', + liyutang_info:'结束阶段,若你的体力值为全场最少或之一,你获得一点护甲,持续两回合', + mizhilianou:'蜜汁藕', + mizhilianou_info:'你可以将一张红桃牌当作桃使用,持续四回合', + xiajiao:'虾饺', + xiajiao_info:'你在摸牌阶段额外摸一张牌,然后弃置一张牌,持续三回合', + tanhuadong:'昙花冻', + tanhuadong_info:'出牌阶段结束时,你摸一张牌,持续三回合', + qingtuan:'青团', + qingtuan_info:'你在回合内使用首张杀时摸一张牌,持续两回合', + luyugeng:'鲈鱼羹', + luyugeng_info:'出牌阶段限一次,你可以弃置一张基本牌并发现一张牌,持续三回合', + yuanbaorou:'元宝肉', + yuanbaorou_info:'你在出牌阶段可以额外使用一张杀,持续四回合', + molicha:'茉莉茶', + molicha_info:'你不能成为其他角色的黑色牌的目标,持续四回合', + mapodoufu:'麻婆豆腐', + mapodoufu_info:'结束阶段,你弃置一名随机敌人的一张随机牌,持续两回合', + }, + list:[ + ['spade',2,'tanhuadong'], + ['club',1,'molicha'], + ['club',3,'chunbing'], + ['heart',12,'yougeng'], + ['heart',8,'gudonggeng'], + ['heart',1,'liyutang'], + ['diamond',4,'mizhilianou'], + ['diamond',6,'xiajiao'], + ['spade',3,'qingtuan'], + ['club',11,'luyugeng'], + ['heart',4,'mapodoufu'], + ['spade',8,'yuanbaorou'], + + ['spade',7,'yuheng'], + ['club',4,'mutoumianju'], + ['spade',2,'heilonglinpian'], + ['spade',1,'mianlijinzhen'], + ['heart',13,'yunvyuanshen'], + + ['club',8,'feibiao','poison'], + ['diamond',9,'feibiao','poison'], + + ['spade',3,'bingpotong','poison'], + ['club',12,'bingpotong','poison'], + + ['club',5,'shihuifen'], + ['club',1,'shihuifen'], + ['spade',13,'shihuifen'], + + ['diamond',6,'shujinsan'], + ['spade',2,'shujinsan'], + + ['diamond',6,'ziyangdan'], + ['heart',1,'ziyangdan'], + + // ['diamond',7,'dinvxuanshuang'], + ['heart',9,'dinvxuanshuang'], + + ['spade',9,'qiankunbiao'], + ['club',13,'qiankunbiao'], + + ['diamond',9,'shenhuofeiya'], + ['spade',7,'longxugou'], + + ['heart',9,'jinlianzhu'], + ['spade',7,'jinlianzhu'], + + ['heart',6,'liutouge'], + ['club',6,'liutouge'], + + ['club',6,'liufengsan'], + ['club',3,'liufengsan'], + + ['heart',13,'shatang','fire'] + ] + }; +}); diff --git a/card/gwent.js b/card/gwent.js index 6fa1916c0..b98b126d3 100644 --- a/card/gwent.js +++ b/card/gwent.js @@ -1,2347 +1,2347 @@ -'use strict'; -game.import('card',function(lib,game,ui,get,ai,_status){ - return { - name:'gwent', - card:{ - gw_dieyi:{ - fullskin:true - }, - gw_dieyi_equip1:{ - fullskin:true, - vanish:true, - hidden:true, - cardimage:'gw_dieyi', - type:'equip', - subtype:'equip1', - onLose:function(){ - lib.skill.gw_dieyi.process(player); - }, - loseDelay:false, - skills:[], - ai:{ - equipValue:0 - } - }, - gw_dieyi_equip2:{ - fullskin:true, - vanish:true, - hidden:true, - cardimage:'gw_dieyi', - type:'equip', - subtype:'equip2', - onLose:function(){ - lib.skill.gw_dieyi.process(player); - }, - loseDelay:false, - skills:[], - ai:{ - equipValue:0 - } - }, - gw_dieyi_equip3:{ - fullskin:true, - vanish:true, - hidden:true, - cardimage:'gw_dieyi', - type:'equip', - subtype:'equip3', - onLose:function(){ - lib.skill.gw_dieyi.process(player); - }, - loseDelay:false, - skills:[], - ai:{ - equipValue:0 - } - }, - gw_dieyi_equip4:{ - fullskin:true, - vanish:true, - hidden:true, - cardimage:'gw_dieyi', - type:'equip', - subtype:'equip4', - onLose:function(){ - lib.skill.gw_dieyi.process(player); - }, - loseDelay:false, - skills:[], - ai:{ - equipValue:0 - } - }, - gw_dieyi_equip5:{ - fullskin:true, - vanish:true, - hidden:true, - cardimage:'gw_dieyi', - type:'equip', - subtype:'equip5', - onLose:function(){ - lib.skill.gw_dieyi.process(player); - }, - loseDelay:false, - skills:[], - ai:{ - equipValue:0 - } - }, - gw_dieyi_judge:{ - fullskin:true, - vanish:true, - hidden:true, - cardimage:'gw_dieyi', - enable:true, - type:'delay', - filterTarget:true, - effect:function(){ - lib.skill.gw_dieyi.process(player); - }, - }, - gw_hudiewu:{ - fullborder:'gold', - type:'spell', - subtype:'spell_gold', - vanish:true, - enable:function(card,player){ - return game.hasPlayer(function(current){ - return current!=player&¤t.countCards('ej'); - }); - }, - notarget:true, - contentBefore:function(){ - player.$skill('蝴蝶舞','legend','metal'); - game.delay(2); - }, - content:function(){ - 'step 0' - event.targets=game.filterPlayer(function(current){ - return current.countCards('ej'); - }).sortBySeat(); - event.targets.remove(player); - 'step 1' - if(event.targets.length){ - var target=event.targets.shift(); - var ej=target.getCards('ej'); - player.line(target); - target.removeEquipTrigger(); - for(var i=0;imax2){ - return get.damageEffect(get.max(enemies,func,'item'),player,player,'fire'); - } - else{ - var num; - if(max1>max2){ - num=get.sgn(get.damageEffect(get.max(enemies,func,'item'),player,player,'fire')); - } - else if(max1==max2){ - num=0; - } - else{ - num=1; - } - return num+game.countPlayer(function(current){ - if(current.hp>=max2){ - return get.sgn(get.damageEffect(current,player,player,'fire')); - } - }); - } - } - }, - order:0.7, - } - }, - gw_leizhoushu:{ - fullborder:'gold', - type:'spell', - subtype:'spell_gold', - vanish:true, - enable:true, - notarget:true, - contentBefore:function(){ - player.$skill('雷咒术','legend','metal'); - game.delay(2); - }, - content:function(){ - if(player.hasSkill('gw_leizhoushu')){ - if(typeof player.storage.gw_leizhoushu!='number'){ - player.storage.gw_leizhoushu=2; - } - else{ - player.storage.gw_leizhoushu++; - } - player.syncStorage('gw_leizhoushu'); - player.updateMarks(); - } - else{ - player.addSkill('gw_leizhoushu'); - } - }, - contentAfter:function(){ - var evt=_status.event.getParent('phaseUse'); - if(evt&&evt.name=='phaseUse'){ - evt.skipped=true; - } - }, - ai:{ - value:8, - useful:[6,1], - result:{ - player:function(player){ - return 1+game.countPlayer(function(current){ - if(current!=player&¤t.isMaxHandcard()){ - return -get.sgn(get.attitude(player,current)); - } - }); - } - }, - order:0.5, - } - }, - gw_aerdeyin:{ - fullborder:'gold', - type:'spell', - subtype:'spell_gold', - vanish:true, - enable:function(card,player){ - var enemies=player.getEnemies(); - return enemies.length>0; - }, - notarget:true, - contentBefore:function(){ - player.$skill('阿尔德印','legend','metal'); - game.delay(2); - }, - content:function(){ - 'step 0' - var enemies=player.getEnemies(); - event.list=[enemies.randomGet()]; - 'step 1' - if(event.list.length){ - var target=event.list.shift(); - event.target=target; - player.line(target,'green'); - target.damage(); - } - else{ - delete event.target; - } - 'step 2' - if(event.target){ - if(!event.target.isTurnedOver()){ - event.target.turnOver(); - event.target.addSkill('gw_aerdeyin'); - } - event.goto(1); - } - 'step 3' - game.delay(); - }, - contentAfter:function(){ - var evt=_status.event.getParent('phaseUse'); - if(evt&&evt.name=='phaseUse'){ - evt.skipped=true; - } - }, - ai:{ - value:8, - useful:[6,1], - result:{ - player:function(player){ - return game.countPlayer(function(current){ - if(get.distance(player,current,'pure')==1){ - var att=get.sgn(get.attitude(player,current)); - if(current==player.next){ - return -att*1.5; - } - return -att; - } - }); - } - }, - order:0.5, - } - }, - gw_ansha:{ - fullborder:'gold', - type:'spell', - subtype:'spell_gold', - vanish:true, - enable:function(card,player){ - var enemies=player.getEnemies(); - return game.hasPlayer(function(current){ - return current.hp==1&&enemies.contains(current); - }); - }, - notarget:true, - contentBefore:function(){ - player.$skill('暗杀','legend','metal'); - game.delay(2); - }, - content:function(){ - var enemies=player.getEnemies(); - var list=game.filterPlayer(function(current){ - return current.hp==1&&enemies.contains(current); - }); - if(list.length){ - var target=list.randomGet(); - player.line(target); - target.die(); - } - }, - contentAfter:function(){ - var evt=_status.event.getParent('phaseUse'); - if(evt&&evt.name=='phaseUse'){ - evt.skipped=true; - } - }, - ai:{ - value:8, - useful:[6,1], - result:{ - player:1 - }, - order:0.6, - } - }, - gw_xinsheng:{ - fullborder:'gold', - type:'spell', - subtype:'spell_gold', - vanish:true, - enable:function(card,player){ - return game.hasPlayer(function(current){ - return !current.isUnseen(); - }); - }, - notarget:true, - contentBefore:function(){ - player.$skill('新生','legend','metal'); - game.delay(2); - }, - content:function(){ - 'step 0' - var target=get.max(game.filterPlayer(function(current){ - return !current.isUnseen(); - },'list').randomSort(),function(current){ - var att=get.attitude(player,current); - if(att<0&¤t.isDamaged()&¤t.hp<=3){ - return -10; - } - var rank=get.rank(current,true); - if(current.maxHp>=3){ - if(current.hp<=1){ - if(att>0) return att*3+2; - return att*3; - } - else if(current.hp==2){ - if(att>0){ - att*=1.5; - } - else{ - att/=1.5; - } - } - } - if(rank>=7){ - if(att>0){ - return att/10; - } - return -att/5; - } - else if(rank<=4){ - if(att<0){ - return -att/10; - } - return att; - } - return Math.abs(att/2); - },'item'); - event.aitarget=target; - var list=[]; - for(var i in lib.character){ - if(!lib.filter.characterDisabled(i)&&!lib.filter.characterDisabled2(i)){ - list.push(i); - } - } - var players=game.players.concat(game.dead); - for(var i=0;i0){ - return get.rank(button.link,true); - } - else{ - return -get.rank(button.link,true); - } - }; - 'step 1' - event.nametarget=result.links[0]; - player.chooseTarget(true,'使用'+get.translation(event.nametarget)+'替换一名角色的武将牌',function(card,player,target){ - return !target.isUnseen()&&!target.isMin(); - }).ai=function(target){ - if(target==event.aitarget){ - return 1; - } - else{ - return 0; - } - } - 'step 2' - var target=result.targets[0]; - var hp=target.hp; - target.reinit(target.name,event.nametarget); - target.hp=Math.min(hp+1,target.maxHp); - target.update(); - player.line(target,'green'); - 'step 3' - game.triggerEnter(target); - }, - contentAfter:function(){ - var evt=_status.event.getParent('phaseUse'); - if(evt&&evt.name=='phaseUse'){ - evt.skipped=true; - } - }, - ai:{ - value:8, - useful:[6,1], - result:{ - player:1 - }, - order:0.5, - } - }, - gw_niuquzhijing:{ - fullborder:'gold', - type:'spell', - subtype:'spell_gold', - vanish:true, - enable:function(card,player){ - return game.hasPlayer(function(current){ - return current.hp!=player.hp; - }); - }, - notarget:true, - contentBefore:function(){ - var list1=game.filterPlayer(function(current){ - return current.isMaxHp(); - }); - var list2=game.filterPlayer(function(current){ - return current.isMinHp(); - }); - player.line(list1); - for(var i=0;ilist11.length){ - list11.push(list1.randomGet()); - } - while(list22.length0; - }, - content:function(){ - 'step 0' - var cards=target.getCards('h'); - target.lose(cards,ui.special); - target.storage.gw_youer=cards; - target.addSkill('gw_youer'); - 'step 1' - player.draw(); - }, - ai:{ - basic:{ - order:10, - value:7, - useful:[3,1], - }, - result:{ - target:function(player,target){ - if(target.hasSkillTag('noh')) return 3; - var num=-Math.sqrt(target.countCards('h')); - if(player.hasSha()&&player.canUse('sha',target)){ - num-=2; - } - return num; - }, - }, - } - }, - gw_tongdi:{ - fullborder:'silver', - type:'spell', - subtype:'spell_silver', - vanish:true, - enable:true, - filterTarget:function(card,player,target){ - return target!=player&&target.countCards('h'); - }, - content:function(){ - 'step 0' - player.gainPlayerCard(target,'h',true,'visible').set('ai',function(button){ - return get.value(button.link); - }); - 'step 1' - target.gain(game.createCard('sha'),'gain2'); - }, - ai:{ - basic:{ - order:8, - value:9.5, - useful:[5,1], - }, - result:{ - target:function(player,target){ - if(target.getEquip(4)) return -2; - return -1; - } - }, - } - }, - gw_fuyuan:{ - fullborder:'silver', - type:'spell', - subtype:'spell_silver', - vanish:true, - savable:true, - selectTarget:-1, - content:function(){ - target.recover(); - target.draw(); - }, - ai:{ - basic:{ - order:6, - useful:10, - value:[8,6.5,5,4], - }, - result:{ - target:2 - }, - tag:{ - recover:1, - save:1, - } - } - }, - gw_zhuoshao:{ - fullborder:'silver', - type:'spell', - subtype:'spell_silver', - vanish:true, - enable:true, - filterTarget:function(card,player,target){ - return target.isMaxHp(); - }, - cardnature:'fire', - selectTarget:[1,Infinity], - content:function(){ - target.damage('fire'); - }, - ai:{ - basic:{ - order:8.5, - value:7.5, - useful:[4,1], - }, - result:{ - target:-1 - }, - tag:{ - damage:1, - fireDamage:1, - natureDamage:1, - } - } - }, - gw_butianshu:{ - fullborder:'silver', - type:'spell', - subtype:'spell_silver', - vanish:true, - enable:true, - filterTarget:true, - // contentBefore:function(){ - // player.$skill('卜天术','legend','water'); - // game.delay(2); - // }, - content:function(){ - 'step 0' - var list=[]; - for(var i in lib.card){ - if(lib.card[i].mode&&lib.card[i].mode.contains(lib.config.mode)==false) continue; - if(lib.card[i].vanish) continue; - if(lib.card[i].type=='delay') list.push([cards[0].suit,cards[0].number,i]); - } - var dialog=ui.create.dialog('卜天术',[list,'vcard']); - var bing=target.countCards('h')<=1; - player.chooseButton(dialog,true,function(button){ - if(get.effect(target,{name:button.link[2]},player,player)>0){ - if(button.link[2]=='bingliang'){ - if(bing) return 2; - return 0.7; - } - if(button.link[2]=='lebu'){ - return 1; - } - if(button.link[2]=='guiyoujie'){ - return 0.5; - } - if(button.link[2]=='caomu'){ - return 0.3; - } - return 0.2; - } - return 0; - }).filterButton=function(button){ - return !target.hasJudge(button.link[2]); - }; - 'step 1' - var card=game.createCard(result.links[0][2]); - event.judgecard=card; - target.$draw(card); - game.delay(0.7); - 'step 2' - target.addJudge(event.judgecard); - }, - ai:{ - value:8, - useful:[5,1], - result:{ - player:function(player,target){ - var eff=0; - for(var i in lib.card){ - if(lib.card[i].type=='delay'){ - var current=get.effect(target,{name:i},player,player); - if(current>eff){ - eff=current; - } - } - } - return eff; - } - }, - order:6, - } - }, - gw_shizizhaohuan:{ - fullborder:'silver', - type:'spell', - subtype:'spell_silver', - vanish:true, - enable:true, - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - // contentBefore:function(){ - // player.$skill('十字召唤','legend','water'); - // game.delay(2); - // }, - content:function(){ - var list=[]; - list.push(get.cardPile2('juedou')); - list.push(get.cardPile2('huogong')); - list.push(get.cardPile2('nanman')); - list.push(get.cardPile2('huoshaolianying')); - for(var i=0;i0; - }); - var aozu2=game.hasPlayer(function(current){ - return player.canUse('gw_aozuzhilei',current)&¤t.hp<=2&&get.effect(current,{name:'gw_aozuzhilei'},player,player)>0; - }); - var aozu3=game.hasPlayer(function(current){ - return player.canUse('gw_aozuzhilei',current)&&get.effect(current,{name:'gw_aozuzhilei'},player,player)>0; - }); - var baoxue=game.hasPlayer(function(current){ - return player.canUse('gw_baoxueyaoshui',current)&&get.attitude(player,current)<0&&[2,3].contains(current.countCards('h'))&&!current.hasSkillTag('noh'); - }); - var baoxue2=game.hasPlayer(function(current){ - return player.canUse('gw_baoxueyaoshui',current)&&get.attitude(player,current)<0&&[2].contains(current.countCards('h'))&&!current.hasSkillTag('noh'); - }); - var baoxue3=game.hasPlayer(function(current){ - return player.canUse('gw_baoxueyaoshui',current)&&get.attitude(player,current)<0&¤t.countCards('h')>=2&&!current.hasSkillTag('noh'); - }); - var nongwu=game.hasPlayer(function(current){ - return get.attitude(player,current)<0&&(get.attitude(player,current.getNext())<0||get.attitude(player,current.getPrevious())<0); - }); - var nongwu2=game.hasPlayer(function(current){ - return get.attitude(player,current)<0&&get.attitude(player,current.getNext())<0&&get.attitude(player,current.getPrevious())<0; - }); - var yanzi=game.hasPlayer(function(current){ - return get.attitude(player,current)>0&¤t.isMinHandcard(); - }); - player.chooseButton(dialog,true,function(button){ - var name=button.link[2]; - switch(name){ - case 'gw_ciguhanshuang': - if(nongwu2) return 3; - if(nongwu) return 1; - return 0; - case 'gw_baoxueyaoshui': - if(baoxue2) return 2; - if(baoxue) return 1.5; - if(baoxue3) return 0.5; - return 0; - case 'gw_aozuzhilei': - if(aozu2) return 2.5; - if(aozu) return 1.2; - if(aozu3) return 0.2; - return 0; - case 'gw_yanziyaoshui': - if(yanzi) return 2; - return 0.6; - } - if(game.hasPlayer(function(current){ - return player.canUse(name,current)&&get.effect(current,{name:name},player,player)>0; - })){ - return Math.random(); - } - return 0; - }).filterButton=function(button){ - var name=button.link[2]; - if(!lib.card[name].notarget){ - return game.hasPlayer(function(current){ - return player.canUse(name,current); - }) - } - return true; - }; - 'step 1' - player.chooseUseTarget(true,game.createCard(result.links[0][2],get.suit(card),get.number(card))); - }, - ai:{ - value:7, - useful:[4,1], - result:{ - player:function(player){ - return 1; - } - }, - order:7, - } - }, - - gw_nuhaifengbao:{ - fullborder:'silver', - type:'spell', - subtype:'spell_silver', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('gw_nuhaifengbao'); - }, - content:function(){ - target.addSkill('gw_nuhaifengbao'); - }, - ai:{ - value:[7,1], - useful:[4,1], - result:{ - target:function(player,target){ - return -2/Math.sqrt(1+target.hp); - } - }, - order:1.2, - } - }, - gw_baishuang:{ - fullborder:'silver', - type:'spell', - subtype:'spell_silver', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('gw_ciguhanshuang'); - }, - selectTarget:[1,3], - content:function(){ - target.addSkill('gw_ciguhanshuang'); - }, - ai:{ - value:[7.5,1], - useful:[5,1], - result:{ - target:-1 - }, - order:1.2, - } - }, - gw_baobaoshu:{ - fullborder:'silver', - type:'spell', - subtype:'spell_silver', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('gw_baobaoshu'); - }, - selectTarget:[1,2], - content:function(){ - target.addTempSkill('gw_baobaoshu',{player:'phaseAfter'}); - }, - ai:{ - value:[7.5,1], - useful:[5,1], - result:{ - target:function(player,target){ - return -Math.sqrt(target.countCards('h'))-0.5; - } - }, - order:1.2, - } - }, - gw_guaiwuchaoxue:{ - fullborder:'silver', - type:'spell', - subtype:'spell_silver', - enable:true, - usable:1, - forceUsable:true, - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - content:function(){ - var list=get.gainableSkills(function(info,skill){ - return !info.notemp&&info.ai&&info.ai.maixie_hp&&!player.hasSkill(skill); - }); - list.remove('guixin'); - if(list.length){ - var skill=list.randomGet(); - player.popup(skill); - player.addTempSkill(skill,{player:'phaseBegin'}); - var enemies=player.getEnemies(); - if(enemies.length){ - var source=enemies.randomGet(); - source.line(player); - source.addExpose(0.1); - player.damage(source); - player.recover(); - } - } - }, - ai:{ - value:[8,1], - useful:[3,1], - result:{ - target:function(player,target){ - if(target.hp<=1||target.hujia) return 0; - return 1; - } - }, - order:1, - } - }, - - gw_qinpendayu:{ - fullborder:'bronze', - type:'spell', - subtype:'spell_bronze', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('gw_qinpendayu'); - }, - changeTarget:function(player,targets){ - game.filterPlayer(function(current){ - return get.distance(targets[0],current,'pure')==1; - },targets); - }, - content:function(){ - target.addSkill('gw_qinpendayu'); - }, - ai:{ - value:[5,1], - useful:[3,1], - result:{ - player:function(player,target){ - return game.countPlayer(function(current){ - if(current.hasSkill('gw_qinpendayu')) return 0; - if(current==target||(get.distance(target,current,'pure')==1)){ - var num=-get.sgn(get.attitude(player,current)); - if(current.needsToDiscard()) return num; - if(current.needsToDiscard(1)) return 0.7*num; - if(current.needsToDiscard(2)) return 0.4*num; - return 0.1*num; - } - }); - } - }, - order:1.2, - } - }, - gw_birinongwu:{ - fullborder:'bronze', - type:'spell', - subtype:'spell_bronze', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('gw_birinongwu'); - }, - changeTarget:function(player,targets){ - game.filterPlayer(function(current){ - return get.distance(targets[0],current,'pure')==1; - },targets); - }, - content:function(){ - target.addSkill('gw_birinongwu'); - }, - ai:{ - value:[5,1], - useful:[3,1], - result:{ - player:function(player,target){ - return game.countPlayer(function(current){ - if(current.hasSkill('gw_birinongwu')) return 0; - if(current==target||(get.distance(target,current,'pure')==1)){ - return -get.sgn(get.attitude(player,current)); - } - }); - } - }, - order:1.2, - } - }, - gw_ciguhanshuang:{ - fullborder:'bronze', - type:'spell', - subtype:'spell_bronze', - enable:true, - filterTarget:function(card,player,target){ - return !target.hasSkill('gw_ciguhanshuang'); - }, - changeTarget:function(player,targets){ - game.filterPlayer(function(current){ - return get.distance(targets[0],current,'pure')==1; - },targets); - }, - content:function(){ - target.addSkill('gw_ciguhanshuang'); - }, - ai:{ - value:[5,1], - useful:[3,1], - result:{ - player:function(player,target){ - return game.countPlayer(function(current){ - if(current.hasSkill('gw_ciguhanshuang')) return 0; - if(current==target||(get.distance(target,current,'pure')==1)){ - return -get.sgn(get.attitude(player,current)); - } - }); - } - }, - order:1.2, - } - }, - gw_baoxueyaoshui:{ - fullborder:'bronze', - type:'spell', - subtype:'spell_bronze', - enable:true, - filterTarget:true, - content:function(){ - 'step 0' - target.chooseToDiscard('h',2,true).delay=false; - 'step 1' - target.draw(); - }, - ai:{ - value:6, - useful:[3,1], - result:{ - target:function(player,target){ - if(target.hasSkillTag('noh')) return 0.1; - switch(target.countCards('h')){ - case 0:return 0.5; - case 1:return 0; - case 2:return -1.5; - default:return -1; - } - } - }, - order:8, - tag:{ - loseCard:1, - discard:1, - } - } - }, - gw_zhihuanjun:{ - fullborder:'bronze', - type:'spell', - subtype:'spell_bronze', - enable:true, - filterTarget:function(card,player,target){ - return target.isDamaged(); - }, - content:function(){ - 'step 0' - target.loseMaxHp(true); - 'step 1' - if(target.isDamaged()&&target.countCards('h')=player.hp; - }, - content:function(){ - 'step 0' - target.damage('thunder'); - 'step 1' - if(target.isIn()){ - target.draw(); - } - }, - ai:{ - basic:{ - order:1.8, - value:[5,1], - useful:[4,1], - }, - result:{ - target:-1 - }, - tag:{ - damage:1, - thunderDamage:1, - natureDamage:1, - } - } - }, - gw_poxiao:{ - fullborder:'bronze', - type:'spell', - subtype:'spell_bronze', - enable:true, - notarget:true, - content:function(){ - 'step 0' - var choice=1; - if(game.countPlayer(function(current){ - if(current.countCards('j')||current.hasSkillTag('weather')){ - if(get.attitude(player,current)>0){ - choice=0; - } - return true; - } - })){ - player.chooseControl(function(){ - return choice; - }).set('choiceList',[ - '解除任意名角色的天气效果并移除其判定区内的牌', - '随机获得一张铜卡法术(破晓除外)并展示之' - ]); - } - else{ - event.directfalse=true; - } - 'step 1' - if(!event.directfalse&&result.index==0){ - player.chooseTarget(true,[1,Infinity],'解除任意名角色的天气效果并移除其判定区内的牌',function(card,player,target){ - return target.countCards('j')||target.hasSkillTag('weather'); - }).ai=function(target){ - return get.attitude(player,target); - }; - } - else{ - var list=get.libCard(function(info,name){ - return name!='gw_poxiao'&&info.subtype=='spell_bronze'; - }); - if(list.length){ - player.gain(game.createCard(list.randomGet()),'gain2'); - } - else{ - player.draw(); - } - event.finish(); - } - 'step 2' - event.list=result.targets.slice(0).sortBySeat(); - 'step 3' - if(event.list.length){ - var target=event.list.shift(); - player.line(target,'green'); - var cards=target.getCards('j'); - if(cards.length){ - target.discard(cards); - } - if(target.hasSkillTag('weather')){ - var skills=target.getSkills(); - for(var i=0;i0){ - player.updateMarks(); - } - else{ - player.removeSkill('gw_kunenfayin'); - } - }, - } - }, - group:'gw_kunenfayin_count', - onremove:true - }, - gw_baobaoshu:{ - mark:true, - nopop:true, - intro:{ - content:'每使用一张基本牌或锦囊牌,需弃置一张牌' - }, - trigger:{player:'useCard'}, - forced:true, - filter:function(event,player){ - if(player.countCards('he')==0) return false; - var type=get.type(event.card,'trick'); - return type=='basic'||type=='trick'; - }, - content:function(){ - if(!event.isMine()) game.delay(0.5); - player.chooseToDiscard(true,'he'); - }, - ai:{ - weather:true, - effect:{ - player:function(card,player){ - if(!player.needsToDiscard()) return 'zeroplayertarget'; - } - } - } - }, - gw_nuhaifengbao:{ - mark:true, - intro:{ - content:'结束阶段随机弃置一张牌(剩余#回合)' - }, - init:function(player){ - player.storage.gw_nuhaifengbao=2; - }, - trigger:{player:'phaseEnd'}, - forced:true, - nopop:true, - content:function(){ - player.randomDiscard(); - player.storage.gw_nuhaifengbao--; - if(player.storage.gw_nuhaifengbao>0){ - player.updateMarks(); - } - else{ - player.removeSkill('gw_nuhaifengbao'); - } - }, - onremove:true, - ai:{ - neg:true, - weather:true - } - }, - gw_youer:{ - trigger:{global:'phaseEnd',player:'dieBegin'}, - forced:true, - audio:false, - mark:true, - intro:{ - content:'cards' - }, - content:function(){ - if(player.storage.gw_youer){ - if(trigger.name=='phase'){ - player.gain(player.storage.gw_youer); - } - else{ - player.$throw(player.storage.gw_youer,1000); - for(var i=0;i0; - }, - content:function(){ - trigger.num--; - player.removeSkill('gw_ciguhanshuang'); - }, - ai:{ - weather:true - } - }, - gw_dieyi:{ - init:function(player){ - player.storage.gw_dieyi=1; - }, - onremove:true, - trigger:{global:'phaseEnd'}, - forced:true, - mark:true, - nopop:true, - process:function(player){ - if(player.hasSkill('gw_dieyi')){ - player.storage.gw_dieyi++; - } - else{ - player.addSkill('gw_dieyi'); - } - player.syncStorage('gw_dieyi'); - player.updateMarks(); - }, - intro:{ - content:'在当前回合的结束阶段,你随机弃置#张牌' - }, - content:function(){ - player.randomDiscard(player.storage.gw_dieyi); - player.removeSkill('gw_dieyi'); - } - }, - gw_leizhoushu:{ - mark:true, - intro:{ - content:function(storage,player){ - if(storage>=2){ - return '锁定技,准备阶段,你令手牌数为全场最多的所有其他角色各随机弃置一张手牌,若目标不包含敌方角色,将一名随机敌方角色追加为额外目标(重复'+storage+'次)'; - } - else{ - return '锁定技,准备阶段,你令手牌数为全场最多的所有其他角色各随机弃置一张手牌,若目标不包含敌方角色,将一名随机敌方角色追加为额外目标'; - } - } - }, - nopop:true, - trigger:{player:'phaseBegin'}, - forced:true, - filter:function(event,player){ - var list=game.filterPlayer(); - for(var i=0;i0&&event.parent.name=='phaseDraw'; - }, - content:function(){ - if(!player.storage.spell_gain||Math.max.apply(null,player.storage.spell_gain)<0){ - var tmp=player.storage.spell_gain2; - player.storage.spell_gain=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].randomGets(3); - player.storage.spell_gain2=Math.floor((15-Math.max.apply(null,player.storage.spell_gain))/2); - if(tmp){ - for(var i=0;i<3;i++){ - player.storage.spell_gain[i]+=tmp; - } - } - } - for(var i=0;i<3;i++){ - if(player.storage.spell_gain[i]==0){ - var list; - if(i==0){ - list=get.libCard(function(info){ - return info.subtype=='spell_gold'; - }); - if(get.mode()=='stone'){ - list.remove('gw_aerdeyin'); - list.remove('gw_niuquzhijing'); - } - } - else{ - list=get.libCard(function(info){ - return info.subtype=='spell_silver'; - }); - if(get.mode()=='stone'){ - list.remove('gw_butianshu'); - } - } - if(list&&list.length){ - ui.cardPile.insertBefore(game.createCard(list.randomGet()),ui.cardPile.firstChild); - } - } - player.storage.spell_gain[i]--; - } - } - } - }, - help:{ - '昆特牌':'
  • 法术为分金、银、铜三类,金卡和银卡不出现在牌堆中
  • '+ - '摸牌阶段有一定概率摸到银卡,在16个摸牌阶段中至少会摸到2张银卡
  • '+ - '摸牌阶段有一定概率摸到金卡,在16个摸牌阶段中至少会摸到1张金卡
  • '+ - '金卡无视调虎离山、潜行等免疫目标的效果
  • '+ - '进行洗牌时金卡、银卡将从弃牌堆中消失,不进入牌堆' - }, - translate:{ - spell:'法术', - spell_gold:'金卡法术', - spell_silver:'银卡法术', - spell_bronze:'铜卡法术', - - gw_youlanzhimeng:'幽蓝之梦', - gw_guaiwuchaoxue:'怪物巢穴', - gw_guaiwuchaoxue_info:'出牌阶段限用一次,随机获得一个卖血技能直到下一回合开始;令一名随机敌方角色对你造成一点伤害,然后你回复一点体力', - gw_baobaoshu:'雹暴术', - gw_baobaoshu_info:'天气牌,出牌阶段对至多两名角色使用,目标每使用一张基本牌或锦囊牌,需弃置一张牌,直到下一回合结束', - gw_baishuang:'白霜', - gw_baishuang_info:'天气牌,出牌阶段对至多三名角色使用,目标下个摸牌阶段摸牌数-1', - gw_nuhaifengbao:'怒海风暴', - gw_nuhaifengbao_bg:'海', - gw_nuhaifengbao_info:'天气牌,出牌阶段对一名角色使用,目标在结束阶段随机弃置一张牌,持续2回合', - gw_ganhan:'干旱', - gw_ganhan_info:'所有角色减少一点体力上限(不触发技能),然后结束出牌阶段', - gw_huangjiashenpan:'皇家审判', - gw_huangjiashenpan_info:'获得任意一张金卡法术(皇家审判除外),然后结束出牌阶段', - gw_chongci:'冲刺', - gw_chongci_info:'弃置所有牌并随机获得一张非金法术牌,每弃置一张手牌,便随机获得一张类别相同的牌;每弃置一张装备区内的牌,随机装备一件类别相同的装备;获得潜行直到下一回合开始,然后结束出牌阶段', - gw_tunshi:'吞噬', - gw_tunshi_info:'随机移除一名敌方角色的一个随机技能,你获得此技能并减少一点体力和体力上限,被移除技能的角色增加一点体力和体力上限,然后结束出牌阶段', - gw_dieyi:'蝶翼', - gw_dieyi_equip1:'蝶翼·器', - gw_dieyi_equip2:'蝶翼·衣', - gw_dieyi_equip3:'蝶翼·攻', - gw_dieyi_equip4:'蝶翼·防', - gw_dieyi_equip5:'蝶翼·宝', - gw_dieyi_judge:'蝶翼·判', - gw_dieyi_equip1_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', - gw_dieyi_equip2_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', - gw_dieyi_equip3_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', - gw_dieyi_equip4_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', - gw_dieyi_equip5_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', - gw_dieyi_judge_info:'你在判定阶段移去此牌,并获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', - gw_hudiewu:'蝴蝶舞', - gw_hudiewu_info:'将其他角色在场上的所有牌替换为蝶翼(每当你失去一张蝶翼,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌),然后结束出牌阶段', - gw_yigeniyin:'伊格尼印', - gw_yigeniyin_info:'对敌方角色中体力值最高的一名随机角色造成一点火焰伤害,然后对场上体力值最高的所有角色各造成一点火焰伤害,然后结束出牌阶段', - gw_leizhoushu:'雷咒术', - gw_leizhoushu_info:'获得技能雷咒术(在每个准备阶段令全场牌数最多的所有其他角色各随机弃置一张牌,若目标不包含敌方角色,将一名随机敌方角色追加为额外目标,结算X次,X为本局获得此技能的次数),然后结束出牌阶段', - gw_aerdeyin:'阿尔德印', - gw_aerdeyin_bg:'印', - gw_aerdeyin_info:'对一名随机敌方角色造成一点伤害,若目标武将牌正面朝上,则将其翻面;新的一轮开始时,若目标武将牌正面朝上,则在当前回合结束后进行一个额外回合,否则将武将牌翻回正面', - gw_xinsheng:'新生', - gw_xinsheng_info:'选择一名角色,随机观看12张武将牌,选择一张替代其武将牌,并令其增加一点体力,然后结束出牌阶段', - gw_zhongmozhizhan:'终末之战', - gw_zhongmozhizhan_info:'将所有角色区域内的所有牌置入弃牌堆(不触发技能),然后结束出牌阶段', - gw_butianshu:'卜天术', - gw_butianshu_info:'出牌阶段对任意角色使用,将任意一张延时锦囊牌置入其判定区', - gw_zhihuanjun:'致幻菌', - gw_zhihuanjun_info:'出牌阶段对一名已受伤角色使用,令其减少一点体力上限;若该角色仍处于受伤状态且手牌数小于体力上限,则重复此结算', - gw_niuquzhijing:'纽曲之镜', - gw_niuquzhijing_info:'令全场体力最多的角色减少一点体力和体力上限,体力最少的角色增加一点体力和体力上限(不触发技能),然后结束出牌阶段', - gw_ansha:'暗杀', - gw_ansha_info:'令一名体力为1的随机敌方角立即死亡,然后结束出牌阶段', - gw_shizizhaohuan:'十字召唤', - gw_shizizhaohuan_info:'从牌堆中获得一张杀以及决斗、火攻、火烧连营、南蛮入侵四张牌中的随机一张', - gw_zuihouyuanwang:'最后愿望', - gw_zuihouyuanwang_info:'摸X张牌并弃置X张牌,X为存活角色数', - gw_zirankuizeng:'自然馈赠', - gw_zirankuizeng_info:'选择任意一张铜卡法术使用', - gw_poxiao:'破晓', - gw_poxiao_info:'选择一项:解除任意名角色的天气效果并移除其判定区内的牌,或随机获得一张铜卡法术(破晓除外)并展示之', - gw_zumoshoukao:'阻魔手铐', - gw_zumoshoukao_info:'令一名角色非锁定技失效直到下一回合结束', - gw_aozuzhilei:'奥祖之雷', - gw_aozuzhilei_info:'对一名体力值不小于你的角色造成一点雷属性伤害,然后该角色摸一张牌', - gw_zhuoshao:'灼烧', - gw_zhuoshao_info:'对任意名体力值为全场最高的角色使用,造成一点火属性伤害', - gw_fuyuan:'复原', - gw_fuyuan_info:'对一名濒死状态角色使用,目标回复一点体力并摸一张牌', - gw_youer:'诱饵', - gw_youer_bg:'饵', - gw_youer_info:'将一名其他角色的所有手牌移出游戏,然后摸一张牌,当前回合结束后该角色将以此法失去的牌收回手牌', - gw_tongdi:'通敌', - gw_tongdi_info:'观看一名其他角色的手牌并获得其中一张,然后令目标获得一张杀', - gw_baoxueyaoshui:'暴雪药水', - gw_baoxueyaoshui_info:'令一名角色弃置两张手牌并摸一张牌', - gw_birinongwu:'蔽日浓雾', - gw_birinongwu_bg:'雾', - gw_birinongwu_info:'天气牌,出牌阶段对一名角色及其相邻角色使用,目标不能使用杀直到下一个出牌阶段结束', - gw_qinpendayu:'倾盆大雨', - gw_qinpendayu_bg:'雨', - gw_qinpendayu_info:'天气牌,出牌阶段对一名角色及其相邻角色使用,目标手牌上限-1直到下一个弃牌阶段结束', - gw_ciguhanshuang:'刺骨寒霜', - gw_ciguhanshuang_bg:'霜', - gw_ciguhanshuang_info:'天气牌,出牌阶段对一名角色及其相邻角色使用,目标下个摸牌阶段摸牌数-1', - gw_wenyi:'瘟疫', - gw_wenyi_info:'令所有体力值为全场最少的角色随机弃置一张手牌;若没有手牌,改为失去一点体力', - gw_yanziyaoshui:'燕子药水', - gw_yanziyaoshui_info:'令一名角色摸一张牌,若其手牌数为全场最少或之一,改为摸两张', - gw_shanbengshu:'山崩术', - gw_shanbengshu_info:'出牌阶段对自己使用,随机弃置两件敌方角色场上的装备', - gw_kunenfayin:'昆恩法印', - gw_kunenfayin_info:'出牌阶段对一名角色使用,目标防止所有非属性伤害,持续X个角色的回合(X为存活角色数且最多为5)', - }, - cardType:{ - spell:0.5, - spell_bronze:0.2, - spell_silver:0.3, - spell_gold:0.4 - }, - list:[ - ['club',3,'gw_zhihuanjun'], - ['spade',2,'gw_zhihuanjun'], - - ['heart',7,'gw_poxiao'], - ['diamond',4,'gw_poxiao'], - - ['spade',9,'gw_aozuzhilei','thunder'], - ['club',7,'gw_aozuzhilei','thunder'], - - ['club',1,'gw_zumoshoukao'], - ['spade',1,'gw_zumoshoukao'], - - ['diamond',5,'gw_qinpendayu'], - ['club',7,'gw_qinpendayu'], - - ['spade',9,'gw_birinongwu'], - ['heart',13,'gw_birinongwu'], - - ['diamond',11,'gw_ciguhanshuang'], - ['club',7,'gw_ciguhanshuang'], - - ['heart',4,'gw_baoxueyaoshui'], - ['spade',8,'gw_baoxueyaoshui'], - - ['spade',8,'gw_shanbengshu'], - ['spade',2,'gw_kunenfayin'], - ['club',3,'gw_wenyi'], - ['heart',8,'gw_yanziyaoshui'], - ], - }; -}); +'use strict'; +game.import('card',function(lib,game,ui,get,ai,_status){ + return { + name:'gwent', + card:{ + gw_dieyi:{ + fullskin:true + }, + gw_dieyi_equip1:{ + fullskin:true, + vanish:true, + hidden:true, + cardimage:'gw_dieyi', + type:'equip', + subtype:'equip1', + onLose:function(){ + lib.skill.gw_dieyi.process(player); + }, + loseDelay:false, + skills:[], + ai:{ + equipValue:0 + } + }, + gw_dieyi_equip2:{ + fullskin:true, + vanish:true, + hidden:true, + cardimage:'gw_dieyi', + type:'equip', + subtype:'equip2', + onLose:function(){ + lib.skill.gw_dieyi.process(player); + }, + loseDelay:false, + skills:[], + ai:{ + equipValue:0 + } + }, + gw_dieyi_equip3:{ + fullskin:true, + vanish:true, + hidden:true, + cardimage:'gw_dieyi', + type:'equip', + subtype:'equip3', + onLose:function(){ + lib.skill.gw_dieyi.process(player); + }, + loseDelay:false, + skills:[], + ai:{ + equipValue:0 + } + }, + gw_dieyi_equip4:{ + fullskin:true, + vanish:true, + hidden:true, + cardimage:'gw_dieyi', + type:'equip', + subtype:'equip4', + onLose:function(){ + lib.skill.gw_dieyi.process(player); + }, + loseDelay:false, + skills:[], + ai:{ + equipValue:0 + } + }, + gw_dieyi_equip5:{ + fullskin:true, + vanish:true, + hidden:true, + cardimage:'gw_dieyi', + type:'equip', + subtype:'equip5', + onLose:function(){ + lib.skill.gw_dieyi.process(player); + }, + loseDelay:false, + skills:[], + ai:{ + equipValue:0 + } + }, + gw_dieyi_judge:{ + fullskin:true, + vanish:true, + hidden:true, + cardimage:'gw_dieyi', + enable:true, + type:'delay', + filterTarget:true, + effect:function(){ + lib.skill.gw_dieyi.process(player); + }, + }, + gw_hudiewu:{ + fullborder:'gold', + type:'spell', + subtype:'spell_gold', + vanish:true, + enable:function(card,player){ + return game.hasPlayer(function(current){ + return current!=player&¤t.countCards('ej'); + }); + }, + notarget:true, + contentBefore:function(){ + player.$skill('蝴蝶舞','legend','metal'); + game.delay(2); + }, + content:function(){ + 'step 0' + event.targets=game.filterPlayer(function(current){ + return current.countCards('ej'); + }).sortBySeat(); + event.targets.remove(player); + 'step 1' + if(event.targets.length){ + var target=event.targets.shift(); + var ej=target.getCards('ej'); + player.line(target); + target.removeEquipTrigger(); + for(var i=0;imax2){ + return get.damageEffect(get.max(enemies,func,'item'),player,player,'fire'); + } + else{ + var num; + if(max1>max2){ + num=get.sgn(get.damageEffect(get.max(enemies,func,'item'),player,player,'fire')); + } + else if(max1==max2){ + num=0; + } + else{ + num=1; + } + return num+game.countPlayer(function(current){ + if(current.hp>=max2){ + return get.sgn(get.damageEffect(current,player,player,'fire')); + } + }); + } + } + }, + order:0.7, + } + }, + gw_leizhoushu:{ + fullborder:'gold', + type:'spell', + subtype:'spell_gold', + vanish:true, + enable:true, + notarget:true, + contentBefore:function(){ + player.$skill('雷咒术','legend','metal'); + game.delay(2); + }, + content:function(){ + if(player.hasSkill('gw_leizhoushu')){ + if(typeof player.storage.gw_leizhoushu!='number'){ + player.storage.gw_leizhoushu=2; + } + else{ + player.storage.gw_leizhoushu++; + } + player.syncStorage('gw_leizhoushu'); + player.updateMarks(); + } + else{ + player.addSkill('gw_leizhoushu'); + } + }, + contentAfter:function(){ + var evt=_status.event.getParent('phaseUse'); + if(evt&&evt.name=='phaseUse'){ + evt.skipped=true; + } + }, + ai:{ + value:8, + useful:[6,1], + result:{ + player:function(player){ + return 1+game.countPlayer(function(current){ + if(current!=player&¤t.isMaxHandcard()){ + return -get.sgn(get.attitude(player,current)); + } + }); + } + }, + order:0.5, + } + }, + gw_aerdeyin:{ + fullborder:'gold', + type:'spell', + subtype:'spell_gold', + vanish:true, + enable:function(card,player){ + var enemies=player.getEnemies(); + return enemies.length>0; + }, + notarget:true, + contentBefore:function(){ + player.$skill('阿尔德印','legend','metal'); + game.delay(2); + }, + content:function(){ + 'step 0' + var enemies=player.getEnemies(); + event.list=[enemies.randomGet()]; + 'step 1' + if(event.list.length){ + var target=event.list.shift(); + event.target=target; + player.line(target,'green'); + target.damage(); + } + else{ + delete event.target; + } + 'step 2' + if(event.target){ + if(!event.target.isTurnedOver()){ + event.target.turnOver(); + event.target.addSkill('gw_aerdeyin'); + } + event.goto(1); + } + 'step 3' + game.delay(); + }, + contentAfter:function(){ + var evt=_status.event.getParent('phaseUse'); + if(evt&&evt.name=='phaseUse'){ + evt.skipped=true; + } + }, + ai:{ + value:8, + useful:[6,1], + result:{ + player:function(player){ + return game.countPlayer(function(current){ + if(get.distance(player,current,'pure')==1){ + var att=get.sgn(get.attitude(player,current)); + if(current==player.next){ + return -att*1.5; + } + return -att; + } + }); + } + }, + order:0.5, + } + }, + gw_ansha:{ + fullborder:'gold', + type:'spell', + subtype:'spell_gold', + vanish:true, + enable:function(card,player){ + var enemies=player.getEnemies(); + return game.hasPlayer(function(current){ + return current.hp==1&&enemies.contains(current); + }); + }, + notarget:true, + contentBefore:function(){ + player.$skill('暗杀','legend','metal'); + game.delay(2); + }, + content:function(){ + var enemies=player.getEnemies(); + var list=game.filterPlayer(function(current){ + return current.hp==1&&enemies.contains(current); + }); + if(list.length){ + var target=list.randomGet(); + player.line(target); + target.die(); + } + }, + contentAfter:function(){ + var evt=_status.event.getParent('phaseUse'); + if(evt&&evt.name=='phaseUse'){ + evt.skipped=true; + } + }, + ai:{ + value:8, + useful:[6,1], + result:{ + player:1 + }, + order:0.6, + } + }, + gw_xinsheng:{ + fullborder:'gold', + type:'spell', + subtype:'spell_gold', + vanish:true, + enable:function(card,player){ + return game.hasPlayer(function(current){ + return !current.isUnseen(); + }); + }, + notarget:true, + contentBefore:function(){ + player.$skill('新生','legend','metal'); + game.delay(2); + }, + content:function(){ + 'step 0' + var target=get.max(game.filterPlayer(function(current){ + return !current.isUnseen(); + },'list').randomSort(),function(current){ + var att=get.attitude(player,current); + if(att<0&¤t.isDamaged()&¤t.hp<=3){ + return -10; + } + var rank=get.rank(current,true); + if(current.maxHp>=3){ + if(current.hp<=1){ + if(att>0) return att*3+2; + return att*3; + } + else if(current.hp==2){ + if(att>0){ + att*=1.5; + } + else{ + att/=1.5; + } + } + } + if(rank>=7){ + if(att>0){ + return att/10; + } + return -att/5; + } + else if(rank<=4){ + if(att<0){ + return -att/10; + } + return att; + } + return Math.abs(att/2); + },'item'); + event.aitarget=target; + var list=[]; + for(var i in lib.character){ + if(!lib.filter.characterDisabled(i)&&!lib.filter.characterDisabled2(i)){ + list.push(i); + } + } + var players=game.players.concat(game.dead); + for(var i=0;i0){ + return get.rank(button.link,true); + } + else{ + return -get.rank(button.link,true); + } + }; + 'step 1' + event.nametarget=result.links[0]; + player.chooseTarget(true,'使用'+get.translation(event.nametarget)+'替换一名角色的武将牌',function(card,player,target){ + return !target.isUnseen()&&!target.isMin(); + }).ai=function(target){ + if(target==event.aitarget){ + return 1; + } + else{ + return 0; + } + } + 'step 2' + var target=result.targets[0]; + var hp=target.hp; + target.reinit(target.name,event.nametarget); + target.hp=Math.min(hp+1,target.maxHp); + target.update(); + player.line(target,'green'); + 'step 3' + game.triggerEnter(target); + }, + contentAfter:function(){ + var evt=_status.event.getParent('phaseUse'); + if(evt&&evt.name=='phaseUse'){ + evt.skipped=true; + } + }, + ai:{ + value:8, + useful:[6,1], + result:{ + player:1 + }, + order:0.5, + } + }, + gw_niuquzhijing:{ + fullborder:'gold', + type:'spell', + subtype:'spell_gold', + vanish:true, + enable:function(card,player){ + return game.hasPlayer(function(current){ + return current.hp!=player.hp; + }); + }, + notarget:true, + contentBefore:function(){ + var list1=game.filterPlayer(function(current){ + return current.isMaxHp(); + }); + var list2=game.filterPlayer(function(current){ + return current.isMinHp(); + }); + player.line(list1); + for(var i=0;ilist11.length){ + list11.push(list1.randomGet()); + } + while(list22.length0; + }, + content:function(){ + 'step 0' + var cards=target.getCards('h'); + target.lose(cards,ui.special); + target.storage.gw_youer=cards; + target.addSkill('gw_youer'); + 'step 1' + player.draw(); + }, + ai:{ + basic:{ + order:10, + value:7, + useful:[3,1], + }, + result:{ + target:function(player,target){ + if(target.hasSkillTag('noh')) return 3; + var num=-Math.sqrt(target.countCards('h')); + if(player.hasSha()&&player.canUse('sha',target)){ + num-=2; + } + return num; + }, + }, + } + }, + gw_tongdi:{ + fullborder:'silver', + type:'spell', + subtype:'spell_silver', + vanish:true, + enable:true, + filterTarget:function(card,player,target){ + return target!=player&&target.countCards('h'); + }, + content:function(){ + 'step 0' + player.gainPlayerCard(target,'h',true,'visible').set('ai',function(button){ + return get.value(button.link); + }); + 'step 1' + target.gain(game.createCard('sha'),'gain2'); + }, + ai:{ + basic:{ + order:8, + value:9.5, + useful:[5,1], + }, + result:{ + target:function(player,target){ + if(target.getEquip(4)) return -2; + return -1; + } + }, + } + }, + gw_fuyuan:{ + fullborder:'silver', + type:'spell', + subtype:'spell_silver', + vanish:true, + savable:true, + selectTarget:-1, + content:function(){ + target.recover(); + target.draw(); + }, + ai:{ + basic:{ + order:6, + useful:10, + value:[8,6.5,5,4], + }, + result:{ + target:2 + }, + tag:{ + recover:1, + save:1, + } + } + }, + gw_zhuoshao:{ + fullborder:'silver', + type:'spell', + subtype:'spell_silver', + vanish:true, + enable:true, + filterTarget:function(card,player,target){ + return target.isMaxHp(); + }, + cardnature:'fire', + selectTarget:[1,Infinity], + content:function(){ + target.damage('fire'); + }, + ai:{ + basic:{ + order:8.5, + value:7.5, + useful:[4,1], + }, + result:{ + target:-1 + }, + tag:{ + damage:1, + fireDamage:1, + natureDamage:1, + } + } + }, + gw_butianshu:{ + fullborder:'silver', + type:'spell', + subtype:'spell_silver', + vanish:true, + enable:true, + filterTarget:true, + // contentBefore:function(){ + // player.$skill('卜天术','legend','water'); + // game.delay(2); + // }, + content:function(){ + 'step 0' + var list=[]; + for(var i in lib.card){ + if(lib.card[i].mode&&lib.card[i].mode.contains(lib.config.mode)==false) continue; + if(lib.card[i].vanish) continue; + if(lib.card[i].type=='delay') list.push([cards[0].suit,cards[0].number,i]); + } + var dialog=ui.create.dialog('卜天术',[list,'vcard']); + var bing=target.countCards('h')<=1; + player.chooseButton(dialog,true,function(button){ + if(get.effect(target,{name:button.link[2]},player,player)>0){ + if(button.link[2]=='bingliang'){ + if(bing) return 2; + return 0.7; + } + if(button.link[2]=='lebu'){ + return 1; + } + if(button.link[2]=='guiyoujie'){ + return 0.5; + } + if(button.link[2]=='caomu'){ + return 0.3; + } + return 0.2; + } + return 0; + }).filterButton=function(button){ + return !target.hasJudge(button.link[2]); + }; + 'step 1' + var card=game.createCard(result.links[0][2]); + event.judgecard=card; + target.$draw(card); + game.delay(0.7); + 'step 2' + target.addJudge(event.judgecard); + }, + ai:{ + value:8, + useful:[5,1], + result:{ + player:function(player,target){ + var eff=0; + for(var i in lib.card){ + if(lib.card[i].type=='delay'){ + var current=get.effect(target,{name:i},player,player); + if(current>eff){ + eff=current; + } + } + } + return eff; + } + }, + order:6, + } + }, + gw_shizizhaohuan:{ + fullborder:'silver', + type:'spell', + subtype:'spell_silver', + vanish:true, + enable:true, + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + // contentBefore:function(){ + // player.$skill('十字召唤','legend','water'); + // game.delay(2); + // }, + content:function(){ + var list=[]; + list.push(get.cardPile2('juedou')); + list.push(get.cardPile2('huogong')); + list.push(get.cardPile2('nanman')); + list.push(get.cardPile2('huoshaolianying')); + for(var i=0;i0; + }); + var aozu2=game.hasPlayer(function(current){ + return player.canUse('gw_aozuzhilei',current)&¤t.hp<=2&&get.effect(current,{name:'gw_aozuzhilei'},player,player)>0; + }); + var aozu3=game.hasPlayer(function(current){ + return player.canUse('gw_aozuzhilei',current)&&get.effect(current,{name:'gw_aozuzhilei'},player,player)>0; + }); + var baoxue=game.hasPlayer(function(current){ + return player.canUse('gw_baoxueyaoshui',current)&&get.attitude(player,current)<0&&[2,3].contains(current.countCards('h'))&&!current.hasSkillTag('noh'); + }); + var baoxue2=game.hasPlayer(function(current){ + return player.canUse('gw_baoxueyaoshui',current)&&get.attitude(player,current)<0&&[2].contains(current.countCards('h'))&&!current.hasSkillTag('noh'); + }); + var baoxue3=game.hasPlayer(function(current){ + return player.canUse('gw_baoxueyaoshui',current)&&get.attitude(player,current)<0&¤t.countCards('h')>=2&&!current.hasSkillTag('noh'); + }); + var nongwu=game.hasPlayer(function(current){ + return get.attitude(player,current)<0&&(get.attitude(player,current.getNext())<0||get.attitude(player,current.getPrevious())<0); + }); + var nongwu2=game.hasPlayer(function(current){ + return get.attitude(player,current)<0&&get.attitude(player,current.getNext())<0&&get.attitude(player,current.getPrevious())<0; + }); + var yanzi=game.hasPlayer(function(current){ + return get.attitude(player,current)>0&¤t.isMinHandcard(); + }); + player.chooseButton(dialog,true,function(button){ + var name=button.link[2]; + switch(name){ + case 'gw_ciguhanshuang': + if(nongwu2) return 3; + if(nongwu) return 1; + return 0; + case 'gw_baoxueyaoshui': + if(baoxue2) return 2; + if(baoxue) return 1.5; + if(baoxue3) return 0.5; + return 0; + case 'gw_aozuzhilei': + if(aozu2) return 2.5; + if(aozu) return 1.2; + if(aozu3) return 0.2; + return 0; + case 'gw_yanziyaoshui': + if(yanzi) return 2; + return 0.6; + } + if(game.hasPlayer(function(current){ + return player.canUse(name,current)&&get.effect(current,{name:name},player,player)>0; + })){ + return Math.random(); + } + return 0; + }).filterButton=function(button){ + var name=button.link[2]; + if(!lib.card[name].notarget){ + return game.hasPlayer(function(current){ + return player.canUse(name,current); + }) + } + return true; + }; + 'step 1' + player.chooseUseTarget(true,game.createCard(result.links[0][2],get.suit(card),get.number(card))); + }, + ai:{ + value:7, + useful:[4,1], + result:{ + player:function(player){ + return 1; + } + }, + order:7, + } + }, + + gw_nuhaifengbao:{ + fullborder:'silver', + type:'spell', + subtype:'spell_silver', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('gw_nuhaifengbao'); + }, + content:function(){ + target.addSkill('gw_nuhaifengbao'); + }, + ai:{ + value:[7,1], + useful:[4,1], + result:{ + target:function(player,target){ + return -2/Math.sqrt(1+target.hp); + } + }, + order:1.2, + } + }, + gw_baishuang:{ + fullborder:'silver', + type:'spell', + subtype:'spell_silver', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('gw_ciguhanshuang'); + }, + selectTarget:[1,3], + content:function(){ + target.addSkill('gw_ciguhanshuang'); + }, + ai:{ + value:[7.5,1], + useful:[5,1], + result:{ + target:-1 + }, + order:1.2, + } + }, + gw_baobaoshu:{ + fullborder:'silver', + type:'spell', + subtype:'spell_silver', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('gw_baobaoshu'); + }, + selectTarget:[1,2], + content:function(){ + target.addTempSkill('gw_baobaoshu',{player:'phaseAfter'}); + }, + ai:{ + value:[7.5,1], + useful:[5,1], + result:{ + target:function(player,target){ + return -Math.sqrt(target.countCards('h'))-0.5; + } + }, + order:1.2, + } + }, + gw_guaiwuchaoxue:{ + fullborder:'silver', + type:'spell', + subtype:'spell_silver', + enable:true, + usable:1, + forceUsable:true, + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + content:function(){ + var list=get.gainableSkills(function(info,skill){ + return !info.notemp&&info.ai&&info.ai.maixie_hp&&!player.hasSkill(skill); + }); + list.remove('guixin'); + if(list.length){ + var skill=list.randomGet(); + player.popup(skill); + player.addTempSkill(skill,{player:'phaseBegin'}); + var enemies=player.getEnemies(); + if(enemies.length){ + var source=enemies.randomGet(); + source.line(player); + source.addExpose(0.1); + player.damage(source); + player.recover(); + } + } + }, + ai:{ + value:[8,1], + useful:[3,1], + result:{ + target:function(player,target){ + if(target.hp<=1||target.hujia) return 0; + return 1; + } + }, + order:1, + } + }, + + gw_qinpendayu:{ + fullborder:'bronze', + type:'spell', + subtype:'spell_bronze', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('gw_qinpendayu'); + }, + changeTarget:function(player,targets){ + game.filterPlayer(function(current){ + return get.distance(targets[0],current,'pure')==1; + },targets); + }, + content:function(){ + target.addSkill('gw_qinpendayu'); + }, + ai:{ + value:[5,1], + useful:[3,1], + result:{ + player:function(player,target){ + return game.countPlayer(function(current){ + if(current.hasSkill('gw_qinpendayu')) return 0; + if(current==target||(get.distance(target,current,'pure')==1)){ + var num=-get.sgn(get.attitude(player,current)); + if(current.needsToDiscard()) return num; + if(current.needsToDiscard(1)) return 0.7*num; + if(current.needsToDiscard(2)) return 0.4*num; + return 0.1*num; + } + }); + } + }, + order:1.2, + } + }, + gw_birinongwu:{ + fullborder:'bronze', + type:'spell', + subtype:'spell_bronze', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('gw_birinongwu'); + }, + changeTarget:function(player,targets){ + game.filterPlayer(function(current){ + return get.distance(targets[0],current,'pure')==1; + },targets); + }, + content:function(){ + target.addSkill('gw_birinongwu'); + }, + ai:{ + value:[5,1], + useful:[3,1], + result:{ + player:function(player,target){ + return game.countPlayer(function(current){ + if(current.hasSkill('gw_birinongwu')) return 0; + if(current==target||(get.distance(target,current,'pure')==1)){ + return -get.sgn(get.attitude(player,current)); + } + }); + } + }, + order:1.2, + } + }, + gw_ciguhanshuang:{ + fullborder:'bronze', + type:'spell', + subtype:'spell_bronze', + enable:true, + filterTarget:function(card,player,target){ + return !target.hasSkill('gw_ciguhanshuang'); + }, + changeTarget:function(player,targets){ + game.filterPlayer(function(current){ + return get.distance(targets[0],current,'pure')==1; + },targets); + }, + content:function(){ + target.addSkill('gw_ciguhanshuang'); + }, + ai:{ + value:[5,1], + useful:[3,1], + result:{ + player:function(player,target){ + return game.countPlayer(function(current){ + if(current.hasSkill('gw_ciguhanshuang')) return 0; + if(current==target||(get.distance(target,current,'pure')==1)){ + return -get.sgn(get.attitude(player,current)); + } + }); + } + }, + order:1.2, + } + }, + gw_baoxueyaoshui:{ + fullborder:'bronze', + type:'spell', + subtype:'spell_bronze', + enable:true, + filterTarget:true, + content:function(){ + 'step 0' + target.chooseToDiscard('h',2,true).delay=false; + 'step 1' + target.draw(); + }, + ai:{ + value:6, + useful:[3,1], + result:{ + target:function(player,target){ + if(target.hasSkillTag('noh')) return 0.1; + switch(target.countCards('h')){ + case 0:return 0.5; + case 1:return 0; + case 2:return -1.5; + default:return -1; + } + } + }, + order:8, + tag:{ + loseCard:1, + discard:1, + } + } + }, + gw_zhihuanjun:{ + fullborder:'bronze', + type:'spell', + subtype:'spell_bronze', + enable:true, + filterTarget:function(card,player,target){ + return target.isDamaged(); + }, + content:function(){ + 'step 0' + target.loseMaxHp(true); + 'step 1' + if(target.isDamaged()&&target.countCards('h')=player.hp; + }, + content:function(){ + 'step 0' + target.damage('thunder'); + 'step 1' + if(target.isIn()){ + target.draw(); + } + }, + ai:{ + basic:{ + order:1.8, + value:[5,1], + useful:[4,1], + }, + result:{ + target:-1 + }, + tag:{ + damage:1, + thunderDamage:1, + natureDamage:1, + } + } + }, + gw_poxiao:{ + fullborder:'bronze', + type:'spell', + subtype:'spell_bronze', + enable:true, + notarget:true, + content:function(){ + 'step 0' + var choice=1; + if(game.countPlayer(function(current){ + if(current.countCards('j')||current.hasSkillTag('weather')){ + if(get.attitude(player,current)>0){ + choice=0; + } + return true; + } + })){ + player.chooseControl(function(){ + return choice; + }).set('choiceList',[ + '解除任意名角色的天气效果并移除其判定区内的牌', + '随机获得一张铜卡法术(破晓除外)并展示之' + ]); + } + else{ + event.directfalse=true; + } + 'step 1' + if(!event.directfalse&&result.index==0){ + player.chooseTarget(true,[1,Infinity],'解除任意名角色的天气效果并移除其判定区内的牌',function(card,player,target){ + return target.countCards('j')||target.hasSkillTag('weather'); + }).ai=function(target){ + return get.attitude(player,target); + }; + } + else{ + var list=get.libCard(function(info,name){ + return name!='gw_poxiao'&&info.subtype=='spell_bronze'; + }); + if(list.length){ + player.gain(game.createCard(list.randomGet()),'gain2'); + } + else{ + player.draw(); + } + event.finish(); + } + 'step 2' + event.list=result.targets.slice(0).sortBySeat(); + 'step 3' + if(event.list.length){ + var target=event.list.shift(); + player.line(target,'green'); + var cards=target.getCards('j'); + if(cards.length){ + target.discard(cards); + } + if(target.hasSkillTag('weather')){ + var skills=target.getSkills(); + for(var i=0;i0){ + player.updateMarks(); + } + else{ + player.removeSkill('gw_kunenfayin'); + } + }, + } + }, + group:'gw_kunenfayin_count', + onremove:true + }, + gw_baobaoshu:{ + mark:true, + nopop:true, + intro:{ + content:'每使用一张基本牌或锦囊牌,需弃置一张牌' + }, + trigger:{player:'useCard'}, + forced:true, + filter:function(event,player){ + if(player.countCards('he')==0) return false; + var type=get.type(event.card,'trick'); + return type=='basic'||type=='trick'; + }, + content:function(){ + if(!event.isMine()) game.delay(0.5); + player.chooseToDiscard(true,'he'); + }, + ai:{ + weather:true, + effect:{ + player:function(card,player){ + if(!player.needsToDiscard()) return 'zeroplayertarget'; + } + } + } + }, + gw_nuhaifengbao:{ + mark:true, + intro:{ + content:'结束阶段随机弃置一张牌(剩余#回合)' + }, + init:function(player){ + player.storage.gw_nuhaifengbao=2; + }, + trigger:{player:'phaseEnd'}, + forced:true, + nopop:true, + content:function(){ + player.randomDiscard(); + player.storage.gw_nuhaifengbao--; + if(player.storage.gw_nuhaifengbao>0){ + player.updateMarks(); + } + else{ + player.removeSkill('gw_nuhaifengbao'); + } + }, + onremove:true, + ai:{ + neg:true, + weather:true + } + }, + gw_youer:{ + trigger:{global:'phaseEnd',player:'dieBegin'}, + forced:true, + audio:false, + mark:true, + intro:{ + content:'cards' + }, + content:function(){ + if(player.storage.gw_youer){ + if(trigger.name=='phase'){ + player.gain(player.storage.gw_youer); + } + else{ + player.$throw(player.storage.gw_youer,1000); + for(var i=0;i0; + }, + content:function(){ + trigger.num--; + player.removeSkill('gw_ciguhanshuang'); + }, + ai:{ + weather:true + } + }, + gw_dieyi:{ + init:function(player){ + player.storage.gw_dieyi=1; + }, + onremove:true, + trigger:{global:'phaseEnd'}, + forced:true, + mark:true, + nopop:true, + process:function(player){ + if(player.hasSkill('gw_dieyi')){ + player.storage.gw_dieyi++; + } + else{ + player.addSkill('gw_dieyi'); + } + player.syncStorage('gw_dieyi'); + player.updateMarks(); + }, + intro:{ + content:'在当前回合的结束阶段,你随机弃置#张牌' + }, + content:function(){ + player.randomDiscard(player.storage.gw_dieyi); + player.removeSkill('gw_dieyi'); + } + }, + gw_leizhoushu:{ + mark:true, + intro:{ + content:function(storage,player){ + if(storage>=2){ + return '锁定技,准备阶段,你令手牌数为全场最多的所有其他角色各随机弃置一张手牌,若目标不包含敌方角色,将一名随机敌方角色追加为额外目标(重复'+storage+'次)'; + } + else{ + return '锁定技,准备阶段,你令手牌数为全场最多的所有其他角色各随机弃置一张手牌,若目标不包含敌方角色,将一名随机敌方角色追加为额外目标'; + } + } + }, + nopop:true, + trigger:{player:'phaseBegin'}, + forced:true, + filter:function(event,player){ + var list=game.filterPlayer(); + for(var i=0;i0&&event.parent.name=='phaseDraw'; + }, + content:function(){ + if(!player.storage.spell_gain||Math.max.apply(null,player.storage.spell_gain)<0){ + var tmp=player.storage.spell_gain2; + player.storage.spell_gain=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].randomGets(3); + player.storage.spell_gain2=Math.floor((15-Math.max.apply(null,player.storage.spell_gain))/2); + if(tmp){ + for(var i=0;i<3;i++){ + player.storage.spell_gain[i]+=tmp; + } + } + } + for(var i=0;i<3;i++){ + if(player.storage.spell_gain[i]==0){ + var list; + if(i==0){ + list=get.libCard(function(info){ + return info.subtype=='spell_gold'; + }); + if(get.mode()=='stone'){ + list.remove('gw_aerdeyin'); + list.remove('gw_niuquzhijing'); + } + } + else{ + list=get.libCard(function(info){ + return info.subtype=='spell_silver'; + }); + if(get.mode()=='stone'){ + list.remove('gw_butianshu'); + } + } + if(list&&list.length){ + ui.cardPile.insertBefore(game.createCard(list.randomGet()),ui.cardPile.firstChild); + } + } + player.storage.spell_gain[i]--; + } + } + } + }, + help:{ + '昆特牌':'
    • 法术为分金、银、铜三类,金卡和银卡不出现在牌堆中
    • '+ + '摸牌阶段有一定概率摸到银卡,在16个摸牌阶段中至少会摸到2张银卡
    • '+ + '摸牌阶段有一定概率摸到金卡,在16个摸牌阶段中至少会摸到1张金卡
    • '+ + '金卡无视调虎离山、潜行等免疫目标的效果
    • '+ + '进行洗牌时金卡、银卡将从弃牌堆中消失,不进入牌堆' + }, + translate:{ + spell:'法术', + spell_gold:'金卡法术', + spell_silver:'银卡法术', + spell_bronze:'铜卡法术', + + gw_youlanzhimeng:'幽蓝之梦', + gw_guaiwuchaoxue:'怪物巢穴', + gw_guaiwuchaoxue_info:'出牌阶段限用一次,随机获得一个卖血技能直到下一回合开始;令一名随机敌方角色对你造成一点伤害,然后你回复一点体力', + gw_baobaoshu:'雹暴术', + gw_baobaoshu_info:'天气牌,出牌阶段对至多两名角色使用,目标每使用一张基本牌或锦囊牌,需弃置一张牌,直到下一回合结束', + gw_baishuang:'白霜', + gw_baishuang_info:'天气牌,出牌阶段对至多三名角色使用,目标下个摸牌阶段摸牌数-1', + gw_nuhaifengbao:'怒海风暴', + gw_nuhaifengbao_bg:'海', + gw_nuhaifengbao_info:'天气牌,出牌阶段对一名角色使用,目标在结束阶段随机弃置一张牌,持续2回合', + gw_ganhan:'干旱', + gw_ganhan_info:'所有角色减少一点体力上限(不触发技能),然后结束出牌阶段', + gw_huangjiashenpan:'皇家审判', + gw_huangjiashenpan_info:'获得任意一张金卡法术(皇家审判除外),然后结束出牌阶段', + gw_chongci:'冲刺', + gw_chongci_info:'弃置所有牌并随机获得一张非金法术牌,每弃置一张手牌,便随机获得一张类别相同的牌;每弃置一张装备区内的牌,随机装备一件类别相同的装备;获得潜行直到下一回合开始,然后结束出牌阶段', + gw_tunshi:'吞噬', + gw_tunshi_info:'随机移除一名敌方角色的一个随机技能,你获得此技能并减少一点体力和体力上限,被移除技能的角色增加一点体力和体力上限,然后结束出牌阶段', + gw_dieyi:'蝶翼', + gw_dieyi_equip1:'蝶翼·器', + gw_dieyi_equip2:'蝶翼·衣', + gw_dieyi_equip3:'蝶翼·攻', + gw_dieyi_equip4:'蝶翼·防', + gw_dieyi_equip5:'蝶翼·宝', + gw_dieyi_judge:'蝶翼·判', + gw_dieyi_equip1_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', + gw_dieyi_equip2_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', + gw_dieyi_equip3_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', + gw_dieyi_equip4_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', + gw_dieyi_equip5_info:'在你从装备区中失去此牌后,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', + gw_dieyi_judge_info:'你在判定阶段移去此牌,并获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌', + gw_hudiewu:'蝴蝶舞', + gw_hudiewu_info:'将其他角色在场上的所有牌替换为蝶翼(每当你失去一张蝶翼,你获得一枚“蝶翼”标记;在任意角色的结束阶段,你移去所有“蝶翼”标记,并随机弃置等量的牌),然后结束出牌阶段', + gw_yigeniyin:'伊格尼印', + gw_yigeniyin_info:'对敌方角色中体力值最高的一名随机角色造成一点火焰伤害,然后对场上体力值最高的所有角色各造成一点火焰伤害,然后结束出牌阶段', + gw_leizhoushu:'雷咒术', + gw_leizhoushu_info:'获得技能雷咒术(在每个准备阶段令全场牌数最多的所有其他角色各随机弃置一张牌,若目标不包含敌方角色,将一名随机敌方角色追加为额外目标,结算X次,X为本局获得此技能的次数),然后结束出牌阶段', + gw_aerdeyin:'阿尔德印', + gw_aerdeyin_bg:'印', + gw_aerdeyin_info:'对一名随机敌方角色造成一点伤害,若目标武将牌正面朝上,则将其翻面;新的一轮开始时,若目标武将牌正面朝上,则在当前回合结束后进行一个额外回合,否则将武将牌翻回正面', + gw_xinsheng:'新生', + gw_xinsheng_info:'选择一名角色,随机观看12张武将牌,选择一张替代其武将牌,并令其增加一点体力,然后结束出牌阶段', + gw_zhongmozhizhan:'终末之战', + gw_zhongmozhizhan_info:'将所有角色区域内的所有牌置入弃牌堆(不触发技能),然后结束出牌阶段', + gw_butianshu:'卜天术', + gw_butianshu_info:'出牌阶段对任意角色使用,将任意一张延时锦囊牌置入其判定区', + gw_zhihuanjun:'致幻菌', + gw_zhihuanjun_info:'出牌阶段对一名已受伤角色使用,令其减少一点体力上限;若该角色仍处于受伤状态且手牌数小于体力上限,则重复此结算', + gw_niuquzhijing:'纽曲之镜', + gw_niuquzhijing_info:'令全场体力最多的角色减少一点体力和体力上限,体力最少的角色增加一点体力和体力上限(不触发技能),然后结束出牌阶段', + gw_ansha:'暗杀', + gw_ansha_info:'令一名体力为1的随机敌方角立即死亡,然后结束出牌阶段', + gw_shizizhaohuan:'十字召唤', + gw_shizizhaohuan_info:'从牌堆中获得一张杀以及决斗、火攻、火烧连营、南蛮入侵四张牌中的随机一张', + gw_zuihouyuanwang:'最后愿望', + gw_zuihouyuanwang_info:'摸X张牌并弃置X张牌,X为存活角色数', + gw_zirankuizeng:'自然馈赠', + gw_zirankuizeng_info:'选择任意一张铜卡法术使用', + gw_poxiao:'破晓', + gw_poxiao_info:'选择一项:解除任意名角色的天气效果并移除其判定区内的牌,或随机获得一张铜卡法术(破晓除外)并展示之', + gw_zumoshoukao:'阻魔手铐', + gw_zumoshoukao_info:'令一名角色非锁定技失效直到下一回合结束', + gw_aozuzhilei:'奥祖之雷', + gw_aozuzhilei_info:'对一名体力值不小于你的角色造成一点雷属性伤害,然后该角色摸一张牌', + gw_zhuoshao:'灼烧', + gw_zhuoshao_info:'对任意名体力值为全场最高的角色使用,造成一点火属性伤害', + gw_fuyuan:'复原', + gw_fuyuan_info:'对一名濒死状态角色使用,目标回复一点体力并摸一张牌', + gw_youer:'诱饵', + gw_youer_bg:'饵', + gw_youer_info:'将一名其他角色的所有手牌移出游戏,然后摸一张牌,当前回合结束后该角色将以此法失去的牌收回手牌', + gw_tongdi:'通敌', + gw_tongdi_info:'观看一名其他角色的手牌并获得其中一张,然后令目标获得一张杀', + gw_baoxueyaoshui:'暴雪药水', + gw_baoxueyaoshui_info:'令一名角色弃置两张手牌并摸一张牌', + gw_birinongwu:'蔽日浓雾', + gw_birinongwu_bg:'雾', + gw_birinongwu_info:'天气牌,出牌阶段对一名角色及其相邻角色使用,目标不能使用杀直到下一个出牌阶段结束', + gw_qinpendayu:'倾盆大雨', + gw_qinpendayu_bg:'雨', + gw_qinpendayu_info:'天气牌,出牌阶段对一名角色及其相邻角色使用,目标手牌上限-1直到下一个弃牌阶段结束', + gw_ciguhanshuang:'刺骨寒霜', + gw_ciguhanshuang_bg:'霜', + gw_ciguhanshuang_info:'天气牌,出牌阶段对一名角色及其相邻角色使用,目标下个摸牌阶段摸牌数-1', + gw_wenyi:'瘟疫', + gw_wenyi_info:'令所有体力值为全场最少的角色随机弃置一张手牌;若没有手牌,改为失去一点体力', + gw_yanziyaoshui:'燕子药水', + gw_yanziyaoshui_info:'令一名角色摸一张牌,若其手牌数为全场最少或之一,改为摸两张', + gw_shanbengshu:'山崩术', + gw_shanbengshu_info:'出牌阶段对自己使用,随机弃置两件敌方角色场上的装备', + gw_kunenfayin:'昆恩法印', + gw_kunenfayin_info:'出牌阶段对一名角色使用,目标防止所有非属性伤害,持续X个角色的回合(X为存活角色数且最多为5)', + }, + cardType:{ + spell:0.5, + spell_bronze:0.2, + spell_silver:0.3, + spell_gold:0.4 + }, + list:[ + ['club',3,'gw_zhihuanjun'], + ['spade',2,'gw_zhihuanjun'], + + ['heart',7,'gw_poxiao'], + ['diamond',4,'gw_poxiao'], + + ['spade',9,'gw_aozuzhilei','thunder'], + ['club',7,'gw_aozuzhilei','thunder'], + + ['club',1,'gw_zumoshoukao'], + ['spade',1,'gw_zumoshoukao'], + + ['diamond',5,'gw_qinpendayu'], + ['club',7,'gw_qinpendayu'], + + ['spade',9,'gw_birinongwu'], + ['heart',13,'gw_birinongwu'], + + ['diamond',11,'gw_ciguhanshuang'], + ['club',7,'gw_ciguhanshuang'], + + ['heart',4,'gw_baoxueyaoshui'], + ['spade',8,'gw_baoxueyaoshui'], + + ['spade',8,'gw_shanbengshu'], + ['spade',2,'gw_kunenfayin'], + ['club',3,'gw_wenyi'], + ['heart',8,'gw_yanziyaoshui'], + ], + }; +}); diff --git a/card/hearth.js b/card/hearth.js index 6cba14e78..a754f5e61 100644 --- a/card/hearth.js +++ b/card/hearth.js @@ -1,741 +1,741 @@ -'use strict'; -game.import('card',function(lib,game,ui,get,ai,_status){ - return { - name:'hearth', - card:{ - linghunzhihuo:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:true, - content:function(){ - 'step 0' - target.damage('fire'); - 'step 1' - var hs=player.getCards('h'); - if(hs.length){ - player.discard(hs.randomGet()); - } - }, - ai:{ - basic:{ - order:1.8, - value:[6,1], - useful:[4,1], - }, - result:{ - player:function(player,target){ - if(player==target) return -1; - if(player.countCards('h')>=player.hp) return -0.1; - if(player.countCards('h')>1) return -0.5; - return 0; - }, - target:-1 - }, - tag:{ - damage:1, - fireDamage:1, - natureDamage:1, - } - } - }, - jihuocard:{ - fullskin:true, - type:'trick', - enable:true, - toself:true, - filterTarget:function(card,player,target){ - return player==target; - }, - selectTarget:-1, - modTarget:true, - content:function(){ - if(_status.currentPhase==target){ - target.addTempSkill('jihuocard2'); - } - target.draw(); - }, - ai:{ - order:10, - result:{ - target:1 - } - } - }, - zhaomingdan:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:function(card,player,target){ - return player!=target&&target.countCards('hej')>0; - }, - content:function(){ - 'step 0' - if(target.countCards('hej')){ - var next=player.discardPlayerCard('hej',target,true); - next.visible=true; - next.delay=false; - } - else{ - event.goto(2); - } - 'step 1' - if(result.bool){ - game.delay(0.5); - } - 'step 2' - target.draw(false); - target.$draw(); - game.delay(0.5); - 'step 3' - player.draw(); - }, - ai:{ - order:9.5, - value:6, - useful:3, - result:{ - target:function(player,target){ - if(get.attitude(player,target)>0){ - var js=target.getCards('j'); - if(js.length){ - var jj=js[0].viewAs?{name:js[0].viewAs}:js[0]; - if(jj.name=='zhaomingdan') return 3; - if(js.length==1&&get.effect(target,jj,target,player)>=0){ - return 0; - } - return 3; - } - } - var es=target.getCards('e'); - var nh=target.countCards('h'); - var noe=(es.length==0||target.hasSkillTag('noe')); - var noe2=(es.length==1&&es[0].name=='baiyin'&&target.hp0; - }, - content:function(){ - "step 0" - if(target.countCards('h')==0){ - event.finish(); - return; - } - var rand=Math.random()<0.5; - target.chooseCard(true).ai=function(card){ - if(rand) return Math.random(); - return get.value(card); - }; - "step 1" - event.dialog=ui.create.dialog(get.translation(target.name)+'展示的手牌',result.cards); - event.card2=result.cards[0]; - event.videoId=lib.status.videoId++; - game.addVideo('cardDialog',null,[get.translation(target.name)+'展示的手牌',get.cardsInfo(result.cards),event.videoId]); - game.log(target,'展示了',event.card2); - player.chooseToDiscard(function(card){ - return get.suit(card)==get.suit(_status.event.parent.card2); - },function(card){ - if(get.damageEffect(target,player,player,'thunder')>0){ - return 6-get.value(card,_status.event.player); - } - return -1; - }).prompt=false; - game.delay(2); - "step 2" - if(result.bool){ - target.damage('thunder'); - } - else{ - target.addTempSkill('huogong2'); - } - game.addVideo('cardDialog',null,event.videoId); - event.dialog.close(); - }, - ai:{ - basic:{ - order:4, - value:[3,1], - useful:1, - }, - wuxie:function(target,card,player,current,state){ - if(get.attitude(current,player)>=0&&state>0) return false; - }, - result:{ - player:function(player){ - var nh=player.countCards('h'); - if(nh<=player.hp&&nh<=4&&_status.event.name=='chooseToUse'){ - if(typeof _status.event.filterCard=='function'&& - _status.event.filterCard({name:'shandianjian'})){ - return -10; - } - if(_status.event.skill){ - var viewAs=get.info(_status.event.skill).viewAs; - if(viewAs=='shandianjian') return -10; - if(viewAs&&viewAs.name=='shandianjian') return -10; - } - } - return 0; - }, - target:function(player,target){ - if(target.hasSkill('huogong2')||target.countCards('h')==0) return 0; - if(player.countCards('h')<=1) return 0; - if(target==player){ - if(typeof _status.event.filterCard=='function'&& - _status.event.filterCard({name:'shandianjian'})){ - return -1.5; - } - if(_status.event.skill){ - var viewAs=get.info(_status.event.skill).viewAs; - if(viewAs=='shandianjian') return -1.5; - if(viewAs&&viewAs.name=='shandianjian') return -1.5; - } - return 0; - } - return -1.5; - } - }, - tag:{ - damage:1, - thunderDamage:1, - natureDamage:1, - norepeat:1 - } - } - }, - shihuawuqi:{ - fullskin:true, - type:'basic', - enable:true, - usable:1, - filterTarget:function(card,player,target){ - return player==target; - }, - selectTarget:-1, - content:function(){ - player.addTempSkill('shihuawuqi'); - if(!player.countCards('h','sha')){ - var card=get.cardPile('sha'); - if(card){ - player.gain(card,'gain2'); - } - } - }, - ai:{ - value:4, - useful:2, - order:8, - result:{ - target:function(player,target){ - return target.countCards('h','sha')?0:1; - } - } - } - }, - siwangchanrao:{ - enable:true, - type:'trick', - filterTarget:function(card,player,target){ - return player!=target&&target.countCards('h')>0; - }, - selectTarget:1, - content:function(){ - 'step 0' - var hs=target.getCards('h'); - if(hs.length){ - target.discard(hs.randomGet()); - } - 'step 1' - if(!target.countCards('h')){ - player.draw(); - } - }, - ai:{ - order:9, - value:4, - useful:1, - result:{ - target:-1, - player:function(player,target){ - if(target.countCards('h')==1) return 1; - } - } - } - }, - dunpaigedang:{ - fullskin:true, - enable:true, - type:'trick', - toself:true, - filterTarget:function(card,player,target){ - return player==target; - }, - selectTarget:-1, - modTarget:true, - content:function(){ - 'step 0' - target.changeHujia(); - target.draw(); - 'step 1' - if(target.countCards('he')){ - target.chooseToDiscard('he',true); - } - }, - ai:{ - order:8.5, - value:7, - useful:3, - result:{ - target:1 - } - } - }, - chuansongmen:{ - fullskin:true, - type:'trick', - enable:true, - discard:false, - toself:true, - selectTarget:-1, - filterTarget:function(card,player,target){ - return target==player; - }, - modTarget:true, - // usable:3, - // forceUsable:true, - content:function(){ - 'step 0' - var gained=get.cards()[0]; - target.gain(gained,'gain2'); - if(event.getParent(3).name=='phaseUse'&&_status.currentPhase==target&& - lib.filter.filterCard(gained,target,event.getParent(2))){ - var next=target.chooseToUse(); - next.filterCard=function(card){ - return card==gained; - }; - next.prompt='是否使用'+get.translation(gained)+'?'; - if(cards[0]){ - ui.special.appendChild(cards[0]); - } - else{ - event.finish(); - } - } - else{ - // if(cards[0]){ - // cards[0].discard(); - // } - event.finish(); - } - 'step 1' - if(result.bool&&!target.hasSkill('chuansongmen3')){ - if(target.hasSkill('chuansongmen2')){ - target.addTempSkill('chuansongmen3'); - } - else{ - target.addTempSkill('chuansongmen2'); - } - cards[0].fix(); - target.gain(cards,'gain2'); - } - else{ - cards[0].discard(); - } - }, - ai:{ - order:9.5, - value:7, - useful:3, - result:{ - target:1 - }, - tag:{ - norepeat:1 - } - } - }, - tanshezhiren:{ - fullskin:true, - type:'trick', - enable:true, - // chongzhu:true, - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - modTarget:true, - content:function(){ - 'step 0' - event.current=target; - event.num=game.countPlayer(); - if(event.num%2==0){ - event.num--; - } - 'step 1' - if(event.num){ - var enemies=event.current.getEnemies(); - enemies.remove(player); - for(var i=0;i2) return 1; - return 0; - }, - }, - tag:{ - recover:1 - } - } - }, - shenenshu:{ - fullskin:true, - enable:true, - type:'trick', - selectTarget:-1, - filterTarget:function(card,player,target){ - return target==player; - }, - modTarget:true, - content:function(){ - 'step 0' - var cards=target.getCards('h'); - if(cards.length){ - target.lose(cards)._triggered=null; - } - event.num=1+cards.length; - 'step 1' - var cards=[]; - var list=get.typeCard('basic'); - list.remove('du'); - if(list.length){ - for(var i=0;i=6){ - return 0; - } - } - return 1; - } - } - } - }, - zhiliaobo:{ - fullskin:true, - enable:true, - filterTarget:function(card,player,target){ - return target.hp=0){ - if(target.hasSkillTag('maixie')){ - if(ui.selected.cards.length) return 0; - } - else{ - return 0; - } - } - if(player.hasSkillTag('notricksource')) return 0; - if(target.hasSkillTag('notrick')) return 0; - if(card.name=='tao') return 0; - if(target.hp==1&&card.name=='jiu') return 0; - if(get.type(card)!='basic'){ - return 10-get.value(card); - } - return 8-get.value(card); - }; - "step 1" - if(!result.bool||result.cards.length<2){ - if(result.bool) target.damage(2-result.cards.length,'thunder'); - else target.damage(2,'thunder'); - } - }, - ai:{ - basic:{ - order:7, - useful:[5,1] - }, - result:{ - target:function(player,target){ - if(target.hasSkillTag('nothunder')) return 0; - if(player.hasUnknown(2)) return 0; - var nh=target.countCards('he'); - if(target==player) nh--; - if(nh==2) return -2.5; - if(nh==1) return -3; - if(nh==0) return -4; - return -2; - }, - }, - tag:{ - damage:1, - natureDamage:1, - thunderDamage:1, - multitarget:1, - multineg:1, - discard:2, - loseCard:2, - } - } - } - }, - skill:{ - chuansongmen2:{}, - chuansongmen3:{}, - shihuawuqi:{ - mod:{ - attackFrom:function(from,to,distance){ - return distance-1; - } - } - }, - jihuocard2:{ - mod:{ - maxHandcard:function(player,num){ - return num+2; - } - } - } - }, - translate:{ - linghunzhihuo:'灵魂之火', - linghunzhihuo_info:'对一名角色造成一点火焰伤害,然后随机弃置一张手牌', - shenenshu:'神恩术', - shenenshu_info:'出牌阶段对自己使用,将所有手牌(含此张)替换为基本牌', - zhiliaobo:'治疗波', - zhiliaobo_info:'出牌阶段对一名受伤角色使用,目标进行一次判定,若结果为红色,则回复一点体力,否则获得一点护甲', - yuansuhuimie:'元素毁灭', - yuansuhuimie_info:'对所有角色使用,令目标弃置0~2张牌,并受到2-X点雷电伤害,X为其弃置的手牌数', - xingjiegoutong:'星界沟通', - xingjiegoutong_info:'增加一点体力上限并回复一点体力,弃置你的所有手牌', - tanshezhiren:'弹射之刃', - tanshezhiren_info:'出牌阶段对自己使用,依次按敌方-友方-敌方-的顺序随机弃置阵营内一名随机角色的一张牌(目标不包含你),共结算X次,X为存活角色数,若X为偶数,改为X-1', - chuansongmen:'传送门', - chuansongmen_info:'摸一张牌并展示,若发生在出牌阶段,你可以立即使用摸到的牌,若如此做,你将传送门收回手牌(每阶段最多收回2张传送门)', - dunpaigedang:'盾牌格挡', - dunpaigedang_info:'获得一点护甲值,摸一张牌,然后弃置一张牌', - siwangchanrao:'死亡缠绕', - siwangchanrao_infox:'弃置一名其他角色的一张手牌,若其此时没有手牌,则你摸一张牌', - shihuawuqi:'石化武器', - shihuawuqi_infox:'本回合内攻击范围+1;若你手牌中没有杀,则从牌堆中获得一张杀', - shandianjian:'闪电箭', - shandianjian_info:'目标角色展示一张手牌,然后若你能弃掉一张与所展示牌相同花色的手牌,则对该角色造成1点雷电伤害。', - shijieshu:'视界术', - shijieshu_info:'目标从牌堆或弃牌堆中随机装备两张类别不同的装备牌,然后弃置一张牌', - zhaomingdan:'照明弹', - zhaomingdan_info:'观看一名其他角色的手牌,并弃置其区域内的一张牌,然后其与你各摸一张牌', - jihuocard:'激活', - jihuocard_info:'摸一张牌,本回合手牌上限+2', - }, - list:[ - ['heart',2,'shenenshu'], - ['diamond',12,'shenenshu'], - ['club',7,'zhiliaobo'], - ['spade',1,'zhiliaobo'], - ['spade',13,'yuansuhuimie'], - ['spade',13,'xingjiegoutong'], - ['diamond',2,'tanshezhiren'], - ['diamond',2,'chuansongmen'], - ['heart',2,'chuansongmen'], - ['club',3,'dunpaigedang'], - ['club',3,'shandianjian','thunder'], - ['spade',1,'shandianjian','thunder'], - ['spade',7,'shijieshu'], - ['diamond',5,'zhaomingdan'], - ['heart',10,'zhaomingdan'], - ['diamond',2,'jihuocard'], - ['diamond',1,'linghunzhihuo'], - ], - }; -}); +'use strict'; +game.import('card',function(lib,game,ui,get,ai,_status){ + return { + name:'hearth', + card:{ + linghunzhihuo:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:true, + content:function(){ + 'step 0' + target.damage('fire'); + 'step 1' + var hs=player.getCards('h'); + if(hs.length){ + player.discard(hs.randomGet()); + } + }, + ai:{ + basic:{ + order:1.8, + value:[6,1], + useful:[4,1], + }, + result:{ + player:function(player,target){ + if(player==target) return -1; + if(player.countCards('h')>=player.hp) return -0.1; + if(player.countCards('h')>1) return -0.5; + return 0; + }, + target:-1 + }, + tag:{ + damage:1, + fireDamage:1, + natureDamage:1, + } + } + }, + jihuocard:{ + fullskin:true, + type:'trick', + enable:true, + toself:true, + filterTarget:function(card,player,target){ + return player==target; + }, + selectTarget:-1, + modTarget:true, + content:function(){ + if(_status.currentPhase==target){ + target.addTempSkill('jihuocard2'); + } + target.draw(); + }, + ai:{ + order:10, + result:{ + target:1 + } + } + }, + zhaomingdan:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:function(card,player,target){ + return player!=target&&target.countCards('hej')>0; + }, + content:function(){ + 'step 0' + if(target.countCards('hej')){ + var next=player.discardPlayerCard('hej',target,true); + next.visible=true; + next.delay=false; + } + else{ + event.goto(2); + } + 'step 1' + if(result.bool){ + game.delay(0.5); + } + 'step 2' + target.draw(false); + target.$draw(); + game.delay(0.5); + 'step 3' + player.draw(); + }, + ai:{ + order:9.5, + value:6, + useful:3, + result:{ + target:function(player,target){ + if(get.attitude(player,target)>0){ + var js=target.getCards('j'); + if(js.length){ + var jj=js[0].viewAs?{name:js[0].viewAs}:js[0]; + if(jj.name=='zhaomingdan') return 3; + if(js.length==1&&get.effect(target,jj,target,player)>=0){ + return 0; + } + return 3; + } + } + var es=target.getCards('e'); + var nh=target.countCards('h'); + var noe=(es.length==0||target.hasSkillTag('noe')); + var noe2=(es.length==1&&es[0].name=='baiyin'&&target.hp0; + }, + content:function(){ + "step 0" + if(target.countCards('h')==0){ + event.finish(); + return; + } + var rand=Math.random()<0.5; + target.chooseCard(true).ai=function(card){ + if(rand) return Math.random(); + return get.value(card); + }; + "step 1" + event.dialog=ui.create.dialog(get.translation(target.name)+'展示的手牌',result.cards); + event.card2=result.cards[0]; + event.videoId=lib.status.videoId++; + game.addVideo('cardDialog',null,[get.translation(target.name)+'展示的手牌',get.cardsInfo(result.cards),event.videoId]); + game.log(target,'展示了',event.card2); + player.chooseToDiscard(function(card){ + return get.suit(card)==get.suit(_status.event.parent.card2); + },function(card){ + if(get.damageEffect(target,player,player,'thunder')>0){ + return 6-get.value(card,_status.event.player); + } + return -1; + }).prompt=false; + game.delay(2); + "step 2" + if(result.bool){ + target.damage('thunder'); + } + else{ + target.addTempSkill('huogong2'); + } + game.addVideo('cardDialog',null,event.videoId); + event.dialog.close(); + }, + ai:{ + basic:{ + order:4, + value:[3,1], + useful:1, + }, + wuxie:function(target,card,player,current,state){ + if(get.attitude(current,player)>=0&&state>0) return false; + }, + result:{ + player:function(player){ + var nh=player.countCards('h'); + if(nh<=player.hp&&nh<=4&&_status.event.name=='chooseToUse'){ + if(typeof _status.event.filterCard=='function'&& + _status.event.filterCard({name:'shandianjian'})){ + return -10; + } + if(_status.event.skill){ + var viewAs=get.info(_status.event.skill).viewAs; + if(viewAs=='shandianjian') return -10; + if(viewAs&&viewAs.name=='shandianjian') return -10; + } + } + return 0; + }, + target:function(player,target){ + if(target.hasSkill('huogong2')||target.countCards('h')==0) return 0; + if(player.countCards('h')<=1) return 0; + if(target==player){ + if(typeof _status.event.filterCard=='function'&& + _status.event.filterCard({name:'shandianjian'})){ + return -1.5; + } + if(_status.event.skill){ + var viewAs=get.info(_status.event.skill).viewAs; + if(viewAs=='shandianjian') return -1.5; + if(viewAs&&viewAs.name=='shandianjian') return -1.5; + } + return 0; + } + return -1.5; + } + }, + tag:{ + damage:1, + thunderDamage:1, + natureDamage:1, + norepeat:1 + } + } + }, + shihuawuqi:{ + fullskin:true, + type:'basic', + enable:true, + usable:1, + filterTarget:function(card,player,target){ + return player==target; + }, + selectTarget:-1, + content:function(){ + player.addTempSkill('shihuawuqi'); + if(!player.countCards('h','sha')){ + var card=get.cardPile('sha'); + if(card){ + player.gain(card,'gain2'); + } + } + }, + ai:{ + value:4, + useful:2, + order:8, + result:{ + target:function(player,target){ + return target.countCards('h','sha')?0:1; + } + } + } + }, + siwangchanrao:{ + enable:true, + type:'trick', + filterTarget:function(card,player,target){ + return player!=target&&target.countCards('h')>0; + }, + selectTarget:1, + content:function(){ + 'step 0' + var hs=target.getCards('h'); + if(hs.length){ + target.discard(hs.randomGet()); + } + 'step 1' + if(!target.countCards('h')){ + player.draw(); + } + }, + ai:{ + order:9, + value:4, + useful:1, + result:{ + target:-1, + player:function(player,target){ + if(target.countCards('h')==1) return 1; + } + } + } + }, + dunpaigedang:{ + fullskin:true, + enable:true, + type:'trick', + toself:true, + filterTarget:function(card,player,target){ + return player==target; + }, + selectTarget:-1, + modTarget:true, + content:function(){ + 'step 0' + target.changeHujia(); + target.draw(); + 'step 1' + if(target.countCards('he')){ + target.chooseToDiscard('he',true); + } + }, + ai:{ + order:8.5, + value:7, + useful:3, + result:{ + target:1 + } + } + }, + chuansongmen:{ + fullskin:true, + type:'trick', + enable:true, + discard:false, + toself:true, + selectTarget:-1, + filterTarget:function(card,player,target){ + return target==player; + }, + modTarget:true, + // usable:3, + // forceUsable:true, + content:function(){ + 'step 0' + var gained=get.cards()[0]; + target.gain(gained,'gain2'); + if(event.getParent(3).name=='phaseUse'&&_status.currentPhase==target&& + lib.filter.filterCard(gained,target,event.getParent(2))){ + var next=target.chooseToUse(); + next.filterCard=function(card){ + return card==gained; + }; + next.prompt='是否使用'+get.translation(gained)+'?'; + if(cards[0]){ + ui.special.appendChild(cards[0]); + } + else{ + event.finish(); + } + } + else{ + // if(cards[0]){ + // cards[0].discard(); + // } + event.finish(); + } + 'step 1' + if(result.bool&&!target.hasSkill('chuansongmen3')){ + if(target.hasSkill('chuansongmen2')){ + target.addTempSkill('chuansongmen3'); + } + else{ + target.addTempSkill('chuansongmen2'); + } + cards[0].fix(); + target.gain(cards,'gain2'); + } + else{ + cards[0].discard(); + } + }, + ai:{ + order:9.5, + value:7, + useful:3, + result:{ + target:1 + }, + tag:{ + norepeat:1 + } + } + }, + tanshezhiren:{ + fullskin:true, + type:'trick', + enable:true, + // chongzhu:true, + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + modTarget:true, + content:function(){ + 'step 0' + event.current=target; + event.num=game.countPlayer(); + if(event.num%2==0){ + event.num--; + } + 'step 1' + if(event.num){ + var enemies=event.current.getEnemies(); + enemies.remove(player); + for(var i=0;i2) return 1; + return 0; + }, + }, + tag:{ + recover:1 + } + } + }, + shenenshu:{ + fullskin:true, + enable:true, + type:'trick', + selectTarget:-1, + filterTarget:function(card,player,target){ + return target==player; + }, + modTarget:true, + content:function(){ + 'step 0' + var cards=target.getCards('h'); + if(cards.length){ + target.lose(cards)._triggered=null; + } + event.num=1+cards.length; + 'step 1' + var cards=[]; + var list=get.typeCard('basic'); + list.remove('du'); + if(list.length){ + for(var i=0;i=6){ + return 0; + } + } + return 1; + } + } + } + }, + zhiliaobo:{ + fullskin:true, + enable:true, + filterTarget:function(card,player,target){ + return target.hp=0){ + if(target.hasSkillTag('maixie')){ + if(ui.selected.cards.length) return 0; + } + else{ + return 0; + } + } + if(player.hasSkillTag('notricksource')) return 0; + if(target.hasSkillTag('notrick')) return 0; + if(card.name=='tao') return 0; + if(target.hp==1&&card.name=='jiu') return 0; + if(get.type(card)!='basic'){ + return 10-get.value(card); + } + return 8-get.value(card); + }; + "step 1" + if(!result.bool||result.cards.length<2){ + if(result.bool) target.damage(2-result.cards.length,'thunder'); + else target.damage(2,'thunder'); + } + }, + ai:{ + basic:{ + order:7, + useful:[5,1] + }, + result:{ + target:function(player,target){ + if(target.hasSkillTag('nothunder')) return 0; + if(player.hasUnknown(2)) return 0; + var nh=target.countCards('he'); + if(target==player) nh--; + if(nh==2) return -2.5; + if(nh==1) return -3; + if(nh==0) return -4; + return -2; + }, + }, + tag:{ + damage:1, + natureDamage:1, + thunderDamage:1, + multitarget:1, + multineg:1, + discard:2, + loseCard:2, + } + } + } + }, + skill:{ + chuansongmen2:{}, + chuansongmen3:{}, + shihuawuqi:{ + mod:{ + attackFrom:function(from,to,distance){ + return distance-1; + } + } + }, + jihuocard2:{ + mod:{ + maxHandcard:function(player,num){ + return num+2; + } + } + } + }, + translate:{ + linghunzhihuo:'灵魂之火', + linghunzhihuo_info:'对一名角色造成一点火焰伤害,然后随机弃置一张手牌', + shenenshu:'神恩术', + shenenshu_info:'出牌阶段对自己使用,将所有手牌(含此张)替换为基本牌', + zhiliaobo:'治疗波', + zhiliaobo_info:'出牌阶段对一名受伤角色使用,目标进行一次判定,若结果为红色,则回复一点体力,否则获得一点护甲', + yuansuhuimie:'元素毁灭', + yuansuhuimie_info:'对所有角色使用,令目标弃置0~2张牌,并受到2-X点雷电伤害,X为其弃置的手牌数', + xingjiegoutong:'星界沟通', + xingjiegoutong_info:'增加一点体力上限并回复一点体力,弃置你的所有手牌', + tanshezhiren:'弹射之刃', + tanshezhiren_info:'出牌阶段对自己使用,依次按敌方-友方-敌方-的顺序随机弃置阵营内一名随机角色的一张牌(目标不包含你),共结算X次,X为存活角色数,若X为偶数,改为X-1', + chuansongmen:'传送门', + chuansongmen_info:'摸一张牌并展示,若发生在出牌阶段,你可以立即使用摸到的牌,若如此做,你将传送门收回手牌(每阶段最多收回2张传送门)', + dunpaigedang:'盾牌格挡', + dunpaigedang_info:'获得一点护甲值,摸一张牌,然后弃置一张牌', + siwangchanrao:'死亡缠绕', + siwangchanrao_infox:'弃置一名其他角色的一张手牌,若其此时没有手牌,则你摸一张牌', + shihuawuqi:'石化武器', + shihuawuqi_infox:'本回合内攻击范围+1;若你手牌中没有杀,则从牌堆中获得一张杀', + shandianjian:'闪电箭', + shandianjian_info:'目标角色展示一张手牌,然后若你能弃掉一张与所展示牌相同花色的手牌,则对该角色造成1点雷电伤害。', + shijieshu:'视界术', + shijieshu_info:'目标从牌堆或弃牌堆中随机装备两张类别不同的装备牌,然后弃置一张牌', + zhaomingdan:'照明弹', + zhaomingdan_info:'观看一名其他角色的手牌,并弃置其区域内的一张牌,然后其与你各摸一张牌', + jihuocard:'激活', + jihuocard_info:'摸一张牌,本回合手牌上限+2', + }, + list:[ + ['heart',2,'shenenshu'], + ['diamond',12,'shenenshu'], + ['club',7,'zhiliaobo'], + ['spade',1,'zhiliaobo'], + ['spade',13,'yuansuhuimie'], + ['spade',13,'xingjiegoutong'], + ['diamond',2,'tanshezhiren'], + ['diamond',2,'chuansongmen'], + ['heart',2,'chuansongmen'], + ['club',3,'dunpaigedang'], + ['club',3,'shandianjian','thunder'], + ['spade',1,'shandianjian','thunder'], + ['spade',7,'shijieshu'], + ['diamond',5,'zhaomingdan'], + ['heart',10,'zhaomingdan'], + ['diamond',2,'jihuocard'], + ['diamond',1,'linghunzhihuo'], + ], + }; +}); diff --git a/card/huanlekapai.js b/card/huanlekapai.js index a1589e0f0..f2428bd4a 100644 --- a/card/huanlekapai.js +++ b/card/huanlekapai.js @@ -18,6 +18,7 @@ game.import('card',function(lib,game,ui,get,ai,_status){ }, "mianju":{ audio:true, + fullskin:true, type:"equip", subtype:"equip2", skills:["mianju"], diff --git a/card/mtg.js b/card/mtg.js index 1fa704995..d5b1f22f5 100644 --- a/card/mtg.js +++ b/card/mtg.js @@ -1,731 +1,731 @@ -'use strict'; -game.import('card',function(lib,game,ui,get,ai,_status){ - return { - name:'mtg', - card:{ - mtg_lindixiliu:{ - type:'land', - fullborder:'wood', - enable:function(card,player){ - return !player.hasSkill('land_used'); - }, - notarget:true, - content:function(){ - 'step 0' - game.changeLand('mtg_lindixiliu',player); - var card=get.cardPile2(function(card){ - return get.suit(card)=='heart'; - }); - if(card){ - player.gain(card,'draw'); - } - 'step 1' - player.chooseToDiscard('he',[1,Infinity],{suit:'heart'},'林地溪流').set('ai',function(card){ - return 8-get.value(card); - }).set('prompt2','弃置任意张红桃牌,每弃置一张牌,将一张延时锦囊牌置入一名随机敌方角色的判定区'); - 'step 2' - if(result.bool){ - event.num=result.cards.length; - event.targets=player.getEnemies(); - } - 'step 3' - if(event.num&&event.targets&&event.targets.length){ - var target=event.targets.randomGet(); - var list=get.inpile('delay'); - for(var i=0;i0){ - player.useCard({name:name},target); - return; - } - } - } - }, - ai:{ - value:5, - useful:3, - order:4, - result:{ - player:1 - } - } - }, - mtg_haidao:{ - type:'land', - fullborder:'wood', - enable:function(card,player){ - return !player.hasSkill('land_used'); - }, - notarget:true, - content:function(){ - 'step 0' - game.changeLand('mtg_haidao',player); - if(player.isHealthy()){ - player.changeHujia(); - event.finish(); - } - else{ - player._temp_mtg_haidao=true; - player.chooseToDiscard('he','海岛').set('ai',function(card){ - return 5-get.value(card); - }).set('prompt2','弃置一张牌并回复一点体力,或取消并获得一点护甲'); - } - 'step 1' - if(result.bool){ - player.recover(); - } - else{ - player.changeHujia(); - } - 'step 2' - delete player._temp_mtg_haidao; - }, - ai:{ - value:5, - useful:3, - order:4, - result:{ - player:1 - } - } - }, - mtg_yixialan:{ - type:'land', - fullborder:'wood', - enable:function(card,player){ - return !player.hasSkill('land_used'); - }, - notarget:true, - content:function(){ - 'step 0' - game.changeLand('mtg_yixialan',player); - player.chooseControl('基本牌','锦囊牌').set('prompt','选择一个类型获得该类型的一张牌').set('ai',function(){ - var player=_status.event.player; - if(!player.hasSha()||!player.hasShan()||player.hp==1) return 0; - return 1; - }); - 'step 1' - if(result.control=='基本牌'){ - player.gain(game.createCard(get.inpile('basic').randomGet()),'gain2'); - } - else{ - player.gain(game.createCard(get.inpile('trick','trick').randomGet()),'gain2'); - } - }, - ai:{ - value:5, - useful:3, - order:4, - result:{ - player:1 - } - } - }, - mtg_shuimomuxue:{ - type:'land', - fullborder:'wood', - enable:function(card,player){ - return !player.hasSkill('land_used'); - }, - notarget:true, - content:function(){ - 'step 0' - game.changeLand('mtg_shuimomuxue',player); - if(player.countDiscardableCards('he')){ - player.chooseToDiscard('是否弃置一张牌并摸两张牌?','he').set('ai',function(card){ - return 8-get.value(card); - }); - } - else{ - event.finish(); - } - 'step 1' - if(result.bool){ - player.draw(2); - } - }, - ai:{ - value:5, - useful:3, - order:4, - result:{ - player:function(player){ - if(!player.countCards('h',function(card){ - return card.name!='mtg_shuimomuxue'&&get.value(card)<8; - })){ - return 0; - } - return 1; - } - } - } - }, - mtg_feixu:{ - type:'land', - fullborder:'wood', - enable:function(card,player){ - return !player.hasSkill('land_used'); - }, - notarget:true, - content:function(){ - game.changeLand('mtg_feixu',player); - player.discoverCard(ui.discardPile.childNodes); - }, - ai:{ - value:5, - useful:3, - order:4, - result:{ - player:1 - } - } - }, - mtg_shamolvzhou:{ - type:'land', - fullborder:'wood', - enable:function(card,player){ - return !player.hasSkill('land_used'); - }, - notarget:true, - content:function(){ - game.changeLand('mtg_shamolvzhou',player); - player.discoverCard(get.inpile('basic')); - }, - ai:{ - value:5, - useful:3, - order:4, - result:{ - player:1 - } - } - }, - mtg_duzhao:{ - type:'land', - fullborder:'wood', - enable:function(card,player){ - return !player.hasSkill('land_used'); - }, - notarget:true, - content:function(){ - 'step 0' - game.changeLand('mtg_duzhao',player); - player.chooseTarget('选择一名角色令其获得一张毒',true).set('ai',function(target){ - if(target.hasSkillTag('nodu')) return 0; - return -get.attitude(_status.event.player,target)/Math.sqrt(target.hp+1); - }); - 'step 1' - if(result.bool){ - var target=result.targets[0]; - player.line(target,'green'); - target.gain(game.createCard('du'),'gain2'); - } - }, - ai:{ - value:5, - useful:3, - order:4, - result:{ - player:function(player){ - if(game.countPlayer(function(current){ - if(current.hasSkillTag('nodu')) return false; - return get.attitude(player,current)<0; - })>game.countPlayer(function(current){ - if(current.hasSkillTag('nodu')) return false; - return get.attitude(player,current)>0; - })){ - return 1; - } - return 0; - } - } - } - }, - mtg_bingheyaosai:{ - type:'land', - fullborder:'wood', - enable:function(card,player){ - return !player.hasSkill('land_used'); - }, - notarget:true, - content:function(){ - 'step 0' - game.changeLand('mtg_bingheyaosai',player); - player.draw(2); - 'step 1' - player.chooseToDiscard('he',2,true); - }, - ai:{ - value:5, - useful:3, - order:2, - result:{ - player:1 - } - } - } - }, - skill:{ - mtg_bingheyaosai_skill:{ - trigger:{player:'useCard'}, - forced:true, - filter:function(event,player){ - if(player.countCards('he')==0) return false; - return event.card.name=='sha'; - }, - autodelay:true, - content:function(){ - player.chooseToDiscard(true,'he'); - }, - ai:{ - mapValue:-4 - } - }, - mtg_duzhao_skill:{ - trigger:{player:'phaseEnd'}, - forced:true, - content:function(){ - player.gain(game.createCard('du'),'gain2'); - }, - ai:{ - mapValue:-5 - } - }, - mtg_shamolvzhou_skill:{ - mod:{ - ignoredHandcard:function(card){ - if(get.type(card)=='basic'){ - return true; - } - } - }, - ai:{ - mapValue:3 - } - }, - mtg_haidao_skill:{ - trigger:{player:'changeHujiaBefore'}, - forced:true, - filter:function(event,player){ - return player.isDamaged()&&!player._temp_mtg_haidao&&event.num>0; - }, - content:function(){ - player.recover(trigger.num); - trigger.cancel(); - }, - ai:{ - mapValue:1 - } - }, - mtg_yixialan_skill:{ - enable:'phaseUse', - filter:function(event,player){ - return player.countCards('h',{type:'basic'})>0; - }, - filterCard:{type:'basic'}, - prepare:function(cards,player){ - player.$throw(cards,1000); - }, - discard:false, - delay:0.5, - check:function(card){ - return 7-get.value(card); - }, - usable:1, - content:function(){ - var card=get.cardPile(function(card){ - return get.type(card,'trick')=='trick' - }); - if(card){ - player.gain(card,'draw'); - } - else{ - player.draw(); - } - }, - ai:{ - mapValue:2, - order:1, - result:{ - player:1, - }, - } - }, - mtg_shuimomuxue_skill:{ - mod:{ - maxHandcard:function(player,num){ - return num-1; - } - }, - trigger:{player:'phaseDiscardEnd'}, - forced:true, - filter:function(event){ - return event.cards&&event.cards.length>0; - }, - content:function(){ - player.draw(); - }, - ai:{ - mapValue:2 - } - }, - mtg_feixu_skill:{ - trigger:{player:'phaseBegin'}, - silent:true, - content:function(){ - var num=ui.discardPile.childNodes.length; - if(num){ - var card=ui.discardPile.childNodes[get.rand(num)]; - if(card){ - ui.cardPile.insertBefore(card,ui.cardPile.firstChild); - } - } - }, - ai:{ - mapValue:0 - } - }, - mtg_youlin_skill:{ - enable:'phaseUse', - usable:1, - filter:function(event,player){ - return player.countCards('h',{type:['trick','delay']}); - }, - filterCard:function(card){ - return get.type(card,'trick')=='trick'; - }, - check:function(card){ - return 7-get.value(card); - }, - content:function(){ - player.discoverCard(); - }, - ai:{ - mapValue:1, - order:7, - result:{ - player:2 - } - } - }, - mtg_linzhongjianta_skill:{ - enable:'chooseToUse', - filterCard:function(card){ - return get.type(card)=='basic'; - }, - usable:1, - viewAs:{name:'sha'}, - viewAsFilter:function(player){ - if(!player.getEquip(1)) return false; - if(!player.countCards('h',{type:'basic'})) return false; - }, - prompt:'将一张基本牌当杀使用', - check:function(card){return 6-get.value(card)}, - ai:{ - mapValue:2, - respondSha:true, - order:function(){ - return get.order({name:'sha'})-0.1; - }, - skillTagFilter:function(player,tag,arg){ - if(arg!='use') return false; - if(!player.getEquip(1)) return false; - if(!player.countCards('h',{type:'basic'})) return false; - }, - } - }, - mtg_cangbaohaiwan_skill:{ - trigger:{player:'drawBegin'}, - silent:true, - content:function(){ - if(Math.random()<1/3){ - var list=[]; - for(var i=0;i
    • 地图牌可于出牌阶段使用,每阶段最多使用一张地图牌
    • '+ - '地图牌分为两部分:即时效果以及地图效果,即时效果由使用者在使用时选择;地图效果对所有角色有效
    • '+ - '当使用者死亡或下个回合开始时,当前地图效果消失
    • '+ - '新地图被使用时会覆盖当前地图效果' - }, - list:[ - ['club',12,'mtg_yixialan'], - ['spade',11,'mtg_shuimomuxue'], - ['diamond',5,'mtg_haidao'], - ['club',10,'mtg_youlin'], - ['club',8,'mtg_feixu'], - ['heart',6,'mtg_shamolvzhou'], - - ['club',12,'mtg_cangbaohaiwan'], - ['spade',11,'mtg_lindixiliu'], - ['diamond',5,'mtg_bingheyaosai'], - ['club',10,'mtg_longlushanfeng'], - ['club',8,'mtg_duzhao'], - ['heart',6,'mtg_linzhongjianta'], - ], - }; -}); +'use strict'; +game.import('card',function(lib,game,ui,get,ai,_status){ + return { + name:'mtg', + card:{ + mtg_lindixiliu:{ + type:'land', + fullborder:'wood', + enable:function(card,player){ + return !player.hasSkill('land_used'); + }, + notarget:true, + content:function(){ + 'step 0' + game.changeLand('mtg_lindixiliu',player); + var card=get.cardPile2(function(card){ + return get.suit(card)=='heart'; + }); + if(card){ + player.gain(card,'draw'); + } + 'step 1' + player.chooseToDiscard('he',[1,Infinity],{suit:'heart'},'林地溪流').set('ai',function(card){ + return 8-get.value(card); + }).set('prompt2','弃置任意张红桃牌,每弃置一张牌,将一张延时锦囊牌置入一名随机敌方角色的判定区'); + 'step 2' + if(result.bool){ + event.num=result.cards.length; + event.targets=player.getEnemies(); + } + 'step 3' + if(event.num&&event.targets&&event.targets.length){ + var target=event.targets.randomGet(); + var list=get.inpile('delay'); + for(var i=0;i0){ + player.useCard({name:name},target); + return; + } + } + } + }, + ai:{ + value:5, + useful:3, + order:4, + result:{ + player:1 + } + } + }, + mtg_haidao:{ + type:'land', + fullborder:'wood', + enable:function(card,player){ + return !player.hasSkill('land_used'); + }, + notarget:true, + content:function(){ + 'step 0' + game.changeLand('mtg_haidao',player); + if(player.isHealthy()){ + player.changeHujia(); + event.finish(); + } + else{ + player._temp_mtg_haidao=true; + player.chooseToDiscard('he','海岛').set('ai',function(card){ + return 5-get.value(card); + }).set('prompt2','弃置一张牌并回复一点体力,或取消并获得一点护甲'); + } + 'step 1' + if(result.bool){ + player.recover(); + } + else{ + player.changeHujia(); + } + 'step 2' + delete player._temp_mtg_haidao; + }, + ai:{ + value:5, + useful:3, + order:4, + result:{ + player:1 + } + } + }, + mtg_yixialan:{ + type:'land', + fullborder:'wood', + enable:function(card,player){ + return !player.hasSkill('land_used'); + }, + notarget:true, + content:function(){ + 'step 0' + game.changeLand('mtg_yixialan',player); + player.chooseControl('基本牌','锦囊牌').set('prompt','选择一个类型获得该类型的一张牌').set('ai',function(){ + var player=_status.event.player; + if(!player.hasSha()||!player.hasShan()||player.hp==1) return 0; + return 1; + }); + 'step 1' + if(result.control=='基本牌'){ + player.gain(game.createCard(get.inpile('basic').randomGet()),'gain2'); + } + else{ + player.gain(game.createCard(get.inpile('trick','trick').randomGet()),'gain2'); + } + }, + ai:{ + value:5, + useful:3, + order:4, + result:{ + player:1 + } + } + }, + mtg_shuimomuxue:{ + type:'land', + fullborder:'wood', + enable:function(card,player){ + return !player.hasSkill('land_used'); + }, + notarget:true, + content:function(){ + 'step 0' + game.changeLand('mtg_shuimomuxue',player); + if(player.countDiscardableCards('he')){ + player.chooseToDiscard('是否弃置一张牌并摸两张牌?','he').set('ai',function(card){ + return 8-get.value(card); + }); + } + else{ + event.finish(); + } + 'step 1' + if(result.bool){ + player.draw(2); + } + }, + ai:{ + value:5, + useful:3, + order:4, + result:{ + player:function(player){ + if(!player.countCards('h',function(card){ + return card.name!='mtg_shuimomuxue'&&get.value(card)<8; + })){ + return 0; + } + return 1; + } + } + } + }, + mtg_feixu:{ + type:'land', + fullborder:'wood', + enable:function(card,player){ + return !player.hasSkill('land_used'); + }, + notarget:true, + content:function(){ + game.changeLand('mtg_feixu',player); + player.discoverCard(ui.discardPile.childNodes); + }, + ai:{ + value:5, + useful:3, + order:4, + result:{ + player:1 + } + } + }, + mtg_shamolvzhou:{ + type:'land', + fullborder:'wood', + enable:function(card,player){ + return !player.hasSkill('land_used'); + }, + notarget:true, + content:function(){ + game.changeLand('mtg_shamolvzhou',player); + player.discoverCard(get.inpile('basic')); + }, + ai:{ + value:5, + useful:3, + order:4, + result:{ + player:1 + } + } + }, + mtg_duzhao:{ + type:'land', + fullborder:'wood', + enable:function(card,player){ + return !player.hasSkill('land_used'); + }, + notarget:true, + content:function(){ + 'step 0' + game.changeLand('mtg_duzhao',player); + player.chooseTarget('选择一名角色令其获得一张毒',true).set('ai',function(target){ + if(target.hasSkillTag('nodu')) return 0; + return -get.attitude(_status.event.player,target)/Math.sqrt(target.hp+1); + }); + 'step 1' + if(result.bool){ + var target=result.targets[0]; + player.line(target,'green'); + target.gain(game.createCard('du'),'gain2'); + } + }, + ai:{ + value:5, + useful:3, + order:4, + result:{ + player:function(player){ + if(game.countPlayer(function(current){ + if(current.hasSkillTag('nodu')) return false; + return get.attitude(player,current)<0; + })>game.countPlayer(function(current){ + if(current.hasSkillTag('nodu')) return false; + return get.attitude(player,current)>0; + })){ + return 1; + } + return 0; + } + } + } + }, + mtg_bingheyaosai:{ + type:'land', + fullborder:'wood', + enable:function(card,player){ + return !player.hasSkill('land_used'); + }, + notarget:true, + content:function(){ + 'step 0' + game.changeLand('mtg_bingheyaosai',player); + player.draw(2); + 'step 1' + player.chooseToDiscard('he',2,true); + }, + ai:{ + value:5, + useful:3, + order:2, + result:{ + player:1 + } + } + } + }, + skill:{ + mtg_bingheyaosai_skill:{ + trigger:{player:'useCard'}, + forced:true, + filter:function(event,player){ + if(player.countCards('he')==0) return false; + return event.card.name=='sha'; + }, + autodelay:true, + content:function(){ + player.chooseToDiscard(true,'he'); + }, + ai:{ + mapValue:-4 + } + }, + mtg_duzhao_skill:{ + trigger:{player:'phaseEnd'}, + forced:true, + content:function(){ + player.gain(game.createCard('du'),'gain2'); + }, + ai:{ + mapValue:-5 + } + }, + mtg_shamolvzhou_skill:{ + mod:{ + ignoredHandcard:function(card){ + if(get.type(card)=='basic'){ + return true; + } + } + }, + ai:{ + mapValue:3 + } + }, + mtg_haidao_skill:{ + trigger:{player:'changeHujiaBefore'}, + forced:true, + filter:function(event,player){ + return player.isDamaged()&&!player._temp_mtg_haidao&&event.num>0; + }, + content:function(){ + player.recover(trigger.num); + trigger.cancel(); + }, + ai:{ + mapValue:1 + } + }, + mtg_yixialan_skill:{ + enable:'phaseUse', + filter:function(event,player){ + return player.countCards('h',{type:'basic'})>0; + }, + filterCard:{type:'basic'}, + prepare:function(cards,player){ + player.$throw(cards,1000); + }, + discard:false, + delay:0.5, + check:function(card){ + return 7-get.value(card); + }, + usable:1, + content:function(){ + var card=get.cardPile(function(card){ + return get.type(card,'trick')=='trick' + }); + if(card){ + player.gain(card,'draw'); + } + else{ + player.draw(); + } + }, + ai:{ + mapValue:2, + order:1, + result:{ + player:1, + }, + } + }, + mtg_shuimomuxue_skill:{ + mod:{ + maxHandcard:function(player,num){ + return num-1; + } + }, + trigger:{player:'phaseDiscardEnd'}, + forced:true, + filter:function(event){ + return event.cards&&event.cards.length>0; + }, + content:function(){ + player.draw(); + }, + ai:{ + mapValue:2 + } + }, + mtg_feixu_skill:{ + trigger:{player:'phaseBegin'}, + silent:true, + content:function(){ + var num=ui.discardPile.childNodes.length; + if(num){ + var card=ui.discardPile.childNodes[get.rand(num)]; + if(card){ + ui.cardPile.insertBefore(card,ui.cardPile.firstChild); + } + } + }, + ai:{ + mapValue:0 + } + }, + mtg_youlin_skill:{ + enable:'phaseUse', + usable:1, + filter:function(event,player){ + return player.countCards('h',{type:['trick','delay']}); + }, + filterCard:function(card){ + return get.type(card,'trick')=='trick'; + }, + check:function(card){ + return 7-get.value(card); + }, + content:function(){ + player.discoverCard(); + }, + ai:{ + mapValue:1, + order:7, + result:{ + player:2 + } + } + }, + mtg_linzhongjianta_skill:{ + enable:'chooseToUse', + filterCard:function(card){ + return get.type(card)=='basic'; + }, + usable:1, + viewAs:{name:'sha'}, + viewAsFilter:function(player){ + if(!player.getEquip(1)) return false; + if(!player.countCards('h',{type:'basic'})) return false; + }, + prompt:'将一张基本牌当杀使用', + check:function(card){return 6-get.value(card)}, + ai:{ + mapValue:2, + respondSha:true, + order:function(){ + return get.order({name:'sha'})-0.1; + }, + skillTagFilter:function(player,tag,arg){ + if(arg!='use') return false; + if(!player.getEquip(1)) return false; + if(!player.countCards('h',{type:'basic'})) return false; + }, + } + }, + mtg_cangbaohaiwan_skill:{ + trigger:{player:'drawBegin'}, + silent:true, + content:function(){ + if(Math.random()<1/3){ + var list=[]; + for(var i=0;i
    • 地图牌可于出牌阶段使用,每阶段最多使用一张地图牌
    • '+ + '地图牌分为两部分:即时效果以及地图效果,即时效果由使用者在使用时选择;地图效果对所有角色有效
    • '+ + '当使用者死亡或下个回合开始时,当前地图效果消失
    • '+ + '新地图被使用时会覆盖当前地图效果' + }, + list:[ + ['club',12,'mtg_yixialan'], + ['spade',11,'mtg_shuimomuxue'], + ['diamond',5,'mtg_haidao'], + ['club',10,'mtg_youlin'], + ['club',8,'mtg_feixu'], + ['heart',6,'mtg_shamolvzhou'], + + ['club',12,'mtg_cangbaohaiwan'], + ['spade',11,'mtg_lindixiliu'], + ['diamond',5,'mtg_bingheyaosai'], + ['club',10,'mtg_longlushanfeng'], + ['club',8,'mtg_duzhao'], + ['heart',6,'mtg_linzhongjianta'], + ], + }; +}); diff --git a/card/sp.js b/card/sp.js index c7a8ecbd8..ff4216816 100644 --- a/card/sp.js +++ b/card/sp.js @@ -1,702 +1,702 @@ -'use strict'; -game.import('card',function(lib,game,ui,get,ai,_status){ - return { - name:'sp', - connect:true, - card:{ - jinchan:{ - audio:true, - fullskin:true, - wuxieable:true, - type:'trick', - notarget:true, - global:['g_jinchan','g_jinchan2'], - content:function(){ - var evt=event.getParent(3)._trigger; - if(evt.jinchan){ - var type=get.type(evt.card,'trick'); - if(type=='basic'||type=='trick'){ - evt.cancel(); - } - } - player.draw(2); - }, - ai:{ - useful:function(){ - var player=_status.event.player; - var nj=player.countCards('h','jinchan'); - var num=player.getHandcardLimit(); - if(nj>=num){ - return 10; - } - if(nj==num-1){ - return 6; - } - return 1; - }, - result:{ - player:1 - }, - value:5 - } - }, - qijia:{ - audio:true, - fullskin:true, - type:'trick', - enable:true, - filterTarget:function(card,player,target){ - if(target==player) return false; - if(target.getEquip(5)){ - return target.countCards('e')>1; - } - else{ - return target.countCards('e')>0; - } - }, - content:function(){ - 'step 0' - var e1=[],e2=[]; - var he=target.getCards('he'); - for(var i=0;ie2.length||(target.hp>=3&&Math.random()<0.3)){ - choice=1; - } - event.e1=e1; - event.e2=e2; - target.chooseControl(choice).set('choiceList',['弃置'+get.translation(e1),'弃置'+get.translation(e2)]); - } - else{ - if(e1.length){ - target.discard(e1); - } - else if(e2.length){ - target.discard(e2); - } - event.finish(); - } - 'step 1' - if(result.index==0){ - target.discard(event.e1); - } - else{ - target.discard(event.e2); - } - }, - ai:{ - order:9.01, - useful:1, - value:5, - result:{ - target:function(player,target){ - var num1=0,num2=0; - for(var i=1;i<=4;i++){ - var card=target.getEquip(i); - if(card){ - if(i==1||i==4){ - num1+=get.equipValue(card); - } - else{ - num2+=get.equipValue(card); - } - } - } - var num; - if(num1==0){ - num=num2; - } - else if(num2==0){ - num=num1; - } - else{ - num=Math.min(num1,num2); - } - if(num>0){ - return -0.8-num/10; - } - else{ - return 0; - } - } - }, - tag:{ - loseCard:1, - discard:1 - } - } - }, - fulei:{ - audio:true, - fullskin:true, - type:'delay', - cardnature:'thunder', - modTarget:function(card,player,target){ - return lib.filter.judge(card,player,target); - }, - enable:function(card,player){ - return player.canAddJudge(card); - }, - filterTarget:function(card,player,target){ - return (lib.filter.judge(card,player,target)&&player==target); - }, - selectTarget:[-1,-1], - judge:function(card){ - if(get.suit(card)=='spade') return -6; - return 0; - }, - effect:function(){ - 'step 0' - if(result.bool==false){ - if(!card.storage.fulei){ - card.storage.fulei=1; - } - else{ - card.storage.fulei++; - } - player.damage(card.storage.fulei,'thunder','nosource'); - } - 'step 1' - player.addJudgeNext(card); - }, - cancel:function(){ - player.addJudgeNext(card); - }, - ai:{ - basic:{ - order:1, - useful:0, - value:0, - }, - result:{ - target:function(player,target){ - return lib.card.shandian.ai.result.target(player,target); - } - }, - } - }, - qibaodao:{ - audio:true, - fullskin:true, - type:'equip', - subtype:'equip1', - skills:['qibaodao','qibaodao2'], - distance:{attackFrom:-1}, - ai:{ - equipValue:function(card,player){ - if(game.hasPlayer(function(current){ - return player.canUse('sha',current)&¤t.isHealthy()&&get.attitude(player,current)<0; - })){ - return 5; - } - return 3; - }, - basic:{ - equipValue:5 - } - }, - }, - zhungangshuo:{ - audio:true, - fullskin:true, - type:'equip', - subtype:'equip1', - skills:['zhungangshuo'], - distance:{attackFrom:-2}, - ai:{ - equipValue:4 - }, - }, - lanyinjia:{ - fullskin:true, - type:'equip', - subtype:'equip2', - skills:['lanyinjia','lanyinjia2'], - ai:{ - equipValue:6 - } - }, - yinyueqiang:{ - audio:true, - fullskin:true, - type:'equip', - subtype:'equip1', - distance:{attackFrom:-2}, - ai:{ - basic:{ - equipValue:4 - } - }, - skills:['yinyueqiang'] - }, - - du:{ - type:'basic', - fullskin:true, - toself:true, - ai:{ - value:-5, - useful:6, - result:{ - player:function(player,target){ - if(player.hasSkillTag('usedu')) return 5; - return -1; - } - }, - order:7.5 - }, - enable:true, - modTarget:true, - global:'g_du', - filterTarget:function(card,player,target){ - return target==player; - }, - delay:false, - content:function(){}, - selectTarget:-1 - }, - shengdong:{ - audio:true, - fullskin:true, - enable:function(){ - return game.countPlayer()>2; - }, - chongzhu:function(){ - return game.countPlayer()<=2; - }, - singleCard:true, - type:'trick', - selectTarget:2, - complexTarget:true, - multitarget:true, - targetprompt:['给一张牌','得两张牌'], - filterTarget:function(card,player,target){ - return target!=player; - }, - content:function(){ - 'step 0' - if(!player.countCards('h')){ - event.finish(); - } - else{ - event.target1=target; - event.target2=event.addedTarget; - player.chooseCard('h','将一张手牌交给'+get.translation(event.target1),true); - } - 'step 1' - player.$giveAuto(result.cards,event.target1); - event.target1.gain(result.cards,player); - 'step 2' - if(!event.target1.countCards('h')){ - event.finish(); - } - else{ - var he=event.target1.getCards('he'); - if(he.length<=2){ - event.directresult=he; - } - else{ - event.target1.chooseCard('he','将两张牌交给'+get.translation(event.target2),2,true); - } - } - 'step 3' - if(!event.directresult){ - event.directresult=result.cards; - } - event.target1.$giveAuto(event.directresult,event.target2); - event.target2.gain(event.directresult,event.target1); - }, - ai:{ - order:2.5, - value:[4,1], - useful:1, - wuxie:function(){ - return 0; - }, - result:{ - target:function(player,target){ - var ok=false; - var hs=player.getCards('h'); - if(hs.length<=1) return 0; - for(var i=0;i1).set('prompt2','弃置一张非基本牌,或取消并弃置两张牌'); - event.more=true; - } - else{ - target.chooseToDiscard('he',2,true).set('ai',get.disvalue2); - } - 'step 2' - if(event.more&&!result.bool){ - target.chooseToDiscard('he',2,true).set('ai',get.disvalue2); - } - }, - ai:{ - order:7, - useful:4, - value:10, - tag:{ - draw:2 - }, - result:{ - target:function(player,target){ - if(target.hasJudge('lebu')) return 0; - return Math.max(1,2-target.countCards('h')/10); - } - } - } - }, - caomu:{ - audio:true, - fullskin:true, - enable:true, - type:'delay', - filterTarget:function(card,player,target){ - return (lib.filter.judge(card,player,target)&&player!=target); - }, - judge:function(card){ - if(get.suit(card)=='club') return 0; - return -3; - }, - effect:function(){ - if(result.bool==false){ - player.addTempSkill('caomu_skill'); - } - }, - ai:{ - basic:{ - order:1, - useful:1, - value:4.5, - }, - result:{ - player:function(player,target){ - return game.countPlayer(function(current){ - if(get.distance(target,current)<=1&¤t!=target){ - var att=get.attitude(player,current); - if(att>3){ - return 1.1; - } - else if(att>0){ - return 1; - } - else if(att<-3){ - return -1.1; - } - else if(att<0){ - return -1; - } - } - }); - }, - target:function(player,target){ - if(target.hasJudge('bingliang')) return 0; - return -1.5/Math.sqrt(target.countCards('h')+1); - } - }, - } - } - }, - skill:{ - lanyinjia:{ - equipSkill:true, - enable:['chooseToUse','chooseToRespond'], - filterCard:true, - viewAs:{name:'shan'}, - viewAsFilter:function(player){ - if(!player.countCards('h')) return false; - }, - prompt:'将一张手牌当闪使用或打出', - check:function(card){ - return 6-get.value(card); - }, - ai:{ - respondShan:true, - skillTagFilter:function(player){ - if(!player.countCards('h')) return false; - }, - effect:{ - target:function(card,player,target,current){ - if(get.tag(card,'respondShan')&¤t<0&&target.countCards('h')) return 0.59 - } - }, - order:4, - useful:-0.5, - value:-0.5 - } - }, - lanyinjia2:{ - equipSkill:true, - trigger:{player:'damageBegin4'}, - forced:true, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&player.getEquip('lanyinjia'); - }, - content:function(){ - var card=player.getEquip('lanyinjia'); - if(card){ - player.discard(card); - } - }, - }, - zhungangshuo:{ - equipSkill:true, - trigger:{player:'useCardToPlayered'}, - logTarget:'target', - filter:function(event,player){ - return event.card.name=='sha'&&(event.player.countCards('h')||player.countCards('h')); - }, - check:function(event,player){ - var target=event.target; - if(get.attitude(player,target)>=0) return false; - if(player.hasCard(function(card){ - return get.value(card)>=8; - })){ - return false; - } - var n1=event.target.countCards('h'); - return n1>0&&n1<=player.countCards('h'); - }, - content:function(){ - 'step 0' - game.delayx(); - 'step 1' - trigger.target.discardPlayerCard('h',player,true); - 'step 2' - player.discardPlayerCard('h',trigger.target,true); - } - }, - qibaodao:{ - equipSkill:true, - trigger:{source:'damageBegin1'}, - forced:true, - filter:function(event){ - return event.card&&event.card.name=='sha'&&event.player.isHealthy(); - }, - content:function(){ - trigger.num++; - }, - ai:{ - effect:{ - player:function(card,player,target){ - if(card.name=='sha'&&target.isHealthy()&&get.attitude(player,target)>0){ - return [1,-2]; - } - } - } - } - }, - qibaodao2:{ - inherit:'qinggang_skill', - }, - g_jinchan:{ - cardSkill:true, - trigger:{target:'useCardToBefore'}, - forced:true, - popup:false, - filter:function(event,player){ - if(event.player==player) return false; - if(event.getParent().directHit.contains(player)) return false; - var num=player.countCards('h','jinchan'); - return num&&num==player.countCards('h'); - }, - content:function(){ - 'step 0' - player.chooseToUse({name:'jinchan'},'是否对'+get.translation(trigger.card)+'使用【金蝉脱壳】?').set('ai1',function(card){ - return _status.event.bool; - }).set('bool',-get.effect(player,trigger.card,trigger.player,player)).set('respondTo',[trigger.player,trigger.card]); - trigger.jinchan=true; - 'step 1' - delete trigger.jinchan; - } - }, - g_jinchan2:{ - trigger:{player:'discardAfter'}, - forced:true, - filter:function(event,player){ - for(var i=0;i=num){ + return 10; + } + if(nj==num-1){ + return 6; + } + return 1; + }, + result:{ + player:1 + }, + value:5 + } + }, + qijia:{ + audio:true, + fullskin:true, + type:'trick', + enable:true, + filterTarget:function(card,player,target){ + if(target==player) return false; + if(target.getEquip(5)){ + return target.countCards('e')>1; + } + else{ + return target.countCards('e')>0; + } + }, + content:function(){ + 'step 0' + var e1=[],e2=[]; + var he=target.getCards('he'); + for(var i=0;ie2.length||(target.hp>=3&&Math.random()<0.3)){ + choice=1; + } + event.e1=e1; + event.e2=e2; + target.chooseControl(choice).set('choiceList',['弃置'+get.translation(e1),'弃置'+get.translation(e2)]); + } + else{ + if(e1.length){ + target.discard(e1); + } + else if(e2.length){ + target.discard(e2); + } + event.finish(); + } + 'step 1' + if(result.index==0){ + target.discard(event.e1); + } + else{ + target.discard(event.e2); + } + }, + ai:{ + order:9.01, + useful:1, + value:5, + result:{ + target:function(player,target){ + var num1=0,num2=0; + for(var i=1;i<=4;i++){ + var card=target.getEquip(i); + if(card){ + if(i==1||i==4){ + num1+=get.equipValue(card); + } + else{ + num2+=get.equipValue(card); + } + } + } + var num; + if(num1==0){ + num=num2; + } + else if(num2==0){ + num=num1; + } + else{ + num=Math.min(num1,num2); + } + if(num>0){ + return -0.8-num/10; + } + else{ + return 0; + } + } + }, + tag:{ + loseCard:1, + discard:1 + } + } + }, + fulei:{ + audio:true, + fullskin:true, + type:'delay', + cardnature:'thunder', + modTarget:function(card,player,target){ + return lib.filter.judge(card,player,target); + }, + enable:function(card,player){ + return player.canAddJudge(card); + }, + filterTarget:function(card,player,target){ + return (lib.filter.judge(card,player,target)&&player==target); + }, + selectTarget:[-1,-1], + judge:function(card){ + if(get.suit(card)=='spade') return -6; + return 0; + }, + effect:function(){ + 'step 0' + if(result.bool==false){ + if(!card.storage.fulei){ + card.storage.fulei=1; + } + else{ + card.storage.fulei++; + } + player.damage(card.storage.fulei,'thunder','nosource'); + } + 'step 1' + player.addJudgeNext(card); + }, + cancel:function(){ + player.addJudgeNext(card); + }, + ai:{ + basic:{ + order:1, + useful:0, + value:0, + }, + result:{ + target:function(player,target){ + return lib.card.shandian.ai.result.target(player,target); + } + }, + } + }, + qibaodao:{ + audio:true, + fullskin:true, + type:'equip', + subtype:'equip1', + skills:['qibaodao','qibaodao2'], + distance:{attackFrom:-1}, + ai:{ + equipValue:function(card,player){ + if(game.hasPlayer(function(current){ + return player.canUse('sha',current)&¤t.isHealthy()&&get.attitude(player,current)<0; + })){ + return 5; + } + return 3; + }, + basic:{ + equipValue:5 + } + }, + }, + zhungangshuo:{ + audio:true, + fullskin:true, + type:'equip', + subtype:'equip1', + skills:['zhungangshuo'], + distance:{attackFrom:-2}, + ai:{ + equipValue:4 + }, + }, + lanyinjia:{ + fullskin:true, + type:'equip', + subtype:'equip2', + skills:['lanyinjia','lanyinjia2'], + ai:{ + equipValue:6 + } + }, + yinyueqiang:{ + audio:true, + fullskin:true, + type:'equip', + subtype:'equip1', + distance:{attackFrom:-2}, + ai:{ + basic:{ + equipValue:4 + } + }, + skills:['yinyueqiang'] + }, + + du:{ + type:'basic', + fullskin:true, + toself:true, + ai:{ + value:-5, + useful:6, + result:{ + player:function(player,target){ + if(player.hasSkillTag('usedu')) return 5; + return -1; + } + }, + order:7.5 + }, + enable:true, + modTarget:true, + global:'g_du', + filterTarget:function(card,player,target){ + return target==player; + }, + delay:false, + content:function(){}, + selectTarget:-1 + }, + shengdong:{ + audio:true, + fullskin:true, + enable:function(){ + return game.countPlayer()>2; + }, + chongzhu:function(){ + return game.countPlayer()<=2; + }, + singleCard:true, + type:'trick', + selectTarget:2, + complexTarget:true, + multitarget:true, + targetprompt:['给一张牌','得两张牌'], + filterTarget:function(card,player,target){ + return target!=player; + }, + content:function(){ + 'step 0' + if(!player.countCards('h')){ + event.finish(); + } + else{ + event.target1=target; + event.target2=event.addedTarget; + player.chooseCard('h','将一张手牌交给'+get.translation(event.target1),true); + } + 'step 1' + player.$giveAuto(result.cards,event.target1); + event.target1.gain(result.cards,player); + 'step 2' + if(!event.target1.countCards('h')){ + event.finish(); + } + else{ + var he=event.target1.getCards('he'); + if(he.length<=2){ + event.directresult=he; + } + else{ + event.target1.chooseCard('he','将两张牌交给'+get.translation(event.target2),2,true); + } + } + 'step 3' + if(!event.directresult){ + event.directresult=result.cards; + } + event.target1.$giveAuto(event.directresult,event.target2); + event.target2.gain(event.directresult,event.target1); + }, + ai:{ + order:2.5, + value:[4,1], + useful:1, + wuxie:function(){ + return 0; + }, + result:{ + target:function(player,target){ + var ok=false; + var hs=player.getCards('h'); + if(hs.length<=1) return 0; + for(var i=0;i1).set('prompt2','弃置一张非基本牌,或取消并弃置两张牌'); + event.more=true; + } + else{ + target.chooseToDiscard('he',2,true).set('ai',get.disvalue2); + } + 'step 2' + if(event.more&&!result.bool){ + target.chooseToDiscard('he',2,true).set('ai',get.disvalue2); + } + }, + ai:{ + order:7, + useful:4, + value:10, + tag:{ + draw:2 + }, + result:{ + target:function(player,target){ + if(target.hasJudge('lebu')) return 0; + return Math.max(1,2-target.countCards('h')/10); + } + } + } + }, + caomu:{ + audio:true, + fullskin:true, + enable:true, + type:'delay', + filterTarget:function(card,player,target){ + return (lib.filter.judge(card,player,target)&&player!=target); + }, + judge:function(card){ + if(get.suit(card)=='club') return 0; + return -3; + }, + effect:function(){ + if(result.bool==false){ + player.addTempSkill('caomu_skill'); + } + }, + ai:{ + basic:{ + order:1, + useful:1, + value:4.5, + }, + result:{ + player:function(player,target){ + return game.countPlayer(function(current){ + if(get.distance(target,current)<=1&¤t!=target){ + var att=get.attitude(player,current); + if(att>3){ + return 1.1; + } + else if(att>0){ + return 1; + } + else if(att<-3){ + return -1.1; + } + else if(att<0){ + return -1; + } + } + }); + }, + target:function(player,target){ + if(target.hasJudge('bingliang')) return 0; + return -1.5/Math.sqrt(target.countCards('h')+1); + } + }, + } + } + }, + skill:{ + lanyinjia:{ + equipSkill:true, + enable:['chooseToUse','chooseToRespond'], + filterCard:true, + viewAs:{name:'shan'}, + viewAsFilter:function(player){ + if(!player.countCards('h')) return false; + }, + prompt:'将一张手牌当闪使用或打出', + check:function(card){ + return 6-get.value(card); + }, + ai:{ + respondShan:true, + skillTagFilter:function(player){ + if(!player.countCards('h')) return false; + }, + effect:{ + target:function(card,player,target,current){ + if(get.tag(card,'respondShan')&¤t<0&&target.countCards('h')) return 0.59 + } + }, + order:4, + useful:-0.5, + value:-0.5 + } + }, + lanyinjia2:{ + equipSkill:true, + trigger:{player:'damageBegin4'}, + forced:true, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&player.getEquip('lanyinjia'); + }, + content:function(){ + var card=player.getEquip('lanyinjia'); + if(card){ + player.discard(card); + } + }, + }, + zhungangshuo:{ + equipSkill:true, + trigger:{player:'useCardToPlayered'}, + logTarget:'target', + filter:function(event,player){ + return event.card.name=='sha'&&(event.player.countCards('h')||player.countCards('h')); + }, + check:function(event,player){ + var target=event.target; + if(get.attitude(player,target)>=0) return false; + if(player.hasCard(function(card){ + return get.value(card)>=8; + })){ + return false; + } + var n1=event.target.countCards('h'); + return n1>0&&n1<=player.countCards('h'); + }, + content:function(){ + 'step 0' + game.delayx(); + 'step 1' + trigger.target.discardPlayerCard('h',player,true); + 'step 2' + player.discardPlayerCard('h',trigger.target,true); + } + }, + qibaodao:{ + equipSkill:true, + trigger:{source:'damageBegin1'}, + forced:true, + filter:function(event){ + return event.card&&event.card.name=='sha'&&event.player.isHealthy(); + }, + content:function(){ + trigger.num++; + }, + ai:{ + effect:{ + player:function(card,player,target){ + if(card.name=='sha'&&target.isHealthy()&&get.attitude(player,target)>0){ + return [1,-2]; + } + } + } + } + }, + qibaodao2:{ + inherit:'qinggang_skill', + }, + g_jinchan:{ + cardSkill:true, + trigger:{target:'useCardToBefore'}, + forced:true, + popup:false, + filter:function(event,player){ + if(event.player==player) return false; + if(event.getParent().directHit.contains(player)) return false; + var num=player.countCards('h','jinchan'); + return num&&num==player.countCards('h'); + }, + content:function(){ + 'step 0' + player.chooseToUse({name:'jinchan'},'是否对'+get.translation(trigger.card)+'使用【金蝉脱壳】?').set('ai1',function(card){ + return _status.event.bool; + }).set('bool',-get.effect(player,trigger.card,trigger.player,player)).set('respondTo',[trigger.player,trigger.card]); + trigger.jinchan=true; + 'step 1' + delete trigger.jinchan; + } + }, + g_jinchan2:{ + trigger:{player:'discardAfter'}, + forced:true, + filter:function(event,player){ + for(var i=0;i0; - // } - // }, - // selectTarget:2, - // multitarget:true, - // complexTarget:true, - // multicheck:function(){ - // return game.hasPlayer(function(current){ - // return current.getEquip(5); - // })&&game.hasPlayer(function(current){ - // return !current.getEquip(5); - // }); - // }, - // content:function(){ - // if(target.getEquip(5)){ - // target.$give(target.getEquip(5),event.addedTarget); - // event.addedTarget.equip(target.getEquip(5)); - // game.delay(); - // } - // }, - // ai:{ - // order:1, - // result:{ - // target:function(player,target){ - // if(target.getCards('e',{subtype:'equip5'}).length){ - // if(get.attitude(target,player)>0){ - // return -0.5; - // } - // return -1; - // } - // return 1; - // } - // }, - // tag:{ - // loseCard:1 - // } - // } - // }, - liuxinghuoyu:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:true, - cardcolor:'red', - cardnature:'fire', - content:function(){ - "step 0" - if(target.countCards('he')<2){ - event.directfalse=true; - } - else{ - target.chooseToDiscard('he',2).ai=function(card){ - if(target.hasSkillTag('nofire')) return 0; - if(get.damageEffect(target,player,target,'fire')>=0) return 0; - if(player.hasSkillTag('notricksource')) return 0; - if(target.hasSkillTag('notrick')) return 0; - if(card.name=='tao') return 0; - if(target.hp==1&&card.name=='jiu') return 0; - if(target.hp==1&&get.type(card)!='basic'){ - return 10-get.value(card); - } - return 8-get.value(card); - }; - } - "step 1" - if(event.directfalse||!result.bool){ - target.damage('fire'); - } - }, - ai:{ - basic:{ - order:4, - value:7, - useful:2, - }, - result:{ - target:function(player,target){ - if(target.hasSkillTag('nofire')) return 0; - if(get.damageEffect(target,player,player)<0&&get.attitude(player,target)>0){ - return -2; - } - var nh=target.countCards('he'); - if(target==player) nh--; - switch(nh){ - case 0:case 1:return -2; - case 2:return -1.5; - case 3:return -1; - default:return -0.7; - } - } - }, - tag:{ - damage:1, - fireDamage:1, - natureDamage:1, - discard:1, - loseCard:1, - position:'he', - } - } - }, - dujian:{ - fullskin:true, - type:'basic', - enable:true, - filterTarget:function(card,player,target){ - return target.countCards('h')>0; - }, - content:function(){ - "step 0" - if(target.countCards('h')==0||player.countCards('h')==0){ - event.finish(); - return; - } - player.chooseCard(true); - "step 1" - event.card1=result.cards[0]; - var rand=Math.random()<0.5; - target.chooseCard(true).ai=function(card){ - if(rand) return Math.random(); - return get.value(card); - }; - "step 2" - event.card2=result.cards[0]; - ui.arena.classList.add('thrownhighlight'); - game.addVideo('thrownhighlight1'); - player.$compare(event.card1,target,event.card2); - game.delay(4); - "step 3" - game.log(player,'展示了',event.card1); - game.log(target,'展示了',event.card2); - if(get.color(event.card2)==get.color(event.card1)){ - player.discard(event.card1).animate=false; - target.$gain2(event.card2); - var clone=event.card1.clone; - if(clone){ - clone.style.transition='all 0.5s'; - clone.style.transform='scale(1.2)'; - clone.delete(); - game.addVideo('deletenode',player,get.cardsInfo([clone])); - } - target.loseHp(); - } - else{ - player.$gain2(event.card1); - target.$gain2(event.card2); - target.addTempSkill('dujian2'); - } - ui.arena.classList.remove('thrownhighlight'); - game.addVideo('thrownhighlight2'); - }, - ai:{ - basic:{ - order:2, - value:3, - useful:1, - }, - result:{ - player:function(player,target){ - if(player.countCards('h')<=Math.min(5,Math.max(2,player.hp))&&_status.event.name=='chooseToUse'){ - if(typeof _status.event.filterCard=='function'&& - _status.event.filterCard({name:'dujian'})){ - return -10; - } - if(_status.event.skill){ - var viewAs=get.info(_status.event.skill).viewAs; - if(viewAs=='dujian') return -10; - if(viewAs&&viewAs.name=='dujian') return -10; - } - } - return 0; - }, - target:function(player,target){ - if(target.hasSkill('dujian2')||target.countCards('h')==0) return 0; - if(player.countCards('h')<=1) return 0; - return -1.5; - } - }, - tag:{ - loseHp:1 - } - } - }, - yangpijuan:{ - fullskin:true, - type:'trick', - enable:true, - toself:true, - filterTarget:function(card,player,target){ - return target==player; - }, - modTarget:true, - content:function(){ - 'step 0' - var choice; - if(target.countCards('h','shan')==0||target.countCards('h','sha')==0||target.hp<=1){ - choice='basic'; - } - else{ - var e2=target.getEquip(2); - var e3=target.getEquip(3); - if((e2&&e3)||((e2||e3)&&target.needsToDiscard()<=1)||Math.random()<0.5){ - choice='trick'; - } - else{ - choice='equip'; - } - } - target.chooseControl('basic','trick','equip',function(){ - return choice; - }).set('prompt','选择一种卡牌类型'); - 'step 1' - var list=get.inpile(result.control,'trick'); - list=list.randomGets(3); - for(var i=0;i=0) return 0; - return 1; - } - else{ - if(get.attitude(player,target)>0) return 0; - if(get.damageEffect(target,player,target)>=0) return 0; - return -1; - } - } - }, - } - }, - yuruyi:{ - type:'equip', - subtype:'equip5', - skills:['yuruyi'], - fullskin:true, - ai:{ - basic:{ - equipValue:6 - } - }, - }, - fengyinzhidan:{ - type:'basic', - enable:true, - fullskin:true, - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - modTarget:true, - // usable:1, - content:function(){ - 'step 0' - event.num=2; - var list=[]; - event.list=list; - for(var i=0;i0&&info.selectTarget[1]>=info.selectTarget[0]){ - list.push(lib.inpile[i]); - } - } - else if(typeof info.selectTarget=='number'){ - list.push(lib.inpile[i]); - } - } - } - } - 'step 1' - var list=event.list; - while(list.length){ - var card={name:list.randomRemove()}; - var info=get.info(card); - var targets=game.filterPlayer(function(current){ - return lib.filter.filterTarget(card,target,current); - }); - if(targets.length){ - targets.sort(lib.sort.seat); - if(info.selectTarget==-1){ - target.useCard(card,targets,'noai'); - } - else{ - var num=info.selectTarget; - if(Array.isArray(num)){ - if(targets.length0){ - event.redo(); - } - break; - } - } - }, - ai:{ - order:9, - value:8, - useful:3, - result:{ - target:1 - } - } - }, - yuchanqian:{ - fullskin:true, - type:'jiqi', - addinfo:'杀', - autoViewAs:'sha', - global:['g_yuchan_swap','g_yuchan_equip'], - ai:{ - value:6, - useful:[5,1] - } - }, - yuchankun:{ - fullskin:true, - type:'jiqi', - addinfo:'药', - autoViewAs:'caoyao', - global:['g_yuchan_swap','g_yuchan_equip'], - ai:{ - value:6, - useful:[7,2] - } - }, - yuchanzhen:{ - fullskin:true, - type:'jiqi', - autoViewAs:'jiu', - addinfo:'酒', - global:['g_yuchan_swap','g_yuchan_equip'], - savable:function(card,player,dying){ - return dying==player; - }, - ai:{ - value:6, - useful:[4,1] - } - }, - yuchanxun:{ - fullskin:true, - type:'jiqi', - autoViewAs:'tao', - addinfo:'桃', - global:['g_yuchan_swap','g_yuchan_equip'], - savable:true, - ai:{ - value:6, - useful:[8,6.5] - } - }, - yuchankan:{ - fullskin:true, - type:'jiqi', - autoViewAs:'shenmiguo', - global:['g_yuchan_swap','g_yuchan_equip'], - addinfo:'果', - ai:{ - order:1, - useful:4, - value:6, - result:{ - player:function(){ - var cardname=_status.event.cardname; - if(cardname=='tiesuo') return 0; - if(cardname=='jiu') return 0; - if(cardname=='tianxianjiu') return 0; - if(cardname=='toulianghuanzhu') return 0; - if(cardname=='shijieshu') return 0; - if(cardname=='xietianzi') return 0; - if(cardname=='huogong') return 0; - if(cardname=='shandianjian') return 0; - return 1; - } - }, - } - }, - yuchanli:{ - fullskin:true, - type:'jiqi', - autoViewAs:'tianxianjiu', - global:['g_yuchan_swap','g_yuchan_equip'], - addinfo:'仙', - savable:function(card,player,dying){ - return dying==player; - }, - ai:{ - value:6, - useful:1 - } - }, - yuchangen:{ - fullskin:true, - type:'jiqi', - addinfo:'蛋', - autoViewAs:'fengyinzhidan', - global:['g_yuchan_swap','g_yuchan_equip'], - ai:{ - value:6, - useful:1 - } - }, - yuchandui:{ - fullskin:true, - type:'jiqi', - addinfo:'雪', - autoViewAs:'xuejibingbao', - global:['g_yuchan_swap','g_yuchan_equip'], - ai:{ - value:6, - useful:4 - } - }, - mujiaren:{ - fullskin:true, - enable:true, - type:'jiguan', - usable:1, - forceUsable:true, - wuxieable:true, - selectTarget:-1, - filterTarget:function(card,player,target){ - return target==player; - }, - content:function(){ - 'step 0' - var cards=target.getCards('h',function(card){ - return get.type(card)!='basic'; - }); - if(cards.length){ - target.lose(cards)._triggered=null; - } - event.num=1+cards.length; - 'step 1' - var cards=[]; - var list=get.typeCard('jiguan'); - for(var i=0;i=0) return 0; - return 8-get.useful(card); - }); - } - else{ - target.damage('fire'); - event.parent.preResult=true; - event.finish(); - } - "step 1" - if(result.bool==false){ - target.damage('fire'); - event.parent.preResult=true; - } - }, - contentAfter:function(){ - if(!event.preResult) player.draw(); - }, - ai:{ - wuxie:function(target,card,player,viewer){ - if(get.attitude(viewer,target)>0){ - if(target.countCards('h')>0||target.hp>1) return 0; - } - }, - basic:{ - order:9, - useful:1 - }, - result:{ - target:function(player,target){ - if(player.hasUnknown(2)) return 0; - var nh=target.countCards('h'); - if(get.mode()=='identity'){ - if(target.isZhu&&nh<=1&&target.hp<=1) return -100; - } - if(nh==0) return -1; - if(nh==1) return -0.7 - return -0.5; - }, - }, - tag:{ - discard:1, - loseCard:1, - damage:1, - natureDamage:1, - fireDamage:1, - multitarget:1, - multineg:1, - } - } - }, - donghuangzhong:{ - fullskin:true, - type:'equip', - subtype:'equip5', - nomod:true, - nopower:true, - unique:true, - skills:['donghuangzhong'], - ai:{ - equipValue:7 - } - }, - xuanyuanjian:{ - fullskin:true, - type:'equip', - subtype:'equip1', - nomod:true, - nopower:true, - unique:true, - skills:['xuanyuanjian','xuanyuanjian2','xuanyuanjian3'], - enable:function(card,player){ - return player.hasSkill('xuanyuan')||player.hp>2; - }, - distance:{attackFrom:-2}, - onEquip:function(){ - if(!player.hasSkill('xuanyuan')&&player.hp<=2){ - player.discard(card); - } - else{ - player.changeHujia(); - } - }, - ai:{ - equipValue:9 - } - }, - pangufu:{ - fullskin:true, - type:'equip', - subtype:'equip1', - skills:['pangufu'], - nomod:true, - nopower:true, - unique:true, - distance:{attackFrom:-3}, - ai:{ - equipValue:8 - } - }, - lianyaohu:{ - fullskin:true, - type:'equip', - subtype:'equip5', - equipDelay:false, - loseDelay:false, - nomod:true, - nopower:true, - unique:true, - onEquip:function(){ - player.markSkill('lianyaohu_skill'); - }, - onLose:function(){ - player.unmarkSkill('lianyaohu_skill'); - }, - clearLose:true, - ai:{ - equipValue:6 - }, - skills:['lianhua','shouna','lianyaohu_skill'] - }, - haotianta:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['haotianta'], - nomod:true, - nopower:true, - unique:true, - ai:{ - equipValue:7 - } - }, - fuxiqin:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['kongxin'], - nomod:true, - nopower:true, - unique:true, - ai:{ - equipValue:6 - } - }, - shennongding:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['shennongding'], - nomod:true, - nopower:true, - unique:true, - ai:{ - equipValue:6 - } - }, - kongdongyin:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['kongdongyin'], - nomod:true, - nopower:true, - unique:true, - ai:{ - equipValue:function(card,player){ - if(player.hp==2) return 7; - if(player.hp==1) return 10; - return 5; - }, - basic:{ - equipValue:7 - } - } - }, - kunlunjingc:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['kunlunjingc'], - nomod:true, - nopower:true, - unique:true, - ai:{ - equipValue:6 - } - }, - nvwashi:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['nvwashi'], - nomod:true, - nopower:true, - unique:true, - ai:{ - equipValue:5 - } - }, - guisheqi:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:true, - content:function(){ - target.changeHujia(); - }, - ai:{ - basic:{ - order:5, - useful:3, - value:[6,2,1] - }, - result:{ - target:function(player,target){ - return 2/Math.max(1,Math.sqrt(target.hp)); - }, - }, - } - }, - jiguanfeng:{ - fullskin:true, - type:'jiguan', - enable:true, - wuxieable:true, - filterTarget:function(card,player,target){ - return target!=player; - }, - content:function(){ - "step 0" - var next=target.chooseToRespond({name:'shan'}); - next.autochoose=lib.filter.autoRespondShan; - "step 1" - if(result.bool==false){ - if(!target.hasSkill('fengyin')){ - target.addTempSkill('fengyin',{player:'phaseBegin'}); - } - target.damage(); - } - else{ - event.finish(); - } - }, - ai:{ - basic:{ - order:9, - useful:3, - value:6.5, - }, - result:{ - target:-2, - }, - tag:{ - respond:1, - respondShan:1, - // damage:1, - } - } - }, - jiguanyuan:{ - fullskin:true, - type:'jiguan', - wuxieable:true, - enable:function(card,player){ - var hs=player.getCards('he'); - return hs.length>1||(hs.length==1&&hs[0]!=card); - }, - filterTarget:function(card,player,target){ - return target!=player&&!target.hasSkill('jiguanyuan'); - }, - content:function(){ - 'step 0' - if(player.countCards('he')){ - player.chooseCard(true,'he').set('prompt2','你将'+ - get.translation(cards)+'和选择牌置于'+get.translation(target)+ - '的武将牌上,然后摸一张牌;'+get.translation(target)+ - '于下一结束阶段获得武将牌上的牌'); - } - else{ - event.finish(); - } - 'step 1' - player.$throw(result.cards); - player.lose(result.cards,ui.special); - ui.special.appendChild(cards[0]); - event.togive=[cards[0],result.cards[0]]; - game.delay(); - 'step 2' - // target.gain(event.togive).delay=false; - target.$gain2(event.togive); - target.storage.jiguanyuan=event.togive; - target.addSkill('jiguanyuan'); - game.log(target,'从',player,'获得了',event.togive); - player.draw(); - }, - ai:{ - basic:{ - order:2, - useful:2, - value:7 - }, - result:{ - target:function(player,target){ - var players=game.filterPlayer(function(current){ - return (current!=player&&!current.isTurnedOver()&& - get.attitude(player,current)>=3&&get.attitude(current,player)>=3) - }); - players.sort(lib.sort.seat); - if(target==players[0]) return 2; - return 0.5; - }, - }, - } - }, - shenmiguo_old:{ - fullskin:true, - type:'trick', - enable:true, - selectTarget:-1, - filterTarget:function(card,player,target){ - return target==player; - }, - modTarget:true, - content:function(){ - var list=[]; - for(var i in lib.card){ - if(lib.card[i].derivation){ - list.push(i); - } - } - if(get.mode()=='stone'){ - list.remove('hslingjian_jinjilengdong'); - } - if(list.length){ - target.gain(game.createCard(list.randomGet()),'draw'); - } - }, - ai:{ - basic:{ - order:7.3, - useful:2, - value:6 - }, - result:{ - target:2, - }, - } - }, - shenmiguo:{ - fullskin:true, - type:'basic', - global:'g_shenmiguo', - content:function(){ - if(Array.isArray(player.storage.shenmiguo)){ - player.useCard(player.storage.shenmiguo[0],player.storage.shenmiguo[1]); - } - }, - ai:{ - order:1, - useful:6, - value:6, - result:{ - player:function(){ - var cardname=_status.event.cardname; - if(get.tag({name:cardname},'norepeat')) return 0; - return 1; - } - }, - } - }, - qinglianxindeng:{ - fullskin:true, - type:'equip', - subtype:'equip2', - skills:['qinglianxindeng'], - ai:{ - basic:{ - equipValue:8 - } - }, - }, - lingjiandai:{ - fullskin:true, - enable:true, - type:'jiguan', - wuxieable:true, - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - modTarget:true, - content:function(){ - var list=get.typeCard('hslingjian'); - if(list.length){ - list=list.randomGets(3); - for(var i=0;i0; - }, - content:function(){ - target.discard(target.getCards('he').randomGet()); - }, - ai:{ - order:9, - result:{ - target:-1, - }, - useful:[2,0.5], - value:[2,0.5], - } - }, - hslingjian_zhongxinghujia:{ - type:'hslingjian', - fullimage:true, - vanish:true, - enable:true, - derivation:true, - derivationpack:'swd', - filterTarget:function(card,player,target){ - return !target.isMin(); - }, - content:function(){ - 'step 0' - var list=[]; - for(var i=0;i6) return num; - } - } - if(hs.length>4) return num+0.5; - } - else{ - if(hs.length){ - if(hs.length<=3) return num; - return num+0.5; - } - } - return num+1; - } - } - }, - useful:[2,0.5], - value:[2,0.5], - } - }, - hslingjian_xingtigaizao:{ - type:'hslingjian', - fullimage:true, - vanish:true, - enable:true, - derivation:true, - derivationpack:'swd', - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - content:function(){ - target.draw(); - target.addSkill('hslingjian_xingtigaizao'); - if(typeof target.storage.hslingjian_xingtigaizao=='number'){ - target.storage.hslingjian_xingtigaizao++; - } - else{ - target.storage.hslingjian_xingtigaizao=1; - } - }, - ai:{ - order:9, - result:{ - target:function(player,target){ - if(!player.needsToDiscard()) return 1; - return 0; - } - }, - useful:[2,0.5], - value:[2,0.5], - } - }, - hslingjian_shijianhuisu:{ - type:'hslingjian', - fullimage:true, - vanish:true, - enable:true, - derivation:true, - derivationpack:'swd', - filterTarget:function(card,player,target){ - return target!=player&&target.countCards('e')>0; - }, - content:function(){ - var es=target.getCards('e'); - target.gain(es); - target.$gain2(es); - }, - ai:{ - order:5, - result:{ - target:function(player,target){ - if(target.hasSkillTag('noe')||target.hasSkillTag('reverseEquip')) return target.countCards('e')*2; - if(target.getEquip('baiyin')&&target.isDamaged()) return 2; - if(target.getEquip('xuanyuanjian')||target.getEquip('qiankundai')) return 1; - if(target.hasSkill('jiguanyaoshu_skill')) return 0.5; - var num=0; - var es=target.getCards('e'); - for(var i=0;i4) return -1.2*num; - else if(target.hp==4) return -1*num; - else if(target.hp==3) return -0.9*num; - else if(target.hp==2) return -0.5*num; - else{ - if(target.maxHp>2){ - if(target.hujia) return 0.5*num; - return num; - } - return 0; - } - }, - }, - useful:[2,0.5], - value:[2,0.5], - } - }, - hslingjian_shengxiuhaojiao:{ - type:'hslingjian', - fullimage:true, - vanish:true, - enable:true, - derivation:true, - derivationpack:'swd', - filterTarget:function(card,player,target){ - return !target.hasSkill('hslingjian_chaofeng'); - }, - content:function(){ - target.addTempSkill('hslingjian_chaofeng',{player:'phaseBegin'}); - }, - ai:{ - order:2, - result:{ - target:function(player,target){ - if(get.distance(player,target,'absolute')<=1) return 0; - if(target.countCards('h')<=target.hp) return -0.1; - return -1; - } - }, - useful:[2,0.5], - value:[2,0.5], - } - }, - hslingjian_yinmilichang:{ - type:'hslingjian', - fullimage:true, - vanish:true, - enable:true, - derivation:true, - derivationpack:'swd', - filterTarget:function(card,player,target){ - return player!=target&&!target.hasSkill('qianxing'); - }, - content:function(){ - target.tempHide(); - }, - ai:{ - order:2, - result:{ - target:function(player,target){ - if(get.distance(player,target,'absolute')<=1) return 0; - if(target.hp==1) return 2; - if(target.hp==2&&target.countCards('h')<=2) return 1.2; - return 1; - } - }, - useful:[2,0.5], - value:[2,0.5], - } - }, - xingjunyan:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['xingjunyan'], - ai:{ - basic:{ - equipValue:4 - }, - }, - }, - qinglonglingzhu:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['qinglonglingzhu'], - ai:{ - basic:{ - equipValue:5 - }, - }, - }, - baihupifeng:{ - fullskin:true, - type:"equip", - subtype:"equip2", - skills:['baihupifeng'], - ai:{ - equipValue:function(card,player){ - if(player.hp<=2) return 8; - return 6; - }, - basic:{ - equipValue:7 - }, - }, - }, - fengxueren:{ - fullskin:true, - type:"equip", - subtype:"equip1", - distance:{attackFrom:-1}, - skills:['fengxueren'], - ai:{ - basic:{ - equipValue:5 - }, - }, - }, - chilongya:{ - fullskin:true, - type:"equip", - subtype:"equip1", - distance:{attackFrom:-1}, - skills:['chilongya'], - ai:{ - basic:{ - equipValue:4 - }, - }, - }, - daihuofenglun:{ - type:'equip', - subtype:'equip4', - fullskin:true, - cardnature:'fire', - distance:{globalFrom:-2,globalTo:-1}, - ai:{ - basic:{ - equipValue:4 - }, - }, - }, - xiayuncailing:{ - type:'equip', - subtype:'equip3', - fullskin:true, - distance:{globalFrom:1,globalTo:2}, - }, - shentoumianju:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['shentou'], - ai:{ - basic:{ - equipValue:7, - } - } - }, - xianluhui:{ - fullskin:true, - type:'trick', - enable:true, - selectTarget:-1, - reverseOrder:true, - filterTarget:function(card,player,target){ - return target.isDamaged(); - }, - content:function(){ - target.changeHujia(); - }, - ai:{ - tag:{ - multitarget:1, - }, - basic:{ - order:7, - useful:3, - value:3, - }, - result:{ - target:function(player,target){ - if(target.hp<=1) return 1.5; - if(target.hp==2) return 1.2; - return 1; - }, - }, - } - }, - xiangyuye:{ - type:'basic', - enable:true, - fullskin:true, - filterTarget:function(card,player,target){ - return get.distance(player,target,'attack')>1; - }, - content:function(){ - "step 0" - if(!target.countCards('h',{color:'black'})){ - target.loseHp(); - event.finish(); - } - else{ - target.chooseToDiscard({color:'black'},'弃置一张黑色手牌或受流失一点体力').ai=function(card){ - return 8-get.value(card); - }; - } - "step 1" - if(!result.bool){ - target.loseHp(); - } - }, - ai:{ - basic:{ - order:9, - value:3, - useful:1, - }, - result:{ - target:-2 - }, - tag:{ - discard:1, - loseHp:1 - } - } - }, - caoyao:{ - fullskin:true, - type:'basic', - range:{global:1}, - enable:true, - filterTarget:function(card,player,target){ - return target.hp0}, - notarget:true, - mode:['identity','guozhan'], - fullskin:true, - content:function(){ - "step 0" - var list=[]; - for(var i=0;i3) return 7; - } - return -10; - }, - result:{ - player:function(player){ - for(var i=0;i3) return 2; - } - return -10; - } - }, - } - }, - tianxianjiu:{ - fullskin:true, - type:'basic', - toself:true, - enable:function(event,player){ - return !player.hasSkill('tianxianjiu'); - }, - savable:function(card,player,dying){ - return dying==player; - }, - usable:1, - selectTarget:-1, - logv:false, - modTarget:true, - filterTarget:function(card,player,target){ - return target==player; - }, - content:function(){ - "step 0" - if(target.isDying()) target.recover(); - else{ - target.addTempSkill('tianxianjiu',['phaseAfter','shaAfter']); - if(cards&&cards.length){ - card=cards[0]; - } - if(target==targets[0]&&card.clone&&(card.clone.parentNode==player.parentNode||card.clone.parentNode==ui.arena)){ - card.clone.moveDelete(target); - game.addVideo('gain2',target,get.cardsInfo([card])); - } - if(!target.node.jiu&&lib.config.jiu_effect){ - target.node.jiu=ui.create.div('.playerjiu',target.node.avatar); - target.node.jiu2=ui.create.div('.playerjiu',target.node.avatar2); - } - } - }, - ai:{ - basic:{ - useful:function(card,i){ - if(_status.event.player.hp>1){ - if(i==0) return 5; - return 1; - } - if(i==0) return 7.3; - return 3; - }, - value:function(card,player,i){ - if(player.hp>1){ - if(i==0) return 5; - return 1; - } - if(i==0) return 7.3; - return 3; - }, - }, - order:function(){ - return get.order({name:'sha'})+0.2; - }, - result:{ - target:function(player,target){ - if(target&&target.isDying()) return 2; - if(lib.config.mode=='stone'&&!player.isMin()){ - if(player.getActCount()+1>=player.actcount) return false; - } - var shas=player.getCards('h','sha'); - if(shas.length>1&&player.getCardUsable('sha')>1){ - return 0; - } - var card; - if(shas.length){ - for(var i=0;i0); - })){ - return 1; - } - } - return 0; - }, - }, - } - }, - huanpodan:{ - fullskin:true, - type:'basic', - enable:true, - logv:false, - filterTarget:function(card,player,target){ - return !target.hasSkill('huanpodan_skill'); - }, - content:function(){ - target.addSkill('huanpodan_skill'); - if(cards&&cards.length){ - card=cards[0]; - } - if(target==targets[0]&&card.clone&&(card.clone.parentNode==player.parentNode||card.clone.parentNode==ui.arena)){ - card.clone.moveDelete(target); - game.addVideo('gain2',target,get.cardsInfo([card])); - } - }, - ai:{ - basic:{ - value:8, - useful:4, - }, - order:2, - result:{ - target:function(player,target){ - return 1/Math.sqrt(1+target.hp); - }, - }, - } - }, - langeguaiyi:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['longfan'], - ai:{ - basic:{ - equipValue:7, - } - } - }, - guiyoujie:{ - fullskin:true, - type:'delay', - filterTarget:function(card,player,target){ - return (lib.filter.judge(card,player,target)&&player!=target); - }, - judge:function(card){ - if(get.color(card)=='black') return -3; - return 0; - }, - effect:function(){ - if(result.bool==false){ - player.loseHp(); - player.randomDiscard(); - } - }, - ai:{ - basic:{ - order:1, - useful:1, - value:6, - }, - result:{ - target:function(player,target){ - if(target.hasSkillTag('noturn')) return 0; - return -3; - } - }, - } - }, - yufulu:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['touzhi'], - ai:{ - basic:{ - equipValue:5 - } - } - }, - xixueguizhihuan:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['xixue'], - ai:{ - basic:{ - equipValue:5 - } - } - }, - zhufangshenshi:{ - fullskin:true, - type:'trick', - enable:true, - global:'g_zhufangshenshi', - filterTarget:function(card,player,target){ - return target!=player; - }, - content:function(){ - player.storage.zhufangshenshi=target; - player.addTempSkill('zhufangshenshi'); - }, - ai:{ - tag:{ - norepeat:1 - }, - value:4, - wuxie:function(){ - return 0; - }, - useful:[2,1], - basic:{ - order:7, - }, - result:{ - player:function(player,target){ - if(get.attitude(player,target)<0){ - if(get.distance(player,target)>1) return 1; - return 0.6; - } - return 0.3; - } - } - }, - }, - jingleishan:{ - fullskin:true, - type:'trick', - enable:true, - selectTarget:-1, - reverseOrder:true, - cardcolor:'black', - cardnature:'thunder', - filterTarget:function(card,player,target){ - return target!=player; - }, - content:function(){ - "step 0" - var next=target.chooseToRespond({name:'sha'}); - next.ai=function(card){ - if(get.damageEffect(target,player,target,'thunder')>=0) return 0; - if(player.hasSkillTag('notricksource')) return 0; - if(target.hasSkillTag('notrick')) return 0; - return 11-get.value(card); - }; - next.autochoose=lib.filter.autoRespondSha; - "step 1" - if(result.bool==false){ - target.damage('thunder'); - } - }, - ai:{ - wuxie:function(target,card,player,viewer){ - if(get.attitude(viewer,target)>0&&target.countCards('h','sha')){ - if(!target.countCards('h')||target.hp==1||Math.random()<0.7) return 0; - } - }, - basic:{ - order:9, - useful:[5,1], - value:5 - }, - result:{ - target:function(player,target){ - if(target.hasSkillTag('nothunder')) return 0; - if(target.hasUnknown(2)) return 0; - var nh=target.countCards('h'); - if(lib.config.mode=='identity'){ - if(target.isZhu&&nh<=2&&target.hp<=1) return -100; - } - if(nh==0) return -2; - if(nh==1) return -1.7 - return -1.5; - }, - }, - tag:{ - respond:1, - respondSha:1, - damage:1, - natureDamage:1, - thunderDamage:1, - multitarget:1, - multineg:1, - } - } - }, - chiyuxi:{ - fullskin:true, - type:'trick', - enable:true, - selectTarget:-1, - reverseOrder:true, - cardcolor:'red', - cardnature:'fire', - filterTarget:function(card,player,target){ - return target!=player; - }, - content:function(){ - "step 0" - var next=target.chooseToRespond({name:'shan'}); - next.ai=function(card){ - if(get.damageEffect(target,player,target,'fire')>=0) return 0; - if(player.hasSkillTag('notricksource')) return 0; - if(target.hasSkillTag('notrick')) return 0; - if(target.hasSkillTag('noShan')){ - return -1; - } - return 11-get.value(card); - }; - next.autochoose=lib.filter.autoRespondShan; - "step 1" - if(result.bool==false){ - target.damage('fire'); - } - }, - ai:{ - wuxie:function(target,card,player,viewer){ - if(get.attitude(viewer,target)>0&&target.countCards('h','shan')){ - if(!target.countCards('h')||target.hp==1||Math.random()<0.7) return 0; - } - }, - basic:{ - order:9, - useful:1, - value:5 - }, - result:{ - target:function(player,target){ - if(target.hasSkillTag('nofire')) return 0; - if(player.hasUnknown(2)) return 0; - var nh=target.countCards('h'); - if(lib.config.mode=='identity'){ - if(target.isZhu&&nh<=2&&target.hp<=1) return -100; - } - if(nh==0) return -2; - if(nh==1) return -1.7 - return -1.5; - }, - }, - tag:{ - respond:1, - respondShan:1, - damage:1, - natureDamage:1, - fireDamage:1, - multitarget:1, - multineg:1, - } - } - }, - guangshatianyi:{ - fullskin:true, - type:'equip', - subtype:'equip2', - skills:['guangshatianyi'], - ai:{ - basic:{ - equipValue:6 - } - } - }, - guilingzhitao:{ - type:'equip', - fullskin:true, - subtype:'equip5', - skills:['nigong'], - ai:{ - equipValue:function(card,player){ - if(!player.storage.nigong) return 5; - return 5+player.storage.nigong; - }, - basic:{ - equipValue:5 - } - }, - equipDelay:false, - loseDelay:false, - clearLose:true, - onLose:function(){ - player.storage.nigong=0; - player.unmarkSkill('nigong'); - }, - onEquip:function(){ - player.storage.nigong=0; - player.markSkill('nigong'); - } - }, - qipoguyu:{ - type:'equip', - subtype:'equip5', - skills:['xujin'], - equipDelay:false, - loseDelay:false, - clearLose:true, - onLose:function(){ - player.storage.xujin=0; - }, - onEquip:function(){ - player.storage.xujin=0; - } - }, - sadengjinhuan:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['sadengjinhuan'], - ai:{ - basic:{ - equipValue:5.5 - } - }, - }, - sifeizhenmian:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['yiluan'], - ai:{ - basic:{ - equipValue:6 - } - }, - }, - shuchui:{ - fullskin:true, - type:'equip', - subtype:'equip5', - skills:['shuchui'], - ai:{ - basic:{ - equipValue:5.5 - } - }, - }, - ximohu:{ - type:'equip', - subtype:'equip5', - skills:['ximohu'], - ai:{ - basic:{ - equipValue:6 - } - }, - }, - guiyanfadao:{ - fullskin:true, - type:'equip', - subtype:'equip1', - distance:{attackFrom:-1}, - ai:{ - basic:{ - equipValue:3 - } - }, - skills:['guiyanfadao'] - }, - qiankundai:{ - fullskin:true, - type:'equip', - subtype:'equip5', - onLose:function(){ - player.draw(); - }, - skills:['qiankundai'], - ai:{ - order:9.5, - equipValue:function(card,player){ - if(player.countCards('h','qiankundai')) return 6; - return 1; - }, - basic:{ - equipValue:5, - } - } - }, - }, - skill:{ - qiankundai:{ - mod:{ - maxHandcard:function(player,num){ - return num+1; - } - }, - }, - g_hufu_sha:{ - enable:['chooseToRespond','chooseToUse'], - filter:function(event,player){ - return player.countCards('h','hufu')>0; - }, - filterCard:{name:'hufu'}, - viewAs:{name:'sha'}, - prompt:'将一张玉符当杀使用或打出', - check:function(card){return 1}, - ai:{ - order:1, - useful:7.5, - value:7.5 - } - }, - g_hufu_shan:{ - enable:['chooseToRespond','chooseToUse'], - filter:function(event,player){ - return player.countCards('h','hufu')>0; - }, - filterCard:{name:'hufu'}, - viewAs:{name:'shan'}, - prompt:'将一张玉符当闪使用或打出', - check:function(){return 1}, - ai:{ - order:1, - useful:7.5, - value:7.5 - } - }, - g_hufu_jiu:{ - enable:['chooseToRespond','chooseToUse'], - filter:function(event,player){ - return player.countCards('h','hufu')>0; - }, - filterCard:{name:'hufu'}, - viewAs:{name:'jiu'}, - prompt:'将一张玉符当酒使用', - check:function(){return 1}, - }, - zhiluxiaohu:{ - trigger:{source:'damageAfter'}, - forced:true, - popup:false, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&event.getParent(3).name=='zhiluxiaohu'; - }, - content:function(){ - player.draw(); - } - }, - zhufangshenshi:{ - mod:{ - targetInRange:function(card,player,target,now){ - if(player.storage.zhufangshenshi==target) return true; - }, - }, - mark:true, - intro:{ - content:'player' - }, - onremove:true, - }, - g_zhufangshenshi:{ - trigger:{player:'useCardAfter'}, - forced:true, - popup:false, - filter:function(event,player){ - return event.card.name=='zhufangshenshi'; - }, - content:function(){ - player.draw(); - } - }, - huanpodan_skill:{ - mark:true, - intro:{ - content:'防止一次死亡,改为弃置所有牌,将体力值变为1并摸一张牌' - }, - trigger:{player:'dieBefore'}, - forced:true, - content:function(){ - 'step 0' - trigger.cancel(); - player.discard(player.getCards('he')); - player.removeSkill('huanpodan_skill'); - 'step 1' - player.changeHp(1-player.hp); - 'step 2' - player.draw(); - } - }, - dujian2:{}, - g_yuchan_swap:{ - trigger:{player:'useCardAfter'}, - silent:true, - priority:-1, - content:function(){ - var hs=player.getCards('h'); - var list=['yuchanqian','yuchankun','yuchanzhen','yuchanxun','yuchangen','yuchanli','yuchankan','yuchandui']; - for(var i=0;i0; - } - } - return false; - }, - filterCard:{type:'basic'}, - selectCard:[1,Infinity], - prompt:'弃置任意张基本牌并摸等量的牌', - check:function(card){ - return 6-get.value(card) - }, - content:function(){ - player.draw(cards.length); - }, - ai:{ - order:1, - result:{ - player:1 - }, - }, - }, - yuchanqian_equip1:{}, - yuchanqian_equip2:{}, - yuchanqian_equip3:{}, - yuchanqian_equip4:{}, - yuchanqian_equip5:{}, - yuchankun_equip1:{}, - yuchankun_equip2:{}, - yuchankun_equip3:{}, - yuchankun_equip4:{}, - yuchankun_equip5:{}, - yuchanzhen_equip1:{}, - yuchanzhen_equip2:{}, - yuchanzhen_equip3:{}, - yuchanzhen_equip4:{}, - yuchanzhen_equip5:{}, - yuchanxun_equip1:{}, - yuchanxun_equip2:{}, - yuchanxun_equip3:{}, - yuchanxun_equip4:{}, - yuchanxun_equip5:{}, - yuchankan_equip1:{}, - yuchankan_equip2:{}, - yuchankan_equip3:{}, - yuchankan_equip4:{}, - yuchankan_equip5:{}, - yuchanli_equip1:{}, - yuchanli_equip2:{}, - yuchanli_equip3:{}, - yuchanli_equip4:{}, - yuchanli_equip5:{}, - yuchangen_equip1:{}, - yuchangen_equip2:{}, - yuchangen_equip3:{}, - yuchangen_equip4:{}, - yuchangen_equip5:{}, - yuchandui_equip1:{}, - yuchandui_equip2:{}, - yuchandui_equip3:{}, - yuchandui_equip4:{}, - yuchandui_equip5:{}, - lianyaohu_skill:{ - mark:true, - intro:{ - content:function(storage,player){ - var card=player.getEquip('lianyaohu'); - if(card&&card.storage.shouna&&card.storage.shouna.length){ - return '共有'+get.cnNumber(card.storage.shouna.length)+'张牌'; - } - return '共有〇张牌'; - }, - mark:function(dialog,storage,player){ - var card=player.getEquip('lianyaohu'); - if(card&&card.storage.shouna&&card.storage.shouna.length){ - dialog.addAuto(card.storage.shouna); - } - else{ - return '共有〇张牌'; - } - }, - markcount:function(storage,player){ - var card=player.getEquip('lianyaohu'); - if(card&&card.storage.shouna) return card.storage.shouna.length; - return 0; - } - } - }, - g_shencaojie:{ - trigger:{source:'damageBegin',player:'damageBegin'}, - direct:true, - filter:function(event,player){ - if(get.type(event.card)!='trick') return false; - if(player.hasCard('shencaojie')) return true; - return false; - }, - content:function(){ - player.chooseToUse(get.prompt('shencaojie',trigger.player).replace(/发动/,'使用'),function(card,player){ - if(card.name!='shencaojie') return false; - return lib.filter.cardEnabled(card,player,'forceEnable'); - },trigger.player,-1).targetRequired=true; - } - }, - g_shenmiguo:{ - trigger:{player:'useCardAfter'}, - direct:true, - filter:function(event,player){ - if(event.parent.name=='g_shenmiguo') return false; - if(_status.currentPhase!=player) return false; - if(event.parent.parent.name!='phaseUse') return false; - if(!event.targets||!event.card) return false; - if(event.card.name=='shenmiguo') return false; - if(event.card.name=='yuchankan') return false; - if(player.hasSkill('shenmiguo2')) return false; - if(get.info(event.card).complexTarget) return false; - if(!lib.filter.cardEnabled(event.card,player,event.parent)) return false; - var type=get.type(event.card); - if(type!='basic'&&type!='trick') return false; - var card=game.createCard(event.card.name,event.card.suit,event.card.number,event.card.nature); - var targets=event._targets||event.targets; - for(var i=0;i1; - }, - content:function(){ - var value=get.value(ui.cardPile.firstChild); - var num=Math.min(20,ui.cardPile.childElementCount); - var list=[],list2=[],list3=[]; - for(var i=1;ivalue){ - list.push(ui.cardPile.childNodes[i]); - if(val>value+1&&val>=7){ - list2.push(ui.cardPile.childNodes[i]); - } - if(val>value+1&&val>=8){ - list3.push(ui.cardPile.childNodes[i]); - } - } - } - var card; - if(list3.length){ - card=list3.randomGet(); - } - else if(list2.length){ - card=list2.randomGet(); - } - else if(list.length){ - card=list.randomGet(); - } - if(card){ - ui.cardPile.insertBefore(card,ui.cardPile.firstChild); - } - } - }, - shuchui:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return player.canUse('sha',target); - }, - filter:function(event,player){ - return player.countCards('h','sha')>0&&lib.filter.cardUsable({name:'sha'},player); - }, - content:function(){ - 'step 0' - player.addSkill('shuchui2'); - player.storage.shuchui2=false; - event.num=0; - 'step 1' - var card=player.getCards('h','sha')[0]; - if(card){ - player.useCard(card,target); - } - else{ - if(player.storage.shuchui2){ - player.draw(); - } - player.removeSkill('shuchui2'); - event.finish(); - } - 'step 2' - if(event.num++<2&&target.isAlive()){ - event.goto(1); - } - else{ - if(player.storage.shuchui2){ - player.draw(); - } - player.removeSkill('shuchui2'); - } - }, - ai:{ - order:function(){ - return get.order({name:'sha'})+0.11; - }, - result:{ - target:function(player,target){ - return get.effect(target,{name:'sha'},player,target); - } - } - } - }, - shuchui2:{ - trigger:{source:'damageEnd'}, - forced:true, - popup:false, - onremove:true, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&!player.storage.shuchui2; - }, - content:function(){ - player.storage.shuchui2=true; - } - }, - xuejibingbao:{ - trigger:{player:'phaseDrawBegin'}, - forced:true, - mark:true, - temp:true, - intro:{ - content:'摸牌阶段摸牌数+1' - }, - nopop:true, - content:function(){ - trigger.num++; - player.storage.xuejibingbao--; - if(player.storage.xuejibingbao<=0){ - player.removeSkill('xuejibingbao'); - delete player.storage.xuejibingbao; - } - else{ - player.updateMarks(); - } - } - }, - gouhunluo:{ - mark:true, - intro:{ - content:function(storage,player){ - if(storage==1){ - '在'+get.translation(player.storage.gouhunluo2)+'的下个准备阶段失去一点体力并弃置所有手牌' - } - return '在'+storage+'轮后'+get.translation(player.storage.gouhunluo2)+'的准备阶段失去一点体力并弃置所有手牌' - } - }, - nopop:true, - temp:true, - trigger:{global:'phaseBegin'}, - forced:true, - popup:false, - filter:function(event,player){ - return player.storage.gouhunluo2==event.player; - }, - content:function(){ - 'step 0' - player.storage.gouhunluo--; - if(player.storage.gouhunluo<=0){ - player.logSkill('gouhunluo'); - player.loseHp(); - player.removeSkill('gouhunluo'); - delete player.storage.gouhunluo; - delete player.storage.gouhunluo2; - } - else{ - player.updateMarks(); - event.finish(); - } - 'step 1' - var es=player.getCards('h'); - if(es.length){ - player.discard(es); - } - }, - group:'gouhunluo2' - }, - gouhunluo2:{ - trigger:{global:'dieBegin'}, - forced:true, - popup:false, - filter:function(event,player){ - return player.storage.gouhunluo2==event.player; - }, - content:function(){ - player.removeSkill('gouhunluo'); - delete player.storage.gouhunluo; - delete player.storage.gouhunluo2; - } - }, - jiguanyuan:{ - mark:'card', - intro:{ - content:'cards' - }, - trigger:{player:'phaseEnd'}, - forced:true, - temp:true, - popup:false, - content:function(){ - player.gain(player.storage.jiguanyuan,'gain2'); - player.removeSkill('jiguanyuan'); - delete player.storage.jiguanyuan; - } - }, - g_qinglongzhigui:{ - trigger:{player:'phaseBegin'}, - forced:true, - filter:function(event,player){ - return player.countCards('h','qinglongzhigui')>0; - }, - content:function(){ - 'step 0' - player.showCards(get.translation(player)+'发动了【青龙之圭】',player.getCards('h','qinglongzhigui')); - player.draw(2); - 'step 1' - player.chooseToDiscard('he',true); - } - }, - g_baishouzhihu:{ - trigger:{player:'discardEnd'}, - direct:true, - filter:function(event,player){ - return player.countCards('h','baishouzhihu')>0; - }, - content:function(){ - "step 0" - player.chooseTarget([1,1],get.prompt('baishouzhihu'),function(card,player,target){ - if(player==target) return false; - return target.countCards('he')>0; - }).ai=function(target){ - return -get.attitude(player,target); - }; - "step 1" - if(result.bool){ - player.showCards(get.translation(player)+'发动了【白兽之琥】',player.getCards('h','baishouzhihu')); - player.logSkill('_baishouzhihu',result.targets); - result.targets[0].randomDiscard(); - // player.discardPlayerCard(result.targets[0],'he',true); - } - else{ - event.finish(); - } - }, - }, - g_zhuquezhizhang:{ - trigger:{player:'damageEnd'}, - forced:true, - filter:function(event,player){ - return event.source&&event.source!=player&&event.source.isAlive()&&player.countCards('h','zhuquezhizhang')>0; - }, - logTarget:'source', - check:function(event,player){ - return get.damageEffect(event.source,player,player,'fire')>0; - }, - content:function(){ - 'step 0' - player.showCards(get.translation(player)+'发动了【朱雀之璋】',player.getCards('h','zhuquezhizhang')); - trigger.source.damage('fire'); - 'step 1' - game.delay(); - } - }, - g_xuanwuzhihuang:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event,player){ - return player.countCards('h','xuanwuzhihuang')>0&&event.num>0&&player.hp0; - }, - content:function(){ - player.showCards(get.translation(player)+'发动了【黄麟之琮】',player.getCards('h','huanglinzhicong')); - player.changeHujia(); - player.update(); - }, - }, - g_cangchizhibi:{ - trigger:{player:'phaseBegin'}, - direct:true, - filter:function(event,player){ - return player.countCards('h','cangchizhibi')>0; - }, - content:function(){ - 'step 0' - player.chooseTarget([1,3],get.prompt('cangchizhibi')).ai=function(target){ - var att=get.attitude(player,target); - if(target.isLinked()){ - return att; - } - return -att; - }; - 'step 1' - if(result.bool){ - player.showCards(get.translation(player)+'发动了【苍螭之璧】',player.getCards('h','cangchizhibi')); - player.logSkill('_cangchizhibi',result.targets); - for(var i=0;i0&&player.hujia==0 - }, - content:function(){ - 'step 0' - var next=player.chooseToDiscard('he',{color:'black'},get.prompt('huanglinzhicong_duanzao')); - next.ai=function(card){ - return 8-get.value(card); - }; - next.logSkill='huanglinzhicong_equip1' - 'step 1' - if(result.bool){ - player.changeHujia(); - } - } - }, - huanglinzhicong_equip2:{ - inherit:'huanglinzhicong_equip1' - }, - huanglinzhicong_equip3:{ - inherit:'huanglinzhicong_equip1' - }, - huanglinzhicong_equip4:{ - inherit:'huanglinzhicong_equip1' - }, - huanglinzhicong_equip5:{ - inherit:'huanglinzhicong_equip1' - }, - xuanwuzhihuang_equip1:{ - trigger:{player:'phaseEnd'}, - direct:true, - filter:function(event,player){ - return player.countCards('he',{color:'red'})>0&&player.hp0; - }, - content:function(){ - "step 0" - player.chooseCardTarget({ - position:'he', - filterTarget:function(card,player,target){ - return player!=target&&target.hp>=player.hp; - }, - filterCard:function(card,player){ - return get.color(card)=='red'&&lib.filter.cardDiscardable(card,player); - }, - ai1:function(card){ - return 9-get.value(card); - }, - ai2:function(target){ - return get.damageEffect(target,player,player,'fire'); - }, - prompt:get.prompt('zhuquezhizhang_duanzao') - }); - "step 1" - if(result.bool){ - event.target=result.targets[0]; - player.logSkill('zhuquezhizhang_equip1',event.target,'fire'); - player.discard(result.cards); - } - else{ - event.finish(); - } - "step 2" - if(event.target){ - event.target.damage('fire'); - } - }, - }, - zhuquezhizhang_equip2:{ - inherit:'zhuquezhizhang_equip1' - }, - zhuquezhizhang_equip3:{ - inherit:'zhuquezhizhang_equip1' - }, - zhuquezhizhang_equip4:{ - inherit:'zhuquezhizhang_equip1' - }, - zhuquezhizhang_equip5:{ - inherit:'zhuquezhizhang_equip1' - }, - baishouzhihu_equip1:{ - trigger:{player:'phaseEnd'}, - direct:true, - content:function(){ - "step 0" - player.chooseTarget([1,1],get.prompt('baishouzhihu_duanzao'),function(card,player,target){ - if(player==target) return false; - return target.countCards('he')>0; - }).ai=function(target){ - return -get.attitude(player,target); - }; - "step 1" - if(result.bool){ - player.logSkill('baishouzhihu_equip1',result.targets); - result.targets[0].randomDiscard(); - // player.discardPlayerCard(result.targets[0],'he',true); - } - else{ - event.finish(); - } - }, - }, - baishouzhihu_equip2:{ - inherit:'baishouzhihu_equip1' - }, - baishouzhihu_equip3:{ - inherit:'baishouzhihu_equip1' - }, - baishouzhihu_equip4:{ - inherit:'baishouzhihu_equip1' - }, - baishouzhihu_equip5:{ - inherit:'baishouzhihu_equip1' - }, - qinglongzhigui_equip1:{ - trigger:{player:'phaseEnd'}, - forced:true, - content:function(){ - player.draw(); - } - }, - qinglongzhigui_equip2:{ - inherit:'qinglongzhigui_equip1' - }, - qinglongzhigui_equip3:{ - inherit:'qinglongzhigui_equip1' - }, - qinglongzhigui_equip4:{ - inherit:'qinglongzhigui_equip1' - }, - qinglongzhigui_equip5:{ - inherit:'qinglongzhigui_equip1' - }, - kunlunjingc:{ - enable:'phaseUse', - usable:1, - filter:function(event,player){ - return player.countCards('h')>0; - }, - delay:false, - content:function(){ - 'step 0' - var cards=get.cards(3); - event.cards=cards; - player.chooseCardButton('选择一张牌',cards,true); - 'step 1' - event.card=result.links[0]; - player.chooseCard('h',true,'用一张手牌替换'+get.translation(event.card)); - 'step 2' - if(result.bool){ - event.cards[event.cards.indexOf(event.card)]=result.cards[0]; - player.lose(result.cards,ui.special); - var cardx=ui.create.card(); - cardx.classList.add('infohidden'); - cardx.classList.add('infoflip'); - player.$throw(cardx,1000,'nobroadcast'); - } - else{ - event.finish(); - } - 'step 3' - player.gain(event.card); - player.$draw(); - for(var i=event.cards.length-1;i>=0;i--){ - event.cards[i].fix(); - ui.cardPile.insertBefore(event.cards[i],ui.cardPile.firstChild); - } - game.delay(); - }, - ai:{ - order:10, - result:{ - player:1 - } - } - }, - lianhua:{ - enable:'phaseUse', - filter:function(event,player){ - var hu=player.getEquip('lianyaohu'); - if(hu&&hu.storage.shouna&&hu.storage.shouna.length>1){ - return true; - } - return false; - }, - usable:1, - delay:false, - content:function(){ - "step 0" - event.hu=player.getEquip('lianyaohu'); - player.chooseCardButton('弃置两张壶中的牌,然后从牌堆中获得一张类别不同的牌',2,event.hu.storage.shouna).ai=function(){ - return 1; - } - "step 1" - if(result.bool){ - var type=[]; - player.$throw(result.links); - game.log(player,'弃置了',result.links); - for(var i=0;i0; - }, - usable:1, - filterCard:true, - check:function(card){ - return 6-get.value(card); - }, - filterTarget:function(card,player,target){ - return target!=player&&target.countCards('h')>0; - }, - content:function(){ - 'step 0' - var card=target.getCards('h').randomGet(); - var hu=player.getEquip('lianyaohu'); - if(card&&hu){ - if(!hu.storage.shouna){ - hu.storage.shouna=[]; - } - target.$give(card,player); - target.lose(card,ui.special); - event.card=card; - event.hu=hu; - } - 'step 1' - if(!event.card.destroyed){ - event.hu.storage.shouna.push(event.card); - player.updateMarks(); - } - }, - ai:{ - order:5, - result:{ - target:function(player,target){ - return -1/Math.sqrt(1+target.countCards('h')); - } - } - } - }, - shouna_old:{ - trigger:{global:'discardAfter'}, - filter:function(event,player){ - if(player.hasSkill('shouna2')) return false; - if(_status.currentPhase==event.player) return false; - if(event.player==player) return false; - for(var i=0;i0; - }, - content:function(){ - 'step 0' - player.chooseCardTarget({ - filterTarget:true, - filterCard:function(card,player,event){ - if(get.color(card)!='red') return false; - return lib.filter.cardDiscardable(card,player,event); - }, - ai1:function(card){ - return 8-get.useful(card); - }, - ai2:function(target){ - return -get.attitude(player,target); - }, - prompt:get.prompt('donghuangzhong') - }); - 'step 1' - if(result.bool){ - player.logSkill('donghuangzhong',result.targets); - player.discard(result.cards); - event.target=result.targets[0]; - } - else{ - event.finish(); - } - 'step 2' - var target=event.target; - var list=[]; - for(var i=0;i0; - }, - content:function(){ - trigger.player.chooseToDiscard(true,'he'); - } - }, - shouhua:{ - mode:['identity','infinity'], - enable:'phaseUse', - filter:function(event,player){ - return player==game.me; - }, - usable:1, - filterTarget:function(card,player,target){ - return target!=game.zhu&&target!=game.me&&target.hp0){ - return 1+trigger.judge(button.link); - } - if(get.attitude(player,trigger.player)<0){ - return 1-trigger.judge(button.link); - } - return 0; - }; - "step 1" - if(!result.bool){ - event.finish(); - return; - } - player.logSkill('haotianta',trigger.player); - var card=result.links[0]; - event.cards.remove(card); - var judgestr=get.translation(trigger.player)+'的'+trigger.judgestr+'判定'; - event.videoId=lib.status.videoId++; - event.dialog=ui.create.dialog(judgestr); - event.dialog.classList.add('center'); - event.dialog.videoId=event.videoId; - - game.addVideo('judge1',player,[get.cardInfo(card),judgestr,event.videoId]); - for(var i=0;i0){ - trigger.result.bool=true; - trigger.player.popup('洗具'); - } - if(trigger.result.judge<0){ - trigger.result.bool=false; - trigger.player.popup('杯具'); - } - game.log(trigger.player,'的判定结果为',card); - trigger.direct=true; - trigger.position.appendChild(card); - game.delay(2); - } - else{ - event.finish(); - } - "step 2" - ui.arena.classList.remove('thrownhighlight'); - event.dialog.close(); - game.addVideo('judge2',null,event.videoId); - ui.clear(); - var card=trigger.result.card; - trigger.position.appendChild(card); - trigger.result.node.delete(); - game.delay(); - }, - ai:{ - tag:{ - rejudge:1 - } - } - }, - shennongding:{ - enable:'phaseUse', - usable:1, - filterCard:true, - selectCard:2, - check:function(card){ - if(get.tag(card,'recover')>=1) return 0; - return 7-get.value(card); - }, - filter:function(event,player){ - return player.hp=2; - }, - content:function(){ - player.recover(); - }, - ai:{ - result:{ - player:function(player){ - return get.recoverEffect(player); - } - }, - order:2.5 - } - }, - kongdongyin:{ - trigger:{player:'dieBefore'}, - forced:true, - filter:function(event,player){ - return player.maxHp>0; - }, - content:function(){ - trigger.cancel(); - player.hp=1; - player.draw(); - player.discard(player.getCards('e',{subtype:'equip5'})); - game.delay(); - } - }, - - nvwashi:{ - trigger:{global:'dying'}, - priority:6, - filter:function(event,player){ - return event.player.hp<=0&&player.hp>1; - }, - check:function(event,player){ - return get.attitude(player,event.player)>=3&&!event.player.hasSkillTag('nosave'); - }, - logTarget:'player', - content:function(){ - "step 0" - trigger.player.recover(); - "step 1" - player.loseHp(); - }, - ai:{ - threaten:1.2, - expose:0.2 - } - }, - kongxin:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return target!=player&&target.countCards('h'); - }, - filter:function(event,player){ - return player.countCards('h')?true:false; - }, - content:function(){ - "step 0" - player.chooseToCompare(target); - "step 1" - if(result.bool){ - event.bool=true; - player.chooseTarget('选择一个目标视为'+get.translation(target)+'对其使用一张杀',function(card,player,target2){ - return player!=target2&&target.canUse('sha',target2); - }).ai=function(target2){ - return get.effect(target2,{name:'sha'},target,player); - } - } - else{ - target.discardPlayerCard(player); - } - "step 2" - if(event.bool&&result.bool){ - target.useCard({name:'sha'},result.targets); - } - }, - ai:{ - order:7, - result:{ - target:function(player,target){ - if(player.countCards('h')<=1) return 0; - if(get.attitude(player,target)>=0) return 0; - if(game.hasPlayer(function(current){ - return (player!=current&&target.canUse('sha',current)&& - get.effect(current,{name:'sha'},target,player)>0) - })){ - return -1; - } - return 0; - } - } - } - }, - kongxin2:{ - trigger:{player:'dying'}, - priority:10, - forced:true, - popup:false, - filter:function(event,player){ - return player==game.me; - }, - content:function(){ - player.removeSkill('kongxin2'); - game.swapPlayer(player); - player.storage.kongxin.lockOut=false; - player.storage.kongxin.out(); - if(player==game.me) game.swapPlayer(player.storage.kongxin); - if(lib.config.mode=='identity') player.storage.kongxin.setIdentity(); - delete player.storage.kongxin; - }, - }, - qinglianxindeng:{ - trigger:{player:'damageBefore'}, - forced:true, - priority:15, - filter:function(event,player){ - if(event.source&&event.source.hasSkillTag('unequip',false,{ - name:event.card?event.card.name:null, - target:player, - card:event.card - })) return false; - return get.type(event.card,'trick')=='trick'; - }, - content:function(){ - trigger.cancel(); - }, - ai:{ - notrick:true, - effect:{ - target:function(card,player,target,current){ - if(player.hasSkillTag('unequip',false,{ - name:card?card.name:null, - target:player, - card:card - })) return; - if(get.type(card)=='trick'&&get.tag(card,'damage')){ - return 'zeroplayertarget'; - } - }, - } - } - }, - yiluan:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return target!=player&&target.countCards('h'); - }, - content:function(){ - 'step 0' - target.judge(); - 'step 1' - if(result.suit!='heart'){ - var hs=target.getCards('h'); - while(hs.length){ - var chosen=hs.randomRemove(); - if(target.hasUseTarget(chosen)&&!get.info(chosen).multitarget){ - var list=game.filterPlayer(function(current){ - return lib.filter.targetEnabled2(chosen,target,current); - }); - if(list.length){ - target.useCard(chosen,list.randomGet()); - event.finish(); - break; - } - } - } - } - }, - ai:{ - order:10, - result:{ - target:function(player,target){ - if(!target.countCards('h')) return 0; - return -1; - } - } - } - }, - hslingjian_xuanfengzhiren_equip1:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event){ - if(event._notrigger.contains(event.player)) return false; - return event.card&&event.card.name=='sha'&&event.player.countCards('he'); - }, - content:function(){ - trigger.player.discard(trigger.player.getCards('he').randomGet()); - } - }, - hslingjian_xuanfengzhiren_equip2:{ - trigger:{player:'damageEnd'}, - forced:true, - filter:function(event){ - return event.card&&event.card.name=='sha'&&event.source&&event.source.countCards('he'); - }, - content:function(){ - trigger.source.discard(trigger.source.getCards('he').randomGet()); - } - }, - hslingjian_xuanfengzhiren_equip3:{ - trigger:{player:'loseAfter'}, - forced:true, - filter:function(event,player){ - return _status.currentPhase!=player&&!player.hasSkill('hslingjian_xuanfengzhiren_equip3_dist'); - }, - content:function(){ - player.addTempSkill('hslingjian_xuanfengzhiren_equip3_dist'); - } - }, - hslingjian_xuanfengzhiren_equip3_dist:{ - mod:{ - globalTo:function(from,to,distance){ - return distance+1; - } - } - }, - hslingjian_xuanfengzhiren_equip4:{ - trigger:{player:'loseAfter'}, - forced:true, - filter:function(event,player){ - return _status.currentPhase==player&&!player.hasSkill('hslingjian_xuanfengzhiren_equip4_dist'); - }, - content:function(){ - player.addTempSkill('hslingjian_xuanfengzhiren_equip4_dist'); - } - }, - hslingjian_xuanfengzhiren_equip4_dist:{ - mod:{ - globalFrom:function(from,to,distance){ - return distance-1; - } - } - }, - hslingjian_xuanfengzhiren_equip5:{ - enable:'phaseUse', - usable:1, - filterCard:true, - position:'he', - filter:function(event,player){ - return player.countCards('he')>0; - }, - filterTarget:function(card,player,target){ - return target.countCards('he')>0; - }, - check:function(card){ - return 5-get.value(card); - }, - content:function(){ - target.discard(target.getCards('he').randomGet()); - }, - ai:{ - order:5, - result:{ - target:function(player,target){ - var dh=player.countCards('he')-target.countCards('he'); - if(dh>0){ - return -Math.sqrt(dh); - } - return 0; - } - } - } - }, - hslingjian_zhongxinghujia_equip1:{ - trigger:{source:'damageEnd'}, - check:function(event,player){ - return !player.getEquip(2); - }, - filter:function(event){ - return event.card&&event.card.name=='sha'; - }, - content:function(){ - var card=game.createCard(get.inpile('equip2').randomGet()); - player.equip(card); - player.$draw(card); - game.delay(); - } - }, - hslingjian_zhongxinghujia_equip2:{ - trigger:{player:'damageEnd'}, - check:function(event,player){ - return get.attitude(player,event.source)<0; - }, - filter:function(event){ - return event.card&&event.card.name=='sha'&&event.source&&event.source.getEquip(2); - }, - content:function(){ - player.line(trigger.source,'green'); - trigger.source.discard(trigger.source.getEquip(2)); - } - }, - hslingjian_zhongxinghujia_equip3:{ - mod:{ - globalTo:function(from,to,distance){ - if(to.getEquip(2)) return distance+1; - } - } - }, - hslingjian_zhongxinghujia_equip4:{ - mod:{ - globalFrom:function(from,to,distance){ - if(from.getEquip(2)) return distance-1; - } - } - }, - hslingjian_zhongxinghujia_equip5:{ - enable:'phaseUse', - usable:1, - filterCard:true, - position:'he', - filterTarget:true, - selectCard:2, - filter:function(event,player){ - return player.countCards('he')>=2; - }, - check:function(card){ - return 5-get.value(card); - }, - content:function(){ - var card=game.createCard(get.inpile('equip2').randomGet()); - target.equip(card); - target.$draw(card); - game.delay(); - }, - ai:{ - order:1, - result:{ - target:function(player,target){ - if(target.getEquip(2)) return 0; - return 1; - } - } - } - }, - hslingjian_jinjilengdong_equip1:{ - trigger:{source:'damageEnd'}, - check:function(event,player){ - if(event.player.hasSkillTag('noturn')) return 0; - if(event.player.isTurnedOver()){ - return get.attitude(player,event.player)>0; - } - return get.attitude(player,event.player)<=0; - }, - filter:function(event){ - if(event._notrigger.contains(event.player)) return false; - return event.card&&event.card.name=='sha'&&event.player&&event.player.isAlive(); - }, - logTarget:'player', - content:function(){ - trigger.player.draw(2); - trigger.player.turnOver(); - } - }, - hslingjian_jinjilengdong_equip2:{ - trigger:{player:'damageEnd'}, - check:function(event,player){ - if(event.player.hasSkillTag('noturn')) return 0; - if(event.player.isTurnedOver()){ - return get.attitude(player,event.source)>0; - } - return get.attitude(player,event.source)<=0; - }, - filter:function(event){ - return event.card&&event.card.name=='sha'&&event.source&&event.source.isAlive(); - }, - logTarget:'source', - content:function(){ - player.line(trigger.source,'green'); - trigger.source.draw(2); - trigger.source.turnOver(); - } - }, - hslingjian_jinjilengdong_equip3:{ - mod:{ - globalTo:function(from,to,distance){ - if(to.isTurnedOver()) return distance+2; - } - } - }, - hslingjian_jinjilengdong_equip4:{ - mod:{ - globalFrom:function(from,to,distance){ - if(from.isTurnedOver()) return distance-2; - } - } - }, - hslingjian_jinjilengdong_equip5:{ - trigger:{player:'phaseAfter'}, - direct:true, - filter:function(event,player){ - return !player.isTurnedOver(); - }, - content:function(){ - "step 0" - player.chooseTarget(get.prompt('hslingjian_jinjilengdong_duanzao'),function(card,player,target){ - return player!=target&&!target.isTurnedOver(); - }).ai=function(target){ - if(target.hasSkillTag('noturn')) return 0; - return Math.max(0,-get.attitude(player,target)-2); - }; - "step 1" - if(result.bool){ - player.logSkill('hslingjian_jinjilengdong_equip5',result.targets); - player.turnOver(); - result.targets[0].turnOver(); - game.asyncDraw([player,result.targets[0]],2); - } - }, - }, - hslingjian_yinmilichang_equip1:{ - trigger:{source:'damageEnd'}, - direct:true, - filter:function(event){ - return event.card&&event.card.name=='sha'; - }, - content:function(){ - 'step 0' - player.chooseTarget(get.prompt('hslingjian_yinmilichang_duanzao'),function(card,player,target){ - return target!=player&&!target.hasSkill('qianxing'); - }).ai=function(target){ - var att=get.attitude(player,target); - if(get.distance(player,target,'absolute')<=1) return 0; - if(target.hp==1) return 2*att; - if(target.hp==2&&target.countCards('h')<=2) return 1.2*att; - return att; - } - 'step 1' - if(result.bool){ - player.logSkill('hslingjian_yinmilichang_equip1',result.targets); - result.targets[0].tempHide(); - } - } - }, - hslingjian_yinmilichang_equip2:{ - trigger:{player:'damageEnd'}, - forced:true, - filter:function(event,player){ - return !player.hasSkill('qianxing'); - }, - content:function(){ - player.addTempSkill('qianxing'); - } - }, - hslingjian_yinmilichang_equip3:{ - mod:{ - globalTo:function(from,to,distance){ - if(to.hp==1) return distance+1; - } - } - }, - hslingjian_yinmilichang_equip4:{ - mod:{ - globalFrom:function(from,to,distance){ - if(from.hp==1) return distance-1; - } - } - }, - hslingjian_yinmilichang_equip5:{ - mod:{ - targetEnabled:function(card,player,target,now){ - if(target.countCards('h')==0){ - if(card.name=='sha'||card.name=='juedou') return false; - } - } - }, - ai:{ - noh:true, - skillTagFilter:function(player,tag){ - if(tag=='noh'){ - if(player.countCards('h')!=1) return false; - } - } - } - }, - hslingjian_xingtigaizao_equip1:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event){ - return event.card&&event.card.name=='sha'; - }, - content:function(){ - player.draw(); - } - }, - hslingjian_xingtigaizao_equip2:{ - trigger:{player:'damageEnd'}, - forced:true, - filter:function(event){ - return event.card&&event.card.name=='sha'; - }, - content:function(){ - player.draw(); - } - }, - hslingjian_xingtigaizao_equip3:{ - mod:{ - globalTo:function(from,to,distance){ - return distance+1; - }, - globalFrom:function(from,to,distance){ - return distance+1; - } - } - }, - hslingjian_xingtigaizao_equip4:{ - mod:{ - globalTo:function(from,to,distance){ - return distance-1; - }, - globalFrom:function(from,to,distance){ - return distance-1; - } - } - }, - hslingjian_xingtigaizao_equip5:{ - mod:{ - maxHandcard:function(player,num){ - return num-1; - } - }, - trigger:{player:'phaseDrawBegin'}, - forced:true, - content:function(){ - trigger.num++; - } - }, - hslingjian_shengxiuhaojiao_equip1:{ - trigger:{player:'shaBegin'}, - forced:true, - filter:function(event,player){ - return event.target.hasSkill('hslingjian_chaofeng'); - }, - content:function(){ - trigger.directHit=true; - } - }, - hslingjian_shengxiuhaojiao_equip2:{ - mod:{ - targetEnabled:function(card,player,target){ - if(player.hasSkill('hslingjian_chaofeng')) return false; - } - } - }, - hslingjian_shengxiuhaojiao_equip3:{ - mod:{ - globalTo:function(from,to,distance){ - if(to.hp0; - }, - position:'he', - content:function(){ - var es=target.getCards('e'); - target.gain(es); - target.$gain2(es); - }, - check:function(card){ - return 4-get.value(card); - }, - ai:{ - order:5, - result:{ - target:function(player,target){ - if(target.hasSkillTag('noe')) return target.countCards('e')*2; - return -target.countCards('e'); - } - }, - } - }, - jiguanyaoshu_skill_old:{ - enable:'phaseUse', - filter:function(event,player){ - return player.countCards('h',{type:['trick','delay']})>0; - }, - filterCard:{type:['trick','delay']}, - check:function(card){ - return 5-get.value(card); - }, - viewAs:{name:'jiguanshu'} - }, - jiguanyaoshu_skill:{ - trigger:{player:'loseEnd'}, - forced:true, - filter:function(event,player){ - if(_status.currentPhase==player) return false; - for(var i=0;i10) return 10+(value-10)/10; - if(value<9) return 8+value/10; - return value; - }; - if(typeof lib.card[name].ai.equipValue=='number'){ - lib.card[name].ai.equipValue=getValue(lib.card[name].ai.equipValue,dvalue); - } - else if(typeof lib.card[name].ai.equipValue=='function'){ - lib.card[name].ai.equipValue=function(){ - return getValue(lib.card[equipname].ai.equipValue.apply(this,arguments),dvalue); - } - } - else if(lib.card[name].ai.basic&&typeof lib.card[name].ai.basic.equipValue=='number'){ - lib.card[name].ai.basic.equipValue=getValue(lib.card[name].ai.basic.equipValue,dvalue); - } - else if(lib.card[name].ai.basic&&typeof lib.card[name].ai.basic.equipValue=='function'){ - lib.card[name].ai.basic.equipValue=function(){ - return getValue(lib.card[equipname].ai.basic.equipValue.apply(this,arguments),dvalue); - } - } - else{ - if(dvalue==3){ - lib.card[name].ai.equipValue=7; - } - else{ - lib.card[name].ai.equipValue=dvalue; - } - } - if(Array.isArray(lib.card[name].skills)){ - lib.card[name].skills=lib.card[name].skills.slice(0); - } - else{ - lib.card[name].skills=[]; - } - // lib.card[name].filterTarget=function(card,player,target){ - // return !target.isMin(); - // }; - // lib.card[name].selectTarget=1; - // lib.card[name].range={global:1}; - var str=lib.translate[cards[0].name+'_duanzao']; - var str2=get.translation(equip.name,'skill'); - lib.translate[name]=str+str2; - str2=lib.translate[equip.name+'_info']||''; - if(str2[str2.length-1]=='.'||str2[str2.length-1]=='。'){ - str2=str2.slice(0,str2.length-1); - } - for(var i=0;i'+lib.translate[lingjians[i]]+''; - for(var j=0;j'; - if(type=='jiqi') break; - } - str+=''; - } - return str; - }, - check:function(card){ - if(get.type(card)=='jiqi'){ - if(_status.event.player.needsToDiscard()){ - return 0.5; - } - return 0; - } - var num=1+get.value(card); - if(get.position(card)=='e'){ - num+=0.1; - } - return num; - }, - filterCard:function(card){ - var type=get.type(card); - if(type=='equip'){ - if(!lib.inpile.contains(card.name)) return false; - if(lib.card[card.name].nopower) return false; - if(lib.card[card.name].unique) return false; - if(card.nopower) return false; - } - if(ui.selected.cards.length){ - var type2=get.type(ui.selected.cards[0]); - if(type2=='equip'){ - return type=='hslingjian'||type=='jiqi'; - } - else{ - return type=='equip'; - } - } - else{ - return type=='equip'||type=='hslingjian'||type=='jiqi'; - } - }, - selectCard:2, - complexCard:true, - filter:function(event,player){ - if(!player.countCards('h',{type:['hslingjian','jiqi']})) return false; - var es=player.getCards('he',{type:'equip'}); - for(var i=0;i0; - if(event.target.hp==1) return att>0; - if(event.target.hasSkillTag('maixie')){ - return att<=0; - } - if(player.hasSkill('tianxianjiu')) return false; - return att<=0; - }, - filter:function(event,player){ - return !event.target.isTurnedOver(); - }, - logTarget:'target', - content:function(){ - trigger.unhurt=true; - trigger.target.turnOver(); - trigger.target.draw(); - } - }, - chilongya:{ - trigger:{source:'damageBegin'}, - forced:true, - filter:function(event){ - return event.nature=='fire'&&event.notLink(); - }, - content:function(){ - trigger.num++; - } - }, - chilongya2:{ - trigger:{source:'damageBegin'}, - filter:function(event,player){ - return (event.card&&event.card.name=='sha'); - }, - popup:false, - forced:true, - content:function(){ - if(Math.random()<0.5){ - trigger.num++; - trigger.player.addSkill('chilongfengxue'); - } - } - }, - chilongfengxue:{ - trigger:{global:'shaAfter'}, - forced:true, - popup:false, - content:function(){ - player.draw(); - player.removeSkill('chilongfengxue'); - } - }, - shentou:{ - enable:'phaseUse', - usable:1, - filterCard:true, - filter:function(event,player){ - var nh=player.countCards('h'); - if(nh==0) return false; - return game.hasPlayer(function(current){ - return current!=player&¤t.countCards('h')>nh; - }); - }, - check:function(card){ - return 8-get.value(card); - }, - filterTarget:function(card,player,target){ - if(target.countCards('h')==0) return false; - if(target==player) return false; - if(target.countCards('h')<=player.countCards('h')) return false; - return true; - }, - content:function(){ - "step 0" - player.judge(function(card){ - if(get.suit(card)=='club') return -1; - return 1; - }); - "step 1" - if(result.bool){ - var card=target.getCards('h').randomGet(); - if(card){ - player.gain(card,target); - target.$giveAuto(card,player); - } - } - }, - ai:{ - basic:{ - order:5 - }, - result:{ - player:0.3, - target:-1, - } - } - }, - old_longfan:{ - enable:'phaseUse', - usable:1, - prompt:'?', - filterTarget:true, - content:function(){ - "step 0" - if(event.isMine()){ - event.longfan=ui.create.control('〇','〇','〇','〇',function(){ - event.longfan.status--; - }); - event.longfan.status=4; - for(var i=0;i1) event.count(1); - if(event.longfan.status>2) event.count(2); - if(event.longfan.status>3) event.count(3); - },200); - event.count=function(num){ - event.longfan.childNodes[num].num=(event.longfan.childNodes[num].num+1)%10; - if(event.longfan.childNodes[num].num==2) event.longfan.childNodes[num].innerHTML='二'; - else event.longfan.childNodes[num].innerHTML=get.cnNumber(event.longfan.childNodes[num].num); - } - game.pause(); - } - else{ - event.finish(); - var x=Math.random(); - if(x<0.1) target.draw(); - else if(x<0.2) target.chooseToDiscard(true); - else if(x<0.3) target.loseHp(); - else if(x<0.4) target.recover(); - else if(x<0.6){ - if(get.attitude(player,target)>0) target.draw(); - else target.chooseToDiscard(true); - } - else if(x<0.8){ - if(get.attitude(player,target)>0) target.recover(); - else target.loseHp(); - } - } - "step 1" - var str=''; - for(var i=0;iplayer.hp?2:0; - case 'diamond':return 1; - case 'club':return 1; - case 'spade':return 0; - } - }); - "step 1" - switch(result.suit){ - case 'heart':player.recover();break; - case 'diamond':player.draw();break; - case 'club':{ - var targets=player.getEnemies(); - for(var i=0;i0; - }, - discard:false, - prepare:'give', - filterTarget:function(card,player,target){ - if(player==target) return false; - return true; - }, - content:function(){ - target.damage(); - target.gain(cards,player); - // game.delay(); - }, - check:function(card){ - return 10-get.value(card); - }, - position:'he', - ai:{ - basic:{ - order:8 - }, - result:{ - target:-1 - } - } - }, - xixue:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&player.hp1/3) return false; - return true; - }, - content:function(){ - trigger.num--; - } - }, - nigong:{ - trigger:{player:'damageAfter'}, - group:['nigong2','nigong3'], - forced:true, - content:function(){ - player.storage.nigong+=trigger.num; - if(player.storage.nigong>4){ - player.storage.nigong=4; - } - player.updateMarks(); - }, - ai:{ - effect:function(card,player,target){ - if(get.tag(card,'damage')&&!target.hujia) return [1,0.5]; - } - }, - intro:{ - content:'已积攒#点伤害' - } - }, - nigong2:{ - enable:'phaseUse', - filter:function(event,player){ - return player.storage.nigong>1; - }, - filterTarget:function(card,player,target){ - return player!=target; - }, - prompt:function(event){ - var str='弃置所有逆攻标记,'; - if(event.player.storage.nigong%2!=0){ - str+='摸一张牌,'; - } - str+='并对一名其他角色造成'+get.cnNumber(Math.floor(event.player.storage.nigong/2))+'点伤害'; - return str; - }, - content:function(){ - if(player.storage.nigong%2!=0){ - player.draw(); - } - target.damage(Math.floor(player.storage.nigong/2)); - player.storage.nigong=0; - player.updateMarks(); - }, - ai:{ - order:10, - result:{ - target:function(player,target){ - var num=get.damageEffect(target,player,target); - if(player.storage.nigong>=4&&num>0){ - num=0; - } - return num; - } - } - } - }, - nigong3:{ - enable:'phaseUse', - filter:function(event,player){ - return player.storage.nigong==1; - }, - content:function(){ - player.draw(); - player.storage.nigong=0; - player.updateMarks(); - }, - ai:{ - order:10, - result:{ - player:1 - } - } - }, - sadengjinhuan:{ - trigger:{player:'shaMiss'}, - check:function(event,player){ - return get.attitude(player,event.target)<0; - }, - content:function(){ - "step 0" - player.judge(function(card){ - return get.color(card)=='red'?1:0; - }) - "step 1" - if(result.bool){ - trigger.target.chooseToRespond({name:'shan'},'萨登荆环:请额外打出一张闪响应杀').autochoose=lib.filter.autoRespondShan; - } - else{ - event.finish(); - } - "step 2" - if(!result.bool){ - trigger.untrigger(); - trigger.trigger('shaHit'); - trigger._result.bool=false; - } - } - }, - ximohu:{ - trigger:{player:'damageBefore'}, - forced:true, - filter:function(event){ - return event.nature=='thunder'; - }, - content:function(){ - trigger.cancel(); - player.recover(trigger.num); - }, - ai:{ - effect:function(card){ - if(get.tag(card,'thunderDamage')) return [0,2]; - } - } - }, - guiyanfadao:{ - trigger:{player:'shaHit'}, - check:function(event,player){ - var att=get.attitude(player,event.target); - if(player.hasSkill('jiu')) return att>0; - if(event.target.hasSkillTag('maixie_hp')||event.target.hasSkillTag('maixie_defend')){ - return att<=0; - } - if(player.hasSkill('tianxianjiu')) return false; - if(event.target.hujia>0) return att<0; - if(event.target.hp==1) return att>0; - return false; - }, - content:function(){ - trigger.unhurt=true; - trigger.target.loseHp(); - } - }, - guiyanfadao2:{ - trigger:{player:'useCardAfter'}, - forced:true, - popup:false, - content:function(){ - delete player.storage.zhuque_skill.nature; - } - }, - tianxianjiu:{ - trigger:{source:'damageEnd'}, - filter:function(event){ - return (event.card&&(event.card.name=='sha')); - }, - forced:true, - temp:true, - vanish:true, - onremove:function(player){ - if(player.node.jiu){ - player.node.jiu.delete(); - player.node.jiu2.delete(); - delete player.node.jiu; - delete player.node.jiu2; - } - }, - content:function(){ - player.draw(2); - player.removeSkill('tianxianjiu'); - }, - ai:{ - damageBonus:true - } - }, - }, - cardType:{ - hslingjian:0.5, - jiqi:0.4, - jiguan:0.45 - }, - help:{ - '轩辕剑':'
      • 零件、祭器牌可用于煅造装备,煅造得到强化装备,并装备给距离1以内的角色
      • '+ - '煅造装备时失去牌以及装备牌的过程不触发任何技能(如枭姬、祈禳)
      • '+ - '进行洗牌时强化装备将从弃牌堆中消失,不进入牌堆
      • '+ - '专属、特殊装备无法被强化' - }, - translate:{ - qiankundai:'乾坤袋', - qiankundai_info:'你的手牌上限+1。当你失去该装备时,你摸一张牌。', - hufu:'玉符', - hufu_bg:'符', - g_hufu_sha:'符杀', - g_hufu_shan:'符闪', - g_hufu_jiu:'符酒', - hufu_info:'你可以将一张玉符当作杀、闪或酒使用或打出', - // yihuajiemu:'移花接木', - // yihuajiemu_info:'对一名装备区内有宝物的角色使用,将其宝物牌转移至另一名角色', - liuxinghuoyu:'流星火羽', - liuxinghuoyu_info:'出牌阶段,对一名角色使用,令目标弃置2张牌,或受到一点火焰伤害', - g_yuchan_equip:'玉蝉', - yuchanqian_duanzao:'玉蝉', - yuchanqian_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanqian_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanqian_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanqian_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanqian_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankun_duanzao:'玉蝉', - yuchankun_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankun_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankun_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankun_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankun_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanzhen_duanzao:'玉蝉', - yuchanzhen_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanzhen_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanzhen_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanzhen_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanzhen_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanxun_duanzao:'玉蝉', - yuchanxun_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanxun_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanxun_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanxun_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanxun_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankan_duanzao:'玉蝉', - yuchankan_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankan_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankan_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankan_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchankan_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanli_duanzao:'玉蝉', - yuchanli_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanli_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanli_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanli_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanli_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchangen_duanzao:'玉蝉', - yuchangen_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchangen_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchangen_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchangen_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchangen_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchandui_duanzao:'玉蝉', - yuchandui_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchandui_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchandui_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchandui_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchandui_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', - yuchanqian:'乾玉蝉', - yuchanqian_info:'在你行动时可当作杀使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', - yuchankun:'坤玉蝉', - yuchankun_info:'在你行动时可当作草药使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', - yuchanzhen:'震玉蝉', - yuchanzhen_info:'在你行动时可当作酒使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', - yuchanxun:'巽玉蝉', - yuchanxun_info:'在你行动时可当作桃使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', - yuchankan:'坎玉蝉', - yuchankan_info:'在你行动时可当作神秘果使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', - yuchanli:'离玉蝉', - yuchanli_info:'在你行动时可当作天仙酒使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', - yuchangen:'艮玉蝉', - yuchangen_info:'在你行动时可当作封印之蛋使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', - yuchandui:'兑玉蝉', - yuchandui_info:'在你行动时可当作雪肌冰鲍使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', - yangpijuan:'羊皮卷', - yangpijuan_info:'出牌阶段对自己使用,选择一种卡牌类别,发现一张该类别的卡牌', - // pantao:'蟠桃', - // pantao_info:'出牌阶段对自己使用,或对濒死角色使用,目标回复两点体力并获得一点护甲', - shencaojie:'神草结', - shencaojie_info:'你的锦囊牌即将造成伤害时对目标使用,令此伤害+1;你即将受到锦囊牌伤害时对自己使用,令此伤害-1', - yuruyi:'玉如意', - yuruyi_ab:'如意', - yuruyi_info:'你有更高的机率摸到好牌', - fengyinzhidan:'封印之蛋', - fengyinzhidan_info:'随机使用两张普通锦囊牌(随机指定目标)', - shuchui:'鼠槌', - shuchui_info:'出牌阶段限一次,你可以指定一名攻击范围内的角色,依次将手牌中的至多3张杀对该角色使用,若杀造成了伤害,你摸一张牌', - zhiluxiaohu:'指路小狐', - zhiluxiaohu_info:'出牌阶段对自己使用,视为对一名随机敌方角色使用一张杀,若此杀造成伤害,你摸一张牌', - xuejibingbao:'雪肌冰鲍', - xuejibingbao_info:'出牌阶段对一名角色使用,该角色摸牌阶段摸牌数+1,持续2个回合', - gouhunluo:'勾魂锣', - gouhunluo_info:'出牌阶段对一名角色使用,在3轮后你的准备阶段令该角色失去1点体力并弃置所有手牌', - jiguan:'机关', - jiqi:'祭器', - qinglongzhigui:'青龙之圭', - g_qinglongzhigui:'青龙之圭', - qinglongzhigui_info:'可用于煅造装备;此牌在你手牌中时,准备阶段,你摸两张牌然后弃置一张牌', - qinglongzhigui_duanzao:'云屏', - qinglongzhigui_equip1_info:'结束阶段,你摸一张牌', - qinglongzhigui_equip2_info:'结束阶段,你摸一张牌', - qinglongzhigui_equip3_info:'结束阶段,你摸一张牌', - qinglongzhigui_equip4_info:'结束阶段,你摸一张牌', - qinglongzhigui_equip5_info:'结束阶段,你摸一张牌', - baishouzhihu:'白兽之琥', - g_baishouzhihu:'白兽之琥', - baishouzhihu_info:'可用于煅造装备;此牌在你手牌中时,每当你弃置卡牌,你可以弃置一名其他角色的一张随机牌', - baishouzhihu_duanzao:'风牙', - baishouzhihu_equip1_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', - baishouzhihu_equip2_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', - baishouzhihu_equip3_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', - baishouzhihu_equip4_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', - baishouzhihu_equip5_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', - zhuquezhizhang:'朱雀之璋', - g_zhuquezhizhang:'朱雀之璋', - zhuquezhizhang_info:'可用于煅造装备;此牌在你手牌中时,每当你受到其他角色造成的伤害,你对伤害来源造成一点火属性伤害', - zhuquezhizhang_duanzao:'炽翎', - zhuquezhizhang_equip1_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', - zhuquezhizhang_equip2_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', - zhuquezhizhang_equip3_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', - zhuquezhizhang_equip4_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', - zhuquezhizhang_equip5_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', - xuanwuzhihuang:'玄武之璜', - g_xuanwuzhihuang:'玄武之璜', - xuanwuzhihuang_duanzao:'寒晶', - xuanwuzhihuang_info:'可用于煅造装备;此牌在你手牌中时,每当你造成伤害,你回复等量的体力', - xuanwuzhihuang_equip1_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', - xuanwuzhihuang_equip2_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', - xuanwuzhihuang_equip3_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', - xuanwuzhihuang_equip4_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', - xuanwuzhihuang_equip5_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', - huanglinzhicong:'黄麟之琮', - g_huanglinzhicong:'黄麟之琮', - huanglinzhicong_duanzao:'玄甲', - huanglinzhicong_info:'可用于煅造装备;此牌在你手牌中时,准备阶段,若你没有护甲,你获得一点护甲', - huanglinzhicong_equip1_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', - huanglinzhicong_equip2_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', - huanglinzhicong_equip3_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', - huanglinzhicong_equip4_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', - huanglinzhicong_equip5_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', - cangchizhibi:'苍螭之璧', - g_cangchizhibi:'苍螭之璧', - cangchizhibi_duanzao:'灵枢', - cangchizhibi_info:'可用于煅造装备;此牌在你手牌中时,准备阶段,你可以选择至多3名角色横置或重置之', - cangchizhibi_equip1_info:'结束阶段,你可以横置或重置一名角色', - cangchizhibi_equip2_info:'结束阶段,你可以横置或重置一名角色', - cangchizhibi_equip3_info:'结束阶段,你可以横置或重置一名角色', - cangchizhibi_equip4_info:'结束阶段,你可以横置或重置一名角色', - cangchizhibi_equip5_info:'结束阶段,你可以横置或重置一名角色', - - guisheqi:'龟蛇旗', - guisheqi_info:'出牌阶段对一名角色使用,目标获得一点护甲', - jiguanfeng:'机关蜂', - jiguanfeng_info:'出牌阶段对一名其他角色使用,目标需打出一张闪,否则非锁定技失效直到下一回合开始,并受到一点伤害', - jiguanyuan:'机关鸢', - jiguanyuan_info:'出牌阶段对一名其他角色使用,你将此牌和一张其它牌置于一名其他角色的武将牌上,然后摸一张牌;该角色于下一结束阶段获得武将牌上的牌', - jiguantong:'机关火筒', - jiguantong_ab:'火筒', - jiguantong_info:'出牌阶段对所有其他角色使用,目标弃置一张手牌,或受到一点火焰伤害;若没有人选择受到伤害,使用者摸一张牌', - jiutiansuanchi:'九天算尺', - jiutiansuanchi_info:'每当你使用杀造成伤害,你可以弃置一张牌并展示受伤害角色的一张手牌,若此牌与你弃置的牌花色或点数相同,此杀的伤害+2', - shenmiguo:'神秘果', - shenmiguo_info:'出牌阶段内,当你使用一张基本牌或普通锦囊牌后使用,令此牌再结算一次。每阶段限用一次', - qinglianxindeng:'青莲心灯', - qinglianxindeng_info:'你防止锦囊牌造成的伤害', - hslingjian_xuanfengzhiren_duanzao:'风刃', - hslingjian_xuanfengzhiren_duanzao2:'风', - hslingjian_xuanfengzhiren_equip1_info:'每当你用杀造成一次伤害,受伤害角色随机弃置一张牌', - hslingjian_xuanfengzhiren_equip2_info:'每当你受到杀造成的伤害,伤害来源随机弃置一张牌', - hslingjian_xuanfengzhiren_equip3_info:'当你于回合外失去牌后,你本回合的防御距离+1', - hslingjian_xuanfengzhiren_equip4_info:'当你于回合内失去牌后,你本回合的进攻距离+1', - hslingjian_xuanfengzhiren_equip5_info:'出牌阶段限一次,你可以弃置一张牌,然后随机弃置一名其他角色的一张牌', - hslingjian_zhongxinghujia_duanzao:'重甲', - hslingjian_zhongxinghujia_duanzao2:'护', - hslingjian_zhongxinghujia_equip1_info:'每当你用杀造成一次伤害,你可以随机装备一件防具牌', - hslingjian_zhongxinghujia_equip2_info:'每当你受到杀造成的伤害,你可以弃置伤害来源的防具牌', - hslingjian_zhongxinghujia_equip3_info:'当你的装备区内有防具牌时,你的防御距离+1', - hslingjian_zhongxinghujia_equip4_info:'当你的装备区内有防具牌时,你的进攻距离+1', - hslingjian_zhongxinghujia_equip5_info:'出牌阶段限一次,你可以弃置两张牌,然后令一名角色随机装备一件防具', - hslingjian_jinjilengdong_duanzao:'冰冻', - hslingjian_jinjilengdong_duanzao2:'冰', - hslingjian_jinjilengdong_equip1_info:'每当你用杀造成一次伤害,你可以令目标摸两张牌并翻面', - hslingjian_jinjilengdong_equip2_info:'每当你受到杀造成的伤害,你可以令伤害来源摸两张牌并翻面', - hslingjian_jinjilengdong_equip3_info:'你的武将牌背面朝上时防御距离+2', - hslingjian_jinjilengdong_equip4_info:'你的武将牌背面朝上时进攻距离+2', - hslingjian_jinjilengdong_equip5_info:'回合结束后,若你的武将牌正面朝上,你可以与一名武将牌正面朝上的其他角色同时翻面,然后各摸两张牌', - hslingjian_yinmilichang_duanzao:'隐力', - hslingjian_yinmilichang_duanzao2:'隐', - hslingjian_yinmilichang_equip1_info:'每当你用杀造成一次伤害,你可以令一名其他角色获得潜行直到其下一回合开始', - hslingjian_yinmilichang_equip2_info:'每当你受到一次伤害,你本回合内获得潜行', - hslingjian_yinmilichang_equip3_info:'当你的体力值为1时,你的防御距离+1', - hslingjian_yinmilichang_equip4_info:'当你的体力值为1时,你的进攻距离+1', - hslingjian_yinmilichang_equip5_info:'当你没有手牌时,你不能成为杀或决斗的目标', - hslingjian_xingtigaizao_duanzao:'移形', - hslingjian_xingtigaizao_duanzao2:'形', - hslingjian_xingtigaizao_equip1_info:'每当你用杀造成一次伤害,你摸一张牌', - hslingjian_xingtigaizao_equip2_info:'每当你受到杀造成的伤害,你摸一张牌', - hslingjian_xingtigaizao_equip3_info:'你的防御距离+1,进攻距离-1', - hslingjian_xingtigaizao_equip4_info:'你的防御距离-1,进攻距离+1', - hslingjian_xingtigaizao_equip5_info:'你于摸牌阶段额外摸一张牌;你的手牌上限-1', - hslingjian_shengxiuhaojiao_duanzao:'号角', - hslingjian_shengxiuhaojiao_duanzao2:'角', - hslingjian_shengxiuhaojiao_equip1_info:'有嘲讽的角色不能闪避你的杀', - hslingjian_shengxiuhaojiao_equip2_info:'有嘲讽的角色不能对你使用杀', - hslingjian_shengxiuhaojiao_equip3_info:'若你的手牌数大于你的体力值,你的防御距离+1', - hslingjian_shengxiuhaojiao_equip4_info:'若你的手牌数大于你的体力值,你的进攻距离+1', - hslingjian_shengxiuhaojiao_equip5_info:'出牌阶段限一次,你可以弃置两张牌,然后令一名角色获得或解除嘲讽', - hslingjian_shijianhuisu_duanzao:'回溯', - hslingjian_shijianhuisu_duanzao2:'溯', - hslingjian_shijianhuisu_equip1_info:'当你装备一张防具牌时,你摸一张牌', - hslingjian_shijianhuisu_equip2_info:'当你装备一张武器牌时,你摸一张牌', - hslingjian_shijianhuisu_equip3_info:'当你的装备区内没有其他牌时,你的防御距离+1', - hslingjian_shijianhuisu_equip4_info:'当你的装备区内没有其他牌时,你的进攻距离+1', - hslingjian_shijianhuisu_equip5_info:'出牌阶段限一次,你可以弃置一张牌,然后令一名其他角色将其装备区内的牌收回手牌', - _lingjianduanzao:'煅造', - _lingjianduanzao_info:'出牌阶段,你可以将一张装备牌和一张可煅造的牌合成为一件强化装备,并装备给距离1以内的一名角色', - jiguanshu:'机关鼠', - jiguanshu_info:'出牌阶段对自己使用,用随机祭器强化装备区内的一张随机装备,然后用随机零件强化其余的装备', - lingjiandai:'零件袋', - lingjiandai_info:'出牌阶段对自己使用,获得3张随机零件', - mujiaren:'木甲人', - mujiaren_info:'出牌阶段限用一次,将你手牌中的非基本牌(含此张)替换为随机的机关牌', - jiguanyaoshu:'机关要术', - jiguanyaoshu_skill:'巧匠', - jiguanyaoshu_skill_info:'每当你于回合外失去装备区内的牌,你获得一个随机零件', - jiguanyaoshu_info:'出牌阶段对距离1以内的一名角色使用,目标随机装备一件装备牌并获得技能巧匠(每当你于回合外失去装备区内的牌,你获得一个随机零件)', - hslingjian:'零件', - hslingjian_xuanfengzhiren:'旋风之刃', - hslingjian_xuanfengzhiren_info:'可用于煅造装备;随机弃置一名角色的一张牌', - hslingjian_zhongxinghujia:'重型护甲', - hslingjian_zhongxinghujia_info:'可用于煅造装备;令一名角色装备一件随机防具,然后随机弃置其一张手牌', - hslingjian_jinjilengdong:'紧急冷冻', - hslingjian_jinjilengdong_bg:'冻', - hslingjian_jinjilengdong_info:'可用于煅造装备;令一名武将牌正面朝上的其他角色获得两点护甲并翻面,该角色不能使用卡牌,也不能成为卡牌的目标直到武将牌翻回正面', - hslingjian_yinmilichang:'隐秘力场', - hslingjian_yinmilichang_info:'可用于煅造装备;令一名其他角色获得技能潜行,直到其下一回合开始', - hslingjian_xingtigaizao:'型体改造', - hslingjian_xingtigaizao_info:'可用于煅造装备;摸一张牌,本回合手牌上限-1', - hslingjian_shengxiuhaojiao:'生锈号角', - hslingjian_shengxiuhaojiao_info:'可用于煅造装备;令一名角色获得技能嘲讽,直到其下一回合开始', - hslingjian_shijianhuisu:'时间回溯', - hslingjian_shijianhuisu_info:'可用于煅造装备;令一名其他角色将其装备牌收回手牌', - hslingjian_chaofeng:'嘲讽', - hslingjian_chaofeng_info:'锁定技,与你相邻的角色只能选择你为出杀目标', - qinglonglingzhu:'青龙灵珠', - qinglonglingzhu_ab:'灵珠', - qinglonglingzhu_info:'每当你造成一次属性伤害,你可以获得对方的一张牌', - xingjunyan:'星君眼', - xingjunyan_info:'你的杀造成的伤害+1;杀对你造成的伤害+1', - guiyanfadao:'鬼眼法刀', - guiyanfadao_bg:'眼', - guiyanfadao_info:'每当你使用杀命中目标,你可以防止伤害,改为令目标失去一点体力', - tianxianjiu:'天仙酒', - tianxianjiu_bg:'仙', - tianxianjiu_info:'出牌阶段对自己使用,你使用的下一张杀造成伤害后可以摸两张牌;濒死阶段,对自己使用,回复1点体力', - // xiangyuye:'翔羽叶', - // xiangyuye_info:'出牌阶段,对一名攻击范围外的角色使用,令其弃置一张黑色手牌或流失一点体力', - // huanpodan:'还魄丹', - // huanpodan_bg:'魄', - // huanpodan_info:'出牌阶段对一名角色使用,在目标即将死亡时防止其死亡,改为令其弃置所有牌,将体力值回复至1并摸一张牌', - // huanpodan_skill:'还魄丹', - // huanpodan_skill_bg:'丹', - // huanpodan_skill_info:'防止一次死亡,改为弃置所有牌,将体力值变为1并摸一张牌', - ximohu:'吸魔壶', - ximohu_bg:'魔', - // ximohu_info:'锁定技,你将即将受到的雷属性伤害转化为你的体力值', - sadengjinhuan:'萨登荆环', - sadengjinhuan_ab:'荆环', - sadengjinhuan_info:'当你的杀被闪避后,可以进行一次判定,若结果为红色目标需再打出一张闪', - sadengjinhuan_bg:'荆', - qipoguyu:'奇魄古玉', - xujin:'蓄劲', - xujin2:'蓄劲', - // qipoguyu_info:'装备后获得蓄劲技能', - xujin_info:'回合开始前,若你的蓄劲标记数小于当前的体力值,你可以跳过此回合,并获得一枚蓄劲标记。锁定技,每当你即将造成伤害,你令此伤害+X,然后弃置一枚蓄劲标记,X为你拥有的蓄劲标记数', - guilingzhitao:'归灵指套', - nigong:'逆攻', - nigong2:'逆攻', - nigong3:'逆攻', - nigong4:'逆攻', - guilingzhitao_info:'每当你受到一点伤害,你获得一个逆攻标记,标记数不能超过4。出牌阶段,你可以弃置所有逆攻标记并令对一名其他角色造成标记数一半的伤害(若非整数则向下取整并摸一张牌)', - nigong_info:'每当你受到一点伤害,你获得一个逆攻标记,标记数不能超过4。出牌阶段,你可以弃置所有逆攻标记并令对一名其他角色造成标记数一半的伤害(若非整数则向下取整并摸一张牌)', - baihupifeng:'白狐披风', - baihupifeng_bg:'狐', - baihupifeng_info:'结束阶段,若你的体力值是全场最小的之一,你可以回复一点体力', - fengxueren:'封雪刃', - fengxueren_bg:'雪', - fengxueren_info:'你使用杀击中目标后,若目标武将牌正面朝上,你可以防止伤害,然后令目标摸一张牌并翻面', - chilongya:'赤龙牙', - chilongya_info:'锁定技,你的火属性伤害+1', - daihuofenglun:'带火风轮', - daihuofenglun_ab:'风轮', - daihuofenglun_bg:'轮', - daihuofenglun_info:'你的进攻距离+2,你的防御距离-1', - xiayuncailing:'霞云彩绫', - xiayuncailing_ab:'彩绫', - xiayuncailing_bg:'云', - xiayuncailing_info:'你的进攻距离-1,你的防御距离+2', - shentoumianju:'神偷面具', - shentoumianju_bg:'偷', - shentoumianju_info:'出牌阶段,你可以指定一名手牌比你多的角色,弃置一张手牌并进行一次判定,若结果不为梅花,你获得其一张手牌', - shentou:'神偷', - shentou_info:'出牌阶段,你可以进行一次判定,若结果不为梅花,你获得任意一名角色的一张手牌', - xianluhui:'仙炉灰', - xianluhui_info:'令所有已受伤角色获得一点护甲', - caoyao:'草药', - caoyao_info:'出牌阶段,对距离为1以内的角色使用,回复一点体力。', - langeguaiyi:'蓝格怪衣', - langeguaiyi_bg:'格', - langeguaiyi_info:'出牌阶段限一次,你可以进行一次判定,然后按花色执行以下效果。红桃:你回复一点体力;方片:你摸一张牌;梅花:你令一名随机敌方角色随机弃置一张牌;黑桃:无事发生', - longfan:'龙帆', - longfan_info:'出牌阶段限一次,你可以进行一次判定,然后按花色执行以下效果。红桃:你回复一点体力;方片:你摸一张牌;梅花:你令一名随机敌方角色随机弃置一张牌;黑桃:无事发生', - // longfan_info:'0000:翻面;1111:弃手牌;2222:弃装备牌;3333:受伤害;4444:流失体力;5555:连环;6666:摸牌;7777:回复体力;8888:弃置判定牌;9999:置衡', - guiyoujie:'鬼幽结', - guiyoujie_bg:'结', - guiyoujie_info:'出牌阶段,对一名其他角色使用。若判定结果为黑色,其失去一点体力并随机弃置一张牌', - yufulu:'御夫录', - yufulu_info:'出牌阶段,可弃置一张武器牌令一名角色受到一点伤害,然后该角色获得此武器牌', - touzhi:'投掷', - touzhi_info:'出牌阶段,可弃置一张武器牌令一名角色受到一点伤害,然后该角色获得此武器牌', - xixueguizhihuan:'吸血鬼指环', - xixueguizhihuan_ab:'血环', - xixueguizhihuan_info:'锁定技,每当你使用杀造成一点伤害,你回复一点体力', - xixue:'吸血', - xixue_info:'锁定技,每当你使用杀造成一点伤害,你回复一点体力', - zhufangshenshi:'祠符', - zhufangshenshi_info:'出牌阶段,对一名其他角色使用,本回合内对其使用卡牌无视距离,结算后摸一张牌', - jingleishan:'惊雷闪', - jingleishan_info:'出牌阶段,对所有其他角色使用。每名目标角色需打出一张【杀】,否则受到1点雷电伤害。', - chiyuxi:'炽羽袭', - chiyuxi_info:'出牌阶段,对所有其他角色使用。每名目标角色需打出一张【闪】,否则受到1点火焰伤害。', - guangshatianyi:'光纱天衣', - guangshatianyi_bg:'纱', - guangshatianyi_info:'锁定技,每当你即将受到伤害,有三分之一的概率令伤害减一', - sifeizhenmian:'四非真面', - sifeizhenmian_info:'出牌阶段限一次,你可以令一名有手牌的其他角色进行一次判定,若结果为不为红桃且目标有可用的手牌,目标随机使用一张手牌(随机指定目标)', - yiluan:'意乱', - yiluan_info:'出牌阶段限一次,你可以令一名有手牌的其他角色进行一次判定,若结果为不为红桃且目标有可用的手牌,目标随机使用一张手牌(随机指定目标)', - donghuangzhong:'东皇钟', - xuanyuanjian:'轩辕剑', - xuanyuanjian2:'轩辕剑', - pangufu:'盘古斧', - lianyaohu:'炼妖壶', - lianyaohu_skill:'炼妖壶', - lianyaohu_skill_bg:'壶', - haotianta:'昊天塔', - fuxiqin:'伏羲琴', - shennongding:'神农鼎', - kongdongyin:'崆峒印', - kunlunjingc:'昆仑镜', - nvwashi:'女娲石', - donghuangzhong_bg:'钟', - lianyaohu_bg:'壶', - haotianta_bg:'塔', - fuxiqin_bg:'琴', - shennongding_bg:'鼎', - kongdongyin_bg:'印', - kunlunjingc_bg:'镜', - nvwashi_bg:'石', - kongxin:'控心', - lianhua:'炼化', - // dujian:'毒箭', - // dujian_info:'出牌阶段,对一名有手牌或装备牌的角色使用,令其展示一张手牌,若与你选择的手牌颜色相同,其流失一点体力', - lianhua_info:'出牌阶段限一次,你可以弃置两张炼妖壶中的牌,从牌堆中获得一张与弃置的牌类别均不相同的牌', - shouna:'收纳', - shouna_info:'出牌阶段限一次,你可以弃置一张手牌,并将一名其他角色的一张手牌置入炼妖壶', - donghuangzhong_info:'结束阶段,你可以弃置一张红色手牌,并选择一名角色将一张随机单体延时锦囊置入其判定区', - xuanyuanjian_info:'装备时获得一点护甲;每当你即将造成一次伤害,你令此伤害加一并变为雷属性,并在伤害结算后流失一点体力。任何时候,若你体力值不超过2,则立即失去轩辕剑', - pangufu_info:'锁定技,每当你造成一次伤害,受伤角色须弃置一张牌', - haotianta_info:'锁定技,任意一名角色进行判定前,你观看牌堆顶的2张牌,并选择一张作为判定结果,此结果不可被更改,也不能触发技能', - shennongding_info:'出牌阶段,你可以弃置两张手牌,然后回复一点体力。每阶段限一次', - kongdongyin_info:'令你抵挡一次死亡,将体力回复至1,并摸一张牌,发动后进入弃牌堆', - kunlunjingc_info:'出牌阶段限一次,你可以观看牌堆顶的三张牌,然后用一张手牌替换其中的一张', - nvwashi_info:'当一名角色濒死时,若你的体力值大于1,你可以失去一点体力并令其回复一点体力', - kongxin_info:'出牌阶段限一次,你可以与一名其他角色进行拼点,若你赢,你可以指定另一名角色视为对方对该角色使用一张杀,否则对方可弃置你一张牌', - fuxiqin_info:'出牌阶段限一次,你可以与一名其他角色进行拼点,若你赢,你可以指定另一名角色视为对方对该角色使用一张杀,否则对方可弃置你一张牌', - lianyaohu_info:'出牌阶段各限一次,你可以选择一项:1.弃置一张手牌,并将一名其他角色的一张手牌置入炼妖壶;2.弃置两张炼妖壶中的牌,从牌堆中获得一张与弃置的牌类别均不相同的牌', - }, - list:[ - ['heart',1,'hufu'], - ['spade',1,'hufu'], - ['club',1,'qiankundai'], - // ['heart',3,'yihuajiemu'], - // ['diamond',1,'yihuajiemu'], - // ['diamond',7,'yihuajiemu'], - - ['diamond',3,'liuxinghuoyu','fire'], - ['heart',6,'liuxinghuoyu','fire'], - ['heart',9,'liuxinghuoyu','fire'], - - ['spade',1,'baihupifeng'], - ['club',1,'fengxueren'], - ['diamond',1,'langeguaiyi'], - ['heart',1,'daihuofenglun','fire'], - - ['diamond',2,'xiayuncailing'], - // ['heart',2,'pantao'], - // ['heart',2,'huanpodan'], - - ['club',3,'caoyao'], - ['diamond',3,'chilongya','fire'], - ['spade',3,'guiyoujie'], - - ['club',4,'caoyao'], - ['spade',4,'zhufangshenshi'], - // ['spade',4,'huanpodan'], - - ['club',5,'caoyao'], - ['spade',5,'xixueguizhihuan'], - // ['diamond',5,'huanpodan'], - - ['club',6,'shentoumianju'], - ['spade',6,'yufulu'], - - ['diamond',7,'chiyuxi','fire'], - ['club',7,'jingleishan','thunder'], - ['spade',7,'guilingzhitao'], - - ['spade',8,'zhufangshenshi'], - // ['club',8,'xiangyuye','poison'], - - ['spade',9,'yangpijuan'], - ['club',9,'guiyoujie'], - // ['diamond',9,'xiangyuye','poison'], - - // ['diamond',9,'tianxianjiu'], - ['heart',9,'tianxianjiu'], - ['diamond',2,'tianxianjiu'], - - ['spade',2,'qinglonglingzhu'], - ['spade',7,'xingjunyan'], - - //['spade',10,'qipoguyu'], - //['diamond',10,'xiangyuye','poison'], - ['club',7,'yangpijuan'], - - // ['spade',11,'xiangyuye','poison'], - - ['spade',12,'guiyanfadao','poison'], - - ['spade',13,'xianluhui'], - ['diamond',3,'guangshatianyi'], - ['club',13,'sadengjinhuan'], - - ['club',2,'lingjiandai'], - // ['spade',3,'lingjiandai'], - // ['heart',5,'lingjiandai'], - ['diamond',8,'lingjiandai'], - - ['club',2,'jiguanshu'], - // ['spade',2,'jiguanshu'], - // ['heart',2,'jiguanshu'], - ['diamond',2,'jiguanshu'], - - ['club',3,'jiguanyaoshu'], - ['spade',3,'jiguanyaoshu'], - // ['heart',3,'jiguanyaoshu'], - // ['diamond',3,'jiguanyaoshu'], - - ['spade',4,'sifeizhenmian'], - ['heart',13,'qinglianxindeng'], - ['club',3,'jiguanyuan'], - ['diamond',2,'jiguanyuan'], - ['diamond',4,'jiguantong'], - ['club',7,'jiguantong'], - // ['spade',1,'shenmiguo'], - ['spade',2,'shenmiguo'], - ['heart',1,'shenmiguo'], - ['club',3,'jiguanfeng'], - ['spade',4,'jiguanfeng'], - ['spade',9,'guisheqi'], - ['club',7,'guisheqi'], - - ['diamond',13,'donghuangzhong'], - ['diamond',13,'fuxiqin'], - ['spade',13,'kunlunjingc'], - ['spade',13,'xuanyuanjian'], - ['spade',13,'pangufu'], - ['club',13,'lianyaohu'], - ['diamond',13,'haotianta'], - ['club',13,'shennongding'], - ['heart',13,'nvwashi'], - ['heart',13,'kongdongyin'], - - ['heart',6,'qinglongzhigui'], - ['diamond',6,'zhuquezhizhang'], - ['spade',6,'baishouzhihu'], - ['club',6,'xuanwuzhihuang'], - ['spade',7,'cangchizhibi'], - ['heart',5,'huanglinzhicong'], - - ['spade',9,'gouhunluo'], - ['club',7,'gouhunluo'], - - ['spade',1,'xuejibingbao'], - ['club',1,'xuejibingbao'], - - ['heart',3,'zhiluxiaohu'], - ['diamond',4,'zhiluxiaohu'], - - ['club',7,'mujiaren'], - ['heart',6,'mujiaren'], - ['diamond',11,'mujiaren'], - - ['club',6,'shuchui'], - - // ['club',1,'fengyinzhidan'], - // ['diamond',1,'fengyinzhidan'], - // ['heart',1,'fengyinzhidan'], - ['spade',1,'fengyinzhidan'], - - ['heart',9,'yuruyi'], - - ['club',4,'shencaojie'], - ['diamond',4,'shencaojie'], - ['spade',4,'shencaojie'], - - ['spade',1,'yuchanqian'], - ['club',2,'yuchankun'], - ['diamond',3,'yuchanzhen'], - ['heart',4,'yuchanxun'], - ['spade',5,'yuchankan'], - ['club',6,'yuchanli'], - ['diamond',7,'yuchangen'], - ['heart',8,'yuchandui'], - - // ['spade',3,'dujian','poison'], - // ['club',11,'dujian','poison'], - // ['club',12,'dujian','poison'], - ], - }; -}); +'use strict'; +game.import('card',function(lib,game,ui,get,ai,_status){ + return { + name:'swd', + card:{ + hufu:{ + fullskin:true, + type:'basic', + global:['g_hufu_sha','g_hufu_shan','g_hufu_jiu'], + savable:function(card,player,dying){ + return dying==player; + }, + ai:{ + value:[7.5,5,2], + useful:[7.5,5,2], + } + }, + // yihuajiemu:{ + // fullskin:true, + // type:'trick', + // enable:true, + // singleCard:true, + // filterTarget:function(card,player,target){ + // if(target.isMin()) return false; + // if(ui.selected.targets.length){ + // return target.getCards('e',{subtype:'equip5'}).length==0; + // } + // else{ + // return target.getCards('e',{subtype:'equip5'}).length>0; + // } + // }, + // selectTarget:2, + // multitarget:true, + // complexTarget:true, + // multicheck:function(){ + // return game.hasPlayer(function(current){ + // return current.getEquip(5); + // })&&game.hasPlayer(function(current){ + // return !current.getEquip(5); + // }); + // }, + // content:function(){ + // if(target.getEquip(5)){ + // target.$give(target.getEquip(5),event.addedTarget); + // event.addedTarget.equip(target.getEquip(5)); + // game.delay(); + // } + // }, + // ai:{ + // order:1, + // result:{ + // target:function(player,target){ + // if(target.getCards('e',{subtype:'equip5'}).length){ + // if(get.attitude(target,player)>0){ + // return -0.5; + // } + // return -1; + // } + // return 1; + // } + // }, + // tag:{ + // loseCard:1 + // } + // } + // }, + liuxinghuoyu:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:true, + cardcolor:'red', + cardnature:'fire', + content:function(){ + "step 0" + if(target.countCards('he')<2){ + event.directfalse=true; + } + else{ + target.chooseToDiscard('he',2).ai=function(card){ + if(target.hasSkillTag('nofire')) return 0; + if(get.damageEffect(target,player,target,'fire')>=0) return 0; + if(player.hasSkillTag('notricksource')) return 0; + if(target.hasSkillTag('notrick')) return 0; + if(card.name=='tao') return 0; + if(target.hp==1&&card.name=='jiu') return 0; + if(target.hp==1&&get.type(card)!='basic'){ + return 10-get.value(card); + } + return 8-get.value(card); + }; + } + "step 1" + if(event.directfalse||!result.bool){ + target.damage('fire'); + } + }, + ai:{ + basic:{ + order:4, + value:7, + useful:2, + }, + result:{ + target:function(player,target){ + if(target.hasSkillTag('nofire')) return 0; + if(get.damageEffect(target,player,player)<0&&get.attitude(player,target)>0){ + return -2; + } + var nh=target.countCards('he'); + if(target==player) nh--; + switch(nh){ + case 0:case 1:return -2; + case 2:return -1.5; + case 3:return -1; + default:return -0.7; + } + } + }, + tag:{ + damage:1, + fireDamage:1, + natureDamage:1, + discard:1, + loseCard:1, + position:'he', + } + } + }, + dujian:{ + fullskin:true, + type:'basic', + enable:true, + filterTarget:function(card,player,target){ + return target.countCards('h')>0; + }, + content:function(){ + "step 0" + if(target.countCards('h')==0||player.countCards('h')==0){ + event.finish(); + return; + } + player.chooseCard(true); + "step 1" + event.card1=result.cards[0]; + var rand=Math.random()<0.5; + target.chooseCard(true).ai=function(card){ + if(rand) return Math.random(); + return get.value(card); + }; + "step 2" + event.card2=result.cards[0]; + ui.arena.classList.add('thrownhighlight'); + game.addVideo('thrownhighlight1'); + player.$compare(event.card1,target,event.card2); + game.delay(4); + "step 3" + game.log(player,'展示了',event.card1); + game.log(target,'展示了',event.card2); + if(get.color(event.card2)==get.color(event.card1)){ + player.discard(event.card1).animate=false; + target.$gain2(event.card2); + var clone=event.card1.clone; + if(clone){ + clone.style.transition='all 0.5s'; + clone.style.transform='scale(1.2)'; + clone.delete(); + game.addVideo('deletenode',player,get.cardsInfo([clone])); + } + target.loseHp(); + } + else{ + player.$gain2(event.card1); + target.$gain2(event.card2); + target.addTempSkill('dujian2'); + } + ui.arena.classList.remove('thrownhighlight'); + game.addVideo('thrownhighlight2'); + }, + ai:{ + basic:{ + order:2, + value:3, + useful:1, + }, + result:{ + player:function(player,target){ + if(player.countCards('h')<=Math.min(5,Math.max(2,player.hp))&&_status.event.name=='chooseToUse'){ + if(typeof _status.event.filterCard=='function'&& + _status.event.filterCard({name:'dujian'})){ + return -10; + } + if(_status.event.skill){ + var viewAs=get.info(_status.event.skill).viewAs; + if(viewAs=='dujian') return -10; + if(viewAs&&viewAs.name=='dujian') return -10; + } + } + return 0; + }, + target:function(player,target){ + if(target.hasSkill('dujian2')||target.countCards('h')==0) return 0; + if(player.countCards('h')<=1) return 0; + return -1.5; + } + }, + tag:{ + loseHp:1 + } + } + }, + yangpijuan:{ + fullskin:true, + type:'trick', + enable:true, + toself:true, + filterTarget:function(card,player,target){ + return target==player; + }, + modTarget:true, + content:function(){ + 'step 0' + var choice; + if(target.countCards('h','shan')==0||target.countCards('h','sha')==0||target.hp<=1){ + choice='basic'; + } + else{ + var e2=target.getEquip(2); + var e3=target.getEquip(3); + if((e2&&e3)||((e2||e3)&&target.needsToDiscard()<=1)||Math.random()<0.5){ + choice='trick'; + } + else{ + choice='equip'; + } + } + target.chooseControl('basic','trick','equip',function(){ + return choice; + }).set('prompt','选择一种卡牌类型'); + 'step 1' + var list=get.inpile(result.control,'trick'); + list=list.randomGets(3); + for(var i=0;i=0) return 0; + return 1; + } + else{ + if(get.attitude(player,target)>0) return 0; + if(get.damageEffect(target,player,target)>=0) return 0; + return -1; + } + } + }, + } + }, + yuruyi:{ + type:'equip', + subtype:'equip5', + skills:['yuruyi'], + fullskin:true, + ai:{ + basic:{ + equipValue:6 + } + }, + }, + fengyinzhidan:{ + type:'basic', + enable:true, + fullskin:true, + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + modTarget:true, + // usable:1, + content:function(){ + 'step 0' + event.num=2; + var list=[]; + event.list=list; + for(var i=0;i0&&info.selectTarget[1]>=info.selectTarget[0]){ + list.push(lib.inpile[i]); + } + } + else if(typeof info.selectTarget=='number'){ + list.push(lib.inpile[i]); + } + } + } + } + 'step 1' + var list=event.list; + while(list.length){ + var card={name:list.randomRemove()}; + var info=get.info(card); + var targets=game.filterPlayer(function(current){ + return lib.filter.filterTarget(card,target,current); + }); + if(targets.length){ + targets.sort(lib.sort.seat); + if(info.selectTarget==-1){ + target.useCard(card,targets,'noai'); + } + else{ + var num=info.selectTarget; + if(Array.isArray(num)){ + if(targets.length0){ + event.redo(); + } + break; + } + } + }, + ai:{ + order:9, + value:8, + useful:3, + result:{ + target:1 + } + } + }, + yuchanqian:{ + fullskin:true, + type:'jiqi', + addinfo:'杀', + autoViewAs:'sha', + global:['g_yuchan_swap','g_yuchan_equip'], + ai:{ + value:6, + useful:[5,1] + } + }, + yuchankun:{ + fullskin:true, + type:'jiqi', + addinfo:'药', + autoViewAs:'caoyao', + global:['g_yuchan_swap','g_yuchan_equip'], + ai:{ + value:6, + useful:[7,2] + } + }, + yuchanzhen:{ + fullskin:true, + type:'jiqi', + autoViewAs:'jiu', + addinfo:'酒', + global:['g_yuchan_swap','g_yuchan_equip'], + savable:function(card,player,dying){ + return dying==player; + }, + ai:{ + value:6, + useful:[4,1] + } + }, + yuchanxun:{ + fullskin:true, + type:'jiqi', + autoViewAs:'tao', + addinfo:'桃', + global:['g_yuchan_swap','g_yuchan_equip'], + savable:true, + ai:{ + value:6, + useful:[8,6.5] + } + }, + yuchankan:{ + fullskin:true, + type:'jiqi', + autoViewAs:'shenmiguo', + global:['g_yuchan_swap','g_yuchan_equip'], + addinfo:'果', + ai:{ + order:1, + useful:4, + value:6, + result:{ + player:function(){ + var cardname=_status.event.cardname; + if(cardname=='tiesuo') return 0; + if(cardname=='jiu') return 0; + if(cardname=='tianxianjiu') return 0; + if(cardname=='toulianghuanzhu') return 0; + if(cardname=='shijieshu') return 0; + if(cardname=='xietianzi') return 0; + if(cardname=='huogong') return 0; + if(cardname=='shandianjian') return 0; + return 1; + } + }, + } + }, + yuchanli:{ + fullskin:true, + type:'jiqi', + autoViewAs:'tianxianjiu', + global:['g_yuchan_swap','g_yuchan_equip'], + addinfo:'仙', + savable:function(card,player,dying){ + return dying==player; + }, + ai:{ + value:6, + useful:1 + } + }, + yuchangen:{ + fullskin:true, + type:'jiqi', + addinfo:'蛋', + autoViewAs:'fengyinzhidan', + global:['g_yuchan_swap','g_yuchan_equip'], + ai:{ + value:6, + useful:1 + } + }, + yuchandui:{ + fullskin:true, + type:'jiqi', + addinfo:'雪', + autoViewAs:'xuejibingbao', + global:['g_yuchan_swap','g_yuchan_equip'], + ai:{ + value:6, + useful:4 + } + }, + mujiaren:{ + fullskin:true, + enable:true, + type:'jiguan', + usable:1, + forceUsable:true, + wuxieable:true, + selectTarget:-1, + filterTarget:function(card,player,target){ + return target==player; + }, + content:function(){ + 'step 0' + var cards=target.getCards('h',function(card){ + return get.type(card)!='basic'; + }); + if(cards.length){ + target.lose(cards)._triggered=null; + } + event.num=1+cards.length; + 'step 1' + var cards=[]; + var list=get.typeCard('jiguan'); + for(var i=0;i=0) return 0; + return 8-get.useful(card); + }); + } + else{ + target.damage('fire'); + event.parent.preResult=true; + event.finish(); + } + "step 1" + if(result.bool==false){ + target.damage('fire'); + event.parent.preResult=true; + } + }, + contentAfter:function(){ + if(!event.preResult) player.draw(); + }, + ai:{ + wuxie:function(target,card,player,viewer){ + if(get.attitude(viewer,target)>0){ + if(target.countCards('h')>0||target.hp>1) return 0; + } + }, + basic:{ + order:9, + useful:1 + }, + result:{ + target:function(player,target){ + if(player.hasUnknown(2)) return 0; + var nh=target.countCards('h'); + if(get.mode()=='identity'){ + if(target.isZhu&&nh<=1&&target.hp<=1) return -100; + } + if(nh==0) return -1; + if(nh==1) return -0.7 + return -0.5; + }, + }, + tag:{ + discard:1, + loseCard:1, + damage:1, + natureDamage:1, + fireDamage:1, + multitarget:1, + multineg:1, + } + } + }, + donghuangzhong:{ + fullskin:true, + type:'equip', + subtype:'equip5', + nomod:true, + nopower:true, + unique:true, + skills:['donghuangzhong'], + ai:{ + equipValue:7 + } + }, + xuanyuanjian:{ + fullskin:true, + type:'equip', + subtype:'equip1', + nomod:true, + nopower:true, + unique:true, + skills:['xuanyuanjian','xuanyuanjian2','xuanyuanjian3'], + enable:function(card,player){ + return player.hasSkill('xuanyuan')||player.hp>2; + }, + distance:{attackFrom:-2}, + onEquip:function(){ + if(!player.hasSkill('xuanyuan')&&player.hp<=2){ + player.discard(card); + } + else{ + player.changeHujia(); + } + }, + ai:{ + equipValue:9 + } + }, + pangufu:{ + fullskin:true, + type:'equip', + subtype:'equip1', + skills:['pangufu'], + nomod:true, + nopower:true, + unique:true, + distance:{attackFrom:-3}, + ai:{ + equipValue:8 + } + }, + lianyaohu:{ + fullskin:true, + type:'equip', + subtype:'equip5', + equipDelay:false, + loseDelay:false, + nomod:true, + nopower:true, + unique:true, + onEquip:function(){ + player.markSkill('lianyaohu_skill'); + }, + onLose:function(){ + player.unmarkSkill('lianyaohu_skill'); + }, + clearLose:true, + ai:{ + equipValue:6 + }, + skills:['lianhua','shouna','lianyaohu_skill'] + }, + haotianta:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['haotianta'], + nomod:true, + nopower:true, + unique:true, + ai:{ + equipValue:7 + } + }, + fuxiqin:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['kongxin'], + nomod:true, + nopower:true, + unique:true, + ai:{ + equipValue:6 + } + }, + shennongding:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['shennongding'], + nomod:true, + nopower:true, + unique:true, + ai:{ + equipValue:6 + } + }, + kongdongyin:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['kongdongyin'], + nomod:true, + nopower:true, + unique:true, + ai:{ + equipValue:function(card,player){ + if(player.hp==2) return 7; + if(player.hp==1) return 10; + return 5; + }, + basic:{ + equipValue:7 + } + } + }, + kunlunjingc:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['kunlunjingc'], + nomod:true, + nopower:true, + unique:true, + ai:{ + equipValue:6 + } + }, + nvwashi:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['nvwashi'], + nomod:true, + nopower:true, + unique:true, + ai:{ + equipValue:5 + } + }, + guisheqi:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:true, + content:function(){ + target.changeHujia(); + }, + ai:{ + basic:{ + order:5, + useful:3, + value:[6,2,1] + }, + result:{ + target:function(player,target){ + return 2/Math.max(1,Math.sqrt(target.hp)); + }, + }, + } + }, + jiguanfeng:{ + fullskin:true, + type:'jiguan', + enable:true, + wuxieable:true, + filterTarget:function(card,player,target){ + return target!=player; + }, + content:function(){ + "step 0" + var next=target.chooseToRespond({name:'shan'}); + next.autochoose=lib.filter.autoRespondShan; + "step 1" + if(result.bool==false){ + if(!target.hasSkill('fengyin')){ + target.addTempSkill('fengyin',{player:'phaseBegin'}); + } + target.damage(); + } + else{ + event.finish(); + } + }, + ai:{ + basic:{ + order:9, + useful:3, + value:6.5, + }, + result:{ + target:-2, + }, + tag:{ + respond:1, + respondShan:1, + // damage:1, + } + } + }, + jiguanyuan:{ + fullskin:true, + type:'jiguan', + wuxieable:true, + enable:function(card,player){ + var hs=player.getCards('he'); + return hs.length>1||(hs.length==1&&hs[0]!=card); + }, + filterTarget:function(card,player,target){ + return target!=player&&!target.hasSkill('jiguanyuan'); + }, + content:function(){ + 'step 0' + if(player.countCards('he')){ + player.chooseCard(true,'he').set('prompt2','你将'+ + get.translation(cards)+'和选择牌置于'+get.translation(target)+ + '的武将牌上,然后摸一张牌;'+get.translation(target)+ + '于下一结束阶段获得武将牌上的牌'); + } + else{ + event.finish(); + } + 'step 1' + player.$throw(result.cards); + player.lose(result.cards,ui.special); + ui.special.appendChild(cards[0]); + event.togive=[cards[0],result.cards[0]]; + game.delay(); + 'step 2' + // target.gain(event.togive).delay=false; + target.$gain2(event.togive); + target.storage.jiguanyuan=event.togive; + target.addSkill('jiguanyuan'); + game.log(target,'从',player,'获得了',event.togive); + player.draw(); + }, + ai:{ + basic:{ + order:2, + useful:2, + value:7 + }, + result:{ + target:function(player,target){ + var players=game.filterPlayer(function(current){ + return (current!=player&&!current.isTurnedOver()&& + get.attitude(player,current)>=3&&get.attitude(current,player)>=3) + }); + players.sort(lib.sort.seat); + if(target==players[0]) return 2; + return 0.5; + }, + }, + } + }, + shenmiguo_old:{ + fullskin:true, + type:'trick', + enable:true, + selectTarget:-1, + filterTarget:function(card,player,target){ + return target==player; + }, + modTarget:true, + content:function(){ + var list=[]; + for(var i in lib.card){ + if(lib.card[i].derivation){ + list.push(i); + } + } + if(get.mode()=='stone'){ + list.remove('hslingjian_jinjilengdong'); + } + if(list.length){ + target.gain(game.createCard(list.randomGet()),'draw'); + } + }, + ai:{ + basic:{ + order:7.3, + useful:2, + value:6 + }, + result:{ + target:2, + }, + } + }, + shenmiguo:{ + fullskin:true, + type:'basic', + global:'g_shenmiguo', + content:function(){ + if(Array.isArray(player.storage.shenmiguo)){ + player.useCard(player.storage.shenmiguo[0],player.storage.shenmiguo[1]); + } + }, + ai:{ + order:1, + useful:6, + value:6, + result:{ + player:function(){ + var cardname=_status.event.cardname; + if(get.tag({name:cardname},'norepeat')) return 0; + return 1; + } + }, + } + }, + qinglianxindeng:{ + fullskin:true, + type:'equip', + subtype:'equip2', + skills:['qinglianxindeng'], + ai:{ + basic:{ + equipValue:8 + } + }, + }, + lingjiandai:{ + fullskin:true, + enable:true, + type:'jiguan', + wuxieable:true, + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + modTarget:true, + content:function(){ + var list=get.typeCard('hslingjian'); + if(list.length){ + list=list.randomGets(3); + for(var i=0;i0; + }, + content:function(){ + target.discard(target.getCards('he').randomGet()); + }, + ai:{ + order:9, + result:{ + target:-1, + }, + useful:[2,0.5], + value:[2,0.5], + } + }, + hslingjian_zhongxinghujia:{ + type:'hslingjian', + fullimage:true, + vanish:true, + enable:true, + derivation:true, + derivationpack:'swd', + filterTarget:function(card,player,target){ + return !target.isMin(); + }, + content:function(){ + 'step 0' + var list=[]; + for(var i=0;i6) return num; + } + } + if(hs.length>4) return num+0.5; + } + else{ + if(hs.length){ + if(hs.length<=3) return num; + return num+0.5; + } + } + return num+1; + } + } + }, + useful:[2,0.5], + value:[2,0.5], + } + }, + hslingjian_xingtigaizao:{ + type:'hslingjian', + fullimage:true, + vanish:true, + enable:true, + derivation:true, + derivationpack:'swd', + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + content:function(){ + target.draw(); + target.addSkill('hslingjian_xingtigaizao'); + if(typeof target.storage.hslingjian_xingtigaizao=='number'){ + target.storage.hslingjian_xingtigaizao++; + } + else{ + target.storage.hslingjian_xingtigaizao=1; + } + }, + ai:{ + order:9, + result:{ + target:function(player,target){ + if(!player.needsToDiscard()) return 1; + return 0; + } + }, + useful:[2,0.5], + value:[2,0.5], + } + }, + hslingjian_shijianhuisu:{ + type:'hslingjian', + fullimage:true, + vanish:true, + enable:true, + derivation:true, + derivationpack:'swd', + filterTarget:function(card,player,target){ + return target!=player&&target.countCards('e')>0; + }, + content:function(){ + var es=target.getCards('e'); + target.gain(es); + target.$gain2(es); + }, + ai:{ + order:5, + result:{ + target:function(player,target){ + if(target.hasSkillTag('noe')||target.hasSkillTag('reverseEquip')) return target.countCards('e')*2; + if(target.getEquip('baiyin')&&target.isDamaged()) return 2; + if(target.getEquip('xuanyuanjian')||target.getEquip('qiankundai')) return 1; + if(target.hasSkill('jiguanyaoshu_skill')) return 0.5; + var num=0; + var es=target.getCards('e'); + for(var i=0;i4) return -1.2*num; + else if(target.hp==4) return -1*num; + else if(target.hp==3) return -0.9*num; + else if(target.hp==2) return -0.5*num; + else{ + if(target.maxHp>2){ + if(target.hujia) return 0.5*num; + return num; + } + return 0; + } + }, + }, + useful:[2,0.5], + value:[2,0.5], + } + }, + hslingjian_shengxiuhaojiao:{ + type:'hslingjian', + fullimage:true, + vanish:true, + enable:true, + derivation:true, + derivationpack:'swd', + filterTarget:function(card,player,target){ + return !target.hasSkill('hslingjian_chaofeng'); + }, + content:function(){ + target.addTempSkill('hslingjian_chaofeng',{player:'phaseBegin'}); + }, + ai:{ + order:2, + result:{ + target:function(player,target){ + if(get.distance(player,target,'absolute')<=1) return 0; + if(target.countCards('h')<=target.hp) return -0.1; + return -1; + } + }, + useful:[2,0.5], + value:[2,0.5], + } + }, + hslingjian_yinmilichang:{ + type:'hslingjian', + fullimage:true, + vanish:true, + enable:true, + derivation:true, + derivationpack:'swd', + filterTarget:function(card,player,target){ + return player!=target&&!target.hasSkill('qianxing'); + }, + content:function(){ + target.tempHide(); + }, + ai:{ + order:2, + result:{ + target:function(player,target){ + if(get.distance(player,target,'absolute')<=1) return 0; + if(target.hp==1) return 2; + if(target.hp==2&&target.countCards('h')<=2) return 1.2; + return 1; + } + }, + useful:[2,0.5], + value:[2,0.5], + } + }, + xingjunyan:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['xingjunyan'], + ai:{ + basic:{ + equipValue:4 + }, + }, + }, + qinglonglingzhu:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['qinglonglingzhu'], + ai:{ + basic:{ + equipValue:5 + }, + }, + }, + baihupifeng:{ + fullskin:true, + type:"equip", + subtype:"equip2", + skills:['baihupifeng'], + ai:{ + equipValue:function(card,player){ + if(player.hp<=2) return 8; + return 6; + }, + basic:{ + equipValue:7 + }, + }, + }, + fengxueren:{ + fullskin:true, + type:"equip", + subtype:"equip1", + distance:{attackFrom:-1}, + skills:['fengxueren'], + ai:{ + basic:{ + equipValue:5 + }, + }, + }, + chilongya:{ + fullskin:true, + type:"equip", + subtype:"equip1", + distance:{attackFrom:-1}, + skills:['chilongya'], + ai:{ + basic:{ + equipValue:4 + }, + }, + }, + daihuofenglun:{ + type:'equip', + subtype:'equip4', + fullskin:true, + cardnature:'fire', + distance:{globalFrom:-2,globalTo:-1}, + ai:{ + basic:{ + equipValue:4 + }, + }, + }, + xiayuncailing:{ + type:'equip', + subtype:'equip3', + fullskin:true, + distance:{globalFrom:1,globalTo:2}, + }, + shentoumianju:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['shentou'], + ai:{ + basic:{ + equipValue:7, + } + } + }, + xianluhui:{ + fullskin:true, + type:'trick', + enable:true, + selectTarget:-1, + reverseOrder:true, + filterTarget:function(card,player,target){ + return target.isDamaged(); + }, + content:function(){ + target.changeHujia(); + }, + ai:{ + tag:{ + multitarget:1, + }, + basic:{ + order:7, + useful:3, + value:3, + }, + result:{ + target:function(player,target){ + if(target.hp<=1) return 1.5; + if(target.hp==2) return 1.2; + return 1; + }, + }, + } + }, + xiangyuye:{ + type:'basic', + enable:true, + fullskin:true, + filterTarget:function(card,player,target){ + return get.distance(player,target,'attack')>1; + }, + content:function(){ + "step 0" + if(!target.countCards('h',{color:'black'})){ + target.loseHp(); + event.finish(); + } + else{ + target.chooseToDiscard({color:'black'},'弃置一张黑色手牌或受流失一点体力').ai=function(card){ + return 8-get.value(card); + }; + } + "step 1" + if(!result.bool){ + target.loseHp(); + } + }, + ai:{ + basic:{ + order:9, + value:3, + useful:1, + }, + result:{ + target:-2 + }, + tag:{ + discard:1, + loseHp:1 + } + } + }, + caoyao:{ + fullskin:true, + type:'basic', + range:{global:1}, + enable:true, + filterTarget:function(card,player,target){ + return target.hp0}, + notarget:true, + mode:['identity','guozhan'], + fullskin:true, + content:function(){ + "step 0" + var list=[]; + for(var i=0;i3) return 7; + } + return -10; + }, + result:{ + player:function(player){ + for(var i=0;i3) return 2; + } + return -10; + } + }, + } + }, + tianxianjiu:{ + fullskin:true, + type:'basic', + toself:true, + enable:function(event,player){ + return !player.hasSkill('tianxianjiu'); + }, + savable:function(card,player,dying){ + return dying==player; + }, + usable:1, + selectTarget:-1, + logv:false, + modTarget:true, + filterTarget:function(card,player,target){ + return target==player; + }, + content:function(){ + "step 0" + if(target.isDying()) target.recover(); + else{ + target.addTempSkill('tianxianjiu',['phaseAfter','shaAfter']); + if(cards&&cards.length){ + card=cards[0]; + } + if(target==targets[0]&&card.clone&&(card.clone.parentNode==player.parentNode||card.clone.parentNode==ui.arena)){ + card.clone.moveDelete(target); + game.addVideo('gain2',target,get.cardsInfo([card])); + } + if(!target.node.jiu&&lib.config.jiu_effect){ + target.node.jiu=ui.create.div('.playerjiu',target.node.avatar); + target.node.jiu2=ui.create.div('.playerjiu',target.node.avatar2); + } + } + }, + ai:{ + basic:{ + useful:function(card,i){ + if(_status.event.player.hp>1){ + if(i==0) return 5; + return 1; + } + if(i==0) return 7.3; + return 3; + }, + value:function(card,player,i){ + if(player.hp>1){ + if(i==0) return 5; + return 1; + } + if(i==0) return 7.3; + return 3; + }, + }, + order:function(){ + return get.order({name:'sha'})+0.2; + }, + result:{ + target:function(player,target){ + if(target&&target.isDying()) return 2; + if(lib.config.mode=='stone'&&!player.isMin()){ + if(player.getActCount()+1>=player.actcount) return false; + } + var shas=player.getCards('h','sha'); + if(shas.length>1&&player.getCardUsable('sha')>1){ + return 0; + } + var card; + if(shas.length){ + for(var i=0;i0); + })){ + return 1; + } + } + return 0; + }, + }, + } + }, + huanpodan:{ + fullskin:true, + type:'basic', + enable:true, + logv:false, + filterTarget:function(card,player,target){ + return !target.hasSkill('huanpodan_skill'); + }, + content:function(){ + target.addSkill('huanpodan_skill'); + if(cards&&cards.length){ + card=cards[0]; + } + if(target==targets[0]&&card.clone&&(card.clone.parentNode==player.parentNode||card.clone.parentNode==ui.arena)){ + card.clone.moveDelete(target); + game.addVideo('gain2',target,get.cardsInfo([card])); + } + }, + ai:{ + basic:{ + value:8, + useful:4, + }, + order:2, + result:{ + target:function(player,target){ + return 1/Math.sqrt(1+target.hp); + }, + }, + } + }, + langeguaiyi:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['longfan'], + ai:{ + basic:{ + equipValue:7, + } + } + }, + guiyoujie:{ + fullskin:true, + type:'delay', + filterTarget:function(card,player,target){ + return (lib.filter.judge(card,player,target)&&player!=target); + }, + judge:function(card){ + if(get.color(card)=='black') return -3; + return 0; + }, + effect:function(){ + if(result.bool==false){ + player.loseHp(); + player.randomDiscard(); + } + }, + ai:{ + basic:{ + order:1, + useful:1, + value:6, + }, + result:{ + target:function(player,target){ + if(target.hasSkillTag('noturn')) return 0; + return -3; + } + }, + } + }, + yufulu:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['touzhi'], + ai:{ + basic:{ + equipValue:5 + } + } + }, + xixueguizhihuan:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['xixue'], + ai:{ + basic:{ + equipValue:5 + } + } + }, + zhufangshenshi:{ + fullskin:true, + type:'trick', + enable:true, + global:'g_zhufangshenshi', + filterTarget:function(card,player,target){ + return target!=player; + }, + content:function(){ + player.storage.zhufangshenshi=target; + player.addTempSkill('zhufangshenshi'); + }, + ai:{ + tag:{ + norepeat:1 + }, + value:4, + wuxie:function(){ + return 0; + }, + useful:[2,1], + basic:{ + order:7, + }, + result:{ + player:function(player,target){ + if(get.attitude(player,target)<0){ + if(get.distance(player,target)>1) return 1; + return 0.6; + } + return 0.3; + } + } + }, + }, + jingleishan:{ + fullskin:true, + type:'trick', + enable:true, + selectTarget:-1, + reverseOrder:true, + cardcolor:'black', + cardnature:'thunder', + filterTarget:function(card,player,target){ + return target!=player; + }, + content:function(){ + "step 0" + var next=target.chooseToRespond({name:'sha'}); + next.ai=function(card){ + if(get.damageEffect(target,player,target,'thunder')>=0) return 0; + if(player.hasSkillTag('notricksource')) return 0; + if(target.hasSkillTag('notrick')) return 0; + return 11-get.value(card); + }; + next.autochoose=lib.filter.autoRespondSha; + "step 1" + if(result.bool==false){ + target.damage('thunder'); + } + }, + ai:{ + wuxie:function(target,card,player,viewer){ + if(get.attitude(viewer,target)>0&&target.countCards('h','sha')){ + if(!target.countCards('h')||target.hp==1||Math.random()<0.7) return 0; + } + }, + basic:{ + order:9, + useful:[5,1], + value:5 + }, + result:{ + target:function(player,target){ + if(target.hasSkillTag('nothunder')) return 0; + if(target.hasUnknown(2)) return 0; + var nh=target.countCards('h'); + if(lib.config.mode=='identity'){ + if(target.isZhu&&nh<=2&&target.hp<=1) return -100; + } + if(nh==0) return -2; + if(nh==1) return -1.7 + return -1.5; + }, + }, + tag:{ + respond:1, + respondSha:1, + damage:1, + natureDamage:1, + thunderDamage:1, + multitarget:1, + multineg:1, + } + } + }, + chiyuxi:{ + fullskin:true, + type:'trick', + enable:true, + selectTarget:-1, + reverseOrder:true, + cardcolor:'red', + cardnature:'fire', + filterTarget:function(card,player,target){ + return target!=player; + }, + content:function(){ + "step 0" + var next=target.chooseToRespond({name:'shan'}); + next.ai=function(card){ + if(get.damageEffect(target,player,target,'fire')>=0) return 0; + if(player.hasSkillTag('notricksource')) return 0; + if(target.hasSkillTag('notrick')) return 0; + if(target.hasSkillTag('noShan')){ + return -1; + } + return 11-get.value(card); + }; + next.autochoose=lib.filter.autoRespondShan; + "step 1" + if(result.bool==false){ + target.damage('fire'); + } + }, + ai:{ + wuxie:function(target,card,player,viewer){ + if(get.attitude(viewer,target)>0&&target.countCards('h','shan')){ + if(!target.countCards('h')||target.hp==1||Math.random()<0.7) return 0; + } + }, + basic:{ + order:9, + useful:1, + value:5 + }, + result:{ + target:function(player,target){ + if(target.hasSkillTag('nofire')) return 0; + if(player.hasUnknown(2)) return 0; + var nh=target.countCards('h'); + if(lib.config.mode=='identity'){ + if(target.isZhu&&nh<=2&&target.hp<=1) return -100; + } + if(nh==0) return -2; + if(nh==1) return -1.7 + return -1.5; + }, + }, + tag:{ + respond:1, + respondShan:1, + damage:1, + natureDamage:1, + fireDamage:1, + multitarget:1, + multineg:1, + } + } + }, + guangshatianyi:{ + fullskin:true, + type:'equip', + subtype:'equip2', + skills:['guangshatianyi'], + ai:{ + basic:{ + equipValue:6 + } + } + }, + guilingzhitao:{ + type:'equip', + fullskin:true, + subtype:'equip5', + skills:['nigong'], + ai:{ + equipValue:function(card,player){ + if(!player.storage.nigong) return 5; + return 5+player.storage.nigong; + }, + basic:{ + equipValue:5 + } + }, + equipDelay:false, + loseDelay:false, + clearLose:true, + onLose:function(){ + player.storage.nigong=0; + player.unmarkSkill('nigong'); + }, + onEquip:function(){ + player.storage.nigong=0; + player.markSkill('nigong'); + } + }, + qipoguyu:{ + type:'equip', + subtype:'equip5', + skills:['xujin'], + equipDelay:false, + loseDelay:false, + clearLose:true, + onLose:function(){ + player.storage.xujin=0; + }, + onEquip:function(){ + player.storage.xujin=0; + } + }, + sadengjinhuan:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['sadengjinhuan'], + ai:{ + basic:{ + equipValue:5.5 + } + }, + }, + sifeizhenmian:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['yiluan'], + ai:{ + basic:{ + equipValue:6 + } + }, + }, + shuchui:{ + fullskin:true, + type:'equip', + subtype:'equip5', + skills:['shuchui'], + ai:{ + basic:{ + equipValue:5.5 + } + }, + }, + ximohu:{ + type:'equip', + subtype:'equip5', + skills:['ximohu'], + ai:{ + basic:{ + equipValue:6 + } + }, + }, + guiyanfadao:{ + fullskin:true, + type:'equip', + subtype:'equip1', + distance:{attackFrom:-1}, + ai:{ + basic:{ + equipValue:3 + } + }, + skills:['guiyanfadao'] + }, + qiankundai:{ + fullskin:true, + type:'equip', + subtype:'equip5', + onLose:function(){ + player.draw(); + }, + skills:['qiankundai'], + ai:{ + order:9.5, + equipValue:function(card,player){ + if(player.countCards('h','qiankundai')) return 6; + return 1; + }, + basic:{ + equipValue:5, + } + } + }, + }, + skill:{ + qiankundai:{ + mod:{ + maxHandcard:function(player,num){ + return num+1; + } + }, + }, + g_hufu_sha:{ + enable:['chooseToRespond','chooseToUse'], + filter:function(event,player){ + return player.countCards('h','hufu')>0; + }, + filterCard:{name:'hufu'}, + viewAs:{name:'sha'}, + prompt:'将一张玉符当杀使用或打出', + check:function(card){return 1}, + ai:{ + order:1, + useful:7.5, + value:7.5 + } + }, + g_hufu_shan:{ + enable:['chooseToRespond','chooseToUse'], + filter:function(event,player){ + return player.countCards('h','hufu')>0; + }, + filterCard:{name:'hufu'}, + viewAs:{name:'shan'}, + prompt:'将一张玉符当闪使用或打出', + check:function(){return 1}, + ai:{ + order:1, + useful:7.5, + value:7.5 + } + }, + g_hufu_jiu:{ + enable:['chooseToRespond','chooseToUse'], + filter:function(event,player){ + return player.countCards('h','hufu')>0; + }, + filterCard:{name:'hufu'}, + viewAs:{name:'jiu'}, + prompt:'将一张玉符当酒使用', + check:function(){return 1}, + }, + zhiluxiaohu:{ + trigger:{source:'damageAfter'}, + forced:true, + popup:false, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&event.getParent(3).name=='zhiluxiaohu'; + }, + content:function(){ + player.draw(); + } + }, + zhufangshenshi:{ + mod:{ + targetInRange:function(card,player,target,now){ + if(player.storage.zhufangshenshi==target) return true; + }, + }, + mark:true, + intro:{ + content:'player' + }, + onremove:true, + }, + g_zhufangshenshi:{ + trigger:{player:'useCardAfter'}, + forced:true, + popup:false, + filter:function(event,player){ + return event.card.name=='zhufangshenshi'; + }, + content:function(){ + player.draw(); + } + }, + huanpodan_skill:{ + mark:true, + intro:{ + content:'防止一次死亡,改为弃置所有牌,将体力值变为1并摸一张牌' + }, + trigger:{player:'dieBefore'}, + forced:true, + content:function(){ + 'step 0' + trigger.cancel(); + player.discard(player.getCards('he')); + player.removeSkill('huanpodan_skill'); + 'step 1' + player.changeHp(1-player.hp); + 'step 2' + player.draw(); + } + }, + dujian2:{}, + g_yuchan_swap:{ + trigger:{player:'useCardAfter'}, + silent:true, + priority:-1, + content:function(){ + var hs=player.getCards('h'); + var list=['yuchanqian','yuchankun','yuchanzhen','yuchanxun','yuchangen','yuchanli','yuchankan','yuchandui']; + for(var i=0;i0; + } + } + return false; + }, + filterCard:{type:'basic'}, + selectCard:[1,Infinity], + prompt:'弃置任意张基本牌并摸等量的牌', + check:function(card){ + return 6-get.value(card) + }, + content:function(){ + player.draw(cards.length); + }, + ai:{ + order:1, + result:{ + player:1 + }, + }, + }, + yuchanqian_equip1:{}, + yuchanqian_equip2:{}, + yuchanqian_equip3:{}, + yuchanqian_equip4:{}, + yuchanqian_equip5:{}, + yuchankun_equip1:{}, + yuchankun_equip2:{}, + yuchankun_equip3:{}, + yuchankun_equip4:{}, + yuchankun_equip5:{}, + yuchanzhen_equip1:{}, + yuchanzhen_equip2:{}, + yuchanzhen_equip3:{}, + yuchanzhen_equip4:{}, + yuchanzhen_equip5:{}, + yuchanxun_equip1:{}, + yuchanxun_equip2:{}, + yuchanxun_equip3:{}, + yuchanxun_equip4:{}, + yuchanxun_equip5:{}, + yuchankan_equip1:{}, + yuchankan_equip2:{}, + yuchankan_equip3:{}, + yuchankan_equip4:{}, + yuchankan_equip5:{}, + yuchanli_equip1:{}, + yuchanli_equip2:{}, + yuchanli_equip3:{}, + yuchanli_equip4:{}, + yuchanli_equip5:{}, + yuchangen_equip1:{}, + yuchangen_equip2:{}, + yuchangen_equip3:{}, + yuchangen_equip4:{}, + yuchangen_equip5:{}, + yuchandui_equip1:{}, + yuchandui_equip2:{}, + yuchandui_equip3:{}, + yuchandui_equip4:{}, + yuchandui_equip5:{}, + lianyaohu_skill:{ + mark:true, + intro:{ + content:function(storage,player){ + var card=player.getEquip('lianyaohu'); + if(card&&card.storage.shouna&&card.storage.shouna.length){ + return '共有'+get.cnNumber(card.storage.shouna.length)+'张牌'; + } + return '共有〇张牌'; + }, + mark:function(dialog,storage,player){ + var card=player.getEquip('lianyaohu'); + if(card&&card.storage.shouna&&card.storage.shouna.length){ + dialog.addAuto(card.storage.shouna); + } + else{ + return '共有〇张牌'; + } + }, + markcount:function(storage,player){ + var card=player.getEquip('lianyaohu'); + if(card&&card.storage.shouna) return card.storage.shouna.length; + return 0; + } + } + }, + g_shencaojie:{ + trigger:{source:'damageBegin',player:'damageBegin'}, + direct:true, + filter:function(event,player){ + if(get.type(event.card)!='trick') return false; + if(player.hasCard('shencaojie')) return true; + return false; + }, + content:function(){ + player.chooseToUse(get.prompt('shencaojie',trigger.player).replace(/发动/,'使用'),function(card,player){ + if(card.name!='shencaojie') return false; + return lib.filter.cardEnabled(card,player,'forceEnable'); + },trigger.player,-1).targetRequired=true; + } + }, + g_shenmiguo:{ + trigger:{player:'useCardAfter'}, + direct:true, + filter:function(event,player){ + if(event.parent.name=='g_shenmiguo') return false; + if(_status.currentPhase!=player) return false; + if(event.parent.parent.name!='phaseUse') return false; + if(!event.targets||!event.card) return false; + if(event.card.name=='shenmiguo') return false; + if(event.card.name=='yuchankan') return false; + if(player.hasSkill('shenmiguo2')) return false; + if(get.info(event.card).complexTarget) return false; + if(!lib.filter.cardEnabled(event.card,player,event.parent)) return false; + var type=get.type(event.card); + if(type!='basic'&&type!='trick') return false; + var card=game.createCard(event.card.name,event.card.suit,event.card.number,event.card.nature); + var targets=event._targets||event.targets; + for(var i=0;i1; + }, + content:function(){ + var value=get.value(ui.cardPile.firstChild); + var num=Math.min(20,ui.cardPile.childElementCount); + var list=[],list2=[],list3=[]; + for(var i=1;ivalue){ + list.push(ui.cardPile.childNodes[i]); + if(val>value+1&&val>=7){ + list2.push(ui.cardPile.childNodes[i]); + } + if(val>value+1&&val>=8){ + list3.push(ui.cardPile.childNodes[i]); + } + } + } + var card; + if(list3.length){ + card=list3.randomGet(); + } + else if(list2.length){ + card=list2.randomGet(); + } + else if(list.length){ + card=list.randomGet(); + } + if(card){ + ui.cardPile.insertBefore(card,ui.cardPile.firstChild); + } + } + }, + shuchui:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return player.canUse('sha',target); + }, + filter:function(event,player){ + return player.countCards('h','sha')>0&&lib.filter.cardUsable({name:'sha'},player); + }, + content:function(){ + 'step 0' + player.addSkill('shuchui2'); + player.storage.shuchui2=false; + event.num=0; + 'step 1' + var card=player.getCards('h','sha')[0]; + if(card){ + player.useCard(card,target); + } + else{ + if(player.storage.shuchui2){ + player.draw(); + } + player.removeSkill('shuchui2'); + event.finish(); + } + 'step 2' + if(event.num++<2&&target.isAlive()){ + event.goto(1); + } + else{ + if(player.storage.shuchui2){ + player.draw(); + } + player.removeSkill('shuchui2'); + } + }, + ai:{ + order:function(){ + return get.order({name:'sha'})+0.11; + }, + result:{ + target:function(player,target){ + return get.effect(target,{name:'sha'},player,target); + } + } + } + }, + shuchui2:{ + trigger:{source:'damageEnd'}, + forced:true, + popup:false, + onremove:true, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&!player.storage.shuchui2; + }, + content:function(){ + player.storage.shuchui2=true; + } + }, + xuejibingbao:{ + trigger:{player:'phaseDrawBegin'}, + forced:true, + mark:true, + temp:true, + intro:{ + content:'摸牌阶段摸牌数+1' + }, + nopop:true, + content:function(){ + trigger.num++; + player.storage.xuejibingbao--; + if(player.storage.xuejibingbao<=0){ + player.removeSkill('xuejibingbao'); + delete player.storage.xuejibingbao; + } + else{ + player.updateMarks(); + } + } + }, + gouhunluo:{ + mark:true, + intro:{ + content:function(storage,player){ + if(storage==1){ + '在'+get.translation(player.storage.gouhunluo2)+'的下个准备阶段失去一点体力并弃置所有手牌' + } + return '在'+storage+'轮后'+get.translation(player.storage.gouhunluo2)+'的准备阶段失去一点体力并弃置所有手牌' + } + }, + nopop:true, + temp:true, + trigger:{global:'phaseBegin'}, + forced:true, + popup:false, + filter:function(event,player){ + return player.storage.gouhunluo2==event.player; + }, + content:function(){ + 'step 0' + player.storage.gouhunluo--; + if(player.storage.gouhunluo<=0){ + player.logSkill('gouhunluo'); + player.loseHp(); + player.removeSkill('gouhunluo'); + delete player.storage.gouhunluo; + delete player.storage.gouhunluo2; + } + else{ + player.updateMarks(); + event.finish(); + } + 'step 1' + var es=player.getCards('h'); + if(es.length){ + player.discard(es); + } + }, + group:'gouhunluo2' + }, + gouhunluo2:{ + trigger:{global:'dieBegin'}, + forced:true, + popup:false, + filter:function(event,player){ + return player.storage.gouhunluo2==event.player; + }, + content:function(){ + player.removeSkill('gouhunluo'); + delete player.storage.gouhunluo; + delete player.storage.gouhunluo2; + } + }, + jiguanyuan:{ + mark:'card', + intro:{ + content:'cards' + }, + trigger:{player:'phaseEnd'}, + forced:true, + temp:true, + popup:false, + content:function(){ + player.gain(player.storage.jiguanyuan,'gain2'); + player.removeSkill('jiguanyuan'); + delete player.storage.jiguanyuan; + } + }, + g_qinglongzhigui:{ + trigger:{player:'phaseBegin'}, + forced:true, + filter:function(event,player){ + return player.countCards('h','qinglongzhigui')>0; + }, + content:function(){ + 'step 0' + player.showCards(get.translation(player)+'发动了【青龙之圭】',player.getCards('h','qinglongzhigui')); + player.draw(2); + 'step 1' + player.chooseToDiscard('he',true); + } + }, + g_baishouzhihu:{ + trigger:{player:'discardEnd'}, + direct:true, + filter:function(event,player){ + return player.countCards('h','baishouzhihu')>0; + }, + content:function(){ + "step 0" + player.chooseTarget([1,1],get.prompt('baishouzhihu'),function(card,player,target){ + if(player==target) return false; + return target.countCards('he')>0; + }).ai=function(target){ + return -get.attitude(player,target); + }; + "step 1" + if(result.bool){ + player.showCards(get.translation(player)+'发动了【白兽之琥】',player.getCards('h','baishouzhihu')); + player.logSkill('_baishouzhihu',result.targets); + result.targets[0].randomDiscard(); + // player.discardPlayerCard(result.targets[0],'he',true); + } + else{ + event.finish(); + } + }, + }, + g_zhuquezhizhang:{ + trigger:{player:'damageEnd'}, + forced:true, + filter:function(event,player){ + return event.source&&event.source!=player&&event.source.isAlive()&&player.countCards('h','zhuquezhizhang')>0; + }, + logTarget:'source', + check:function(event,player){ + return get.damageEffect(event.source,player,player,'fire')>0; + }, + content:function(){ + 'step 0' + player.showCards(get.translation(player)+'发动了【朱雀之璋】',player.getCards('h','zhuquezhizhang')); + trigger.source.damage('fire'); + 'step 1' + game.delay(); + } + }, + g_xuanwuzhihuang:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event,player){ + return player.countCards('h','xuanwuzhihuang')>0&&event.num>0&&player.hp0; + }, + content:function(){ + player.showCards(get.translation(player)+'发动了【黄麟之琮】',player.getCards('h','huanglinzhicong')); + player.changeHujia(); + player.update(); + }, + }, + g_cangchizhibi:{ + trigger:{player:'phaseBegin'}, + direct:true, + filter:function(event,player){ + return player.countCards('h','cangchizhibi')>0; + }, + content:function(){ + 'step 0' + player.chooseTarget([1,3],get.prompt('cangchizhibi')).ai=function(target){ + var att=get.attitude(player,target); + if(target.isLinked()){ + return att; + } + return -att; + }; + 'step 1' + if(result.bool){ + player.showCards(get.translation(player)+'发动了【苍螭之璧】',player.getCards('h','cangchizhibi')); + player.logSkill('_cangchizhibi',result.targets); + for(var i=0;i0&&player.hujia==0 + }, + content:function(){ + 'step 0' + var next=player.chooseToDiscard('he',{color:'black'},get.prompt('huanglinzhicong_duanzao')); + next.ai=function(card){ + return 8-get.value(card); + }; + next.logSkill='huanglinzhicong_equip1' + 'step 1' + if(result.bool){ + player.changeHujia(); + } + } + }, + huanglinzhicong_equip2:{ + inherit:'huanglinzhicong_equip1' + }, + huanglinzhicong_equip3:{ + inherit:'huanglinzhicong_equip1' + }, + huanglinzhicong_equip4:{ + inherit:'huanglinzhicong_equip1' + }, + huanglinzhicong_equip5:{ + inherit:'huanglinzhicong_equip1' + }, + xuanwuzhihuang_equip1:{ + trigger:{player:'phaseEnd'}, + direct:true, + filter:function(event,player){ + return player.countCards('he',{color:'red'})>0&&player.hp0; + }, + content:function(){ + "step 0" + player.chooseCardTarget({ + position:'he', + filterTarget:function(card,player,target){ + return player!=target&&target.hp>=player.hp; + }, + filterCard:function(card,player){ + return get.color(card)=='red'&&lib.filter.cardDiscardable(card,player); + }, + ai1:function(card){ + return 9-get.value(card); + }, + ai2:function(target){ + return get.damageEffect(target,player,player,'fire'); + }, + prompt:get.prompt('zhuquezhizhang_duanzao') + }); + "step 1" + if(result.bool){ + event.target=result.targets[0]; + player.logSkill('zhuquezhizhang_equip1',event.target,'fire'); + player.discard(result.cards); + } + else{ + event.finish(); + } + "step 2" + if(event.target){ + event.target.damage('fire'); + } + }, + }, + zhuquezhizhang_equip2:{ + inherit:'zhuquezhizhang_equip1' + }, + zhuquezhizhang_equip3:{ + inherit:'zhuquezhizhang_equip1' + }, + zhuquezhizhang_equip4:{ + inherit:'zhuquezhizhang_equip1' + }, + zhuquezhizhang_equip5:{ + inherit:'zhuquezhizhang_equip1' + }, + baishouzhihu_equip1:{ + trigger:{player:'phaseEnd'}, + direct:true, + content:function(){ + "step 0" + player.chooseTarget([1,1],get.prompt('baishouzhihu_duanzao'),function(card,player,target){ + if(player==target) return false; + return target.countCards('he')>0; + }).ai=function(target){ + return -get.attitude(player,target); + }; + "step 1" + if(result.bool){ + player.logSkill('baishouzhihu_equip1',result.targets); + result.targets[0].randomDiscard(); + // player.discardPlayerCard(result.targets[0],'he',true); + } + else{ + event.finish(); + } + }, + }, + baishouzhihu_equip2:{ + inherit:'baishouzhihu_equip1' + }, + baishouzhihu_equip3:{ + inherit:'baishouzhihu_equip1' + }, + baishouzhihu_equip4:{ + inherit:'baishouzhihu_equip1' + }, + baishouzhihu_equip5:{ + inherit:'baishouzhihu_equip1' + }, + qinglongzhigui_equip1:{ + trigger:{player:'phaseEnd'}, + forced:true, + content:function(){ + player.draw(); + } + }, + qinglongzhigui_equip2:{ + inherit:'qinglongzhigui_equip1' + }, + qinglongzhigui_equip3:{ + inherit:'qinglongzhigui_equip1' + }, + qinglongzhigui_equip4:{ + inherit:'qinglongzhigui_equip1' + }, + qinglongzhigui_equip5:{ + inherit:'qinglongzhigui_equip1' + }, + kunlunjingc:{ + enable:'phaseUse', + usable:1, + filter:function(event,player){ + return player.countCards('h')>0; + }, + delay:false, + content:function(){ + 'step 0' + var cards=get.cards(3); + event.cards=cards; + player.chooseCardButton('选择一张牌',cards,true); + 'step 1' + event.card=result.links[0]; + player.chooseCard('h',true,'用一张手牌替换'+get.translation(event.card)); + 'step 2' + if(result.bool){ + event.cards[event.cards.indexOf(event.card)]=result.cards[0]; + player.lose(result.cards,ui.special); + var cardx=ui.create.card(); + cardx.classList.add('infohidden'); + cardx.classList.add('infoflip'); + player.$throw(cardx,1000,'nobroadcast'); + } + else{ + event.finish(); + } + 'step 3' + player.gain(event.card); + player.$draw(); + for(var i=event.cards.length-1;i>=0;i--){ + event.cards[i].fix(); + ui.cardPile.insertBefore(event.cards[i],ui.cardPile.firstChild); + } + game.delay(); + }, + ai:{ + order:10, + result:{ + player:1 + } + } + }, + lianhua:{ + enable:'phaseUse', + filter:function(event,player){ + var hu=player.getEquip('lianyaohu'); + if(hu&&hu.storage.shouna&&hu.storage.shouna.length>1){ + return true; + } + return false; + }, + usable:1, + delay:false, + content:function(){ + "step 0" + event.hu=player.getEquip('lianyaohu'); + player.chooseCardButton('弃置两张壶中的牌,然后从牌堆中获得一张类别不同的牌',2,event.hu.storage.shouna).ai=function(){ + return 1; + } + "step 1" + if(result.bool){ + var type=[]; + player.$throw(result.links); + game.log(player,'弃置了',result.links); + for(var i=0;i0; + }, + usable:1, + filterCard:true, + check:function(card){ + return 6-get.value(card); + }, + filterTarget:function(card,player,target){ + return target!=player&&target.countCards('h')>0; + }, + content:function(){ + 'step 0' + var card=target.getCards('h').randomGet(); + var hu=player.getEquip('lianyaohu'); + if(card&&hu){ + if(!hu.storage.shouna){ + hu.storage.shouna=[]; + } + target.$give(card,player); + target.lose(card,ui.special); + event.card=card; + event.hu=hu; + } + 'step 1' + if(!event.card.destroyed){ + event.hu.storage.shouna.push(event.card); + player.updateMarks(); + } + }, + ai:{ + order:5, + result:{ + target:function(player,target){ + return -1/Math.sqrt(1+target.countCards('h')); + } + } + } + }, + shouna_old:{ + trigger:{global:'discardAfter'}, + filter:function(event,player){ + if(player.hasSkill('shouna2')) return false; + if(_status.currentPhase==event.player) return false; + if(event.player==player) return false; + for(var i=0;i0; + }, + content:function(){ + 'step 0' + player.chooseCardTarget({ + filterTarget:true, + filterCard:function(card,player,event){ + if(get.color(card)!='red') return false; + return lib.filter.cardDiscardable(card,player,event); + }, + ai1:function(card){ + return 8-get.useful(card); + }, + ai2:function(target){ + return -get.attitude(player,target); + }, + prompt:get.prompt('donghuangzhong') + }); + 'step 1' + if(result.bool){ + player.logSkill('donghuangzhong',result.targets); + player.discard(result.cards); + event.target=result.targets[0]; + } + else{ + event.finish(); + } + 'step 2' + var target=event.target; + var list=[]; + for(var i=0;i0; + }, + content:function(){ + trigger.player.chooseToDiscard(true,'he'); + } + }, + shouhua:{ + mode:['identity','infinity'], + enable:'phaseUse', + filter:function(event,player){ + return player==game.me; + }, + usable:1, + filterTarget:function(card,player,target){ + return target!=game.zhu&&target!=game.me&&target.hp0){ + return 1+trigger.judge(button.link); + } + if(get.attitude(player,trigger.player)<0){ + return 1-trigger.judge(button.link); + } + return 0; + }; + "step 1" + if(!result.bool){ + event.finish(); + return; + } + player.logSkill('haotianta',trigger.player); + var card=result.links[0]; + event.cards.remove(card); + var judgestr=get.translation(trigger.player)+'的'+trigger.judgestr+'判定'; + event.videoId=lib.status.videoId++; + event.dialog=ui.create.dialog(judgestr); + event.dialog.classList.add('center'); + event.dialog.videoId=event.videoId; + + game.addVideo('judge1',player,[get.cardInfo(card),judgestr,event.videoId]); + for(var i=0;i0){ + trigger.result.bool=true; + trigger.player.popup('洗具'); + } + if(trigger.result.judge<0){ + trigger.result.bool=false; + trigger.player.popup('杯具'); + } + game.log(trigger.player,'的判定结果为',card); + trigger.direct=true; + trigger.position.appendChild(card); + game.delay(2); + } + else{ + event.finish(); + } + "step 2" + ui.arena.classList.remove('thrownhighlight'); + event.dialog.close(); + game.addVideo('judge2',null,event.videoId); + ui.clear(); + var card=trigger.result.card; + trigger.position.appendChild(card); + trigger.result.node.delete(); + game.delay(); + }, + ai:{ + tag:{ + rejudge:1 + } + } + }, + shennongding:{ + enable:'phaseUse', + usable:1, + filterCard:true, + selectCard:2, + check:function(card){ + if(get.tag(card,'recover')>=1) return 0; + return 7-get.value(card); + }, + filter:function(event,player){ + return player.hp=2; + }, + content:function(){ + player.recover(); + }, + ai:{ + result:{ + player:function(player){ + return get.recoverEffect(player); + } + }, + order:2.5 + } + }, + kongdongyin:{ + trigger:{player:'dieBefore'}, + forced:true, + filter:function(event,player){ + return player.maxHp>0; + }, + content:function(){ + trigger.cancel(); + player.hp=1; + player.draw(); + player.discard(player.getCards('e',{subtype:'equip5'})); + game.delay(); + } + }, + + nvwashi:{ + trigger:{global:'dying'}, + priority:6, + filter:function(event,player){ + return event.player.hp<=0&&player.hp>1; + }, + check:function(event,player){ + return get.attitude(player,event.player)>=3&&!event.player.hasSkillTag('nosave'); + }, + logTarget:'player', + content:function(){ + "step 0" + trigger.player.recover(); + "step 1" + player.loseHp(); + }, + ai:{ + threaten:1.2, + expose:0.2 + } + }, + kongxin:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return target!=player&&target.countCards('h'); + }, + filter:function(event,player){ + return player.countCards('h')?true:false; + }, + content:function(){ + "step 0" + player.chooseToCompare(target); + "step 1" + if(result.bool){ + event.bool=true; + player.chooseTarget('选择一个目标视为'+get.translation(target)+'对其使用一张杀',function(card,player,target2){ + return player!=target2&&target.canUse('sha',target2); + }).ai=function(target2){ + return get.effect(target2,{name:'sha'},target,player); + } + } + else{ + target.discardPlayerCard(player); + } + "step 2" + if(event.bool&&result.bool){ + target.useCard({name:'sha'},result.targets); + } + }, + ai:{ + order:7, + result:{ + target:function(player,target){ + if(player.countCards('h')<=1) return 0; + if(get.attitude(player,target)>=0) return 0; + if(game.hasPlayer(function(current){ + return (player!=current&&target.canUse('sha',current)&& + get.effect(current,{name:'sha'},target,player)>0) + })){ + return -1; + } + return 0; + } + } + } + }, + kongxin2:{ + trigger:{player:'dying'}, + priority:10, + forced:true, + popup:false, + filter:function(event,player){ + return player==game.me; + }, + content:function(){ + player.removeSkill('kongxin2'); + game.swapPlayer(player); + player.storage.kongxin.lockOut=false; + player.storage.kongxin.out(); + if(player==game.me) game.swapPlayer(player.storage.kongxin); + if(lib.config.mode=='identity') player.storage.kongxin.setIdentity(); + delete player.storage.kongxin; + }, + }, + qinglianxindeng:{ + trigger:{player:'damageBefore'}, + forced:true, + priority:15, + filter:function(event,player){ + if(event.source&&event.source.hasSkillTag('unequip',false,{ + name:event.card?event.card.name:null, + target:player, + card:event.card + })) return false; + return get.type(event.card,'trick')=='trick'; + }, + content:function(){ + trigger.cancel(); + }, + ai:{ + notrick:true, + effect:{ + target:function(card,player,target,current){ + if(player.hasSkillTag('unequip',false,{ + name:card?card.name:null, + target:player, + card:card + })) return; + if(get.type(card)=='trick'&&get.tag(card,'damage')){ + return 'zeroplayertarget'; + } + }, + } + } + }, + yiluan:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return target!=player&&target.countCards('h'); + }, + content:function(){ + 'step 0' + target.judge(); + 'step 1' + if(result.suit!='heart'){ + var hs=target.getCards('h'); + while(hs.length){ + var chosen=hs.randomRemove(); + if(target.hasUseTarget(chosen)&&!get.info(chosen).multitarget){ + var list=game.filterPlayer(function(current){ + return lib.filter.targetEnabled2(chosen,target,current); + }); + if(list.length){ + target.useCard(chosen,list.randomGet()); + event.finish(); + break; + } + } + } + } + }, + ai:{ + order:10, + result:{ + target:function(player,target){ + if(!target.countCards('h')) return 0; + return -1; + } + } + } + }, + hslingjian_xuanfengzhiren_equip1:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event){ + if(event._notrigger.contains(event.player)) return false; + return event.card&&event.card.name=='sha'&&event.player.countCards('he'); + }, + content:function(){ + trigger.player.discard(trigger.player.getCards('he').randomGet()); + } + }, + hslingjian_xuanfengzhiren_equip2:{ + trigger:{player:'damageEnd'}, + forced:true, + filter:function(event){ + return event.card&&event.card.name=='sha'&&event.source&&event.source.countCards('he'); + }, + content:function(){ + trigger.source.discard(trigger.source.getCards('he').randomGet()); + } + }, + hslingjian_xuanfengzhiren_equip3:{ + trigger:{player:'loseAfter'}, + forced:true, + filter:function(event,player){ + return _status.currentPhase!=player&&!player.hasSkill('hslingjian_xuanfengzhiren_equip3_dist'); + }, + content:function(){ + player.addTempSkill('hslingjian_xuanfengzhiren_equip3_dist'); + } + }, + hslingjian_xuanfengzhiren_equip3_dist:{ + mod:{ + globalTo:function(from,to,distance){ + return distance+1; + } + } + }, + hslingjian_xuanfengzhiren_equip4:{ + trigger:{player:'loseAfter'}, + forced:true, + filter:function(event,player){ + return _status.currentPhase==player&&!player.hasSkill('hslingjian_xuanfengzhiren_equip4_dist'); + }, + content:function(){ + player.addTempSkill('hslingjian_xuanfengzhiren_equip4_dist'); + } + }, + hslingjian_xuanfengzhiren_equip4_dist:{ + mod:{ + globalFrom:function(from,to,distance){ + return distance-1; + } + } + }, + hslingjian_xuanfengzhiren_equip5:{ + enable:'phaseUse', + usable:1, + filterCard:true, + position:'he', + filter:function(event,player){ + return player.countCards('he')>0; + }, + filterTarget:function(card,player,target){ + return target.countCards('he')>0; + }, + check:function(card){ + return 5-get.value(card); + }, + content:function(){ + target.discard(target.getCards('he').randomGet()); + }, + ai:{ + order:5, + result:{ + target:function(player,target){ + var dh=player.countCards('he')-target.countCards('he'); + if(dh>0){ + return -Math.sqrt(dh); + } + return 0; + } + } + } + }, + hslingjian_zhongxinghujia_equip1:{ + trigger:{source:'damageEnd'}, + check:function(event,player){ + return !player.getEquip(2); + }, + filter:function(event){ + return event.card&&event.card.name=='sha'; + }, + content:function(){ + var card=game.createCard(get.inpile('equip2').randomGet()); + player.equip(card); + player.$draw(card); + game.delay(); + } + }, + hslingjian_zhongxinghujia_equip2:{ + trigger:{player:'damageEnd'}, + check:function(event,player){ + return get.attitude(player,event.source)<0; + }, + filter:function(event){ + return event.card&&event.card.name=='sha'&&event.source&&event.source.getEquip(2); + }, + content:function(){ + player.line(trigger.source,'green'); + trigger.source.discard(trigger.source.getEquip(2)); + } + }, + hslingjian_zhongxinghujia_equip3:{ + mod:{ + globalTo:function(from,to,distance){ + if(to.getEquip(2)) return distance+1; + } + } + }, + hslingjian_zhongxinghujia_equip4:{ + mod:{ + globalFrom:function(from,to,distance){ + if(from.getEquip(2)) return distance-1; + } + } + }, + hslingjian_zhongxinghujia_equip5:{ + enable:'phaseUse', + usable:1, + filterCard:true, + position:'he', + filterTarget:true, + selectCard:2, + filter:function(event,player){ + return player.countCards('he')>=2; + }, + check:function(card){ + return 5-get.value(card); + }, + content:function(){ + var card=game.createCard(get.inpile('equip2').randomGet()); + target.equip(card); + target.$draw(card); + game.delay(); + }, + ai:{ + order:1, + result:{ + target:function(player,target){ + if(target.getEquip(2)) return 0; + return 1; + } + } + } + }, + hslingjian_jinjilengdong_equip1:{ + trigger:{source:'damageEnd'}, + check:function(event,player){ + if(event.player.hasSkillTag('noturn')) return 0; + if(event.player.isTurnedOver()){ + return get.attitude(player,event.player)>0; + } + return get.attitude(player,event.player)<=0; + }, + filter:function(event){ + if(event._notrigger.contains(event.player)) return false; + return event.card&&event.card.name=='sha'&&event.player&&event.player.isAlive(); + }, + logTarget:'player', + content:function(){ + trigger.player.draw(2); + trigger.player.turnOver(); + } + }, + hslingjian_jinjilengdong_equip2:{ + trigger:{player:'damageEnd'}, + check:function(event,player){ + if(event.player.hasSkillTag('noturn')) return 0; + if(event.player.isTurnedOver()){ + return get.attitude(player,event.source)>0; + } + return get.attitude(player,event.source)<=0; + }, + filter:function(event){ + return event.card&&event.card.name=='sha'&&event.source&&event.source.isAlive(); + }, + logTarget:'source', + content:function(){ + player.line(trigger.source,'green'); + trigger.source.draw(2); + trigger.source.turnOver(); + } + }, + hslingjian_jinjilengdong_equip3:{ + mod:{ + globalTo:function(from,to,distance){ + if(to.isTurnedOver()) return distance+2; + } + } + }, + hslingjian_jinjilengdong_equip4:{ + mod:{ + globalFrom:function(from,to,distance){ + if(from.isTurnedOver()) return distance-2; + } + } + }, + hslingjian_jinjilengdong_equip5:{ + trigger:{player:'phaseAfter'}, + direct:true, + filter:function(event,player){ + return !player.isTurnedOver(); + }, + content:function(){ + "step 0" + player.chooseTarget(get.prompt('hslingjian_jinjilengdong_duanzao'),function(card,player,target){ + return player!=target&&!target.isTurnedOver(); + }).ai=function(target){ + if(target.hasSkillTag('noturn')) return 0; + return Math.max(0,-get.attitude(player,target)-2); + }; + "step 1" + if(result.bool){ + player.logSkill('hslingjian_jinjilengdong_equip5',result.targets); + player.turnOver(); + result.targets[0].turnOver(); + game.asyncDraw([player,result.targets[0]],2); + } + }, + }, + hslingjian_yinmilichang_equip1:{ + trigger:{source:'damageEnd'}, + direct:true, + filter:function(event){ + return event.card&&event.card.name=='sha'; + }, + content:function(){ + 'step 0' + player.chooseTarget(get.prompt('hslingjian_yinmilichang_duanzao'),function(card,player,target){ + return target!=player&&!target.hasSkill('qianxing'); + }).ai=function(target){ + var att=get.attitude(player,target); + if(get.distance(player,target,'absolute')<=1) return 0; + if(target.hp==1) return 2*att; + if(target.hp==2&&target.countCards('h')<=2) return 1.2*att; + return att; + } + 'step 1' + if(result.bool){ + player.logSkill('hslingjian_yinmilichang_equip1',result.targets); + result.targets[0].tempHide(); + } + } + }, + hslingjian_yinmilichang_equip2:{ + trigger:{player:'damageEnd'}, + forced:true, + filter:function(event,player){ + return !player.hasSkill('qianxing'); + }, + content:function(){ + player.addTempSkill('qianxing'); + } + }, + hslingjian_yinmilichang_equip3:{ + mod:{ + globalTo:function(from,to,distance){ + if(to.hp==1) return distance+1; + } + } + }, + hslingjian_yinmilichang_equip4:{ + mod:{ + globalFrom:function(from,to,distance){ + if(from.hp==1) return distance-1; + } + } + }, + hslingjian_yinmilichang_equip5:{ + mod:{ + targetEnabled:function(card,player,target,now){ + if(target.countCards('h')==0){ + if(card.name=='sha'||card.name=='juedou') return false; + } + } + }, + ai:{ + noh:true, + skillTagFilter:function(player,tag){ + if(tag=='noh'){ + if(player.countCards('h')!=1) return false; + } + } + } + }, + hslingjian_xingtigaizao_equip1:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event){ + return event.card&&event.card.name=='sha'; + }, + content:function(){ + player.draw(); + } + }, + hslingjian_xingtigaizao_equip2:{ + trigger:{player:'damageEnd'}, + forced:true, + filter:function(event){ + return event.card&&event.card.name=='sha'; + }, + content:function(){ + player.draw(); + } + }, + hslingjian_xingtigaizao_equip3:{ + mod:{ + globalTo:function(from,to,distance){ + return distance+1; + }, + globalFrom:function(from,to,distance){ + return distance+1; + } + } + }, + hslingjian_xingtigaizao_equip4:{ + mod:{ + globalTo:function(from,to,distance){ + return distance-1; + }, + globalFrom:function(from,to,distance){ + return distance-1; + } + } + }, + hslingjian_xingtigaizao_equip5:{ + mod:{ + maxHandcard:function(player,num){ + return num-1; + } + }, + trigger:{player:'phaseDrawBegin'}, + forced:true, + content:function(){ + trigger.num++; + } + }, + hslingjian_shengxiuhaojiao_equip1:{ + trigger:{player:'shaBegin'}, + forced:true, + filter:function(event,player){ + return event.target.hasSkill('hslingjian_chaofeng'); + }, + content:function(){ + trigger.directHit=true; + } + }, + hslingjian_shengxiuhaojiao_equip2:{ + mod:{ + targetEnabled:function(card,player,target){ + if(player.hasSkill('hslingjian_chaofeng')) return false; + } + } + }, + hslingjian_shengxiuhaojiao_equip3:{ + mod:{ + globalTo:function(from,to,distance){ + if(to.hp0; + }, + position:'he', + content:function(){ + var es=target.getCards('e'); + target.gain(es); + target.$gain2(es); + }, + check:function(card){ + return 4-get.value(card); + }, + ai:{ + order:5, + result:{ + target:function(player,target){ + if(target.hasSkillTag('noe')) return target.countCards('e')*2; + return -target.countCards('e'); + } + }, + } + }, + jiguanyaoshu_skill_old:{ + enable:'phaseUse', + filter:function(event,player){ + return player.countCards('h',{type:['trick','delay']})>0; + }, + filterCard:{type:['trick','delay']}, + check:function(card){ + return 5-get.value(card); + }, + viewAs:{name:'jiguanshu'} + }, + jiguanyaoshu_skill:{ + trigger:{player:'loseEnd'}, + forced:true, + filter:function(event,player){ + if(_status.currentPhase==player) return false; + for(var i=0;i10) return 10+(value-10)/10; + if(value<9) return 8+value/10; + return value; + }; + if(typeof lib.card[name].ai.equipValue=='number'){ + lib.card[name].ai.equipValue=getValue(lib.card[name].ai.equipValue,dvalue); + } + else if(typeof lib.card[name].ai.equipValue=='function'){ + lib.card[name].ai.equipValue=function(){ + return getValue(lib.card[equipname].ai.equipValue.apply(this,arguments),dvalue); + } + } + else if(lib.card[name].ai.basic&&typeof lib.card[name].ai.basic.equipValue=='number'){ + lib.card[name].ai.basic.equipValue=getValue(lib.card[name].ai.basic.equipValue,dvalue); + } + else if(lib.card[name].ai.basic&&typeof lib.card[name].ai.basic.equipValue=='function'){ + lib.card[name].ai.basic.equipValue=function(){ + return getValue(lib.card[equipname].ai.basic.equipValue.apply(this,arguments),dvalue); + } + } + else{ + if(dvalue==3){ + lib.card[name].ai.equipValue=7; + } + else{ + lib.card[name].ai.equipValue=dvalue; + } + } + if(Array.isArray(lib.card[name].skills)){ + lib.card[name].skills=lib.card[name].skills.slice(0); + } + else{ + lib.card[name].skills=[]; + } + // lib.card[name].filterTarget=function(card,player,target){ + // return !target.isMin(); + // }; + // lib.card[name].selectTarget=1; + // lib.card[name].range={global:1}; + var str=lib.translate[cards[0].name+'_duanzao']; + var str2=get.translation(equip.name,'skill'); + lib.translate[name]=str+str2; + str2=lib.translate[equip.name+'_info']||''; + if(str2[str2.length-1]=='.'||str2[str2.length-1]=='。'){ + str2=str2.slice(0,str2.length-1); + } + for(var i=0;i'+lib.translate[lingjians[i]]+''; + for(var j=0;j'; + if(type=='jiqi') break; + } + str+=''; + } + return str; + }, + check:function(card){ + if(get.type(card)=='jiqi'){ + if(_status.event.player.needsToDiscard()){ + return 0.5; + } + return 0; + } + var num=1+get.value(card); + if(get.position(card)=='e'){ + num+=0.1; + } + return num; + }, + filterCard:function(card){ + var type=get.type(card); + if(type=='equip'){ + if(!lib.inpile.contains(card.name)) return false; + if(lib.card[card.name].nopower) return false; + if(lib.card[card.name].unique) return false; + if(card.nopower) return false; + } + if(ui.selected.cards.length){ + var type2=get.type(ui.selected.cards[0]); + if(type2=='equip'){ + return type=='hslingjian'||type=='jiqi'; + } + else{ + return type=='equip'; + } + } + else{ + return type=='equip'||type=='hslingjian'||type=='jiqi'; + } + }, + selectCard:2, + complexCard:true, + filter:function(event,player){ + if(!player.countCards('h',{type:['hslingjian','jiqi']})) return false; + var es=player.getCards('he',{type:'equip'}); + for(var i=0;i0; + if(event.target.hp==1) return att>0; + if(event.target.hasSkillTag('maixie')){ + return att<=0; + } + if(player.hasSkill('tianxianjiu')) return false; + return att<=0; + }, + filter:function(event,player){ + return !event.target.isTurnedOver(); + }, + logTarget:'target', + content:function(){ + trigger.unhurt=true; + trigger.target.turnOver(); + trigger.target.draw(); + } + }, + chilongya:{ + trigger:{source:'damageBegin'}, + forced:true, + filter:function(event){ + return event.nature=='fire'&&event.notLink(); + }, + content:function(){ + trigger.num++; + } + }, + chilongya2:{ + trigger:{source:'damageBegin'}, + filter:function(event,player){ + return (event.card&&event.card.name=='sha'); + }, + popup:false, + forced:true, + content:function(){ + if(Math.random()<0.5){ + trigger.num++; + trigger.player.addSkill('chilongfengxue'); + } + } + }, + chilongfengxue:{ + trigger:{global:'shaAfter'}, + forced:true, + popup:false, + content:function(){ + player.draw(); + player.removeSkill('chilongfengxue'); + } + }, + shentou:{ + enable:'phaseUse', + usable:1, + filterCard:true, + filter:function(event,player){ + var nh=player.countCards('h'); + if(nh==0) return false; + return game.hasPlayer(function(current){ + return current!=player&¤t.countCards('h')>nh; + }); + }, + check:function(card){ + return 8-get.value(card); + }, + filterTarget:function(card,player,target){ + if(target.countCards('h')==0) return false; + if(target==player) return false; + if(target.countCards('h')<=player.countCards('h')) return false; + return true; + }, + content:function(){ + "step 0" + player.judge(function(card){ + if(get.suit(card)=='club') return -1; + return 1; + }); + "step 1" + if(result.bool){ + var card=target.getCards('h').randomGet(); + if(card){ + player.gain(card,target); + target.$giveAuto(card,player); + } + } + }, + ai:{ + basic:{ + order:5 + }, + result:{ + player:0.3, + target:-1, + } + } + }, + old_longfan:{ + enable:'phaseUse', + usable:1, + prompt:'?', + filterTarget:true, + content:function(){ + "step 0" + if(event.isMine()){ + event.longfan=ui.create.control('〇','〇','〇','〇',function(){ + event.longfan.status--; + }); + event.longfan.status=4; + for(var i=0;i1) event.count(1); + if(event.longfan.status>2) event.count(2); + if(event.longfan.status>3) event.count(3); + },200); + event.count=function(num){ + event.longfan.childNodes[num].num=(event.longfan.childNodes[num].num+1)%10; + if(event.longfan.childNodes[num].num==2) event.longfan.childNodes[num].innerHTML='二'; + else event.longfan.childNodes[num].innerHTML=get.cnNumber(event.longfan.childNodes[num].num); + } + game.pause(); + } + else{ + event.finish(); + var x=Math.random(); + if(x<0.1) target.draw(); + else if(x<0.2) target.chooseToDiscard(true); + else if(x<0.3) target.loseHp(); + else if(x<0.4) target.recover(); + else if(x<0.6){ + if(get.attitude(player,target)>0) target.draw(); + else target.chooseToDiscard(true); + } + else if(x<0.8){ + if(get.attitude(player,target)>0) target.recover(); + else target.loseHp(); + } + } + "step 1" + var str=''; + for(var i=0;iplayer.hp?2:0; + case 'diamond':return 1; + case 'club':return 1; + case 'spade':return 0; + } + }); + "step 1" + switch(result.suit){ + case 'heart':player.recover();break; + case 'diamond':player.draw();break; + case 'club':{ + var targets=player.getEnemies(); + for(var i=0;i0; + }, + discard:false, + prepare:'give', + filterTarget:function(card,player,target){ + if(player==target) return false; + return true; + }, + content:function(){ + target.damage(); + target.gain(cards,player); + // game.delay(); + }, + check:function(card){ + return 10-get.value(card); + }, + position:'he', + ai:{ + basic:{ + order:8 + }, + result:{ + target:-1 + } + } + }, + xixue:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&player.hp1/3) return false; + return true; + }, + content:function(){ + trigger.num--; + } + }, + nigong:{ + trigger:{player:'damageAfter'}, + group:['nigong2','nigong3'], + forced:true, + content:function(){ + player.storage.nigong+=trigger.num; + if(player.storage.nigong>4){ + player.storage.nigong=4; + } + player.updateMarks(); + }, + ai:{ + effect:function(card,player,target){ + if(get.tag(card,'damage')&&!target.hujia) return [1,0.5]; + } + }, + intro:{ + content:'已积攒#点伤害' + } + }, + nigong2:{ + enable:'phaseUse', + filter:function(event,player){ + return player.storage.nigong>1; + }, + filterTarget:function(card,player,target){ + return player!=target; + }, + prompt:function(event){ + var str='弃置所有逆攻标记,'; + if(event.player.storage.nigong%2!=0){ + str+='摸一张牌,'; + } + str+='并对一名其他角色造成'+get.cnNumber(Math.floor(event.player.storage.nigong/2))+'点伤害'; + return str; + }, + content:function(){ + if(player.storage.nigong%2!=0){ + player.draw(); + } + target.damage(Math.floor(player.storage.nigong/2)); + player.storage.nigong=0; + player.updateMarks(); + }, + ai:{ + order:10, + result:{ + target:function(player,target){ + var num=get.damageEffect(target,player,target); + if(player.storage.nigong>=4&&num>0){ + num=0; + } + return num; + } + } + } + }, + nigong3:{ + enable:'phaseUse', + filter:function(event,player){ + return player.storage.nigong==1; + }, + content:function(){ + player.draw(); + player.storage.nigong=0; + player.updateMarks(); + }, + ai:{ + order:10, + result:{ + player:1 + } + } + }, + sadengjinhuan:{ + trigger:{player:'shaMiss'}, + check:function(event,player){ + return get.attitude(player,event.target)<0; + }, + content:function(){ + "step 0" + player.judge(function(card){ + return get.color(card)=='red'?1:0; + }) + "step 1" + if(result.bool){ + trigger.target.chooseToRespond({name:'shan'},'萨登荆环:请额外打出一张闪响应杀').autochoose=lib.filter.autoRespondShan; + } + else{ + event.finish(); + } + "step 2" + if(!result.bool){ + trigger.untrigger(); + trigger.trigger('shaHit'); + trigger._result.bool=false; + } + } + }, + ximohu:{ + trigger:{player:'damageBefore'}, + forced:true, + filter:function(event){ + return event.nature=='thunder'; + }, + content:function(){ + trigger.cancel(); + player.recover(trigger.num); + }, + ai:{ + effect:function(card){ + if(get.tag(card,'thunderDamage')) return [0,2]; + } + } + }, + guiyanfadao:{ + trigger:{player:'shaHit'}, + check:function(event,player){ + var att=get.attitude(player,event.target); + if(player.hasSkill('jiu')) return att>0; + if(event.target.hasSkillTag('maixie_hp')||event.target.hasSkillTag('maixie_defend')){ + return att<=0; + } + if(player.hasSkill('tianxianjiu')) return false; + if(event.target.hujia>0) return att<0; + if(event.target.hp==1) return att>0; + return false; + }, + content:function(){ + trigger.unhurt=true; + trigger.target.loseHp(); + } + }, + guiyanfadao2:{ + trigger:{player:'useCardAfter'}, + forced:true, + popup:false, + content:function(){ + delete player.storage.zhuque_skill.nature; + } + }, + tianxianjiu:{ + trigger:{source:'damageEnd'}, + filter:function(event){ + return (event.card&&(event.card.name=='sha')); + }, + forced:true, + temp:true, + vanish:true, + onremove:function(player){ + if(player.node.jiu){ + player.node.jiu.delete(); + player.node.jiu2.delete(); + delete player.node.jiu; + delete player.node.jiu2; + } + }, + content:function(){ + player.draw(2); + player.removeSkill('tianxianjiu'); + }, + ai:{ + damageBonus:true + } + }, + }, + cardType:{ + hslingjian:0.5, + jiqi:0.4, + jiguan:0.45 + }, + help:{ + '轩辕剑':'
        • 零件、祭器牌可用于煅造装备,煅造得到强化装备,并装备给距离1以内的角色
        • '+ + '煅造装备时失去牌以及装备牌的过程不触发任何技能(如枭姬、祈禳)
        • '+ + '进行洗牌时强化装备将从弃牌堆中消失,不进入牌堆
        • '+ + '专属、特殊装备无法被强化' + }, + translate:{ + qiankundai:'乾坤袋', + qiankundai_info:'你的手牌上限+1。当你失去该装备时,你摸一张牌。', + hufu:'玉符', + hufu_bg:'符', + g_hufu_sha:'符杀', + g_hufu_shan:'符闪', + g_hufu_jiu:'符酒', + hufu_info:'你可以将一张玉符当作杀、闪或酒使用或打出', + // yihuajiemu:'移花接木', + // yihuajiemu_info:'对一名装备区内有宝物的角色使用,将其宝物牌转移至另一名角色', + liuxinghuoyu:'流星火羽', + liuxinghuoyu_info:'出牌阶段,对一名角色使用,令目标弃置2张牌,或受到一点火焰伤害', + g_yuchan_equip:'玉蝉', + yuchanqian_duanzao:'玉蝉', + yuchanqian_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanqian_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanqian_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanqian_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanqian_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankun_duanzao:'玉蝉', + yuchankun_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankun_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankun_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankun_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankun_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanzhen_duanzao:'玉蝉', + yuchanzhen_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanzhen_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanzhen_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanzhen_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanzhen_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanxun_duanzao:'玉蝉', + yuchanxun_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanxun_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanxun_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanxun_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanxun_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankan_duanzao:'玉蝉', + yuchankan_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankan_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankan_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankan_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchankan_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanli_duanzao:'玉蝉', + yuchanli_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanli_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanli_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanli_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanli_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchangen_duanzao:'玉蝉', + yuchangen_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchangen_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchangen_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchangen_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchangen_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchandui_duanzao:'玉蝉', + yuchandui_equip1_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchandui_equip2_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchandui_equip3_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchandui_equip4_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchandui_equip5_info:'出牌阶段限一次,你可以弃置任意张基本牌并摸等量的牌', + yuchanqian:'乾玉蝉', + yuchanqian_info:'在你行动时可当作杀使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', + yuchankun:'坤玉蝉', + yuchankun_info:'在你行动时可当作草药使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', + yuchanzhen:'震玉蝉', + yuchanzhen_info:'在你行动时可当作酒使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', + yuchanxun:'巽玉蝉', + yuchanxun_info:'在你行动时可当作桃使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', + yuchankan:'坎玉蝉', + yuchankan_info:'在你行动时可当作神秘果使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', + yuchanli:'离玉蝉', + yuchanli_info:'在你行动时可当作天仙酒使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', + yuchangen:'艮玉蝉', + yuchangen_info:'在你行动时可当作封印之蛋使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', + yuchandui:'兑玉蝉', + yuchandui_info:'在你行动时可当作雪肌冰鲍使用;可用于煅造装备;在你使用一张牌后,此牌会随机切换形态', + yangpijuan:'羊皮卷', + yangpijuan_info:'出牌阶段对自己使用,选择一种卡牌类别,发现一张该类别的卡牌', + // pantao:'蟠桃', + // pantao_info:'出牌阶段对自己使用,或对濒死角色使用,目标回复两点体力并获得一点护甲', + shencaojie:'神草结', + shencaojie_info:'你的锦囊牌即将造成伤害时对目标使用,令此伤害+1;你即将受到锦囊牌伤害时对自己使用,令此伤害-1', + yuruyi:'玉如意', + yuruyi_ab:'如意', + yuruyi_info:'你有更高的机率摸到好牌', + fengyinzhidan:'封印之蛋', + fengyinzhidan_info:'随机使用两张普通锦囊牌(随机指定目标)', + shuchui:'鼠槌', + shuchui_info:'出牌阶段限一次,你可以指定一名攻击范围内的角色,依次将手牌中的至多3张杀对该角色使用,若杀造成了伤害,你摸一张牌', + zhiluxiaohu:'指路小狐', + zhiluxiaohu_info:'出牌阶段对自己使用,视为对一名随机敌方角色使用一张杀,若此杀造成伤害,你摸一张牌', + xuejibingbao:'雪肌冰鲍', + xuejibingbao_info:'出牌阶段对一名角色使用,该角色摸牌阶段摸牌数+1,持续2个回合', + gouhunluo:'勾魂锣', + gouhunluo_info:'出牌阶段对一名角色使用,在3轮后你的准备阶段令该角色失去1点体力并弃置所有手牌', + jiguan:'机关', + jiqi:'祭器', + qinglongzhigui:'青龙之圭', + g_qinglongzhigui:'青龙之圭', + qinglongzhigui_info:'可用于煅造装备;此牌在你手牌中时,准备阶段,你摸两张牌然后弃置一张牌', + qinglongzhigui_duanzao:'云屏', + qinglongzhigui_equip1_info:'结束阶段,你摸一张牌', + qinglongzhigui_equip2_info:'结束阶段,你摸一张牌', + qinglongzhigui_equip3_info:'结束阶段,你摸一张牌', + qinglongzhigui_equip4_info:'结束阶段,你摸一张牌', + qinglongzhigui_equip5_info:'结束阶段,你摸一张牌', + baishouzhihu:'白兽之琥', + g_baishouzhihu:'白兽之琥', + baishouzhihu_info:'可用于煅造装备;此牌在你手牌中时,每当你弃置卡牌,你可以弃置一名其他角色的一张随机牌', + baishouzhihu_duanzao:'风牙', + baishouzhihu_equip1_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', + baishouzhihu_equip2_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', + baishouzhihu_equip3_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', + baishouzhihu_equip4_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', + baishouzhihu_equip5_info:'结束阶段,你可以弃置一名其他角色的一张随机牌', + zhuquezhizhang:'朱雀之璋', + g_zhuquezhizhang:'朱雀之璋', + zhuquezhizhang_info:'可用于煅造装备;此牌在你手牌中时,每当你受到其他角色造成的伤害,你对伤害来源造成一点火属性伤害', + zhuquezhizhang_duanzao:'炽翎', + zhuquezhizhang_equip1_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', + zhuquezhizhang_equip2_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', + zhuquezhizhang_equip3_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', + zhuquezhizhang_equip4_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', + zhuquezhizhang_equip5_info:'结束阶段,你可以弃置一张红色牌并对一名体力值不小于你的角色造成一点火属性伤害', + xuanwuzhihuang:'玄武之璜', + g_xuanwuzhihuang:'玄武之璜', + xuanwuzhihuang_duanzao:'寒晶', + xuanwuzhihuang_info:'可用于煅造装备;此牌在你手牌中时,每当你造成伤害,你回复等量的体力', + xuanwuzhihuang_equip1_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', + xuanwuzhihuang_equip2_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', + xuanwuzhihuang_equip3_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', + xuanwuzhihuang_equip4_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', + xuanwuzhihuang_equip5_info:'结束阶段,你可以弃置一张红色牌并回复一点体力', + huanglinzhicong:'黄麟之琮', + g_huanglinzhicong:'黄麟之琮', + huanglinzhicong_duanzao:'玄甲', + huanglinzhicong_info:'可用于煅造装备;此牌在你手牌中时,准备阶段,若你没有护甲,你获得一点护甲', + huanglinzhicong_equip1_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', + huanglinzhicong_equip2_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', + huanglinzhicong_equip3_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', + huanglinzhicong_equip4_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', + huanglinzhicong_equip5_info:'结束阶段,若你没有护甲,你可以弃置一张黑色牌并获得一点护甲', + cangchizhibi:'苍螭之璧', + g_cangchizhibi:'苍螭之璧', + cangchizhibi_duanzao:'灵枢', + cangchizhibi_info:'可用于煅造装备;此牌在你手牌中时,准备阶段,你可以选择至多3名角色横置或重置之', + cangchizhibi_equip1_info:'结束阶段,你可以横置或重置一名角色', + cangchizhibi_equip2_info:'结束阶段,你可以横置或重置一名角色', + cangchizhibi_equip3_info:'结束阶段,你可以横置或重置一名角色', + cangchizhibi_equip4_info:'结束阶段,你可以横置或重置一名角色', + cangchizhibi_equip5_info:'结束阶段,你可以横置或重置一名角色', + + guisheqi:'龟蛇旗', + guisheqi_info:'出牌阶段对一名角色使用,目标获得一点护甲', + jiguanfeng:'机关蜂', + jiguanfeng_info:'出牌阶段对一名其他角色使用,目标需打出一张闪,否则非锁定技失效直到下一回合开始,并受到一点伤害', + jiguanyuan:'机关鸢', + jiguanyuan_info:'出牌阶段对一名其他角色使用,你将此牌和一张其它牌置于一名其他角色的武将牌上,然后摸一张牌;该角色于下一结束阶段获得武将牌上的牌', + jiguantong:'机关火筒', + jiguantong_ab:'火筒', + jiguantong_info:'出牌阶段对所有其他角色使用,目标弃置一张手牌,或受到一点火焰伤害;若没有人选择受到伤害,使用者摸一张牌', + jiutiansuanchi:'九天算尺', + jiutiansuanchi_info:'每当你使用杀造成伤害,你可以弃置一张牌并展示受伤害角色的一张手牌,若此牌与你弃置的牌花色或点数相同,此杀的伤害+2', + shenmiguo:'神秘果', + shenmiguo_info:'出牌阶段内,当你使用一张基本牌或普通锦囊牌后使用,令此牌再结算一次。每阶段限用一次', + qinglianxindeng:'青莲心灯', + qinglianxindeng_info:'你防止锦囊牌造成的伤害', + hslingjian_xuanfengzhiren_duanzao:'风刃', + hslingjian_xuanfengzhiren_duanzao2:'风', + hslingjian_xuanfengzhiren_equip1_info:'每当你用杀造成一次伤害,受伤害角色随机弃置一张牌', + hslingjian_xuanfengzhiren_equip2_info:'每当你受到杀造成的伤害,伤害来源随机弃置一张牌', + hslingjian_xuanfengzhiren_equip3_info:'当你于回合外失去牌后,你本回合的防御距离+1', + hslingjian_xuanfengzhiren_equip4_info:'当你于回合内失去牌后,你本回合的进攻距离+1', + hslingjian_xuanfengzhiren_equip5_info:'出牌阶段限一次,你可以弃置一张牌,然后随机弃置一名其他角色的一张牌', + hslingjian_zhongxinghujia_duanzao:'重甲', + hslingjian_zhongxinghujia_duanzao2:'护', + hslingjian_zhongxinghujia_equip1_info:'每当你用杀造成一次伤害,你可以随机装备一件防具牌', + hslingjian_zhongxinghujia_equip2_info:'每当你受到杀造成的伤害,你可以弃置伤害来源的防具牌', + hslingjian_zhongxinghujia_equip3_info:'当你的装备区内有防具牌时,你的防御距离+1', + hslingjian_zhongxinghujia_equip4_info:'当你的装备区内有防具牌时,你的进攻距离+1', + hslingjian_zhongxinghujia_equip5_info:'出牌阶段限一次,你可以弃置两张牌,然后令一名角色随机装备一件防具', + hslingjian_jinjilengdong_duanzao:'冰冻', + hslingjian_jinjilengdong_duanzao2:'冰', + hslingjian_jinjilengdong_equip1_info:'每当你用杀造成一次伤害,你可以令目标摸两张牌并翻面', + hslingjian_jinjilengdong_equip2_info:'每当你受到杀造成的伤害,你可以令伤害来源摸两张牌并翻面', + hslingjian_jinjilengdong_equip3_info:'你的武将牌背面朝上时防御距离+2', + hslingjian_jinjilengdong_equip4_info:'你的武将牌背面朝上时进攻距离+2', + hslingjian_jinjilengdong_equip5_info:'回合结束后,若你的武将牌正面朝上,你可以与一名武将牌正面朝上的其他角色同时翻面,然后各摸两张牌', + hslingjian_yinmilichang_duanzao:'隐力', + hslingjian_yinmilichang_duanzao2:'隐', + hslingjian_yinmilichang_equip1_info:'每当你用杀造成一次伤害,你可以令一名其他角色获得潜行直到其下一回合开始', + hslingjian_yinmilichang_equip2_info:'每当你受到一次伤害,你本回合内获得潜行', + hslingjian_yinmilichang_equip3_info:'当你的体力值为1时,你的防御距离+1', + hslingjian_yinmilichang_equip4_info:'当你的体力值为1时,你的进攻距离+1', + hslingjian_yinmilichang_equip5_info:'当你没有手牌时,你不能成为杀或决斗的目标', + hslingjian_xingtigaizao_duanzao:'移形', + hslingjian_xingtigaizao_duanzao2:'形', + hslingjian_xingtigaizao_equip1_info:'每当你用杀造成一次伤害,你摸一张牌', + hslingjian_xingtigaizao_equip2_info:'每当你受到杀造成的伤害,你摸一张牌', + hslingjian_xingtigaizao_equip3_info:'你的防御距离+1,进攻距离-1', + hslingjian_xingtigaizao_equip4_info:'你的防御距离-1,进攻距离+1', + hslingjian_xingtigaizao_equip5_info:'你于摸牌阶段额外摸一张牌;你的手牌上限-1', + hslingjian_shengxiuhaojiao_duanzao:'号角', + hslingjian_shengxiuhaojiao_duanzao2:'角', + hslingjian_shengxiuhaojiao_equip1_info:'有嘲讽的角色不能闪避你的杀', + hslingjian_shengxiuhaojiao_equip2_info:'有嘲讽的角色不能对你使用杀', + hslingjian_shengxiuhaojiao_equip3_info:'若你的手牌数大于你的体力值,你的防御距离+1', + hslingjian_shengxiuhaojiao_equip4_info:'若你的手牌数大于你的体力值,你的进攻距离+1', + hslingjian_shengxiuhaojiao_equip5_info:'出牌阶段限一次,你可以弃置两张牌,然后令一名角色获得或解除嘲讽', + hslingjian_shijianhuisu_duanzao:'回溯', + hslingjian_shijianhuisu_duanzao2:'溯', + hslingjian_shijianhuisu_equip1_info:'当你装备一张防具牌时,你摸一张牌', + hslingjian_shijianhuisu_equip2_info:'当你装备一张武器牌时,你摸一张牌', + hslingjian_shijianhuisu_equip3_info:'当你的装备区内没有其他牌时,你的防御距离+1', + hslingjian_shijianhuisu_equip4_info:'当你的装备区内没有其他牌时,你的进攻距离+1', + hslingjian_shijianhuisu_equip5_info:'出牌阶段限一次,你可以弃置一张牌,然后令一名其他角色将其装备区内的牌收回手牌', + _lingjianduanzao:'煅造', + _lingjianduanzao_info:'出牌阶段,你可以将一张装备牌和一张可煅造的牌合成为一件强化装备,并装备给距离1以内的一名角色', + jiguanshu:'机关鼠', + jiguanshu_info:'出牌阶段对自己使用,用随机祭器强化装备区内的一张随机装备,然后用随机零件强化其余的装备', + lingjiandai:'零件袋', + lingjiandai_info:'出牌阶段对自己使用,获得3张随机零件', + mujiaren:'木甲人', + mujiaren_info:'出牌阶段限用一次,将你手牌中的非基本牌(含此张)替换为随机的机关牌', + jiguanyaoshu:'机关要术', + jiguanyaoshu_skill:'巧匠', + jiguanyaoshu_skill_info:'每当你于回合外失去装备区内的牌,你获得一个随机零件', + jiguanyaoshu_info:'出牌阶段对距离1以内的一名角色使用,目标随机装备一件装备牌并获得技能巧匠(每当你于回合外失去装备区内的牌,你获得一个随机零件)', + hslingjian:'零件', + hslingjian_xuanfengzhiren:'旋风之刃', + hslingjian_xuanfengzhiren_info:'可用于煅造装备;随机弃置一名角色的一张牌', + hslingjian_zhongxinghujia:'重型护甲', + hslingjian_zhongxinghujia_info:'可用于煅造装备;令一名角色装备一件随机防具,然后随机弃置其一张手牌', + hslingjian_jinjilengdong:'紧急冷冻', + hslingjian_jinjilengdong_bg:'冻', + hslingjian_jinjilengdong_info:'可用于煅造装备;令一名武将牌正面朝上的其他角色获得两点护甲并翻面,该角色不能使用卡牌,也不能成为卡牌的目标直到武将牌翻回正面', + hslingjian_yinmilichang:'隐秘力场', + hslingjian_yinmilichang_info:'可用于煅造装备;令一名其他角色获得技能潜行,直到其下一回合开始', + hslingjian_xingtigaizao:'型体改造', + hslingjian_xingtigaizao_info:'可用于煅造装备;摸一张牌,本回合手牌上限-1', + hslingjian_shengxiuhaojiao:'生锈号角', + hslingjian_shengxiuhaojiao_info:'可用于煅造装备;令一名角色获得技能嘲讽,直到其下一回合开始', + hslingjian_shijianhuisu:'时间回溯', + hslingjian_shijianhuisu_info:'可用于煅造装备;令一名其他角色将其装备牌收回手牌', + hslingjian_chaofeng:'嘲讽', + hslingjian_chaofeng_info:'锁定技,与你相邻的角色只能选择你为出杀目标', + qinglonglingzhu:'青龙灵珠', + qinglonglingzhu_ab:'灵珠', + qinglonglingzhu_info:'每当你造成一次属性伤害,你可以获得对方的一张牌', + xingjunyan:'星君眼', + xingjunyan_info:'你的杀造成的伤害+1;杀对你造成的伤害+1', + guiyanfadao:'鬼眼法刀', + guiyanfadao_bg:'眼', + guiyanfadao_info:'每当你使用杀命中目标,你可以防止伤害,改为令目标失去一点体力', + tianxianjiu:'天仙酒', + tianxianjiu_bg:'仙', + tianxianjiu_info:'出牌阶段对自己使用,你使用的下一张杀造成伤害后可以摸两张牌;濒死阶段,对自己使用,回复1点体力', + // xiangyuye:'翔羽叶', + // xiangyuye_info:'出牌阶段,对一名攻击范围外的角色使用,令其弃置一张黑色手牌或流失一点体力', + // huanpodan:'还魄丹', + // huanpodan_bg:'魄', + // huanpodan_info:'出牌阶段对一名角色使用,在目标即将死亡时防止其死亡,改为令其弃置所有牌,将体力值回复至1并摸一张牌', + // huanpodan_skill:'还魄丹', + // huanpodan_skill_bg:'丹', + // huanpodan_skill_info:'防止一次死亡,改为弃置所有牌,将体力值变为1并摸一张牌', + ximohu:'吸魔壶', + ximohu_bg:'魔', + // ximohu_info:'锁定技,你将即将受到的雷属性伤害转化为你的体力值', + sadengjinhuan:'萨登荆环', + sadengjinhuan_ab:'荆环', + sadengjinhuan_info:'当你的杀被闪避后,可以进行一次判定,若结果为红色目标需再打出一张闪', + sadengjinhuan_bg:'荆', + qipoguyu:'奇魄古玉', + xujin:'蓄劲', + xujin2:'蓄劲', + // qipoguyu_info:'装备后获得蓄劲技能', + xujin_info:'回合开始前,若你的蓄劲标记数小于当前的体力值,你可以跳过此回合,并获得一枚蓄劲标记。锁定技,每当你即将造成伤害,你令此伤害+X,然后弃置一枚蓄劲标记,X为你拥有的蓄劲标记数', + guilingzhitao:'归灵指套', + nigong:'逆攻', + nigong2:'逆攻', + nigong3:'逆攻', + nigong4:'逆攻', + guilingzhitao_info:'每当你受到一点伤害,你获得一个逆攻标记,标记数不能超过4。出牌阶段,你可以弃置所有逆攻标记并令对一名其他角色造成标记数一半的伤害(若非整数则向下取整并摸一张牌)', + nigong_info:'每当你受到一点伤害,你获得一个逆攻标记,标记数不能超过4。出牌阶段,你可以弃置所有逆攻标记并令对一名其他角色造成标记数一半的伤害(若非整数则向下取整并摸一张牌)', + baihupifeng:'白狐披风', + baihupifeng_bg:'狐', + baihupifeng_info:'结束阶段,若你的体力值是全场最小的之一,你可以回复一点体力', + fengxueren:'封雪刃', + fengxueren_bg:'雪', + fengxueren_info:'你使用杀击中目标后,若目标武将牌正面朝上,你可以防止伤害,然后令目标摸一张牌并翻面', + chilongya:'赤龙牙', + chilongya_info:'锁定技,你的火属性伤害+1', + daihuofenglun:'带火风轮', + daihuofenglun_ab:'风轮', + daihuofenglun_bg:'轮', + daihuofenglun_info:'你的进攻距离+2,你的防御距离-1', + xiayuncailing:'霞云彩绫', + xiayuncailing_ab:'彩绫', + xiayuncailing_bg:'云', + xiayuncailing_info:'你的进攻距离-1,你的防御距离+2', + shentoumianju:'神偷面具', + shentoumianju_bg:'偷', + shentoumianju_info:'出牌阶段,你可以指定一名手牌比你多的角色,弃置一张手牌并进行一次判定,若结果不为梅花,你获得其一张手牌', + shentou:'神偷', + shentou_info:'出牌阶段,你可以进行一次判定,若结果不为梅花,你获得任意一名角色的一张手牌', + xianluhui:'仙炉灰', + xianluhui_info:'令所有已受伤角色获得一点护甲', + caoyao:'草药', + caoyao_info:'出牌阶段,对距离为1以内的角色使用,回复一点体力。', + langeguaiyi:'蓝格怪衣', + langeguaiyi_bg:'格', + langeguaiyi_info:'出牌阶段限一次,你可以进行一次判定,然后按花色执行以下效果。红桃:你回复一点体力;方片:你摸一张牌;梅花:你令一名随机敌方角色随机弃置一张牌;黑桃:无事发生', + longfan:'龙帆', + longfan_info:'出牌阶段限一次,你可以进行一次判定,然后按花色执行以下效果。红桃:你回复一点体力;方片:你摸一张牌;梅花:你令一名随机敌方角色随机弃置一张牌;黑桃:无事发生', + // longfan_info:'0000:翻面;1111:弃手牌;2222:弃装备牌;3333:受伤害;4444:流失体力;5555:连环;6666:摸牌;7777:回复体力;8888:弃置判定牌;9999:置衡', + guiyoujie:'鬼幽结', + guiyoujie_bg:'结', + guiyoujie_info:'出牌阶段,对一名其他角色使用。若判定结果为黑色,其失去一点体力并随机弃置一张牌', + yufulu:'御夫录', + yufulu_info:'出牌阶段,可弃置一张武器牌令一名角色受到一点伤害,然后该角色获得此武器牌', + touzhi:'投掷', + touzhi_info:'出牌阶段,可弃置一张武器牌令一名角色受到一点伤害,然后该角色获得此武器牌', + xixueguizhihuan:'吸血鬼指环', + xixueguizhihuan_ab:'血环', + xixueguizhihuan_info:'锁定技,每当你使用杀造成一点伤害,你回复一点体力', + xixue:'吸血', + xixue_info:'锁定技,每当你使用杀造成一点伤害,你回复一点体力', + zhufangshenshi:'祠符', + zhufangshenshi_info:'出牌阶段,对一名其他角色使用,本回合内对其使用卡牌无视距离,结算后摸一张牌', + jingleishan:'惊雷闪', + jingleishan_info:'出牌阶段,对所有其他角色使用。每名目标角色需打出一张【杀】,否则受到1点雷电伤害。', + chiyuxi:'炽羽袭', + chiyuxi_info:'出牌阶段,对所有其他角色使用。每名目标角色需打出一张【闪】,否则受到1点火焰伤害。', + guangshatianyi:'光纱天衣', + guangshatianyi_bg:'纱', + guangshatianyi_info:'锁定技,每当你即将受到伤害,有三分之一的概率令伤害减一', + sifeizhenmian:'四非真面', + sifeizhenmian_info:'出牌阶段限一次,你可以令一名有手牌的其他角色进行一次判定,若结果为不为红桃且目标有可用的手牌,目标随机使用一张手牌(随机指定目标)', + yiluan:'意乱', + yiluan_info:'出牌阶段限一次,你可以令一名有手牌的其他角色进行一次判定,若结果为不为红桃且目标有可用的手牌,目标随机使用一张手牌(随机指定目标)', + donghuangzhong:'东皇钟', + xuanyuanjian:'轩辕剑', + xuanyuanjian2:'轩辕剑', + pangufu:'盘古斧', + lianyaohu:'炼妖壶', + lianyaohu_skill:'炼妖壶', + lianyaohu_skill_bg:'壶', + haotianta:'昊天塔', + fuxiqin:'伏羲琴', + shennongding:'神农鼎', + kongdongyin:'崆峒印', + kunlunjingc:'昆仑镜', + nvwashi:'女娲石', + donghuangzhong_bg:'钟', + lianyaohu_bg:'壶', + haotianta_bg:'塔', + fuxiqin_bg:'琴', + shennongding_bg:'鼎', + kongdongyin_bg:'印', + kunlunjingc_bg:'镜', + nvwashi_bg:'石', + kongxin:'控心', + lianhua:'炼化', + // dujian:'毒箭', + // dujian_info:'出牌阶段,对一名有手牌或装备牌的角色使用,令其展示一张手牌,若与你选择的手牌颜色相同,其流失一点体力', + lianhua_info:'出牌阶段限一次,你可以弃置两张炼妖壶中的牌,从牌堆中获得一张与弃置的牌类别均不相同的牌', + shouna:'收纳', + shouna_info:'出牌阶段限一次,你可以弃置一张手牌,并将一名其他角色的一张手牌置入炼妖壶', + donghuangzhong_info:'结束阶段,你可以弃置一张红色手牌,并选择一名角色将一张随机单体延时锦囊置入其判定区', + xuanyuanjian_info:'装备时获得一点护甲;每当你即将造成一次伤害,你令此伤害加一并变为雷属性,并在伤害结算后流失一点体力。任何时候,若你体力值不超过2,则立即失去轩辕剑', + pangufu_info:'锁定技,每当你造成一次伤害,受伤角色须弃置一张牌', + haotianta_info:'锁定技,任意一名角色进行判定前,你观看牌堆顶的2张牌,并选择一张作为判定结果,此结果不可被更改,也不能触发技能', + shennongding_info:'出牌阶段,你可以弃置两张手牌,然后回复一点体力。每阶段限一次', + kongdongyin_info:'令你抵挡一次死亡,将体力回复至1,并摸一张牌,发动后进入弃牌堆', + kunlunjingc_info:'出牌阶段限一次,你可以观看牌堆顶的三张牌,然后用一张手牌替换其中的一张', + nvwashi_info:'当一名角色濒死时,若你的体力值大于1,你可以失去一点体力并令其回复一点体力', + kongxin_info:'出牌阶段限一次,你可以与一名其他角色进行拼点,若你赢,你可以指定另一名角色视为对方对该角色使用一张杀,否则对方可弃置你一张牌', + fuxiqin_info:'出牌阶段限一次,你可以与一名其他角色进行拼点,若你赢,你可以指定另一名角色视为对方对该角色使用一张杀,否则对方可弃置你一张牌', + lianyaohu_info:'出牌阶段各限一次,你可以选择一项:1.弃置一张手牌,并将一名其他角色的一张手牌置入炼妖壶;2.弃置两张炼妖壶中的牌,从牌堆中获得一张与弃置的牌类别均不相同的牌', + }, + list:[ + ['heart',1,'hufu'], + ['spade',1,'hufu'], + ['club',1,'qiankundai'], + // ['heart',3,'yihuajiemu'], + // ['diamond',1,'yihuajiemu'], + // ['diamond',7,'yihuajiemu'], + + ['diamond',3,'liuxinghuoyu','fire'], + ['heart',6,'liuxinghuoyu','fire'], + ['heart',9,'liuxinghuoyu','fire'], + + ['spade',1,'baihupifeng'], + ['club',1,'fengxueren'], + ['diamond',1,'langeguaiyi'], + ['heart',1,'daihuofenglun','fire'], + + ['diamond',2,'xiayuncailing'], + // ['heart',2,'pantao'], + // ['heart',2,'huanpodan'], + + ['club',3,'caoyao'], + ['diamond',3,'chilongya','fire'], + ['spade',3,'guiyoujie'], + + ['club',4,'caoyao'], + ['spade',4,'zhufangshenshi'], + // ['spade',4,'huanpodan'], + + ['club',5,'caoyao'], + ['spade',5,'xixueguizhihuan'], + // ['diamond',5,'huanpodan'], + + ['club',6,'shentoumianju'], + ['spade',6,'yufulu'], + + ['diamond',7,'chiyuxi','fire'], + ['club',7,'jingleishan','thunder'], + ['spade',7,'guilingzhitao'], + + ['spade',8,'zhufangshenshi'], + // ['club',8,'xiangyuye','poison'], + + ['spade',9,'yangpijuan'], + ['club',9,'guiyoujie'], + // ['diamond',9,'xiangyuye','poison'], + + // ['diamond',9,'tianxianjiu'], + ['heart',9,'tianxianjiu'], + ['diamond',2,'tianxianjiu'], + + ['spade',2,'qinglonglingzhu'], + ['spade',7,'xingjunyan'], + + //['spade',10,'qipoguyu'], + //['diamond',10,'xiangyuye','poison'], + ['club',7,'yangpijuan'], + + // ['spade',11,'xiangyuye','poison'], + + ['spade',12,'guiyanfadao','poison'], + + ['spade',13,'xianluhui'], + ['diamond',3,'guangshatianyi'], + ['club',13,'sadengjinhuan'], + + ['club',2,'lingjiandai'], + // ['spade',3,'lingjiandai'], + // ['heart',5,'lingjiandai'], + ['diamond',8,'lingjiandai'], + + ['club',2,'jiguanshu'], + // ['spade',2,'jiguanshu'], + // ['heart',2,'jiguanshu'], + ['diamond',2,'jiguanshu'], + + ['club',3,'jiguanyaoshu'], + ['spade',3,'jiguanyaoshu'], + // ['heart',3,'jiguanyaoshu'], + // ['diamond',3,'jiguanyaoshu'], + + ['spade',4,'sifeizhenmian'], + ['heart',13,'qinglianxindeng'], + ['club',3,'jiguanyuan'], + ['diamond',2,'jiguanyuan'], + ['diamond',4,'jiguantong'], + ['club',7,'jiguantong'], + // ['spade',1,'shenmiguo'], + ['spade',2,'shenmiguo'], + ['heart',1,'shenmiguo'], + ['club',3,'jiguanfeng'], + ['spade',4,'jiguanfeng'], + ['spade',9,'guisheqi'], + ['club',7,'guisheqi'], + + ['diamond',13,'donghuangzhong'], + ['diamond',13,'fuxiqin'], + ['spade',13,'kunlunjingc'], + ['spade',13,'xuanyuanjian'], + ['spade',13,'pangufu'], + ['club',13,'lianyaohu'], + ['diamond',13,'haotianta'], + ['club',13,'shennongding'], + ['heart',13,'nvwashi'], + ['heart',13,'kongdongyin'], + + ['heart',6,'qinglongzhigui'], + ['diamond',6,'zhuquezhizhang'], + ['spade',6,'baishouzhihu'], + ['club',6,'xuanwuzhihuang'], + ['spade',7,'cangchizhibi'], + ['heart',5,'huanglinzhicong'], + + ['spade',9,'gouhunluo'], + ['club',7,'gouhunluo'], + + ['spade',1,'xuejibingbao'], + ['club',1,'xuejibingbao'], + + ['heart',3,'zhiluxiaohu'], + ['diamond',4,'zhiluxiaohu'], + + ['club',7,'mujiaren'], + ['heart',6,'mujiaren'], + ['diamond',11,'mujiaren'], + + ['club',6,'shuchui'], + + // ['club',1,'fengyinzhidan'], + // ['diamond',1,'fengyinzhidan'], + // ['heart',1,'fengyinzhidan'], + ['spade',1,'fengyinzhidan'], + + ['heart',9,'yuruyi'], + + ['club',4,'shencaojie'], + ['diamond',4,'shencaojie'], + ['spade',4,'shencaojie'], + + ['spade',1,'yuchanqian'], + ['club',2,'yuchankun'], + ['diamond',3,'yuchanzhen'], + ['heart',4,'yuchanxun'], + ['spade',5,'yuchankan'], + ['club',6,'yuchanli'], + ['diamond',7,'yuchangen'], + ['heart',8,'yuchandui'], + + // ['spade',3,'dujian','poison'], + // ['club',11,'dujian','poison'], + // ['club',12,'dujian','poison'], + ], + }; +}); diff --git a/card/yunchou.js b/card/yunchou.js index 584eff417..ee51bd406 100644 --- a/card/yunchou.js +++ b/card/yunchou.js @@ -1,963 +1,963 @@ -'use strict'; -game.import('card',function(lib,game,ui,get,ai,_status){ - return { - name:'yunchou', - card:{ - diaobingqianjiang:{ - fullskin:true, - type:'trick', - enable:true, - selectTarget:-1, - filterTarget:function(card,player,target){ - return player==target||target.countCards('h'); - }, - contentBefore:function(){ - "step 0" - game.delay(); - player.draw(); - "step 1" - if(get.is.versus()){ - player.chooseControl('顺时针','逆时针',function(event,player){ - if(player.next.side==player.side) return '逆时针'; - return '顺时针'; - }).set('prompt','选择'+get.translation(card)+'的结算方向'); - } - else{ - event.goto(3); - } - "step 2" - if(result&&result.control=='顺时针'){ - var evt=event.getParent(); - evt.fixedSeat=true; - evt.targets.sortBySeat(); - evt.targets.reverse(); - if(evt.targets[evt.targets.length-1]==player){ - evt.targets.unshift(evt.targets.pop()); - } - } - "step 3" - ui.clear(); - var cards=get.cards(Math.ceil(game.countPlayer()/2)); - var dialog=ui.create.dialog('调兵遣将',cards,true); - _status.dieClose.push(dialog); - dialog.videoId=lib.status.videoId++; - game.addVideo('cardDialog',null,['调兵遣将',get.cardsInfo(cards),dialog.videoId]); - event.getParent().preResult=dialog.videoId; - }, - content:function(){ - "step 0" - for(var i=0;i0){ - return get.value(button.link,nextSeat)-5; - } - else{ - return 5-get.value(button.link,nextSeat); - } - } - next.set('closeDialog',false); - next.set('dialogdisplay',true); - 'step 1' - if(result&&result.bool&&result.links&&result.links.length){ - for(var i=0;i0&&target!=player; - }, - content:function(){ - 'step 0' - if(target.countCards('h','sha')){ - var name=get.translation(player.name); - target.chooseControl().set('prompt',get.translation('caochuanjiejian')).set('choiceList',[ - '将手牌中的所有杀交给'+name+',并视为对'+name+'使用一张杀','展示手牌并令'+name+'弃置任意一张' - ],function(){ - if(get.effect(player,{name:'sha'},target,target)<0) return 1; - if(target.countCards('h','sha')>=3) return 1; - return 0; - }); - } - else{ - event.directfalse=true; - } - 'step 1' - if(event.directfalse||result.control=='选项二'){ - if(target.countCards('h')){ - if(!player.isUnderControl(true)){ - target.showHandcards(); - } - else{ - game.log(target,'展示了',target.getCards('h')); - } - player.discardPlayerCard(target,'h',true,'visible'); - } - event.finish(); - } - else{ - var hs=target.getCards('h','sha'); - player.gain(hs,target); - target.$give(hs,player); - } - 'step 2' - target.useCard({name:'sha'},player); - }, - ai:{ - order:4, - value:[5,1], - result:{ - target:function(player,target){ - if(player.hasShan()) return -1; - return 0; - } - } - } - }, - // xiaolicangdao:{ - // fullskin:true, - // type:'trick', - // enable:true, - // filterTarget:function(card,player,target){ - // return target!=player; - // }, - // multitarget:true, - // content:function(){ - // 'step 0' - // if(cards&&cards.length){ - // target.gain(cards,player); - // target.$gain2(cards); - // if(cards.length==1){ - // event.card1=cards[0]; - // } - // } - // 'step 1' - // event.card2=target.getCards('h').randomGet(); - // if(event.card2){ - // target.discard(event.card2); - // } - // else{ - // event.finish(); - // } - // 'step 2' - // if(event.card1&&event.card1.name==event.card2.name){ - // target.damage(); - // } - // }, - // ai:{ - // order:6, - // value:[3,1], - // result:{ - // target:function(player,target){ - // return -2/Math.sqrt(1+target.countCards('h')); - // }, - // }, - // tag:{ - // damage:1, - // discard:1, - // loseCard:1, - // } - // } - // }, - geanguanhuo:{ - fullskin:true, - type:'trick', - filterTarget:function(card,player,target){ - return target!=player&&target.countCards('h')>0; - }, - enable:function(){ - return game.countPlayer()>2; - }, - chongzhu:function(){ - return game.countPlayer()<=2; - }, - multicheck:function(card,player){ - return game.countPlayer(function(current){ - return current!=player&¤t.countCards('h'); - })>1; - }, - multitarget:true, - multiline:true, - singleCard:true, - content:function(){ - 'step 0' - target.chooseToCompare(event.addedTarget); - 'step 1' - if(!result.tie){ - if(result.bool){ - if(event.addedTarget.countCards('he')){ - target.line(event.addedTarget); - target.gainPlayerCard(event.addedTarget,true); - } - } - else{ - if(target.countCards('he')){ - event.addedTarget.line(target); - event.addedTarget.gainPlayerCard(target,true); - } - } - event.finish(); - } - 'step 2' - target.discardPlayerCard(player); - target.line(player); - }, - selectTarget:2, - ai:{ - order:5, - value:[7,1], - useful:[4,1], - result:{ - target:-1, - } - } - }, - shezhanqunru:{ - fullskin:true, - type:'trick', - enable:true, - toself:true, - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - modTarget:true, - content:function(){ - 'step 0' - var list=game.filterPlayer(function(current){ - return current!=target&¤t.countCards('h'); - }); - if(!list.length){ - target.draw(3); - event.finish(); - } - else{ - list.sortBySeat(target); - event.list=list; - event.torespond=[]; - } - 'step 1' - if(event.list.length){ - event.current=event.list.shift(); - event.current.chooseBool('是否响应'+get.translation(target)+'的舌战群儒?',function(event,player){ - if(get.attitude(player,_status.event.source)>=0) return false; - var hs=player.getCards('h'); - var dutag=player.hasSkillTag('nodu'); - for(var i=0;i=8&&value<=7) return true; - if(value<=3) return true; - } - else if(_status.event.hasTarget%2==1){ - if(hs[i].number>=11&&value<=6) return true; - } - } - return false; - }).set('source',target).set('hasTarget',event.torespond.length); - } - else{ - event.goto(3); - } - 'step 2' - if(result.bool){ - event.torespond.push(event.current); - event.current.line(target,'green'); - event.current.popup('响应'); - game.log(event.current,'响应了舌战群儒'); - game.delayx(0.5); - } - event.goto(1); - 'step 3' - if(event.torespond.length==0){ - event.num=1; - } - else{ - event.num=0; - target.chooseToCompare(event.torespond).callback=lib.card.shezhanqunru.callback; - } - 'step 4' - if(event.num>0){ - target.draw(3); - } - }, - callback:function(){ - if(event.card1.number>event.card2.number){ - event.parent.parent.num++; - } - else{ - event.parent.parent.num--; - } - }, - ai:{ - order:8.5, - value:[6,1], - useful:[3,1], - tag:{ - draw:1 - }, - result:{ - target:function(player,target){ - var hs=target.getCards('h'); - for(var i=0;i=7&&value<=6) return 1; - if(value<=3) return 1; - } - return 0; - } - } - } - }, - youdishenru:{ - fullskin:true, - type:'trick', - notarget:true, - wuxieable:true, - global:'g_youdishenru', - content:function(){ - 'step 0' - var info=event.getParent(2).youdiinfo||event.getParent(3).youdiinfo; - if(!info){ - event.finish(); - return; - } - info.evt.cancel(); - event.source=info.source; - event.source.storage.youdishenru=player; - event.source.addSkill('youdishenru'); - 'step 1' - var next=event.source.chooseToUse({name:'sha'},player,-1,'对'+get.translation(player)+'使用一张杀,或受到一点伤害'); - next.ai2=function(){ - return 1; - }; - 'step 2' - if(result.bool){ - if(event.source.storage.youdishenru){ - event.goto(1); - } - else{ - event.source.removeSkill('youdishenru'); - } - } - else{ - event.source.damage(player); - event.source.removeSkill('youdishenru'); - } - }, - ai:{ - value:[5,1], - useful:[5,1], - order:1, - wuxie:function(target,card,player,current,state){ - return -state*get.attitude(player,current); - }, - result:{ - player:function(player){ - if(_status.event.parent.youdiinfo&& - get.attitude(player,_status.event.parent.youdiinfo.source)<=0){ - return 1; - } - return 0; - } - } - } - }, - wangmeizhike:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:function(card,player,target){ - return (target.isMinHp()&&target.isDamaged())||target.isMinHandcard(); - }, - content:function(){ - 'step 0' - if(target.isMinHp()&&target.isDamaged()){ - target.recover(); - event.rec=true; - } - 'step 1' - if(target.isMinHandcard()){ - target.draw(event.rec?1:2); - } - }, - ai:{ - order:2.5, - value:7, - result:{ - target:function(player,target){ - var num=0; - if(target.isMinHp()&&get.recoverEffect(target)>0){ - if(target.hp==1){ - num+=3; - } - else{ - num+=2; - } - } - if(target.isMinHandcard()){ - num+=2; - } - return num; - } - } - } - }, - suolianjia:{ - fullskin:true, - type:"equip", - subtype:"equip2", - skills:['suolianjia'], - onEquip:function(){ - if(player.isLinked()==false) player.link(); - }, - onLose:function(){ - if(player.isLinked()) player.link(); - }, - ai:{ - basic:{ - equipValue:5 - }, - }, - }, - chenhuodajie:{ - fullskin:true, - type:'trick', - filterTarget:true, - global:'g_chenhuodajie', - content:function(){ - if(target.countCards('he')){ - player.gainPlayerCard('he',target,true); - } - }, - ai:{ - order:1, - useful:6, - value:6, - result:{ - target:-1 - }, - tag:{ - loseCard:1 - } - } - }, - fudichouxin:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:function(card,player,target){ - return player!=target&&target.countCards('h'); - }, - content:function(){ - "step 0" - player.chooseToCompare(target).set('preserve','win').clear=false; - "step 1" - if(result.bool){ - player.gain([result.player,result.target]); - result.player.clone.moveDelete(player); - result.target.clone.moveDelete(player); - game.addVideo('gain2',player,get.cardsInfo([result.player,result.target])); - } - else if(!result.cancelled){ - result.player.clone.delete(); - result.target.clone.delete(); - game.addVideo('deletenode',player,get.cardsInfo([result.player,result.target])); - } - }, - ai:{ - order:4, - value:[4,1], - result:{ - target:function(player){ - if(player.countCards('h')<=1) return 0; - return -1; - }, - player:function(player){ - if(player.countCards('h')<=1) return 0; - return 0.5; - } - }, - tag:{ - loseCard:1 - } - } - }, - shuiyanqijun:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:function(card,player,target){ - return target.countCards('e'); - }, - selectTarget:-1, - content:function(){ - if(target.countCards('e')) target.chooseToDiscard('e',true); - }, - reverseOrder:true, - ai:{ - order:9, - result:{ - target:function(player,target){ - if(target.countCards('e')) return -1; - return 0; - } - }, - tag:{ - multitarget:1, - multineg:1 - } - } - }, - toulianghuanzhu:{ - fullskin:true, - type:'trick', - enable:true, - filterTarget:function(card,player,target){ - return target.countCards('h')>0; - }, - content:function(){ - 'step 0' - if(!target.countCards('h')){ - event.finish(); - return; - } - var hs=player.getCards('h'); - if(hs.length){ - var minval=get.value(hs[0]); - var colors=[get.color(hs[0])]; - for(var i=1;i=0){ - if(colors.contains(get.color(button.link))){ - val+=3; - } - } - return val; - } - } - else{ - player.viewHandcards(target); - event.finish(); - } - 'step 1' - if(result.bool){ - event.card=result.links[[0]]; - player.chooseCard('h',true,'用一张手牌替换'+get.translation(event.card)).ai=function(card){ - return -get.value(card); - }; - } - else{ - event.finish(); - } - 'step 2' - if(result.bool){ - player.gain(event.card,target); - target.gain(result.cards,player); - player.$giveAuto(result.cards,target); - target.$giveAuto(event.card,player); - game.log(player,'与',target,'交换了一张手牌'); - if(get.color(event.card)==get.color(result.cards[0])){ - player.draw(); - } - target.addTempSkill('toulianghuanzhu_ai1'); - } - else{ - target.addTempSkill('toulianghuanzhu_ai2'); - } - }, - ai:{ - order:8, - tag:{ - loseCard:1, - norepeat:1, - }, - result:{ - target:function(player,target){ - if(player.countCards('h')<=1) return 0; - if(target.hasSkill('toulianghuanzhu_ai2')) return 0; - if(target.hasSkill('toulianghuanzhu_ai1')) return 0.5; - return -1; - } - }, - useful:[4,1], - value:[6,1] - } - }, - huoshan:{ - fullskin:true, - type:'delay', - cardcolor:'red', - cardnature:'fire', - modTarget:function(card,player,target){ - return lib.filter.judge(card,player,target); - }, - enable:function(card,player){ - return player.canAddJudge(card); - }, - filterTarget:function(card,player,target){ - return (lib.filter.judge(card,player,target)&&player==target); - }, - selectTarget:[-1,-1], - judge:function(card){ - if(get.suit(card)=='heart'&&get.number(card)>1&&get.number(card)<10) return -6; - return 0; - }, - effect:function(){ - if(result.bool==false){ - player.damage(2,'fire','nosource'); - var players=game.filterPlayer(function(current){ - return get.distance(player,current)<=1&&player!=current; - }); - players.sort(lib.sort.seat); - for(var i=0;i1&&get.number(card)<10) return -3; - return 0; - }, - fullskin:true, - effect:function(){ - if(result.bool==false){ - if(player.countCards('he')==0) player.loseHp(); - else{ - player.discard(player.getCards('he').randomGets(3)); - } - var players=get.players(); - for(var i=0;i0){ + return get.value(button.link,nextSeat)-5; + } + else{ + return 5-get.value(button.link,nextSeat); + } + } + next.set('closeDialog',false); + next.set('dialogdisplay',true); + 'step 1' + if(result&&result.bool&&result.links&&result.links.length){ + for(var i=0;i0&&target!=player; + }, + content:function(){ + 'step 0' + if(target.countCards('h','sha')){ + var name=get.translation(player.name); + target.chooseControl().set('prompt',get.translation('caochuanjiejian')).set('choiceList',[ + '将手牌中的所有杀交给'+name+',并视为对'+name+'使用一张杀','展示手牌并令'+name+'弃置任意一张' + ],function(){ + if(get.effect(player,{name:'sha'},target,target)<0) return 1; + if(target.countCards('h','sha')>=3) return 1; + return 0; + }); + } + else{ + event.directfalse=true; + } + 'step 1' + if(event.directfalse||result.control=='选项二'){ + if(target.countCards('h')){ + if(!player.isUnderControl(true)){ + target.showHandcards(); + } + else{ + game.log(target,'展示了',target.getCards('h')); + } + player.discardPlayerCard(target,'h',true,'visible'); + } + event.finish(); + } + else{ + var hs=target.getCards('h','sha'); + player.gain(hs,target); + target.$give(hs,player); + } + 'step 2' + target.useCard({name:'sha'},player); + }, + ai:{ + order:4, + value:[5,1], + result:{ + target:function(player,target){ + if(player.hasShan()) return -1; + return 0; + } + } + } + }, + // xiaolicangdao:{ + // fullskin:true, + // type:'trick', + // enable:true, + // filterTarget:function(card,player,target){ + // return target!=player; + // }, + // multitarget:true, + // content:function(){ + // 'step 0' + // if(cards&&cards.length){ + // target.gain(cards,player); + // target.$gain2(cards); + // if(cards.length==1){ + // event.card1=cards[0]; + // } + // } + // 'step 1' + // event.card2=target.getCards('h').randomGet(); + // if(event.card2){ + // target.discard(event.card2); + // } + // else{ + // event.finish(); + // } + // 'step 2' + // if(event.card1&&event.card1.name==event.card2.name){ + // target.damage(); + // } + // }, + // ai:{ + // order:6, + // value:[3,1], + // result:{ + // target:function(player,target){ + // return -2/Math.sqrt(1+target.countCards('h')); + // }, + // }, + // tag:{ + // damage:1, + // discard:1, + // loseCard:1, + // } + // } + // }, + geanguanhuo:{ + fullskin:true, + type:'trick', + filterTarget:function(card,player,target){ + return target!=player&&target.countCards('h')>0; + }, + enable:function(){ + return game.countPlayer()>2; + }, + chongzhu:function(){ + return game.countPlayer()<=2; + }, + multicheck:function(card,player){ + return game.countPlayer(function(current){ + return current!=player&¤t.countCards('h'); + })>1; + }, + multitarget:true, + multiline:true, + singleCard:true, + content:function(){ + 'step 0' + target.chooseToCompare(event.addedTarget); + 'step 1' + if(!result.tie){ + if(result.bool){ + if(event.addedTarget.countCards('he')){ + target.line(event.addedTarget); + target.gainPlayerCard(event.addedTarget,true); + } + } + else{ + if(target.countCards('he')){ + event.addedTarget.line(target); + event.addedTarget.gainPlayerCard(target,true); + } + } + event.finish(); + } + 'step 2' + target.discardPlayerCard(player); + target.line(player); + }, + selectTarget:2, + ai:{ + order:5, + value:[7,1], + useful:[4,1], + result:{ + target:-1, + } + } + }, + shezhanqunru:{ + fullskin:true, + type:'trick', + enable:true, + toself:true, + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + modTarget:true, + content:function(){ + 'step 0' + var list=game.filterPlayer(function(current){ + return current!=target&¤t.countCards('h'); + }); + if(!list.length){ + target.draw(3); + event.finish(); + } + else{ + list.sortBySeat(target); + event.list=list; + event.torespond=[]; + } + 'step 1' + if(event.list.length){ + event.current=event.list.shift(); + event.current.chooseBool('是否响应'+get.translation(target)+'的舌战群儒?',function(event,player){ + if(get.attitude(player,_status.event.source)>=0) return false; + var hs=player.getCards('h'); + var dutag=player.hasSkillTag('nodu'); + for(var i=0;i=8&&value<=7) return true; + if(value<=3) return true; + } + else if(_status.event.hasTarget%2==1){ + if(hs[i].number>=11&&value<=6) return true; + } + } + return false; + }).set('source',target).set('hasTarget',event.torespond.length); + } + else{ + event.goto(3); + } + 'step 2' + if(result.bool){ + event.torespond.push(event.current); + event.current.line(target,'green'); + event.current.popup('响应'); + game.log(event.current,'响应了舌战群儒'); + game.delayx(0.5); + } + event.goto(1); + 'step 3' + if(event.torespond.length==0){ + event.num=1; + } + else{ + event.num=0; + target.chooseToCompare(event.torespond).callback=lib.card.shezhanqunru.callback; + } + 'step 4' + if(event.num>0){ + target.draw(3); + } + }, + callback:function(){ + if(event.card1.number>event.card2.number){ + event.parent.parent.num++; + } + else{ + event.parent.parent.num--; + } + }, + ai:{ + order:8.5, + value:[6,1], + useful:[3,1], + tag:{ + draw:1 + }, + result:{ + target:function(player,target){ + var hs=target.getCards('h'); + for(var i=0;i=7&&value<=6) return 1; + if(value<=3) return 1; + } + return 0; + } + } + } + }, + youdishenru:{ + fullskin:true, + type:'trick', + notarget:true, + wuxieable:true, + global:'g_youdishenru', + content:function(){ + 'step 0' + var info=event.getParent(2).youdiinfo||event.getParent(3).youdiinfo; + if(!info){ + event.finish(); + return; + } + info.evt.cancel(); + event.source=info.source; + event.source.storage.youdishenru=player; + event.source.addSkill('youdishenru'); + 'step 1' + var next=event.source.chooseToUse({name:'sha'},player,-1,'对'+get.translation(player)+'使用一张杀,或受到一点伤害'); + next.ai2=function(){ + return 1; + }; + 'step 2' + if(result.bool){ + if(event.source.storage.youdishenru){ + event.goto(1); + } + else{ + event.source.removeSkill('youdishenru'); + } + } + else{ + event.source.damage(player); + event.source.removeSkill('youdishenru'); + } + }, + ai:{ + value:[5,1], + useful:[5,1], + order:1, + wuxie:function(target,card,player,current,state){ + return -state*get.attitude(player,current); + }, + result:{ + player:function(player){ + if(_status.event.parent.youdiinfo&& + get.attitude(player,_status.event.parent.youdiinfo.source)<=0){ + return 1; + } + return 0; + } + } + } + }, + wangmeizhike:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:function(card,player,target){ + return (target.isMinHp()&&target.isDamaged())||target.isMinHandcard(); + }, + content:function(){ + 'step 0' + if(target.isMinHp()&&target.isDamaged()){ + target.recover(); + event.rec=true; + } + 'step 1' + if(target.isMinHandcard()){ + target.draw(event.rec?1:2); + } + }, + ai:{ + order:2.5, + value:7, + result:{ + target:function(player,target){ + var num=0; + if(target.isMinHp()&&get.recoverEffect(target)>0){ + if(target.hp==1){ + num+=3; + } + else{ + num+=2; + } + } + if(target.isMinHandcard()){ + num+=2; + } + return num; + } + } + } + }, + suolianjia:{ + fullskin:true, + type:"equip", + subtype:"equip2", + skills:['suolianjia'], + onEquip:function(){ + if(player.isLinked()==false) player.link(); + }, + onLose:function(){ + if(player.isLinked()) player.link(); + }, + ai:{ + basic:{ + equipValue:5 + }, + }, + }, + chenhuodajie:{ + fullskin:true, + type:'trick', + filterTarget:true, + global:'g_chenhuodajie', + content:function(){ + if(target.countCards('he')){ + player.gainPlayerCard('he',target,true); + } + }, + ai:{ + order:1, + useful:6, + value:6, + result:{ + target:-1 + }, + tag:{ + loseCard:1 + } + } + }, + fudichouxin:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:function(card,player,target){ + return player!=target&&target.countCards('h'); + }, + content:function(){ + "step 0" + player.chooseToCompare(target).set('preserve','win').clear=false; + "step 1" + if(result.bool){ + player.gain([result.player,result.target]); + result.player.clone.moveDelete(player); + result.target.clone.moveDelete(player); + game.addVideo('gain2',player,get.cardsInfo([result.player,result.target])); + } + else if(!result.cancelled){ + result.player.clone.delete(); + result.target.clone.delete(); + game.addVideo('deletenode',player,get.cardsInfo([result.player,result.target])); + } + }, + ai:{ + order:4, + value:[4,1], + result:{ + target:function(player){ + if(player.countCards('h')<=1) return 0; + return -1; + }, + player:function(player){ + if(player.countCards('h')<=1) return 0; + return 0.5; + } + }, + tag:{ + loseCard:1 + } + } + }, + shuiyanqijun:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:function(card,player,target){ + return target.countCards('e'); + }, + selectTarget:-1, + content:function(){ + if(target.countCards('e')) target.chooseToDiscard('e',true); + }, + reverseOrder:true, + ai:{ + order:9, + result:{ + target:function(player,target){ + if(target.countCards('e')) return -1; + return 0; + } + }, + tag:{ + multitarget:1, + multineg:1 + } + } + }, + toulianghuanzhu:{ + fullskin:true, + type:'trick', + enable:true, + filterTarget:function(card,player,target){ + return target.countCards('h')>0; + }, + content:function(){ + 'step 0' + if(!target.countCards('h')){ + event.finish(); + return; + } + var hs=player.getCards('h'); + if(hs.length){ + var minval=get.value(hs[0]); + var colors=[get.color(hs[0])]; + for(var i=1;i=0){ + if(colors.contains(get.color(button.link))){ + val+=3; + } + } + return val; + } + } + else{ + player.viewHandcards(target); + event.finish(); + } + 'step 1' + if(result.bool){ + event.card=result.links[[0]]; + player.chooseCard('h',true,'用一张手牌替换'+get.translation(event.card)).ai=function(card){ + return -get.value(card); + }; + } + else{ + event.finish(); + } + 'step 2' + if(result.bool){ + player.gain(event.card,target); + target.gain(result.cards,player); + player.$giveAuto(result.cards,target); + target.$giveAuto(event.card,player); + game.log(player,'与',target,'交换了一张手牌'); + if(get.color(event.card)==get.color(result.cards[0])){ + player.draw(); + } + target.addTempSkill('toulianghuanzhu_ai1'); + } + else{ + target.addTempSkill('toulianghuanzhu_ai2'); + } + }, + ai:{ + order:8, + tag:{ + loseCard:1, + norepeat:1, + }, + result:{ + target:function(player,target){ + if(player.countCards('h')<=1) return 0; + if(target.hasSkill('toulianghuanzhu_ai2')) return 0; + if(target.hasSkill('toulianghuanzhu_ai1')) return 0.5; + return -1; + } + }, + useful:[4,1], + value:[6,1] + } + }, + huoshan:{ + fullskin:true, + type:'delay', + cardcolor:'red', + cardnature:'fire', + modTarget:function(card,player,target){ + return lib.filter.judge(card,player,target); + }, + enable:function(card,player){ + return player.canAddJudge(card); + }, + filterTarget:function(card,player,target){ + return (lib.filter.judge(card,player,target)&&player==target); + }, + selectTarget:[-1,-1], + judge:function(card){ + if(get.suit(card)=='heart'&&get.number(card)>1&&get.number(card)<10) return -6; + return 0; + }, + effect:function(){ + if(result.bool==false){ + player.damage(2,'fire','nosource'); + var players=game.filterPlayer(function(current){ + return get.distance(player,current)<=1&&player!=current; + }); + players.sort(lib.sort.seat); + for(var i=0;i1&&get.number(card)<10) return -3; + return 0; + }, + fullskin:true, + effect:function(){ + if(result.bool==false){ + if(player.countCards('he')==0) player.loseHp(); + else{ + player.discard(player.getCards('he').randomGets(3)); + } + var players=get.players(); + for(var i=0;i0; - }, - selectTarget:-1, - content:function(){ - target.chooseToDiscard('he',true).delay=false; - }, - mode:['guozhan'], - ai:{ - order:7, - result:{ - target:-1, - }, - tag:{ - discard:1 - } - } - }, - dizaizhen:{ - type:'zhenfa', - chongzhu:true, - enable:function(){ - return game.hasPlayer(function(current){ - return current.isNotMajor(); - }); - }, - filterTarget:function(card,player,target){ - return target.isNotMajor(); - }, - selectTarget:-1, - content:function(){ - target.draw(false); - target.$draw(); - }, - mode:['guozhan'], - ai:{ - order:7, - result:{ - target:1, - }, - tag:{ - draw:1 - } - } - }, - fengyangzhen:{ - type:'zhenfa', - chongzhu:true, - enable:true, - filterTarget:function(card,player,target){ - return target.sieged(); - }, - selectTarget:-1, - content:function(){ - target.addTempSkill('feiying',{player:'damageAfter'}); - target.popup('feiying'); - game.log(target,'获得了技能','【飞影】'); - }, - mode:['guozhan'], - ai:{ - order:7, - result:{ - target:2, - }, - } - }, - yunchuizhen:{ - type:'zhenfa', - chongzhu:true, - enable:true, - filterTarget:function(card,player,target){ - return target.siege(); - }, - selectTarget:-1, - content:function(){ - target.addTempSkill('wushuang',{source:'damageAfter'}); - target.popup('wushuang'); - game.log(target,'获得了技能','【无双】'); - }, - mode:['guozhan'], - ai:{ - order:7, - result:{ - target:2, - }, - } - }, - qixingzhen:{ - type:'zhenfa', - chongzhu:true, - enable:function(card,player){ - return player.siege()||player.sieged(); - }, - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - content:function(){ - 'step 0' - event.targets=game.filterPlayer(function(current){ - return current.siege(player); - }); - 'step 1' - if(event.targets.length){ - var current=event.targets.shift(); - player.line(current,'green'); - player.discardPlayerCard(current,true); - event.redo(); - } - 'step 2' - var list=game.filterPlayer(function(current){ - return current.sieged(player); - }); - if(list.length){ - player.useCard({name:'sha'},list,false); - } - }, - mode:['guozhan'], - ai:{ - order:7, - result:{ - target:1, - }, - } - }, - shepanzhen:{ - type:'zhenfa', - chongzhu:true, - enable:function(card,player){ - if(player.identity=='unknown'||player.identity=='ye') return false; - if(get.population(player.identity)<=1) return false; - return game.hasPlayer(function(current){ - return current!=player&¤t.identity==player.identity&&!player.inline(current); - }); - }, - notarget:true, - content:function(){ - var targets=game.filterPlayer(function(current){ - return current.identity==player.identity; - }); - targets.sortBySeat(); - for(var i=1;i0){ - return 6-get.value(card); - } - return 0; - }; - "step 1" - if(result.bool){ - player.useCard({name:'sha'},result.cards,target,false); - } - "step 2" - if(target==player.next) event.player2=player.next.next; - else event.player2=player.previous.previous; - event.player2.chooseCard('将一张非基本牌当作杀对'+get.translation(target)+'使用','he',function(card){ - return get.type(card)!='basic'; - }).ai=function(card){ - if(get.effect(target,{name:'sha'},event.player2,event.player2)>0){ - return 6-get.value(card); - } - return 0; - }; - "step 3" - if(result.bool){ - event.player2.useCard({name:'sha'},result.cards,target,false); - } - }, - mode:['guozhan'], - ai:{ - order:7, - result:{ - target:-2, - }, - } - }, - niaoxiangzhen:{ - type:'zhenfa', - chongzhu:true, - enable:true, - filterTarget:function(card,player,target){ - if(player.identity==target.identity) return false; - if(target.identity=='unknown'||target.identity=='ye') return false; - return target.identity==target.next.identity||target.identity==target.previous.identity - }, - selectTarget:-1, - content:function(){ - "step 0" - var next=target.chooseToRespond({name:'shan'}); - next.ai=function(card){ - if(get.damageEffect(target,player,target)>=0) return 0; - return 1; - }; - next.autochoose=lib.filter.autoRespondShan; - "step 1" - if(result.bool==false){ - target.damage(); - } - }, - ai:{ - basic:{ - order:9, - useful:1 - }, - result:{ - target:-1.5, - }, - tag:{ - respond:1, - respondShan:1, - damage:1, - } - }, - mode:['guozhan'], - }, - }, - skill:{ - - }, - translate:{ - zhenfa:'阵法', - changshezhen:'长蛇阵', - pozhenjue:'破阵决', - tianfuzhen:'天覆阵', - dizaizhen:'地载阵', - fengyangzhen:'风扬阵', - yunchuizhen:'云垂阵', - qixingzhen:'七星阵', - shepanzhen:'蛇蟠阵', - shepanzhen_bg:'列', - yunchuizhen_bg:'垂', - longfeizhen:'龙飞阵', - huyizhen:'虎翼阵', - niaoxiangzhen:'鸟翔阵', - niaoxiangzhen_info:'令所有非你阵营的队列的角色今次打出一张闪,或者受到一点伤害', - qixingzhen_info:'弃置所有围攻你的角色各一张牌,然后视为对所有你围攻的角色使用一张不计入出杀次数的杀', - // longfeizhen_info:'弃置围攻你的角色各一张牌,然后摸一张牌', - // qixingzhen_info:'令我方所有角色进入围攻状态', - // shepanzhen_info:'令我方所有角色进入队列状态', - // yunchuizhen_info:'令所有围攻角色获得技能【无双】,直到其首次造成伤害', - // fengyangzhen_info:'令所有被围攻角色获得技能【飞影】,直到其首次受到伤害', - dizaizhen_info:'所有小势力角色摸一张牌', - changshezhen_info:'若你处于队列中,与你同一队列的所有角色摸一张牌,否则将与你逆时针距离最近的同势力角色移至你下家', - // pozhenjue_info:'将所有角色的顺序随机重排', - tianfuzhen_info:'所有大势力角色弃置一张牌' - }, - list:[ - ["diamond",1,'changshezhen'], - ["club",1,'changshezhen'], - // ["spade",1,'changshezhen'], - // ["heart",1,'changshezhen'], - - ["diamond",2,'tianfuzhen'], - // ["club",2,'tianfuzhen'], - ["spade",2,'tianfuzhen'], - ["heart",2,'tianfuzhen'], - - ["diamond",3,'dizaizhen'], - // ["club",3,'dizaizhen'], - ["spade",3,'dizaizhen'], - ["heart",3,'dizaizhen'], - - // ["diamond",4,'fengyangzhen'], - // ["club",4,'fengyangzhen'], - // ["spade",4,'fengyangzhen'], - // ["heart",4,'fengyangzhen'], - - // ["diamond",5,'zhonghuangzhen'], - // ["club",5,'zhonghuangzhen'], - // ["spade",5,'zhonghuangzhen'], - // ["heart",5,'zhonghuangzhen'], - - // ["diamond",6,'huyizhen'], - // ["club",6,'huyizhen'], - // ["spade",6,'huyizhen'], - // ["heart",6,'huyizhen'], - - ["diamond",7,'qixingzhen'], - ["club",7,'qixingzhen'], - ["spade",7,'qixingzhen'], - // ["heart",7,'qixingzhen'], - - // ["diamond",8,'shepanzhen'], - // ["club",8,'shepanzhen'], - // ["spade",8,'shepanzhen'], - // ["heart",8,'shepanzhen'], - - // ["diamond",9,'longfeizhen'], - // ["club",9,'longfeizhen'], - // ["spade",9,'longfeizhen'], - // ["heart",9,'longfeizhen'], - - ["diamond",11,'niaoxiangzhen'], - // ["club",11,'niaoxiangzhen'], - ["spade",11,'niaoxiangzhen'], - ["heart",11,'niaoxiangzhen'], - - // ["diamond",12,'yunchuizhen'], - // ["club",12,'yunchuizhen'], - // ["spade",12,'yunchuizhen'], - // ["heart",12,'yunchuizhen'], - - // ["diamond",13,'pozhenjue'], - // ["club",13,'pozhenjue'], - // ["spade",13,'pozhenjue'], - //["heart",13,'pozhenjue'], - ], - }; -}); +'use strict'; +game.import('card',function(lib,game,ui,get,ai,_status){ + return { + name:'zhenfa', + card:{ + pozhenjue:{ + type:'zhenfa', + chongzhu:true, + enable:true, + notarget:true, + content:function(){ + var targets=game.filterPlayer(); + var n=targets.length; + while(n--){ + game.swapSeat(targets.randomGet(),targets.randomGet()); + } + }, + mode:['guozhan'], + ai:{ + order:8, + result:{ + player:1, + }, + } + }, + changshezhen:{ + type:'zhenfa', + chongzhu:true, + enable:function(card,player){ + if(player.inline()) return true; + if(player.identity=='unknown'||player.identity=='ye') return false; + return game.hasPlayer(function(current){ + return current!=player&¤t.isFriendOf(player); + }); + }, + notarget:true, + content:function(){ + if(player.inline()){ + var targets=game.filterPlayer(function(current){ + return player.inline(current); + }); + player.line(targets); + game.asyncDraw(targets); + } + else if(player.getNext()){ + var list=game.filterPlayer(function(current){ + return current!=player&¤t.isFriendOf(player); + }); + if(list.length){ + list.sort(function(a,b){ + return get.distance(player,a,'absolute')-get.distance(player,b,'absolute'); + }); + player.line(list[0]); + game.swapSeat(list[0],player.getNext(),true,true); + } + } + }, + mode:['guozhan'], + ai:{ + order:6.5, + result:{ + player:1, + }, + tag:{ + draw:1 + } + } + }, + tianfuzhen:{ + type:'zhenfa', + chongzhu:true, + enable:function(){ + return game.hasPlayer(function(current){ + return current.isMajor(); + }); + }, + filterTarget:function(card,player,target){ + return target.isMajor()&&target.countCards('he')>0; + }, + selectTarget:-1, + content:function(){ + target.chooseToDiscard('he',true).delay=false; + }, + mode:['guozhan'], + ai:{ + order:7, + result:{ + target:-1, + }, + tag:{ + discard:1 + } + } + }, + dizaizhen:{ + type:'zhenfa', + chongzhu:true, + enable:function(){ + return game.hasPlayer(function(current){ + return current.isNotMajor(); + }); + }, + filterTarget:function(card,player,target){ + return target.isNotMajor(); + }, + selectTarget:-1, + content:function(){ + target.draw(false); + target.$draw(); + }, + mode:['guozhan'], + ai:{ + order:7, + result:{ + target:1, + }, + tag:{ + draw:1 + } + } + }, + fengyangzhen:{ + type:'zhenfa', + chongzhu:true, + enable:true, + filterTarget:function(card,player,target){ + return target.sieged(); + }, + selectTarget:-1, + content:function(){ + target.addTempSkill('feiying',{player:'damageAfter'}); + target.popup('feiying'); + game.log(target,'获得了技能','【飞影】'); + }, + mode:['guozhan'], + ai:{ + order:7, + result:{ + target:2, + }, + } + }, + yunchuizhen:{ + type:'zhenfa', + chongzhu:true, + enable:true, + filterTarget:function(card,player,target){ + return target.siege(); + }, + selectTarget:-1, + content:function(){ + target.addTempSkill('wushuang',{source:'damageAfter'}); + target.popup('wushuang'); + game.log(target,'获得了技能','【无双】'); + }, + mode:['guozhan'], + ai:{ + order:7, + result:{ + target:2, + }, + } + }, + qixingzhen:{ + type:'zhenfa', + chongzhu:true, + enable:function(card,player){ + return player.siege()||player.sieged(); + }, + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + content:function(){ + 'step 0' + event.targets=game.filterPlayer(function(current){ + return current.siege(player); + }); + 'step 1' + if(event.targets.length){ + var current=event.targets.shift(); + player.line(current,'green'); + player.discardPlayerCard(current,true); + event.redo(); + } + 'step 2' + var list=game.filterPlayer(function(current){ + return current.sieged(player); + }); + if(list.length){ + player.useCard({name:'sha'},list,false); + } + }, + mode:['guozhan'], + ai:{ + order:7, + result:{ + target:1, + }, + } + }, + shepanzhen:{ + type:'zhenfa', + chongzhu:true, + enable:function(card,player){ + if(player.identity=='unknown'||player.identity=='ye') return false; + if(get.population(player.identity)<=1) return false; + return game.hasPlayer(function(current){ + return current!=player&¤t.identity==player.identity&&!player.inline(current); + }); + }, + notarget:true, + content:function(){ + var targets=game.filterPlayer(function(current){ + return current.identity==player.identity; + }); + targets.sortBySeat(); + for(var i=1;i0){ + return 6-get.value(card); + } + return 0; + }; + "step 1" + if(result.bool){ + player.useCard({name:'sha'},result.cards,target,false); + } + "step 2" + if(target==player.next) event.player2=player.next.next; + else event.player2=player.previous.previous; + event.player2.chooseCard('将一张非基本牌当作杀对'+get.translation(target)+'使用','he',function(card){ + return get.type(card)!='basic'; + }).ai=function(card){ + if(get.effect(target,{name:'sha'},event.player2,event.player2)>0){ + return 6-get.value(card); + } + return 0; + }; + "step 3" + if(result.bool){ + event.player2.useCard({name:'sha'},result.cards,target,false); + } + }, + mode:['guozhan'], + ai:{ + order:7, + result:{ + target:-2, + }, + } + }, + niaoxiangzhen:{ + type:'zhenfa', + chongzhu:true, + enable:true, + filterTarget:function(card,player,target){ + if(player.identity==target.identity) return false; + if(target.identity=='unknown'||target.identity=='ye') return false; + return target.identity==target.next.identity||target.identity==target.previous.identity + }, + selectTarget:-1, + content:function(){ + "step 0" + var next=target.chooseToRespond({name:'shan'}); + next.ai=function(card){ + if(get.damageEffect(target,player,target)>=0) return 0; + return 1; + }; + next.autochoose=lib.filter.autoRespondShan; + "step 1" + if(result.bool==false){ + target.damage(); + } + }, + ai:{ + basic:{ + order:9, + useful:1 + }, + result:{ + target:-1.5, + }, + tag:{ + respond:1, + respondShan:1, + damage:1, + } + }, + mode:['guozhan'], + }, + }, + skill:{ + + }, + translate:{ + zhenfa:'阵法', + changshezhen:'长蛇阵', + pozhenjue:'破阵决', + tianfuzhen:'天覆阵', + dizaizhen:'地载阵', + fengyangzhen:'风扬阵', + yunchuizhen:'云垂阵', + qixingzhen:'七星阵', + shepanzhen:'蛇蟠阵', + shepanzhen_bg:'列', + yunchuizhen_bg:'垂', + longfeizhen:'龙飞阵', + huyizhen:'虎翼阵', + niaoxiangzhen:'鸟翔阵', + niaoxiangzhen_info:'令所有非你阵营的队列的角色今次打出一张闪,或者受到一点伤害', + qixingzhen_info:'弃置所有围攻你的角色各一张牌,然后视为对所有你围攻的角色使用一张不计入出杀次数的杀', + // longfeizhen_info:'弃置围攻你的角色各一张牌,然后摸一张牌', + // qixingzhen_info:'令我方所有角色进入围攻状态', + // shepanzhen_info:'令我方所有角色进入队列状态', + // yunchuizhen_info:'令所有围攻角色获得技能【无双】,直到其首次造成伤害', + // fengyangzhen_info:'令所有被围攻角色获得技能【飞影】,直到其首次受到伤害', + dizaizhen_info:'所有小势力角色摸一张牌', + changshezhen_info:'若你处于队列中,与你同一队列的所有角色摸一张牌,否则将与你逆时针距离最近的同势力角色移至你下家', + // pozhenjue_info:'将所有角色的顺序随机重排', + tianfuzhen_info:'所有大势力角色弃置一张牌' + }, + list:[ + ["diamond",1,'changshezhen'], + ["club",1,'changshezhen'], + // ["spade",1,'changshezhen'], + // ["heart",1,'changshezhen'], + + ["diamond",2,'tianfuzhen'], + // ["club",2,'tianfuzhen'], + ["spade",2,'tianfuzhen'], + ["heart",2,'tianfuzhen'], + + ["diamond",3,'dizaizhen'], + // ["club",3,'dizaizhen'], + ["spade",3,'dizaizhen'], + ["heart",3,'dizaizhen'], + + // ["diamond",4,'fengyangzhen'], + // ["club",4,'fengyangzhen'], + // ["spade",4,'fengyangzhen'], + // ["heart",4,'fengyangzhen'], + + // ["diamond",5,'zhonghuangzhen'], + // ["club",5,'zhonghuangzhen'], + // ["spade",5,'zhonghuangzhen'], + // ["heart",5,'zhonghuangzhen'], + + // ["diamond",6,'huyizhen'], + // ["club",6,'huyizhen'], + // ["spade",6,'huyizhen'], + // ["heart",6,'huyizhen'], + + ["diamond",7,'qixingzhen'], + ["club",7,'qixingzhen'], + ["spade",7,'qixingzhen'], + // ["heart",7,'qixingzhen'], + + // ["diamond",8,'shepanzhen'], + // ["club",8,'shepanzhen'], + // ["spade",8,'shepanzhen'], + // ["heart",8,'shepanzhen'], + + // ["diamond",9,'longfeizhen'], + // ["club",9,'longfeizhen'], + // ["spade",9,'longfeizhen'], + // ["heart",9,'longfeizhen'], + + ["diamond",11,'niaoxiangzhen'], + // ["club",11,'niaoxiangzhen'], + ["spade",11,'niaoxiangzhen'], + ["heart",11,'niaoxiangzhen'], + + // ["diamond",12,'yunchuizhen'], + // ["club",12,'yunchuizhen'], + // ["spade",12,'yunchuizhen'], + // ["heart",12,'yunchuizhen'], + + // ["diamond",13,'pozhenjue'], + // ["club",13,'pozhenjue'], + // ["spade",13,'pozhenjue'], + //["heart",13,'pozhenjue'], + ], + }; +}); diff --git a/card/zhulu.js b/card/zhulu.js index 04ca26e3e..1e40ea780 100644 --- a/card/zhulu.js +++ b/card/zhulu.js @@ -321,6 +321,7 @@ game.import('card',function(lib,game,ui,get,ai,_status){ equipValue:5, }, result:{ + keepAI:true, target:function(player,target){ var card=target.getCards('e'); var val=get.value(card); @@ -367,6 +368,7 @@ game.import('card',function(lib,game,ui,get,ai,_status){ equipValue:5, }, result:{ + keepAI:true, target:function(player,target){ var val=2.5; var card=target.getEquip(2); @@ -394,6 +396,7 @@ game.import('card',function(lib,game,ui,get,ai,_status){ equipValue:5, }, result:{ + keepAI:true, target:function(player,target){ var val=2.5; var card=target.getEquip(2); @@ -421,6 +424,7 @@ game.import('card',function(lib,game,ui,get,ai,_status){ equipValue:5, }, result:{ + keepAI:true, target:function(player,target){ var val=2; var card=target.getEquip(2); @@ -469,6 +473,7 @@ game.import('card',function(lib,game,ui,get,ai,_status){ equipValue:5, }, result:{ + keepAI:true, target:function(player,target){ if(target.sex=='male'){ var val=0; @@ -560,6 +565,7 @@ game.import('card',function(lib,game,ui,get,ai,_status){ equipValue:5, }, result:{ + keepAI:true, target:function(player,target){ return -1-target.countCards('h'); }, diff --git a/character/diy.js b/character/diy.js index 899f274c2..c1c77c053 100755 --- a/character/diy.js +++ b/character/diy.js @@ -814,7 +814,7 @@ game.import('character',function(lib,game,ui,get,ai,_status){ lucia_duqu:{ mod:{ cardSavable:function(card,player){ - if(card.name=='du'&&!player.hasSkill('lucia_duqu_terra')) return true; + if(player.isDying()&&card.name=='du'&&!player.hasSkill('lucia_duqu_terra')) return true; }, }, trigger:{ @@ -850,7 +850,13 @@ game.import('character',function(lib,game,ui,get,ai,_status){ another.gain(game.createCard('du'),'gain2'); } }, - ai:{usedu:true,save:true}, + ai:{ + usedu:true, + save:true, + skillTagFilter:function(player,tag){ + if(tag=='save'&&(!player.isDying()||player.hasSkill('lucia_duqu_terra'))) return false; + }, + }, subSkill:{terra:{sub:true}} }, lucia_zhenren:{ diff --git a/character/gujian.js b/character/gujian.js index f41e07933..bc8fb7523 100644 --- a/character/gujian.js +++ b/character/gujian.js @@ -1,2774 +1,2774 @@ -'use strict'; -game.import('character',function(lib,game,ui,get,ai,_status){ - return { - name:'gujian', - character:{ - gjqt_bailitusu:['male','shu',4,['xuelu','fanshi','shahun']], - gjqt_fengqingxue:['female','wu',3,['qinglan','yuehua','swd_wuxie']], - gjqt_xiangling:['female','wu',3,['xlqianhuan','meihu','xidie']], - gjqt_fanglansheng:['male','wu',3,['fanyin','mingkong','fumo']], - gjqt_yinqianshang:['male','qun',4,['zuiji','zuizhan']], - gjqt_hongyu:['female','shu',4,['jianwu','meiying']], - - gjqt_yuewuyi:['male','wei',4,['yanjia','xiuhua','liuying']], - gjqt_wenrenyu:['female','shu',4,['chizhen','dangping']], - gjqt_xiayize:['male','qun',3,['xuanning','liuguang','yangming']], - gjqt_aruan:['female','wu',3,['zhaolu','jiehuo','yuling']], - - gjqt_xunfang:['female','shu',3,['manwu','xfanghua']], - gjqt_ouyangshaogong:['male','shu',3,['yunyin','shishui','duhun']], - - gjqt_xieyi:['male','qun',3,['lingyan','xunjian','humeng']], - gjqt_yanjiaxieyi:['male','qun',2,['xianju'],['unseen']], - gjqt_chuqi:['male','qun',2,['xuanci'],['unseen']], - - // gjqt_xuange:['male','qun',4,['zhenlu','zhuixing']], - gjqt_beiluo:['male','qun',4,['lingnu','zhenying','cihong']], - gjqt_yunwuyue:['female','wei',3,['yange','woxue','lianjing']], - gjqt_cenying:['female','shu',3,['yunyou','xuanzhen','qingshu']], - // gjqt_changliu:['male','qun',4,['xiange']], - // gjqt_fenglishuang:['female','wei',3,[]], - // gjqt_nishang:[], - }, - characterIntro:{ - gjqt_bailitusu:'原名韩云溪,太子长琴半身。幼时经历灭族之灾,本来已死去,但因体内被封进了太子长琴一半魂魄而得以死而复生。身怀凶剑焚寂的煞气,在某次煞气发作中被紫胤真人所救,后拜入昆仑山天墉城,以“屠绝鬼气,苏醒人魂”之意更名为“百里屠苏”。', - gjqt_fengqingxue:'来自幽暗无边的地界,大哥为幽都娲皇神殿“十巫”之一的巫咸——风广陌。奉命寻兄而来到人间,邂逅百里屠苏,陷入了焚寂的宿命纠葛。后与百里屠苏相知相爱。终局,百里屠苏化为永世无法轮回的荒魂,她为寻找重生之法,放弃自己的轮回,换取下长久的寿命,在人间执着不悔地寻觅了九百年。最后,与重生后的屠苏回到桃花谷相守。', - gjqt_xiangling:'青丘之国国主九尾天狐与人界女子所生,半人半妖。由南疆紫榕林榕爷爷抚养,在山林间无拘无束长大,娇俏可爱。后来下江南寻找生母,却被抓进翻云寨,偶然被百里屠苏相救,为报答救命之恩,便一直跟随百里屠苏。在漫漫征途中成长起来,变得成熟懂事。得知方兰生对自己的感情,十分矛盾,后来终于错过。最后结局,启程前往青丘之国。', - gjqt_fanglansheng:'家住琴川的一介书生,家境殷实,母亲在家常住庵堂,吃斋念佛,父亲虽是琴川附近某间寺庙的住持,但却比商人还会敛财。跟随父亲学过降妖除鬼的佛家法术,并以此自傲。梦想能找一个娇小可爱,温柔美丽的女子共渡此生。对襄铃一见钟情。', - gjqt_yinqianshang:'原名风广陌,是幽都“十巫”之一的巫咸。奉女娲之命前往乌蒙灵谷增强焚寂封印。但因为欧阳少恭与雷严的争夺关系,他受到焚寂之力力量冲击,失去记忆,后为欧阳少恭所救。尹千觞为了报恩,跟随在百里屠苏等人身边监视。在这个过程中,他慢慢恢复过往的记忆。后为阻止欧阳少恭,随众人前往蓬莱决战。最后,对曾给予他一次重生的欧阳少恭以死相陪,一起归于火海。', - gjqt_hongyu:'上古庆枫族族人,紫胤真人的剑灵,宿体为古剑·红玉。在百里屠苏离开昆仑山天墉城后,奉命随行保护。终局之后,她依然回到了天墉城,陪伴在紫胤真人身边。', - - gjqt_yuewuyi:'成长于长安富商家庭。其养父曾是战功显赫的将军,退隐后从商,很快就富甲一方;其养母是精擅偃术的南疆天玄教偃女族传人,待无异一如己出般疼爱有加;无异的生父是捐毒大将兀火罗,亡母是一名中原女子,因此他具有一半胡人的血统。', - gjqt_wenrenyu:'出身于百草谷“天罡”部队。从小在军中生活的她见惯生死,拥有超越其年龄的沉着果敢,头脑冷静严谨,洞察力敏锐,性格大方爽快。', - gjqt_xiayize:'本为当朝圣元帝三子李焱,天资聪颖,勤奋好学,于道术一途颇具天赋。幼时体弱,因身世原因被太华山的诀微长老清和真人带走,从此过上了道家清修的生活。', - gjqt_aruan:'千年前,昭明崩裂损毁,剑心为神农所得,其将剑心植入用辟邪之骨所造之人,是为巫山神女。神女爱慕司幽,却始终得不到司幽回应,心绪起伏加速灵力散逸,不久后死去,司幽自责不已,失去踪迹。神女死后,剑心碎片落地生根,吸纳灵气变为露草,露草渐渐化为人形,形貌与巫山神女一样,而且保留了她零散的记忆,阿阮正是这些露草中的一个。当阿阮灵气耗尽就会重新变为露草,并失去人时的记忆,直到重新吸纳足够的灵力才能恢复人形。', - - gjqt_xunfang:'蓬莱国公主,美丽善良,为欧阳少恭前世妻子,太子长琴转世后,巽芳为寻找丈夫来到中原。蓬莱人寿命虽长却终有极限,终究躲不过容颜老去。当巽芳找到分离多年后的少恭时,自惭形秽,不愿相认,她希望少恭心中的自己永远都是青春貌美,于是化名“寂桐”守护在其身旁。', - gjqt_ouyangshaogong:'前身为太子长琴,今生只有一半仙灵,另一半仙灵被铸进焚寂之中,成为焚寂剑灵。他在漫长时光中经历太多悲欢离合,渐渐迷失自我。后与蓬莱国公主巽芳相爱,度过一段美好时光。之后蓬莱毁于天灾,他误以为巽芳已死,便不再压抑内心的疯狂与憎恨,为逆天改命不惜一切。', - - gjqt_xieyi:'偃术大师,流月城大祭司沈夜之徒。于偃术一途冠绝古今,其制造的偃甲精妙绝伦,为世人所称颂。男主角乐无异对其十分崇拜。曾任流月城破军祭司,精通偃术和法术,在一百年前寻找神剑昭明的西域之行中被沈夜捉拿回流月城,毁去记忆仅靠偃甲和蛊虫心脏跳动,并更名为初七,最终在巫山为抢救昭明剑心、保护乐无异被埋在坍塌的神女墓下。他以自己为原型制作,并放入自己部分记忆的偃甲人谢衣曾收乐无异为徒。', - - gjqt_beiluo:'身负辟邪王族之血的大妖,辟邪先王玄戈的孪生兄弟。北洛幼时流落人界,从此便在那里长大。他对自己的血脉并无认同之感,常年抑制妖力,希望以“人”的身份留在人间。后因先王玄戈的逝世,即位为辟邪王', - gjqt_yunwuyue:'本体是一只魇兽,该族喜独居,以人的梦境为食,以精神力为长,被称为“最接近魔的妖族”。云无月幼时居住在有熊城旁的白梦泽,自幼便与轩辕黄帝的大将缙云结识,对缙云有着一种依赖和仰慕,深受缙云的影响,对人族以及其他生灵有感情,不忍看到眼前的生灵遭到痛苦,会加以援手。', - gjqt_cenying:'出身于一个颇有底蕴的大家族,性格开朗随和、温柔坚韧、心如琉璃。岑缨受到开明长辈的影响,自小多思善学,年纪不大却已经加入了博物学会,有时跟随师长在外游历,探寻更广阔的天地。', - }, - card:{ - yanjiadan_heart:{ - type:'jiguan', - cardcolor:'heart', - fullskin:true, - derivation:'gjqt_xieyi', - enable:true, - notarget:true, - content:function(){ - 'step 0' - var choice=null; - var targets=game.filterPlayer(function(current){ - return get.attitude(player,current)>0; - }); - for(var i=0;i0; - })){ - choice='shatang'; - } - if(!choice){ - for(var i=0;i=0) return false; - if(current.hp==1&&eff<0) return true; - if(get.attitude(player,current)<0&&get.attitude(player,current.getNext()<0)&&get.attitude(player,current.getPrevious())<0){ - return true; - } - return false; - })){ - choice='shenhuofeiya'; - } - player.chooseVCardButton('选择一张牌视为使用之',['liufengsan','shujinsan','shenhuofeiya']).set('ai',function(button){ - if(button.link[2]==_status.event.choice) return 2; - return Math.random(); - }).set('choice',choice).set('filterButton',function(button){ - return _status.event.player.hasUseTarget(button.link[2]); - }); - 'step 1' - player.chooseUseTarget(true,result.links[0][2]); - }, - ai:{ - order:5, - result:{ - player:1 - } - } - }, - yanjiadan_club:{ - type:'jiguan', - cardcolor:'club', - fullskin:true, - derivation:'gjqt_xieyi', - enable:true, - notarget:true, - content:function(){ - 'step 0' - var choice='liutouge'; - player.chooseVCardButton('选择一张牌视为使用之',['bingpotong','liutouge','mianlijinzhen']).set('ai',function(button){ - if(button.link[2]==_status.event.choice) return 2; - return Math.random(); - }).set('choice',choice).set('filterButton',function(button){ - return _status.event.player.hasUseTarget(button.link[2]); - }); - 'step 1' - player.chooseUseTarget(true,result.links[0][2]); - }, - ai:{ - order:5, - result:{ - player:1 - } - } - }, - yanjiadan_spade:{ - type:'jiguan', - cardcolor:'spade', - fullskin:true, - derivation:'gjqt_xieyi', - enable:true, - notarget:true, - content:function(){ - 'step 0' - var choice=null; - if(player.getUseValue('longxugou')>0){ - choice='longxugou'; - } - else if(player.getUseValue('qiankunbiao')>0){ - choice='qiankunbiao'; - } - else if(player.getUseValue('feibiao')>0){ - choice='qiankunbiao'; - } - player.chooseVCardButton('选择一张牌视为使用之',['feibiao','qiankunbiao','longxugou']).set('ai',function(button){ - if(button.link[2]==_status.event.choice) return 2; - return Math.random(); - }).set('choice',choice).set('filterButton',function(button){ - return _status.event.player.hasUseTarget(button.link[2]); - }); - 'step 1' - player.chooseUseTarget(true,result.links[0][2]); - }, - ai:{ - order:5, - result:{ - player:function(player){ - if(player.getUseValue('feibiao')>0) return 1; - if(player.getUseValue('qiankunbiao')>0) return 1; - if(player.getUseValue('longxugou')>0) return 1; - return 0; - } - } - } - } - }, - skill:{ - qingshu:{ - ai:{ - threaten:1.4 - }, - trigger:{player:'phaseEnd'}, - direct:true, - filter:function(event,player){ - for(var i=0;iplayer.storage.yunyou.length; - }, - delay:0, - content:function(){ - var list=get.inpile('land'); - for(var i=0;iplayer.storage.lingyan.length/13) return false; - return get.cardPile2(event.card.name)?true:false; - }, - content:function(){ - var card=get.cardPile2(trigger.card.name); - if(card){ - player.gain(card,'gain2'); - } - } - }, - tongtian:{ - trigger:{player:['useCardAfter','respondAfter']}, - forced:true, - filter:function(event,player){ - var name=event.card.name; - var enemies=player.getEnemies(); - for(var i=0;i0) { - player.markSkill('yange'); - } - }, - filter:function(event,player){ - return player.storage.yange>0; - }, - content:function(){ - 'step 0' - player.chooseTarget(function(card,player,target){ - return player!=target; - },get.prompt2('yange')).set('pskill',trigger.skill).set('ai',function(target){ - if(_status.event.pskill) return 0; - var player=_status.event.player; - var nh=player.countCards('h'); - var nh2=target.countCards('h'); - var num=nh2-nh; - if(player.hp==1){ - return num; - } - if(get.threaten(target)>=1.5){ - if(num<2) return 0; - } - else{ - if(num<3) return 0; - } - return num+Math.sqrt(get.threaten(target)); - }); - 'step 1' - if(result.bool){ - player.storage.yange--; - if(player.storage.yange<=0){ - player.unmarkSkill('yange'); - } - else{ - player.updateMarks(); - } - var target=result.targets[0]; - player.logSkill('yange',target); - var clone=function(pos){ - var cloned=[]; - var cards=target.getCards(pos); - for(var i=0;i0){ - if(target.isTurnedOver()) return 2.5; - if(target.hp==1){ - if(nh==0) return 2; - if(nh==1) return 0.9; - if(ui.selected.targets.length) return 0.3; - } - else if(target.hp==2){ - if(nh==0) return 1.5; - if(nh==1) return 0.5; - if(ui.selected.targets.length) return 0.2; - } - else if(target.hp==3){ - if(nh==0) return 0.4; - if(nh==1) return 0.35; - if(ui.selected.targets.length) return 0.1; - } - if(!target.needsToDiscard(2)) return 0.2; - if(ui.selected.targets.length||!player.needsToDiscard(2)) return 0.05; - } - return 0; - }); - 'step 1' - if(result.bool){ - player.logSkill('lianjing',result.targets); - player.insertEvent('lianjing',lib.skill.lianjing.content_phase); - player.storage.lianjing_targets=result.targets.slice(0); - // player.storage.lianjing--; - // if(player.storage.lianjing<=0){ - // player.unmarkSkill('lianjing'); - // } - game.delay(); - } - }, - content_phase:function(){ - 'step 0' - event.list=[player].concat(player.storage.lianjing_targets); - event.exlist=[]; - event.list.sortBySeat(); - delete player.storage.lianjing_targets; - for(var i=0;i0; - })){ - if(player.hp>1||!player.isTurnedOver()){ - goon=true; - } - } - player.chooseTarget(get.prompt('cihong'),function(card,playe,target){ - return player.canUse('sha',target,false); - }).set('ai',function(target){ - if(!_status.event.goon) return false; - var player=_status.event.player; - if(get.attitude(player,target)>=0) return false; - if(target.hp>3) return false; - return get.effect(target,{name:'sha'},player,player); - }).set('goon',goon); - 'step 1' - if(result.bool){ - event.target=result.targets[0]; - player.logSkill('cihong',event.target); - } - else{ - event.finish(); - } - 'step 2' - if(event.target.isAlive()&&player.countCards('he',{color:'red'})){ - player.chooseToDiscard('he',{color:'red'},'是否弃置一张红色牌视为对'+get.translation(event.target)+'使用一张杀?').set('ai',function(card){ - return 8-get.value(card); - }) - } - else{ - event.goto(4); - } - 'step 3' - if(result.bool){ - player.useCard({name:'sha'},event.target); - } - 'step 4' - if(event.target.isAlive()){ - player.chooseBool('是否失去一点体力并视为对'+get.translation(event.target)+'使用一张杀?').set('choice', - player.hp>event.target.hp&&player.hp>1&&event.target.hp>0 - ); - } - else{ - event.finish(); - } - 'step 5' - if(result.bool){ - player.loseHp(); - player.useCard({name:'sha'},event.target); - } - 'step 6' - if(event.target.isAlive()&&!player.isTurnedOver()){ - player.chooseBool('是将武将牌翻至背面并视为对'+get.translation(event.target)+'使用一张杀?').set('choice', - event.target.hp==1 - ); - } - else{ - event.finish(); - } - 'step 7' - if(result.bool){ - player.turnOver(true); - player.useCard({name:'sha'},event.target); - } - }, - ai:{ - threaten:1.5 - } - }, - zhenying:{ - trigger:{player:['useCard','respond']}, - forced:true, - filter:function(event,player){ - if(get.is.converted(event)) return false; - if(event.card.zhenying_link) return false; - if(get.color(event.card)!='black') return false; - if(['delay','equip'].contains(get.type(event.card))) return false; - return true; - }, - content:function(){ - var fake=game.createCard(trigger.card); - fake.zhenying_link=true; - player.gain(fake,'draw')._triggered=null; - fake.classList.add('glow'); - fake._destroy='zhenying'; - fake._modUseful=function(){return 0.1}; - fake._modValue=function(){return 0.1}; - }, - group:['zhenying_discard','zhenying_lose'], - subSkill:{ - discard:{ - trigger:{player:['useCardAfter','respondAfter']}, - forced:true, - filter:function(event,player){ - if(get.is.converted(event)) return false; - if(!player.countCards('he')) return false; - if(event.card.zhenying_link) return true; - return false; - }, - popup:false, - content:function(){ - player.chooseToDiscard('he',true); - } - }, - lose:{ - trigger:{global:'phaseAfter'}, - silent:true, - content:function(){ - var hs=player.getCards('h',function(card){ - return card.zhenying_link?true:false; - }); - if(hs.length){ - player.lose(hs)._triggered=null; - } - } - } - } - }, - lingnu:{ - trigger:{source:'damageEnd'}, - forced:true, - init:function(player){ - player.storage.lingnu={}; - }, - ai:{ - threaten:1.3 - }, - filter:function(event,player){ - var num=0; - for(var i in player.storage.lingnu){ - num++; - if(num>=3) return false; - } - return event.num>0; - }, - content:function(){ - var check=function(list){ - for(var i=0;i=3) break; - if(list.length){ - var skill=list.randomGet(); - player.addAdditionalSkill('lingnu',skill,true); - player.storage.lingnu[skill]=3; - player.popup(skill); - game.log(player,'获得了技能','【'+get.translation(skill)+'】'); - player.markSkill('lingnu'); - num++; - } - } - }, - intro:{ - content:function(storage){ - var str='
            '; - for(var i in storage){ - str+='
          • '+get.translation(i)+':剩余'+storage[i]+'回合'; - } - str+='
          ' - return str; - }, - markcount:function(storage){ - var num=0; - for(var i in storage){ - num++; - } - return num; - } - }, - group:'lingnu_remove', - subSkill:{ - remove:{ - trigger:{player:'phaseAfter'}, - silent:true, - content:function(){ - var clear=true; - for(var i in player.storage.lingnu){ - player.storage.lingnu[i]--; - if(player.storage.lingnu[i]<=0){ - delete player.storage.lingnu[i]; - player.removeAdditionalSkill('lingnu',i); - } - else{ - clear=false; - } - } - if(clear){ - player.unmarkSkill('lingnu'); - } - else{ - player.updateMarks(); - } - } - } - } - }, - zuiji:{ - enable:'phaseUse', - filterCard:true, - position:'he', - viewAs:{name:'jiu'}, - viewAsFilter:function(player){ - if(!player.countCards('he')) return false; - }, - prompt:'将一张手牌或装备牌当酒使用', - check:function(card){ - return 5-get.value(card); - }, - ai:{ - threaten:1.2 - } - }, - manwu:{ - trigger:{global:'phaseEnd'}, - check:function(event,player){ - return get.attitude(player,event.player)>0; - }, - filter:function(event,player){ - return event.player.isMinHandcard(); - }, - logTarget:'player', - content:function(){ - trigger.player.draw(); - }, - ai:{ - expose:0.1 - } - }, - xfanghua:{ - trigger:{target:'useCardToBegin'}, - priority:-1, - filter:function(event,player){ - return get.color(event.card)=='red'&&player.isDamaged(); - }, - frequent:true, - content:function(){ - player.recover(); - }, - ai:{ - effect:{ - target:function(card,player,target,current){ - if(get.color(card)=='red'&&target.isDamaged()) return [1,1]; - } - } - } - }, - duhun:{ - enable:'chooseToUse', - filter:function(event,player){ - if(event.type!='dying') return false; - if(player!=event.dying) return false; - if(player.maxHp<=1) return false; - if(player.countCards('h')==0) return false; - return true; - }, - // alter:true, - filterTarget:function(card,player,target){ - return target!=player&&target.countCards('h')>0&&target.hp>0&&target.hp<=player.maxHp; - }, - content:function(){ - 'step 0' - player.chooseToCompare(target); - 'step 1' - if(!result.bool){ - player.die(); - event.finish(); - } - else{ - event.num=target.hp-player.hp; - player.loseMaxHp(); - } - 'step 2' - player.changeHp(event.num); - if(get.is.altered('duhun')){ - event.finish(); - } - 'step 3' - event.target.changeHp(-event.num); - 'step 4' - if(event.target.hp<=0){ - event.target.dying({source:player}); - } - }, - ai:{ - order:1, - skillTagFilter:function(player){ - if(player.maxHp<=1) return false; - if(player.hp>0) return false; - if(player.countCards('h')==0) return false; - }, - save:true, - result:{ - target:-1, - player:1 - }, - threaten:2 - }, - }, - yunyin:{ - trigger:{player:'phaseEnd'}, - direct:true, - subSkill:{ - count:{ - trigger:{player:'useCard'}, - silent:true, - filter:function(event,player){ - return _status.currentPhase==player; - }, - content:function(){ - if(!player.storage.yunyin){ - player.storage.yunyin=[]; - } - var suit=get.suit(trigger.card); - if(suit){ - player.storage.yunyin.add(suit); - } - } - }, - set:{ - trigger:{player:'phaseAfter'}, - silent:true, - content:function(){ - delete player.storage.yunyin; - } - } - }, - filter:function(event,player){ - if(!player.storage.yunyin) return true; - var hs=player.getCards('h'); - for(var i=0;i0){ - taoyuan++; - } - else if(eff1<0){ - taoyuan--; - } - if(eff2>0){ - nanman++; - } - else if(eff2<0){ - nanman--; - } - } - player.chooseButton(dialog).ai=function(button){ - var name=button.link[2]; - if(Math.max(taoyuan,nanman)>1){ - if(taoyuan>nanman) return name=='taoyuan'?1:0; - return name=='nanman'?1:0; - } - if(player.countCards('h')=2){ - return name=='wuzhong'?1:0; - } - if(player.hp=2) return true; - } - } - }, - filterCard:function(card){ - if(ui.selected.cards.length&&card.name==ui.selected.cards[0].name) return false; - var info=get.info(card); - return info.type=='equip'&&!info.nomod&&!info.unique&&lib.inpile.contains(card.name); - }, - selectCard:2, - position:'he', - check:function(card){ - return get.value(card); - }, - content:function(){ - var name=cards[0].name+'_'+cards[1].name; - var info1=get.info(cards[0]),info2=get.info(cards[1]); - if(!lib.card[name]){ - var info={ - enable:true, - type:'equip', - subtype:get.subtype(cards[0]), - vanish:true, - cardimage:info1.cardimage||cards[0].name, - filterTarget:function(card,player,target){ - return target==player; - }, - selectTarget:-1, - modTarget:true, - content:lib.element.content.equipCard, - legend:true, - source:[cards[0].name,cards[1].name], - onEquip:[], - onLose:[], - skills:[], - distance:{}, - ai:{ - order:8.9, - equipValue:10, - useful:2.5, - value:function(card,player){ - var value=0; - var info=get.info(card); - var current=player.getEquip(info.subtype); - if(current&&card!=current){ - value=get.value(current,player); - } - var equipValue=info.ai.equipValue||info.ai.basic.equipValue; - if(typeof equipValue=='function') return equipValue(card,player)-value; - return equipValue-value; - }, - result:{ - target:function(player,target){ - return get.equipResult(player,target,name); - } - } - } - } - for(var i in info1.distance){ - info.distance[i]=info1.distance[i]; - } - for(var i in info2.distance){ - if(typeof info.distance[i]=='number'){ - info.distance[i]+=info2.distance[i]; - } - else{ - info.distance[i]=info2.distance[i]; - } - } - if(info1.skills){ - info.skills=info.skills.concat(info1.skills); - } - if(info2.skills){ - info.skills=info.skills.concat(info2.skills); - } - if(info1.onEquip){ - if(Array.isArray(info1.onEquip)){ - info.onEquip=info.onEquip.concat(info1.onEquip); - } - else{ - info.onEquip.push(info1.onEquip); - } - } - if(info2.onEquip){ - if(Array.isArray(info2.onEquip)){ - info.onEquip=info.onEquip.concat(info2.onEquip); - } - else{ - info.onEquip.push(info2.onEquip); - } - } - if(info1.onLose){ - if(Array.isArray(info1.onLose)){ - info.onLose=info.onLose.concat(info1.onLose); - } - else{ - info.onLose.push(info1.onLose); - } - } - if(info2.onLose){ - if(Array.isArray(info2.onLose)){ - info.onLose=info.onLose.concat(info2.onLose); - } - else{ - info.onLose.push(info2.onLose); - } - } - if(info.onEquip.length==0) delete info.onEquip; - if(info.onLose.length==0) delete info.onLose; - lib.card[name]=info; - lib.translate[name]=get.translation(cards[0].name,'skill')+get.translation(cards[1].name,'skill'); - var str=lib.translate[cards[0].name+'_info']; - if(str[str.length-1]=='.'||str[str.length-1]=='。'){ - str=str.slice(0,str.length-1); - } - lib.translate[name+'_info']=str+';'+lib.translate[cards[1].name+'_info']; - try{ - game.addVideo('newcard',null,{ - name:name, - translate:lib.translate[name], - info:lib.translate[name+'_info'], - card:cards[0].name, - legend:true, - }); - } - catch(e){ - console.log(e); - } - } - player.gain(game.createCard({name:name,suit:cards[0].suit,number:cards[0].number}),'gain2'); - }, - ai:{ - order:9.5, - result:{ - player:1 - } - } - }, - meiying:{ - global:'meiying2', - globalSilent:true, - trigger:{global:'phaseEnd'}, - filter:function(event,player){ - return event.player!=player&&!event.player.tempSkills.meiying3&&event.player.isAlive()&&player.countCards('he',{color:'red'})>0; - }, - direct:true, - content:function(){ - "step 0" - var next=player.chooseToDiscard('he','魅影:是否弃置一张红色牌视为对'+get.translation(trigger.player)+'使用一张杀?'); - next.logSkill=['meiying',trigger.player]; - var eff=get.effect(trigger.player,{name:'sha'},player,player); - next.ai=function(card){ - if(eff>0){ - return 7-get.value(card); - } - return 0; - } - "step 1" - if(result.bool){ - player.useCard({name:'sha'},trigger.player).animate=false; - } - }, - ai:{ - expose:0.1 - } - }, - meiying2:{ - trigger:{player:'useCard'}, - filter:function(event,player){ - return _status.currentPhase==player&&event.targets&&(event.targets.length>1||event.targets[0]!=player); - }, - forced:true, - popup:false, - content:function(){ - player.addTempSkill('meiying3'); - } - }, - meiying3:{}, - jianwu:{ - trigger:{player:'shaBegin'}, - forced:true, - filter:function(event,player){ - return get.distance(event.target,player,'attack')>1; - }, - content:function(){ - trigger.directHit=true; - } - }, - zuizhan:{ - trigger:{player:'useCard'}, - popup:false, - filter:function(event,player){ - if(event.card.name!='sha') return false; - return game.hasPlayer(function(current){ - return (event.targets.contains(current)==false&¤t!=player&& - lib.filter.targetEnabled(event.card,player,current)) - }); - }, - content:function(){ - var list=game.filterPlayer(function(current){ - return (trigger.targets.contains(current)==false&¤t!=player&& - lib.filter.targetEnabled(trigger.card,player,current)) - }); - if(list.length){ - event.target=list.randomGet(); - player.line(event.target,'green'); - game.log(event.target,'被追加为额外目标'); - trigger.targets.push(event.target); - player.draw(); - } - } - }, - xidie:{ - trigger:{player:'phaseBegin'}, - direct:true, - filter:function(event,player){ - return player.countCards('h')>player.hp; - }, - content:function(){ - "step 0" - var next=player.chooseToDiscard(get.prompt('xidie'),[1,Math.min(3,player.countCards('h')-player.hp)]); - next.ai=function(card){ - return 6-get.value(card); - } - next.logSkill='xidie'; - "step 1" - if(result.bool){ - player.storage.xidie=result.cards.length; - } - }, - group:'xidie2' - }, - xidie2:{ - trigger:{player:'phaseEnd'}, - forced:true, - filter:function(event,player){ - return player.storage.xidie>0; - }, - content:function(){ - player.draw(player.storage.xidie); - player.storage.xidie=0; - } - }, - meihu:{ - trigger:{player:'damageEnd'}, - check:function(event,player){ - return get.attitude(player,event.source)<4; - }, - filter:function(event,player){ - return event.source&&event.source!=player&&event.source.countCards('h')>0; - }, - logTarget:'source', - content:function(){ - "step 0" - trigger.source.chooseCard('交给'+get.translation(player)+'一张手牌',true).ai=function(card){ - return -get.value(card); - }; - "step 1" - if(result.bool){ - player.gain(result.cards[0],trigger.source); - trigger.source.$give(1,player); - } - }, - ai:{ - maixie_defend:true, - effect:{ - target:function(card,player,target){ - if(get.tag(card,'damage')){ - if(player.hasSkillTag('jueqing',false,target)) return [1,-1.5]; - return [1,0,0,-0.5]; - } - } - } - } - }, - xlqianhuan:{ - trigger:{player:'phaseAfter'}, - check:function(event,player){ - return player.hp==1||player.isTurnedOver(); - }, - filter:function(event,player){ - return player.hp1||player.countCards('h',{color:'black'})>1; - }, - direct:true, - content:function(){ - "step 0" - var next=player.chooseToDiscard(get.prompt('fumo',trigger.source),2,function(card){ - if(get.damageEffect(trigger.source,player,player,'thunder')<=0) return 0; - if(ui.selected.cards.length){ - return get.color(card)==get.color(ui.selected.cards[0]); - } - return player.countCards('h',{color:get.color(card)})>1; - }).set('complexCard',true); - next.ai=function(card){ - if(get.damageEffect(trigger.source,player,player,'thunder')>0){ - return 8-get.value(card); - } - return 0; - }; - next.logSkill=['fumo',trigger.source,'thunder']; - "step 1" - if(result.bool){ - trigger.source.damage('thunder'); - } - }, - ai:{ - maixie_defend:true, - threaten:0.8 - } - }, - fanyin:{ - trigger:{player:'phaseEnd'}, - direct:true, - content:function(){ - "step 0" - player.chooseTarget(get.prompt('fanyin'),function(card,player,target){ - // if(player==target) return false; - if(target.isLinked()) return true; - if(target.isTurnedOver()) return true; - if(target.countCards('j')) return true; - if(target.isMinHp()&&target.isDamaged()) return true; - return false; - }).ai=function(target){ - var num=0; - var att=get.attitude(player,target); - if(att>0){ - if(target.isMinHp()){ - num+=5; - } - if(target.isTurnedOver()){ - num+=5; - } - if(target.countCards('j')){ - num+=2; - } - if(target.isLinked()){ - num++; - } - if(num>0){ - return num+att; - } - } - return num; - } - "step 1" - if(result.bool){ - event.target=result.targets[0]; - player.logSkill('fanyin',event.target); - } - else{ - event.finish(); - } - "step 2" - if(event.target.isLinked()){ - event.target.link(); - } - "step 3" - if(event.target.isTurnedOver()){ - event.target.turnOver(); - } - "step 4" - var cards=event.target.getCards('j'); - if(cards.length){ - event.target.discard(cards); - } - "step 5" - if(event.target.isMinHp()){ - event.target.recover(); - } - }, - ai:{ - expose:0.2, - threaten:1.3 - } - }, - mingkong:{ - trigger:{player:'damageBegin'}, - forced:true, - filter:function(event,player){ - return player.countCards('h')==0&&event.num>=1; - }, - content:function(){ - if(trigger.num>=1){ - trigger.num--; - } - if(trigger.source){ - trigger.source.storage.mingkong=true; - trigger.source.addTempSkill('mingkong2'); - } - }, - ai:{ - effect:{ - target:function(card,player,target){ - if(get.tag(card,'damage')&&target.countCards('h')==0){ - if(player.hasSkillTag('jueqing',false,target)) return; - return 0.1; - } - } - } - }, - }, - mingkong2:{ - trigger:{source:['damageEnd','damageZero']}, - forced:true, - popup:false, - audio:false, - vanish:true, - filter:function(event,player){ - return player.storage.mingkong?true:false; - }, - content:function(){ - player.draw(); - player.storage.mingkong=false; - player.removeSkill('mingkong2'); - } - }, - yuehua:{ - trigger:{player:['useCardAfter','respondAfter','discardAfter']}, - frequent:true, - filter:function(event,player){ - if(player==_status.currentPhase) return false; - if(event.cards){ - for(var i=0;i0; - }, - direct:true, - priority:-5, - content:function(){ - "step 0" - var next=player.chooseToDiscard(get.prompt('qinglan',trigger.player),'he'); - next.logSkill=['qinglan',trigger.player]; - next.ai=function(card){ - if(trigger.num>1||!trigger.source){ - if(get.attitude(player,trigger.player)>0){ - return 9-get.value(card); - } - return -1; - } - else if(get.attitude(player,trigger.player)>0){ - if(trigger.player.hp==1){ - return 8-get.value(card); - } - if(trigger.source.hp==trigger.source.maxHp){ - return 6-get.value(card); - } - } - else if(get.attitude(player,trigger.source)>0&& - trigger.source.hp1){ - if(get.color(card)=='red') return 5-get.value(card); - } - return -1; - } - "step 1" - if(result.bool){ - trigger.cancel(); - if(trigger.source){ - trigger.source.recover(); - } - } - else{ - event.finish(); - } - "step 2" - if(trigger.source){ - trigger.source.draw(); - } - }, - ai:{ - expose:0.1 - } - }, - fanshi:{ - trigger:{player:'phaseDiscardAfter'}, - forced:true, - filter:function(event,player){ - return player.getStat('damage')>0; - }, - check:function(event,player){ - return player.hp==player.maxHp; - }, - content:function(){ - "step 0" - player.loseHp(); - "step 1" - player.draw(); - } - }, - xuelu:{ - unique:true, - trigger:{player:'phaseEnd'}, - direct:true, - filter:function(event,player){ - return player.maxHp>player.hp&&player.countCards('he',{color:'red'})>0; - }, - // alter:true, - content:function(){ - "step 0" - player.chooseCardTarget({ - position:'he', - filterTarget:function(card,player,target){ - return player!=target; - }, - filterCard:function(card,player){ - return get.color(card)=='red'&&lib.filter.cardDiscardable(card,player); - }, - ai1:function(card){ - return 9-get.value(card); - }, - ai2:function(target){ - return get.damageEffect(target,player,player,'fire'); - }, - prompt:get.prompt('xuelu') - }); - "step 1" - if(result.bool){ - event.target=result.targets[0]; - player.logSkill('xuelu',event.target,'fire'); - if(get.is.altered('xuelu')){ - event.num=1; - } - else{ - event.num=Math.min(2,Math.ceil((player.maxHp-player.hp)/2)); - } - player.discard(result.cards); - } - else{ - event.finish(); - } - "step 2" - if(event.target){ - event.target.damage(event.num,'fire'); - } - }, - ai:{ - maixie:true, - expose:0.2, - threaten:function(player,target){ - if(target.hp==1) return 3; - if(target.hp==2) return 1.5; - return 0.5; - }, - effect:{ - target:function(card,player,target){ - if(!target.hasFriend()) return; - if(get.tag(card,'damage')){ - if(target.hp==target.maxHp) return [0,1]; - } - if(get.tag(card,'recover')&&player.hp>=player.maxHp-1) return [0,0]; - } - } - } - }, - xiuhua_old:{ - changeSeat:true, - trigger:{player:'shaHit'}, - filter:function(event,player){ - return event.target!=player.previous; - }, - content:function(){ - game.swapSeat(trigger.target,player,true,true); - } - }, - shahun:{ - enable:'chooseToUse', - skillAnimation:true, - animationColor:'fire', - derivation:'juejing', - // alter:true, - filter:function(event,player){ - return !player.storage.shahun&&player.hp<=0; - }, - content:function(){ - "step 0" - var cards=player.getCards('hej'); - player.discard(cards); - "step 1" - if(player.isLinked()) player.link(); - "step 2" - if(player.isTurnedOver()) player.turnOver(); - "step 3" - player.draw(3); - "step 4" - player.recover(1-player.hp); - player.removeSkill('fanshi'); - player.addSkill('juejing'); - player.storage.shahun=2; - player.markSkill('shahun'); - game.addVideo('storage',player,['shahun',player.storage.shahun]); - }, - group:'shahun2', - intro:{ - content:'turn' - }, - ai:{ - save:true, - skillTagFilter:function(player){ - if(player.storage.shahun) return false; - if(player.hp>0) return false; - }, - result:{ - player:3 - } - } - }, - shahun2:{ - trigger:{player:'phaseAfter'}, - forced:true, - filter:function(event,player){ - return player.storage.shahun?true:false; - }, - content:function(){ - if(player.storage.shahun>1){ - player.storage.shahun--; - game.addVideo('storage',player,['shahun',player.storage.shahun]); - } - else{ - player.die(); - } - } - }, - yanjia_old:{ - enable:'chooseToUse', - filter:function(event,player){ - return player.countCards('he',{type:'equip'})>0; - }, - filterCard:function(card){ - return get.type(card)=='equip'; - }, - position:'he', - viewAs:{name:'wuzhong'}, - prompt:'将一张装备牌当无中生有使用', - check:function(card){ - var player=_status.currentPhase; - if(player.countCards('he',{subtype:get.subtype(card)})>1){ - return 11-get.equipValue(card); - } - if(player.countCards('h')0&&player.storage.xuanning!=3; - }, - filterCard:function(card){ - return get.type(card)=='basic'; - }, - check:function(card){ - return 7-get.useful(card); - }, - content:function(){ - player.storage.xuanning=3; - player.markSkill('xuanning'); - game.addVideo('storage',player,['xuanning',player.storage.xuanning]); - }, - ai:{ - result:{ - player:function(player){ - var num=player.countCards('h'); - if(num>player.hp+1) return 1; - if(player.storage.xuanning>=2) return 0; - if(num>player.hp) return 1 - if(player.storage.xuanning>=1) return 0; - return 1; - }, - }, - order:5 - } - }, - xuanning2:{ - trigger:{player:'damageEnd'}, - forced:true, - filter:function(event,player){ - if(player.storage.xuanning){ - return (event.source&&event.source.countCards('he')>0); - } - return false; - }, - logTarget:'source', - content:function(){ - var he=trigger.source.getCards('he'); - if(he.length){ - trigger.source.discard(he.randomGet()); - } - player.storage.xuanning--; - if(!player.storage.xuanning){ - player.unmarkSkill('xuanning'); - } - game.addVideo('storage',player,['xuanning',player.storage.xuanning]); - }, - ai:{ - maixie_defend:true, - } - }, - liuguang:{ - trigger:{player:'phaseBegin'}, - direct:true, - filter:function(event,player){ - if(player.storage.xuanning) return true; - return false; - }, - content:function(){ - "step 0" - player.chooseTarget(function(card,player,target){ - return player!=target; - },get.prompt('liuguang'),[1,3]).ai=function(target){ - return get.damageEffect(target,player,player); - } - "step 1" - if(result.bool){ - player.storage.xuanning--; - if(!player.storage.xuanning){ - player.unmarkSkill('xuanning'); - } - event.targets=result.targets.slice(0); - event.targets.sort(lib.sort.seat); - player.logSkill('liuguang',result.targets); - game.addVideo('storage',player,['xuanning',player.storage.xuanning]); - } - else{ - event.finish(); - } - "step 2" - if(event.targets.length){ - var target=event.targets.shift(); - var next=target.chooseToDiscard('流光:弃置一张牌或受到一点伤害','he'); - next.ai=function(card){ - if(get.damageEffect(_status.event.player,player,_status.event.player)>=0) return -1; - if(_status.event.player.hp==1) return 9-get.value(card); - return 8-get.value(card); - }; - next.autochoose=function(){ - return this.player.countCards('he')==0; - }; - event.current=target; - } - else{ - event.finish(); - } - "step 3" - if(result.bool&&result.cards&&result.cards.length){ - event.goto(2); - } - else{ - event.current.damage(); - } - }, - ai:{ - expose:0.2, - threaten:1.3 - } - }, - yangming:{ - enable:'phaseUse', - filter:function(event,player){ - if(player.storage.yangming2>0) return false; - return player.countCards('h',{color:'red'})>0; - }, - filterCard:function(card){ - return get.color(card)=='red'; - }, - content:function(){ - player.storage.yangming2=2; - player.addSkill('yangming2'); - game.addVideo('storage',player,['yangming2',player.storage.yangming2]); - }, - check:function(card){ - return 6-get.value(card); - }, - ai:{ - result:{ - player:function(player){ - if(player.countCards('h')<=player.hp&&player.hp==player.maxHp){ - return 0; - } - return 1; - } - }, - order:6, - threaten:1.3 - } - }, - yangming2:{ - trigger:{player:'phaseUseEnd'}, - direct:true, - mark:true, - content:function(){ - "step 0" - player.storage.yangming2--; - game.addVideo('storage',player,['yangming2',player.storage.yangming2]); - if(player.storage.yangming2>0){ - event.finish(); - } - else{ - player.removeSkill('yangming2'); - var num=game.countPlayer(function(current){ - return get.distance(player,current)<=1&¤t.isDamaged(); - }); - if(num==0){ - event.finish(); - } - else{ - player.chooseTarget(function(card,player,target){ - return get.distance(player,target)<=1&&target.hp1) return false; - if(target.hp>2) return false; - if(get.attitude(player,target)>=0) return false; - return get.damageEffect(target,player,player,'fire'); - } - } - } - }, - jiehuo_old:{ - unique:true, - forbid:['infinity'], - skillAnimation:true, - animationColor:'fire', - init:function(player){ - player.storage.jiehuo=false; - }, - enable:'phaseUse', - filter:function(event,player){ - //if(player.maxHp<=1) return false; - return !player.storage.jiehuo - }, - intro:{ - content:'limited' - }, - // mark:true, - line:'fire', - filterTarget:function(card,player,target){ - return player!=target; - }, - selectTarget:-1, - content:function(){ - if(!player.storage.jiehuo2){ - player.storage.jiehuo2=player.maxHp; - player.addSkill('jiehuo2'); - } - player.storage.jiehuo=true; - target.damage(Math.min(target.hp,player.storage.jiehuo2),'fire'); - } - }, - jiehuo2:{ - trigger:{player:'phaseUseEnd'}, - forced:true, - popup:false, - content:function(){ - player.die(); - } - }, - yuling:{ - unique:true, - locked:true, - group:['yuling1','yuling2','yuling3','yuling4','yuling5','yuling6'], - intro:{ - content:'time' - }, - // alter:true, - ai:{ - noh:true, - threaten:0.8, - effect:{ - target:function(card,player,target){ - if(card.name=='bingliang') return 0; - if(card.name=='lebu') return 1.5; - if(card.name=='guohe'){ - if(!target.countCards('e')) return 0; - return 0.5; - } - if(card.name=='liuxinghuoyu') return 0; - } - } - } - }, - yuling1:{ - trigger:{player:['phaseDrawBefore','phaseDiscardBefore']}, - priority:10, - forced:true, - popup:false, - check:function(){ - return false; - }, - content:function(){ - trigger.cancel(); - } - }, - yuling2:{ - trigger:{player:['loseEnd','drawEnd'],global:'gameDrawAfter'}, - check:function(event,player){ - return player.countCards('h')<2; - }, - priority:10, - forced:true, - filter:function(event,player){ - return player.countCards('h')<5; - }, - content:function(){ - player.draw(5-player.countCards('h')); - } - }, - yuling3:{ - trigger:{player:'gainEnd'}, - priority:10, - forced:true, - filter:function(event,player){ - return player.countCards('h')>5; - }, - check:function(event,player){ - return player.countCards('h')<2; - }, - content:function(){ - player.chooseToDiscard(true,player.countCards('h')-5); - } - }, - yuling4:{ - mod:{ - cardEnabled:function(card,player){ - if(_status.currentPhase!=player) return; - var num=2; - if(get.is.altered('yuling')) num=1; - if(player.countUsed()>=player.maxHp+num) return false; - } - } - }, - yuling5:{ - trigger:{player:['useCardAfter','phaseBegin']}, - silent:true, - content:function(){ - player.storage.yuling=player.maxHp+2-player.countUsed(); - } - }, - yuling6:{ - trigger:{player:'phaseAfter'}, - silent:true, - content:function(){ - delete player.storage.yuling; - } - }, - }, - translate:{ - gjqt_bailitusu:'百里屠苏', - gjqt_fengqingxue:'风晴雪', - gjqt_fanglansheng:'方兰生', - gjqt_xiangling:'襄铃', - gjqt_yinqianshang:'尹千觞', - gjqt_hongyu:'红玉', - gjqt_ouyangshaogong:'欧阳少恭', - gjqt_xunfang:'巽芳', - - gjqt_yuewuyi:'乐无异', - gjqt_wenrenyu:'闻人羽', - gjqt_xiayize:'夏夷则', - gjqt_aruan:'阿阮', - gjqt_xieyi:'谢衣', - gjqt_yanjiaxieyi:'偃甲谢衣', - gjqt_chuqi:'初七', - - gjqt_beiluo:'北洛', - gjqt_yunwuyue:'云无月', - gjqt_cenying:'岑缨', - - yunyou:'云游', - yunyou_info:'每两轮限一次,出牌阶段,你可以发现一张地图牌本局未使用过的地图牌并使用之', - xuanzhen:'玄阵', - // xuanzhen_bg:'阵', - xuanzhen_info:'每轮限一次,当你成为一名其他角色的卡牌惟一目标时,你可以发现一张牌代替此牌', - qingshu:'青书', - qingshu_info:'结束阶段,你可以令一名角色永久获得一个你使用过且不是当前地图的地图牌效果(每个地图最多发动一次)', - yanjiadan_heart:'偃甲蛋', - yanjiadan_heart_info:'可以当作紫阳丹、玉女元参或沙棠使用', - yanjiadan_diamond:'偃甲蛋', - yanjiadan_diamond_info:'可以当作流风散、舒筋散或神火飞鸦使用', - yanjiadan_club:'偃甲蛋', - yanjiadan_club_info:'可以当作天女散花、六骰格或锦里针使用', - yanjiadan_spade:'偃甲蛋', - yanjiadan_spade_info:'可以当作飞镖、乾坤镖或龙须钩使用', - lingyan:'灵偃', - lingyan_bg:'偃', - lingyan_info:'出牌阶段限一次,你可以将一张点数与武将牌上的牌均不同的手牌置于武将牌上,然后获得与其花色对应的一枚偃甲蛋', - xunjian:'寻剑', - xunjian_info:'锁定技,每当你使用或打出一张牌,若牌堆中有同名牌,你有X的机率获得之,X为你的“灵偃”牌数/13', - xunjian_old_info:'觉醒技,结束阶段,若你武将牌上有13张牌,你失去技能灵偃并获得技能通天', - tongtian:'通天', - tongtian_info:'锁定技,在你使用或打出一张牌后,若敌方角色手中有同名牌,你随机获得其中一张', - xianju:'仙居', - xianju_info:'锁定技,奇数游戏轮次开始时,你获得潜行直到下一轮开始;偶数游戏轮次开始时,你随机获得一张机关牌', - xuanci:'旋刺', - xuanci_info:'出牌阶段限一次,你可以将一张梅花牌当作飞镖使用;锁定技,你使用飞镖无距离限制,你使用飞镖后对目标结算后视为对目标使用一张杀', - humeng:'湖梦', - humeng_sub:'偃甲谢衣', - humeng_info:'觉醒技,当你使用过4种不同的偃甲蛋后,你获得替身偃甲谢衣;觉醒技,当你进入濒死状态时,你弃置所有牌,摸四张牌,变身为初七并激活偃甲谢衣', - yange:'魇歌', - yange_info:'游戏开始时,你获得数量等于敌方角色数的“魇”标记;准备阶段开始时,你可以移去一枚“魇”标记并变为一名其他存活角色的复制,此回合结束后你变回原角色', - woxue:'卧雪', - woxue_info:'每当你于回合外使用或打出一张牌,你可以视为对当前回合角色使用【白霜】', - lingnu:'灵怒', - lingnu_info:'锁定技,每当你造成一点伤害,你随机获得一个与杀相关的技能,技能在你行动3回合后消失(同一时间最多拥有3个以此法获得的技能)', - zhenying:'振影', - zhenying_info:'每当你使用或打出一张非转化的黑色牌(装备或延时锦囊牌除外),你可以获得一张此牌的“替身”;你使用或打出“替身”牌后需弃置一张牌(没有则不弃);当前回合结束后,“替身”牌消失', - cihong:'刺鸿', - cihong_bg:'鸿', - cihong_info:'每三轮限一次,结束阶段,你可以指定一名其他角色并可以依次选择:1. 弃置一张红色牌;2. 失去一点体力;3. 将武将牌翻至背面;每选择一项,视为对目标使用一张杀', - lianjing:'莲境', - lianjing_info:'每两轮限一次,回合结束后,你可以选择至多2名其他角色,将其他角色移出游戏,然后你与所选的角色依次进行一个回合', - zuiji:'醉饮', - zuiji_info:'出牌阶段,你可以将一张手牌或装备牌当作酒使用', - manwu:'曼舞', - manwu_info:'在一名角色的结束阶段,若其手牌数为全场最少或之一,你可以令其摸一张牌', - xfanghua:'芳华', - xfanghua_info:'在你成为红色牌的目标后,你可以回复一点体力', - yunyin:'云音', - yunyin_info:'结束阶段,你可以弃置一张与本回合使用过的卡牌花色均不相同的手牌,视为使用一张基本牌或普通锦囊牌', - shishui:'逝水', - shishui_info:'锁定技,每当你使用一张红色牌,你令目标流失一点体力', - duhun:'渡魂', - duhun_info:'濒死阶段,你可以与一名体力值不超过你的体力上限的角色拼点,若你赢,你失去一点体力上限并与该角色交换体力值;若你没赢,你立即死亡', - duhun_info_alter:'濒死阶段,你可以与一名体力值不超过你的体力上限的角色拼点,若你赢,你失去一点体力上限并将体力值回复至与该角色相同;若你没赢,你立即死亡', - chizhen:'驰阵', - chizhen_info:'出牌阶段开始时,你可以摸X张牌并弃置X张牌,若你弃置了杀,可以视为使用一张决斗(X为你已损失的体力值且至少为1)', - xidie:'戏蝶', - xidie2:'戏蝶', - xidie_info:'准备阶段,若你的手牌数大于体力值,可以弃置至多X张牌,并于结束阶段摸等量的牌,X为你的体力值与手牌数之差且不超过3', - meihu:'魅狐', - meihu2:'魅狐', - meihu_info:'当你受到伤害后,可令伤害来源交给你一张手牌', - jianwu:'剑舞', - jianwu_info:'锁定技,攻击范围不含你的角色无法闪避你的杀', - meiying:'魅影', - meiying_info:'一名其他角色的回合结束时,若其未于此回合内使用过指定另一名角色为目标的牌,你可以弃置一张红色牌视为对其使用一张杀', - zuizhan:'乱斩', - zuizhan_info:'每当你使用一张杀,可以摸一张牌,然后此杀随机增加一个额外目标', - xlqianhuan:'千幻', - xlqianhuan_info:'回合结束后,若你已受伤,你可以回复一点体力并将武将牌翻面。若你的武将牌背面朝上,你不能使用卡牌,也不能成为卡牌的目标', - fumo:'伏魔', - fumo_info:'每当你受到一次伤害,可以弃置两张颜色相同的手牌并对伤害来源造成一点雷电伤害', - fanyin:'梵音', - fanyin_info:'结束阶段,你可以令一名角色复原武将牌并移除判定区内的牌;若其体力值是全场最少的之一,其回复一点体力', - mingkong:'明空', - mingkong_info:'锁定技,若你没有手牌,你受到的伤害-1,然后伤害来源摸一张牌', - qinglan:'晴岚', - qinglan_info:'每当有一名角色即将受到属性伤害,你可以弃置一张牌令其防止此伤害,然后伤害来源摸一张牌并回复一点体力', - yuehua:'月华', - yuehua_info:'每当你于回合外使用、打出或弃置红色牌,你可以摸一张牌', - xuelu:'血戮', - xuelu_info:'结束阶段,若你已受伤,你可以弃置一张红色牌并对一名其他角色造成一点火焰伤害;若你已损失体力值不少于3,改为造成两点火焰伤害', - xuelu_info_alter:'结束阶段,若你已受伤,你可以弃置一张红色牌并对一名其他角色造成一点火焰伤害', - fanshi:'反噬', - fanshi_info:'锁定技,弃牌阶段结束时,若你本回合内造成过伤害,你流失一点体力并摸一张牌', - shahun:'煞魂', - shahun2:'煞魂', - shahun_info:'限定技,濒死阶段,你可以复原武将牌,弃置所有牌并摸三张牌,然后将体力回复至1;若如此做,你失去技能【反噬】,获得技能【绝境】,并于两回合后立即死亡', - shahun_info_alter:'限定技,濒死阶段,你可以复原武将牌,弃置所有牌并摸三张牌,然后将体力回复至1;若如此做,你失去技能【反噬】,获得技能【绝境】,并于两回合后立即死亡', - - yanjia:'偃甲', - yanjia_info:'出牌阶段,你可以将两张非特殊装备牌合成为一张强化装备', - xiuhua:'袖花', - xiuhua_info:'每当一件其他角色的装备因被替换或弃置进入弃牌堆,你可以获得之', - liuying:'流影', - liuying_info:'每当你使用一张杀结算完毕后,你可以指定一名本回合未成为过你的杀的目标的角色,并亮出牌堆顶的一张牌,若为黑色,你对该角色使用一张杀', - boyun:'拨云', - boyun1:'拨云', - boyun2:'拨云', - boyun_info:'在你的回合内,你可以弃置一张装备牌,并展示牌堆顶的一张牌,若其为装备牌,你须将其交给任意一张角色并对其造成一点伤害,否则你摸一张牌', - jizhan:'疾战', - jizhan_info:'出牌阶段限一次,你可以将移动到任意一名角色的前一位,视为对其使用了一张不计入出杀次数的杀', - qianjun:'千军', - qianjun_info:'每当你使用一张杀,你可以弃置一张牌,令距离目标1以内的所有角色成为额外目标', - xuanning:'玄凝', - xuanning1:'玄凝', - xuanning2:'玄凝', - liuguang:'流光', - yangming:'养命', - yangming2:'养命', - xuanning_info:'出牌阶段,你可以弃置一基本牌,获得至多3个玄凝标记。当你受到伤害时,你失去一枚玄凝标记,伤害来源随机弃置一张牌', - liuguang_info:'准备阶段,若你有玄凝标记,你可以弃置一枚玄凝标记,选择至多三名角色依次令其选择一项:弃置一张牌,或受到一点伤害,并终止流光结算', - yangming_info:'出牌阶段,你可以弃置一张红色牌,并在下个出牌阶段结束时令距离1以内的任意名角色回复一点体力,在此之前不可再次发动', - zhaolu:'朝露', - jiehuo:'劫火', - yuling:'御灵', - yuling1:'御灵', - yuling2:'御灵', - yuling3:'御灵', - yuling4:'御灵', - zhaolu_info:'锁定技,每隔X回合,你流失一点体力上限,每当你受到一点伤害或有人死亡,视为减少两个回合,X为现存角色数且至多为5', - jiehuo_info:'限定技,出牌阶段,你可以对一名其他角色造成两点火焰伤害,然后死亡', - yuling_info:'锁定技,你没有摸牌和弃牌阶段,你的手牌数始终为5,你在一个出牌阶段最多使用X+2张牌,X为你的体力上限', - yuling_info_alter:'锁定技,你没有摸牌和弃牌阶段,你的手牌数始终为5,你在一个出牌阶段最多使用X+1张牌,X为你的体力上限', - }, - }; -}); +'use strict'; +game.import('character',function(lib,game,ui,get,ai,_status){ + return { + name:'gujian', + character:{ + gjqt_bailitusu:['male','shu',4,['xuelu','fanshi','shahun']], + gjqt_fengqingxue:['female','wu',3,['qinglan','yuehua','swd_wuxie']], + gjqt_xiangling:['female','wu',3,['xlqianhuan','meihu','xidie']], + gjqt_fanglansheng:['male','wu',3,['fanyin','mingkong','fumo']], + gjqt_yinqianshang:['male','qun',4,['zuiji','zuizhan']], + gjqt_hongyu:['female','shu',4,['jianwu','meiying']], + + gjqt_yuewuyi:['male','wei',4,['yanjia','xiuhua','liuying']], + gjqt_wenrenyu:['female','shu',4,['chizhen','dangping']], + gjqt_xiayize:['male','qun',3,['xuanning','liuguang','yangming']], + gjqt_aruan:['female','wu',3,['zhaolu','jiehuo','yuling']], + + gjqt_xunfang:['female','shu',3,['manwu','xfanghua']], + gjqt_ouyangshaogong:['male','shu',3,['yunyin','shishui','duhun']], + + gjqt_xieyi:['male','qun',3,['lingyan','xunjian','humeng']], + gjqt_yanjiaxieyi:['male','qun',2,['xianju'],['unseen']], + gjqt_chuqi:['male','qun',2,['xuanci'],['unseen']], + + // gjqt_xuange:['male','qun',4,['zhenlu','zhuixing']], + gjqt_beiluo:['male','qun',4,['lingnu','zhenying','cihong']], + gjqt_yunwuyue:['female','wei',3,['yange','woxue','lianjing']], + gjqt_cenying:['female','shu',3,['yunyou','xuanzhen','qingshu']], + // gjqt_changliu:['male','qun',4,['xiange']], + // gjqt_fenglishuang:['female','wei',3,[]], + // gjqt_nishang:[], + }, + characterIntro:{ + gjqt_bailitusu:'原名韩云溪,太子长琴半身。幼时经历灭族之灾,本来已死去,但因体内被封进了太子长琴一半魂魄而得以死而复生。身怀凶剑焚寂的煞气,在某次煞气发作中被紫胤真人所救,后拜入昆仑山天墉城,以“屠绝鬼气,苏醒人魂”之意更名为“百里屠苏”。', + gjqt_fengqingxue:'来自幽暗无边的地界,大哥为幽都娲皇神殿“十巫”之一的巫咸——风广陌。奉命寻兄而来到人间,邂逅百里屠苏,陷入了焚寂的宿命纠葛。后与百里屠苏相知相爱。终局,百里屠苏化为永世无法轮回的荒魂,她为寻找重生之法,放弃自己的轮回,换取下长久的寿命,在人间执着不悔地寻觅了九百年。最后,与重生后的屠苏回到桃花谷相守。', + gjqt_xiangling:'青丘之国国主九尾天狐与人界女子所生,半人半妖。由南疆紫榕林榕爷爷抚养,在山林间无拘无束长大,娇俏可爱。后来下江南寻找生母,却被抓进翻云寨,偶然被百里屠苏相救,为报答救命之恩,便一直跟随百里屠苏。在漫漫征途中成长起来,变得成熟懂事。得知方兰生对自己的感情,十分矛盾,后来终于错过。最后结局,启程前往青丘之国。', + gjqt_fanglansheng:'家住琴川的一介书生,家境殷实,母亲在家常住庵堂,吃斋念佛,父亲虽是琴川附近某间寺庙的住持,但却比商人还会敛财。跟随父亲学过降妖除鬼的佛家法术,并以此自傲。梦想能找一个娇小可爱,温柔美丽的女子共渡此生。对襄铃一见钟情。', + gjqt_yinqianshang:'原名风广陌,是幽都“十巫”之一的巫咸。奉女娲之命前往乌蒙灵谷增强焚寂封印。但因为欧阳少恭与雷严的争夺关系,他受到焚寂之力力量冲击,失去记忆,后为欧阳少恭所救。尹千觞为了报恩,跟随在百里屠苏等人身边监视。在这个过程中,他慢慢恢复过往的记忆。后为阻止欧阳少恭,随众人前往蓬莱决战。最后,对曾给予他一次重生的欧阳少恭以死相陪,一起归于火海。', + gjqt_hongyu:'上古庆枫族族人,紫胤真人的剑灵,宿体为古剑·红玉。在百里屠苏离开昆仑山天墉城后,奉命随行保护。终局之后,她依然回到了天墉城,陪伴在紫胤真人身边。', + + gjqt_yuewuyi:'成长于长安富商家庭。其养父曾是战功显赫的将军,退隐后从商,很快就富甲一方;其养母是精擅偃术的南疆天玄教偃女族传人,待无异一如己出般疼爱有加;无异的生父是捐毒大将兀火罗,亡母是一名中原女子,因此他具有一半胡人的血统。', + gjqt_wenrenyu:'出身于百草谷“天罡”部队。从小在军中生活的她见惯生死,拥有超越其年龄的沉着果敢,头脑冷静严谨,洞察力敏锐,性格大方爽快。', + gjqt_xiayize:'本为当朝圣元帝三子李焱,天资聪颖,勤奋好学,于道术一途颇具天赋。幼时体弱,因身世原因被太华山的诀微长老清和真人带走,从此过上了道家清修的生活。', + gjqt_aruan:'千年前,昭明崩裂损毁,剑心为神农所得,其将剑心植入用辟邪之骨所造之人,是为巫山神女。神女爱慕司幽,却始终得不到司幽回应,心绪起伏加速灵力散逸,不久后死去,司幽自责不已,失去踪迹。神女死后,剑心碎片落地生根,吸纳灵气变为露草,露草渐渐化为人形,形貌与巫山神女一样,而且保留了她零散的记忆,阿阮正是这些露草中的一个。当阿阮灵气耗尽就会重新变为露草,并失去人时的记忆,直到重新吸纳足够的灵力才能恢复人形。', + + gjqt_xunfang:'蓬莱国公主,美丽善良,为欧阳少恭前世妻子,太子长琴转世后,巽芳为寻找丈夫来到中原。蓬莱人寿命虽长却终有极限,终究躲不过容颜老去。当巽芳找到分离多年后的少恭时,自惭形秽,不愿相认,她希望少恭心中的自己永远都是青春貌美,于是化名“寂桐”守护在其身旁。', + gjqt_ouyangshaogong:'前身为太子长琴,今生只有一半仙灵,另一半仙灵被铸进焚寂之中,成为焚寂剑灵。他在漫长时光中经历太多悲欢离合,渐渐迷失自我。后与蓬莱国公主巽芳相爱,度过一段美好时光。之后蓬莱毁于天灾,他误以为巽芳已死,便不再压抑内心的疯狂与憎恨,为逆天改命不惜一切。', + + gjqt_xieyi:'偃术大师,流月城大祭司沈夜之徒。于偃术一途冠绝古今,其制造的偃甲精妙绝伦,为世人所称颂。男主角乐无异对其十分崇拜。曾任流月城破军祭司,精通偃术和法术,在一百年前寻找神剑昭明的西域之行中被沈夜捉拿回流月城,毁去记忆仅靠偃甲和蛊虫心脏跳动,并更名为初七,最终在巫山为抢救昭明剑心、保护乐无异被埋在坍塌的神女墓下。他以自己为原型制作,并放入自己部分记忆的偃甲人谢衣曾收乐无异为徒。', + + gjqt_beiluo:'身负辟邪王族之血的大妖,辟邪先王玄戈的孪生兄弟。北洛幼时流落人界,从此便在那里长大。他对自己的血脉并无认同之感,常年抑制妖力,希望以“人”的身份留在人间。后因先王玄戈的逝世,即位为辟邪王', + gjqt_yunwuyue:'本体是一只魇兽,该族喜独居,以人的梦境为食,以精神力为长,被称为“最接近魔的妖族”。云无月幼时居住在有熊城旁的白梦泽,自幼便与轩辕黄帝的大将缙云结识,对缙云有着一种依赖和仰慕,深受缙云的影响,对人族以及其他生灵有感情,不忍看到眼前的生灵遭到痛苦,会加以援手。', + gjqt_cenying:'出身于一个颇有底蕴的大家族,性格开朗随和、温柔坚韧、心如琉璃。岑缨受到开明长辈的影响,自小多思善学,年纪不大却已经加入了博物学会,有时跟随师长在外游历,探寻更广阔的天地。', + }, + card:{ + yanjiadan_heart:{ + type:'jiguan', + cardcolor:'heart', + fullskin:true, + derivation:'gjqt_xieyi', + enable:true, + notarget:true, + content:function(){ + 'step 0' + var choice=null; + var targets=game.filterPlayer(function(current){ + return get.attitude(player,current)>0; + }); + for(var i=0;i0; + })){ + choice='shatang'; + } + if(!choice){ + for(var i=0;i=0) return false; + if(current.hp==1&&eff<0) return true; + if(get.attitude(player,current)<0&&get.attitude(player,current.getNext()<0)&&get.attitude(player,current.getPrevious())<0){ + return true; + } + return false; + })){ + choice='shenhuofeiya'; + } + player.chooseVCardButton('选择一张牌视为使用之',['liufengsan','shujinsan','shenhuofeiya']).set('ai',function(button){ + if(button.link[2]==_status.event.choice) return 2; + return Math.random(); + }).set('choice',choice).set('filterButton',function(button){ + return _status.event.player.hasUseTarget(button.link[2]); + }); + 'step 1' + player.chooseUseTarget(true,result.links[0][2]); + }, + ai:{ + order:5, + result:{ + player:1 + } + } + }, + yanjiadan_club:{ + type:'jiguan', + cardcolor:'club', + fullskin:true, + derivation:'gjqt_xieyi', + enable:true, + notarget:true, + content:function(){ + 'step 0' + var choice='liutouge'; + player.chooseVCardButton('选择一张牌视为使用之',['bingpotong','liutouge','mianlijinzhen']).set('ai',function(button){ + if(button.link[2]==_status.event.choice) return 2; + return Math.random(); + }).set('choice',choice).set('filterButton',function(button){ + return _status.event.player.hasUseTarget(button.link[2]); + }); + 'step 1' + player.chooseUseTarget(true,result.links[0][2]); + }, + ai:{ + order:5, + result:{ + player:1 + } + } + }, + yanjiadan_spade:{ + type:'jiguan', + cardcolor:'spade', + fullskin:true, + derivation:'gjqt_xieyi', + enable:true, + notarget:true, + content:function(){ + 'step 0' + var choice=null; + if(player.getUseValue('longxugou')>0){ + choice='longxugou'; + } + else if(player.getUseValue('qiankunbiao')>0){ + choice='qiankunbiao'; + } + else if(player.getUseValue('feibiao')>0){ + choice='qiankunbiao'; + } + player.chooseVCardButton('选择一张牌视为使用之',['feibiao','qiankunbiao','longxugou']).set('ai',function(button){ + if(button.link[2]==_status.event.choice) return 2; + return Math.random(); + }).set('choice',choice).set('filterButton',function(button){ + return _status.event.player.hasUseTarget(button.link[2]); + }); + 'step 1' + player.chooseUseTarget(true,result.links[0][2]); + }, + ai:{ + order:5, + result:{ + player:function(player){ + if(player.getUseValue('feibiao')>0) return 1; + if(player.getUseValue('qiankunbiao')>0) return 1; + if(player.getUseValue('longxugou')>0) return 1; + return 0; + } + } + } + } + }, + skill:{ + qingshu:{ + ai:{ + threaten:1.4 + }, + trigger:{player:'phaseEnd'}, + direct:true, + filter:function(event,player){ + for(var i=0;iplayer.storage.yunyou.length; + }, + delay:0, + content:function(){ + var list=get.inpile('land'); + for(var i=0;iplayer.storage.lingyan.length/13) return false; + return get.cardPile2(event.card.name)?true:false; + }, + content:function(){ + var card=get.cardPile2(trigger.card.name); + if(card){ + player.gain(card,'gain2'); + } + } + }, + tongtian:{ + trigger:{player:['useCardAfter','respondAfter']}, + forced:true, + filter:function(event,player){ + var name=event.card.name; + var enemies=player.getEnemies(); + for(var i=0;i0) { + player.markSkill('yange'); + } + }, + filter:function(event,player){ + return player.storage.yange>0; + }, + content:function(){ + 'step 0' + player.chooseTarget(function(card,player,target){ + return player!=target; + },get.prompt2('yange')).set('pskill',trigger.skill).set('ai',function(target){ + if(_status.event.pskill) return 0; + var player=_status.event.player; + var nh=player.countCards('h'); + var nh2=target.countCards('h'); + var num=nh2-nh; + if(player.hp==1){ + return num; + } + if(get.threaten(target)>=1.5){ + if(num<2) return 0; + } + else{ + if(num<3) return 0; + } + return num+Math.sqrt(get.threaten(target)); + }); + 'step 1' + if(result.bool){ + player.storage.yange--; + if(player.storage.yange<=0){ + player.unmarkSkill('yange'); + } + else{ + player.updateMarks(); + } + var target=result.targets[0]; + player.logSkill('yange',target); + var clone=function(pos){ + var cloned=[]; + var cards=target.getCards(pos); + for(var i=0;i0){ + if(target.isTurnedOver()) return 2.5; + if(target.hp==1){ + if(nh==0) return 2; + if(nh==1) return 0.9; + if(ui.selected.targets.length) return 0.3; + } + else if(target.hp==2){ + if(nh==0) return 1.5; + if(nh==1) return 0.5; + if(ui.selected.targets.length) return 0.2; + } + else if(target.hp==3){ + if(nh==0) return 0.4; + if(nh==1) return 0.35; + if(ui.selected.targets.length) return 0.1; + } + if(!target.needsToDiscard(2)) return 0.2; + if(ui.selected.targets.length||!player.needsToDiscard(2)) return 0.05; + } + return 0; + }); + 'step 1' + if(result.bool){ + player.logSkill('lianjing',result.targets); + player.insertEvent('lianjing',lib.skill.lianjing.content_phase); + player.storage.lianjing_targets=result.targets.slice(0); + // player.storage.lianjing--; + // if(player.storage.lianjing<=0){ + // player.unmarkSkill('lianjing'); + // } + game.delay(); + } + }, + content_phase:function(){ + 'step 0' + event.list=[player].concat(player.storage.lianjing_targets); + event.exlist=[]; + event.list.sortBySeat(); + delete player.storage.lianjing_targets; + for(var i=0;i0; + })){ + if(player.hp>1||!player.isTurnedOver()){ + goon=true; + } + } + player.chooseTarget(get.prompt('cihong'),function(card,playe,target){ + return player.canUse('sha',target,false); + }).set('ai',function(target){ + if(!_status.event.goon) return false; + var player=_status.event.player; + if(get.attitude(player,target)>=0) return false; + if(target.hp>3) return false; + return get.effect(target,{name:'sha'},player,player); + }).set('goon',goon); + 'step 1' + if(result.bool){ + event.target=result.targets[0]; + player.logSkill('cihong',event.target); + } + else{ + event.finish(); + } + 'step 2' + if(event.target.isAlive()&&player.countCards('he',{color:'red'})){ + player.chooseToDiscard('he',{color:'red'},'是否弃置一张红色牌视为对'+get.translation(event.target)+'使用一张杀?').set('ai',function(card){ + return 8-get.value(card); + }) + } + else{ + event.goto(4); + } + 'step 3' + if(result.bool){ + player.useCard({name:'sha'},event.target); + } + 'step 4' + if(event.target.isAlive()){ + player.chooseBool('是否失去一点体力并视为对'+get.translation(event.target)+'使用一张杀?').set('choice', + player.hp>event.target.hp&&player.hp>1&&event.target.hp>0 + ); + } + else{ + event.finish(); + } + 'step 5' + if(result.bool){ + player.loseHp(); + player.useCard({name:'sha'},event.target); + } + 'step 6' + if(event.target.isAlive()&&!player.isTurnedOver()){ + player.chooseBool('是将武将牌翻至背面并视为对'+get.translation(event.target)+'使用一张杀?').set('choice', + event.target.hp==1 + ); + } + else{ + event.finish(); + } + 'step 7' + if(result.bool){ + player.turnOver(true); + player.useCard({name:'sha'},event.target); + } + }, + ai:{ + threaten:1.5 + } + }, + zhenying:{ + trigger:{player:['useCard','respond']}, + forced:true, + filter:function(event,player){ + if(get.is.converted(event)) return false; + if(event.card.zhenying_link) return false; + if(get.color(event.card)!='black') return false; + if(['delay','equip'].contains(get.type(event.card))) return false; + return true; + }, + content:function(){ + var fake=game.createCard(trigger.card); + fake.zhenying_link=true; + player.gain(fake,'draw')._triggered=null; + fake.classList.add('glow'); + fake._destroy='zhenying'; + fake._modUseful=function(){return 0.1}; + fake._modValue=function(){return 0.1}; + }, + group:['zhenying_discard','zhenying_lose'], + subSkill:{ + discard:{ + trigger:{player:['useCardAfter','respondAfter']}, + forced:true, + filter:function(event,player){ + if(get.is.converted(event)) return false; + if(!player.countCards('he')) return false; + if(event.card.zhenying_link) return true; + return false; + }, + popup:false, + content:function(){ + player.chooseToDiscard('he',true); + } + }, + lose:{ + trigger:{global:'phaseAfter'}, + silent:true, + content:function(){ + var hs=player.getCards('h',function(card){ + return card.zhenying_link?true:false; + }); + if(hs.length){ + player.lose(hs)._triggered=null; + } + } + } + } + }, + lingnu:{ + trigger:{source:'damageEnd'}, + forced:true, + init:function(player){ + player.storage.lingnu={}; + }, + ai:{ + threaten:1.3 + }, + filter:function(event,player){ + var num=0; + for(var i in player.storage.lingnu){ + num++; + if(num>=3) return false; + } + return event.num>0; + }, + content:function(){ + var check=function(list){ + for(var i=0;i=3) break; + if(list.length){ + var skill=list.randomGet(); + player.addAdditionalSkill('lingnu',skill,true); + player.storage.lingnu[skill]=3; + player.popup(skill); + game.log(player,'获得了技能','【'+get.translation(skill)+'】'); + player.markSkill('lingnu'); + num++; + } + } + }, + intro:{ + content:function(storage){ + var str='
            '; + for(var i in storage){ + str+='
          • '+get.translation(i)+':剩余'+storage[i]+'回合'; + } + str+='
          ' + return str; + }, + markcount:function(storage){ + var num=0; + for(var i in storage){ + num++; + } + return num; + } + }, + group:'lingnu_remove', + subSkill:{ + remove:{ + trigger:{player:'phaseAfter'}, + silent:true, + content:function(){ + var clear=true; + for(var i in player.storage.lingnu){ + player.storage.lingnu[i]--; + if(player.storage.lingnu[i]<=0){ + delete player.storage.lingnu[i]; + player.removeAdditionalSkill('lingnu',i); + } + else{ + clear=false; + } + } + if(clear){ + player.unmarkSkill('lingnu'); + } + else{ + player.updateMarks(); + } + } + } + } + }, + zuiji:{ + enable:'phaseUse', + filterCard:true, + position:'he', + viewAs:{name:'jiu'}, + viewAsFilter:function(player){ + if(!player.countCards('he')) return false; + }, + prompt:'将一张手牌或装备牌当酒使用', + check:function(card){ + return 5-get.value(card); + }, + ai:{ + threaten:1.2 + } + }, + manwu:{ + trigger:{global:'phaseEnd'}, + check:function(event,player){ + return get.attitude(player,event.player)>0; + }, + filter:function(event,player){ + return event.player.isMinHandcard(); + }, + logTarget:'player', + content:function(){ + trigger.player.draw(); + }, + ai:{ + expose:0.1 + } + }, + xfanghua:{ + trigger:{target:'useCardToBegin'}, + priority:-1, + filter:function(event,player){ + return get.color(event.card)=='red'&&player.isDamaged(); + }, + frequent:true, + content:function(){ + player.recover(); + }, + ai:{ + effect:{ + target:function(card,player,target,current){ + if(get.color(card)=='red'&&target.isDamaged()) return [1,1]; + } + } + } + }, + duhun:{ + enable:'chooseToUse', + filter:function(event,player){ + if(event.type!='dying') return false; + if(player!=event.dying) return false; + if(player.maxHp<=1) return false; + if(player.countCards('h')==0) return false; + return true; + }, + // alter:true, + filterTarget:function(card,player,target){ + return target!=player&&target.countCards('h')>0&&target.hp>0&&target.hp<=player.maxHp; + }, + content:function(){ + 'step 0' + player.chooseToCompare(target); + 'step 1' + if(!result.bool){ + player.die(); + event.finish(); + } + else{ + event.num=target.hp-player.hp; + player.loseMaxHp(); + } + 'step 2' + player.changeHp(event.num); + if(get.is.altered('duhun')){ + event.finish(); + } + 'step 3' + event.target.changeHp(-event.num); + 'step 4' + if(event.target.hp<=0){ + event.target.dying({source:player}); + } + }, + ai:{ + order:1, + skillTagFilter:function(player){ + if(player.maxHp<=1) return false; + if(player.hp>0) return false; + if(player.countCards('h')==0) return false; + }, + save:true, + result:{ + target:-1, + player:1 + }, + threaten:2 + }, + }, + yunyin:{ + trigger:{player:'phaseEnd'}, + direct:true, + subSkill:{ + count:{ + trigger:{player:'useCard'}, + silent:true, + filter:function(event,player){ + return _status.currentPhase==player; + }, + content:function(){ + if(!player.storage.yunyin){ + player.storage.yunyin=[]; + } + var suit=get.suit(trigger.card); + if(suit){ + player.storage.yunyin.add(suit); + } + } + }, + set:{ + trigger:{player:'phaseAfter'}, + silent:true, + content:function(){ + delete player.storage.yunyin; + } + } + }, + filter:function(event,player){ + if(!player.storage.yunyin) return true; + var hs=player.getCards('h'); + for(var i=0;i0){ + taoyuan++; + } + else if(eff1<0){ + taoyuan--; + } + if(eff2>0){ + nanman++; + } + else if(eff2<0){ + nanman--; + } + } + player.chooseButton(dialog).ai=function(button){ + var name=button.link[2]; + if(Math.max(taoyuan,nanman)>1){ + if(taoyuan>nanman) return name=='taoyuan'?1:0; + return name=='nanman'?1:0; + } + if(player.countCards('h')=2){ + return name=='wuzhong'?1:0; + } + if(player.hp=2) return true; + } + } + }, + filterCard:function(card){ + if(ui.selected.cards.length&&card.name==ui.selected.cards[0].name) return false; + var info=get.info(card); + return info.type=='equip'&&!info.nomod&&!info.unique&&lib.inpile.contains(card.name); + }, + selectCard:2, + position:'he', + check:function(card){ + return get.value(card); + }, + content:function(){ + var name=cards[0].name+'_'+cards[1].name; + var info1=get.info(cards[0]),info2=get.info(cards[1]); + if(!lib.card[name]){ + var info={ + enable:true, + type:'equip', + subtype:get.subtype(cards[0]), + vanish:true, + cardimage:info1.cardimage||cards[0].name, + filterTarget:function(card,player,target){ + return target==player; + }, + selectTarget:-1, + modTarget:true, + content:lib.element.content.equipCard, + legend:true, + source:[cards[0].name,cards[1].name], + onEquip:[], + onLose:[], + skills:[], + distance:{}, + ai:{ + order:8.9, + equipValue:10, + useful:2.5, + value:function(card,player){ + var value=0; + var info=get.info(card); + var current=player.getEquip(info.subtype); + if(current&&card!=current){ + value=get.value(current,player); + } + var equipValue=info.ai.equipValue||info.ai.basic.equipValue; + if(typeof equipValue=='function') return equipValue(card,player)-value; + return equipValue-value; + }, + result:{ + target:function(player,target){ + return get.equipResult(player,target,name); + } + } + } + } + for(var i in info1.distance){ + info.distance[i]=info1.distance[i]; + } + for(var i in info2.distance){ + if(typeof info.distance[i]=='number'){ + info.distance[i]+=info2.distance[i]; + } + else{ + info.distance[i]=info2.distance[i]; + } + } + if(info1.skills){ + info.skills=info.skills.concat(info1.skills); + } + if(info2.skills){ + info.skills=info.skills.concat(info2.skills); + } + if(info1.onEquip){ + if(Array.isArray(info1.onEquip)){ + info.onEquip=info.onEquip.concat(info1.onEquip); + } + else{ + info.onEquip.push(info1.onEquip); + } + } + if(info2.onEquip){ + if(Array.isArray(info2.onEquip)){ + info.onEquip=info.onEquip.concat(info2.onEquip); + } + else{ + info.onEquip.push(info2.onEquip); + } + } + if(info1.onLose){ + if(Array.isArray(info1.onLose)){ + info.onLose=info.onLose.concat(info1.onLose); + } + else{ + info.onLose.push(info1.onLose); + } + } + if(info2.onLose){ + if(Array.isArray(info2.onLose)){ + info.onLose=info.onLose.concat(info2.onLose); + } + else{ + info.onLose.push(info2.onLose); + } + } + if(info.onEquip.length==0) delete info.onEquip; + if(info.onLose.length==0) delete info.onLose; + lib.card[name]=info; + lib.translate[name]=get.translation(cards[0].name,'skill')+get.translation(cards[1].name,'skill'); + var str=lib.translate[cards[0].name+'_info']; + if(str[str.length-1]=='.'||str[str.length-1]=='。'){ + str=str.slice(0,str.length-1); + } + lib.translate[name+'_info']=str+';'+lib.translate[cards[1].name+'_info']; + try{ + game.addVideo('newcard',null,{ + name:name, + translate:lib.translate[name], + info:lib.translate[name+'_info'], + card:cards[0].name, + legend:true, + }); + } + catch(e){ + console.log(e); + } + } + player.gain(game.createCard({name:name,suit:cards[0].suit,number:cards[0].number}),'gain2'); + }, + ai:{ + order:9.5, + result:{ + player:1 + } + } + }, + meiying:{ + global:'meiying2', + globalSilent:true, + trigger:{global:'phaseEnd'}, + filter:function(event,player){ + return event.player!=player&&!event.player.tempSkills.meiying3&&event.player.isAlive()&&player.countCards('he',{color:'red'})>0; + }, + direct:true, + content:function(){ + "step 0" + var next=player.chooseToDiscard('he','魅影:是否弃置一张红色牌视为对'+get.translation(trigger.player)+'使用一张杀?'); + next.logSkill=['meiying',trigger.player]; + var eff=get.effect(trigger.player,{name:'sha'},player,player); + next.ai=function(card){ + if(eff>0){ + return 7-get.value(card); + } + return 0; + } + "step 1" + if(result.bool){ + player.useCard({name:'sha'},trigger.player).animate=false; + } + }, + ai:{ + expose:0.1 + } + }, + meiying2:{ + trigger:{player:'useCard'}, + filter:function(event,player){ + return _status.currentPhase==player&&event.targets&&(event.targets.length>1||event.targets[0]!=player); + }, + forced:true, + popup:false, + content:function(){ + player.addTempSkill('meiying3'); + } + }, + meiying3:{}, + jianwu:{ + trigger:{player:'shaBegin'}, + forced:true, + filter:function(event,player){ + return get.distance(event.target,player,'attack')>1; + }, + content:function(){ + trigger.directHit=true; + } + }, + zuizhan:{ + trigger:{player:'useCard'}, + popup:false, + filter:function(event,player){ + if(event.card.name!='sha') return false; + return game.hasPlayer(function(current){ + return (event.targets.contains(current)==false&¤t!=player&& + lib.filter.targetEnabled(event.card,player,current)) + }); + }, + content:function(){ + var list=game.filterPlayer(function(current){ + return (trigger.targets.contains(current)==false&¤t!=player&& + lib.filter.targetEnabled(trigger.card,player,current)) + }); + if(list.length){ + event.target=list.randomGet(); + player.line(event.target,'green'); + game.log(event.target,'被追加为额外目标'); + trigger.targets.push(event.target); + player.draw(); + } + } + }, + xidie:{ + trigger:{player:'phaseBegin'}, + direct:true, + filter:function(event,player){ + return player.countCards('h')>player.hp; + }, + content:function(){ + "step 0" + var next=player.chooseToDiscard(get.prompt('xidie'),[1,Math.min(3,player.countCards('h')-player.hp)]); + next.ai=function(card){ + return 6-get.value(card); + } + next.logSkill='xidie'; + "step 1" + if(result.bool){ + player.storage.xidie=result.cards.length; + } + }, + group:'xidie2' + }, + xidie2:{ + trigger:{player:'phaseEnd'}, + forced:true, + filter:function(event,player){ + return player.storage.xidie>0; + }, + content:function(){ + player.draw(player.storage.xidie); + player.storage.xidie=0; + } + }, + meihu:{ + trigger:{player:'damageEnd'}, + check:function(event,player){ + return get.attitude(player,event.source)<4; + }, + filter:function(event,player){ + return event.source&&event.source!=player&&event.source.countCards('h')>0; + }, + logTarget:'source', + content:function(){ + "step 0" + trigger.source.chooseCard('交给'+get.translation(player)+'一张手牌',true).ai=function(card){ + return -get.value(card); + }; + "step 1" + if(result.bool){ + player.gain(result.cards[0],trigger.source); + trigger.source.$give(1,player); + } + }, + ai:{ + maixie_defend:true, + effect:{ + target:function(card,player,target){ + if(get.tag(card,'damage')){ + if(player.hasSkillTag('jueqing',false,target)) return [1,-1.5]; + return [1,0,0,-0.5]; + } + } + } + } + }, + xlqianhuan:{ + trigger:{player:'phaseAfter'}, + check:function(event,player){ + return player.hp==1||player.isTurnedOver(); + }, + filter:function(event,player){ + return player.hp1||player.countCards('h',{color:'black'})>1; + }, + direct:true, + content:function(){ + "step 0" + var next=player.chooseToDiscard(get.prompt('fumo',trigger.source),2,function(card){ + if(get.damageEffect(trigger.source,player,player,'thunder')<=0) return 0; + if(ui.selected.cards.length){ + return get.color(card)==get.color(ui.selected.cards[0]); + } + return player.countCards('h',{color:get.color(card)})>1; + }).set('complexCard',true); + next.ai=function(card){ + if(get.damageEffect(trigger.source,player,player,'thunder')>0){ + return 8-get.value(card); + } + return 0; + }; + next.logSkill=['fumo',trigger.source,'thunder']; + "step 1" + if(result.bool){ + trigger.source.damage('thunder'); + } + }, + ai:{ + maixie_defend:true, + threaten:0.8 + } + }, + fanyin:{ + trigger:{player:'phaseEnd'}, + direct:true, + content:function(){ + "step 0" + player.chooseTarget(get.prompt('fanyin'),function(card,player,target){ + // if(player==target) return false; + if(target.isLinked()) return true; + if(target.isTurnedOver()) return true; + if(target.countCards('j')) return true; + if(target.isMinHp()&&target.isDamaged()) return true; + return false; + }).ai=function(target){ + var num=0; + var att=get.attitude(player,target); + if(att>0){ + if(target.isMinHp()){ + num+=5; + } + if(target.isTurnedOver()){ + num+=5; + } + if(target.countCards('j')){ + num+=2; + } + if(target.isLinked()){ + num++; + } + if(num>0){ + return num+att; + } + } + return num; + } + "step 1" + if(result.bool){ + event.target=result.targets[0]; + player.logSkill('fanyin',event.target); + } + else{ + event.finish(); + } + "step 2" + if(event.target.isLinked()){ + event.target.link(); + } + "step 3" + if(event.target.isTurnedOver()){ + event.target.turnOver(); + } + "step 4" + var cards=event.target.getCards('j'); + if(cards.length){ + event.target.discard(cards); + } + "step 5" + if(event.target.isMinHp()){ + event.target.recover(); + } + }, + ai:{ + expose:0.2, + threaten:1.3 + } + }, + mingkong:{ + trigger:{player:'damageBegin'}, + forced:true, + filter:function(event,player){ + return player.countCards('h')==0&&event.num>=1; + }, + content:function(){ + if(trigger.num>=1){ + trigger.num--; + } + if(trigger.source){ + trigger.source.storage.mingkong=true; + trigger.source.addTempSkill('mingkong2'); + } + }, + ai:{ + effect:{ + target:function(card,player,target){ + if(get.tag(card,'damage')&&target.countCards('h')==0){ + if(player.hasSkillTag('jueqing',false,target)) return; + return 0.1; + } + } + } + }, + }, + mingkong2:{ + trigger:{source:['damageEnd','damageZero']}, + forced:true, + popup:false, + audio:false, + vanish:true, + filter:function(event,player){ + return player.storage.mingkong?true:false; + }, + content:function(){ + player.draw(); + player.storage.mingkong=false; + player.removeSkill('mingkong2'); + } + }, + yuehua:{ + trigger:{player:['useCardAfter','respondAfter','discardAfter']}, + frequent:true, + filter:function(event,player){ + if(player==_status.currentPhase) return false; + if(event.cards){ + for(var i=0;i0; + }, + direct:true, + priority:-5, + content:function(){ + "step 0" + var next=player.chooseToDiscard(get.prompt('qinglan',trigger.player),'he'); + next.logSkill=['qinglan',trigger.player]; + next.ai=function(card){ + if(trigger.num>1||!trigger.source){ + if(get.attitude(player,trigger.player)>0){ + return 9-get.value(card); + } + return -1; + } + else if(get.attitude(player,trigger.player)>0){ + if(trigger.player.hp==1){ + return 8-get.value(card); + } + if(trigger.source.hp==trigger.source.maxHp){ + return 6-get.value(card); + } + } + else if(get.attitude(player,trigger.source)>0&& + trigger.source.hp1){ + if(get.color(card)=='red') return 5-get.value(card); + } + return -1; + } + "step 1" + if(result.bool){ + trigger.cancel(); + if(trigger.source){ + trigger.source.recover(); + } + } + else{ + event.finish(); + } + "step 2" + if(trigger.source){ + trigger.source.draw(); + } + }, + ai:{ + expose:0.1 + } + }, + fanshi:{ + trigger:{player:'phaseDiscardAfter'}, + forced:true, + filter:function(event,player){ + return player.getStat('damage')>0; + }, + check:function(event,player){ + return player.hp==player.maxHp; + }, + content:function(){ + "step 0" + player.loseHp(); + "step 1" + player.draw(); + } + }, + xuelu:{ + unique:true, + trigger:{player:'phaseEnd'}, + direct:true, + filter:function(event,player){ + return player.maxHp>player.hp&&player.countCards('he',{color:'red'})>0; + }, + // alter:true, + content:function(){ + "step 0" + player.chooseCardTarget({ + position:'he', + filterTarget:function(card,player,target){ + return player!=target; + }, + filterCard:function(card,player){ + return get.color(card)=='red'&&lib.filter.cardDiscardable(card,player); + }, + ai1:function(card){ + return 9-get.value(card); + }, + ai2:function(target){ + return get.damageEffect(target,player,player,'fire'); + }, + prompt:get.prompt('xuelu') + }); + "step 1" + if(result.bool){ + event.target=result.targets[0]; + player.logSkill('xuelu',event.target,'fire'); + if(get.is.altered('xuelu')){ + event.num=1; + } + else{ + event.num=Math.min(2,Math.ceil((player.maxHp-player.hp)/2)); + } + player.discard(result.cards); + } + else{ + event.finish(); + } + "step 2" + if(event.target){ + event.target.damage(event.num,'fire'); + } + }, + ai:{ + maixie:true, + expose:0.2, + threaten:function(player,target){ + if(target.hp==1) return 3; + if(target.hp==2) return 1.5; + return 0.5; + }, + effect:{ + target:function(card,player,target){ + if(!target.hasFriend()) return; + if(get.tag(card,'damage')){ + if(target.hp==target.maxHp) return [0,1]; + } + if(get.tag(card,'recover')&&player.hp>=player.maxHp-1) return [0,0]; + } + } + } + }, + xiuhua_old:{ + changeSeat:true, + trigger:{player:'shaHit'}, + filter:function(event,player){ + return event.target!=player.previous; + }, + content:function(){ + game.swapSeat(trigger.target,player,true,true); + } + }, + shahun:{ + enable:'chooseToUse', + skillAnimation:true, + animationColor:'fire', + derivation:'juejing', + // alter:true, + filter:function(event,player){ + return !player.storage.shahun&&player.hp<=0; + }, + content:function(){ + "step 0" + var cards=player.getCards('hej'); + player.discard(cards); + "step 1" + if(player.isLinked()) player.link(); + "step 2" + if(player.isTurnedOver()) player.turnOver(); + "step 3" + player.draw(3); + "step 4" + player.recover(1-player.hp); + player.removeSkill('fanshi'); + player.addSkill('juejing'); + player.storage.shahun=2; + player.markSkill('shahun'); + game.addVideo('storage',player,['shahun',player.storage.shahun]); + }, + group:'shahun2', + intro:{ + content:'turn' + }, + ai:{ + save:true, + skillTagFilter:function(player){ + if(player.storage.shahun) return false; + if(player.hp>0) return false; + }, + result:{ + player:3 + } + } + }, + shahun2:{ + trigger:{player:'phaseAfter'}, + forced:true, + filter:function(event,player){ + return player.storage.shahun?true:false; + }, + content:function(){ + if(player.storage.shahun>1){ + player.storage.shahun--; + game.addVideo('storage',player,['shahun',player.storage.shahun]); + } + else{ + player.die(); + } + } + }, + yanjia_old:{ + enable:'chooseToUse', + filter:function(event,player){ + return player.countCards('he',{type:'equip'})>0; + }, + filterCard:function(card){ + return get.type(card)=='equip'; + }, + position:'he', + viewAs:{name:'wuzhong'}, + prompt:'将一张装备牌当无中生有使用', + check:function(card){ + var player=_status.currentPhase; + if(player.countCards('he',{subtype:get.subtype(card)})>1){ + return 11-get.equipValue(card); + } + if(player.countCards('h')0&&player.storage.xuanning!=3; + }, + filterCard:function(card){ + return get.type(card)=='basic'; + }, + check:function(card){ + return 7-get.useful(card); + }, + content:function(){ + player.storage.xuanning=3; + player.markSkill('xuanning'); + game.addVideo('storage',player,['xuanning',player.storage.xuanning]); + }, + ai:{ + result:{ + player:function(player){ + var num=player.countCards('h'); + if(num>player.hp+1) return 1; + if(player.storage.xuanning>=2) return 0; + if(num>player.hp) return 1 + if(player.storage.xuanning>=1) return 0; + return 1; + }, + }, + order:5 + } + }, + xuanning2:{ + trigger:{player:'damageEnd'}, + forced:true, + filter:function(event,player){ + if(player.storage.xuanning){ + return (event.source&&event.source.countCards('he')>0); + } + return false; + }, + logTarget:'source', + content:function(){ + var he=trigger.source.getCards('he'); + if(he.length){ + trigger.source.discard(he.randomGet()); + } + player.storage.xuanning--; + if(!player.storage.xuanning){ + player.unmarkSkill('xuanning'); + } + game.addVideo('storage',player,['xuanning',player.storage.xuanning]); + }, + ai:{ + maixie_defend:true, + } + }, + liuguang:{ + trigger:{player:'phaseBegin'}, + direct:true, + filter:function(event,player){ + if(player.storage.xuanning) return true; + return false; + }, + content:function(){ + "step 0" + player.chooseTarget(function(card,player,target){ + return player!=target; + },get.prompt('liuguang'),[1,3]).ai=function(target){ + return get.damageEffect(target,player,player); + } + "step 1" + if(result.bool){ + player.storage.xuanning--; + if(!player.storage.xuanning){ + player.unmarkSkill('xuanning'); + } + event.targets=result.targets.slice(0); + event.targets.sort(lib.sort.seat); + player.logSkill('liuguang',result.targets); + game.addVideo('storage',player,['xuanning',player.storage.xuanning]); + } + else{ + event.finish(); + } + "step 2" + if(event.targets.length){ + var target=event.targets.shift(); + var next=target.chooseToDiscard('流光:弃置一张牌或受到一点伤害','he'); + next.ai=function(card){ + if(get.damageEffect(_status.event.player,player,_status.event.player)>=0) return -1; + if(_status.event.player.hp==1) return 9-get.value(card); + return 8-get.value(card); + }; + next.autochoose=function(){ + return this.player.countCards('he')==0; + }; + event.current=target; + } + else{ + event.finish(); + } + "step 3" + if(result.bool&&result.cards&&result.cards.length){ + event.goto(2); + } + else{ + event.current.damage(); + } + }, + ai:{ + expose:0.2, + threaten:1.3 + } + }, + yangming:{ + enable:'phaseUse', + filter:function(event,player){ + if(player.storage.yangming2>0) return false; + return player.countCards('h',{color:'red'})>0; + }, + filterCard:function(card){ + return get.color(card)=='red'; + }, + content:function(){ + player.storage.yangming2=2; + player.addSkill('yangming2'); + game.addVideo('storage',player,['yangming2',player.storage.yangming2]); + }, + check:function(card){ + return 6-get.value(card); + }, + ai:{ + result:{ + player:function(player){ + if(player.countCards('h')<=player.hp&&player.hp==player.maxHp){ + return 0; + } + return 1; + } + }, + order:6, + threaten:1.3 + } + }, + yangming2:{ + trigger:{player:'phaseUseEnd'}, + direct:true, + mark:true, + content:function(){ + "step 0" + player.storage.yangming2--; + game.addVideo('storage',player,['yangming2',player.storage.yangming2]); + if(player.storage.yangming2>0){ + event.finish(); + } + else{ + player.removeSkill('yangming2'); + var num=game.countPlayer(function(current){ + return get.distance(player,current)<=1&¤t.isDamaged(); + }); + if(num==0){ + event.finish(); + } + else{ + player.chooseTarget(function(card,player,target){ + return get.distance(player,target)<=1&&target.hp1) return false; + if(target.hp>2) return false; + if(get.attitude(player,target)>=0) return false; + return get.damageEffect(target,player,player,'fire'); + } + } + } + }, + jiehuo_old:{ + unique:true, + forbid:['infinity'], + skillAnimation:true, + animationColor:'fire', + init:function(player){ + player.storage.jiehuo=false; + }, + enable:'phaseUse', + filter:function(event,player){ + //if(player.maxHp<=1) return false; + return !player.storage.jiehuo + }, + intro:{ + content:'limited' + }, + // mark:true, + line:'fire', + filterTarget:function(card,player,target){ + return player!=target; + }, + selectTarget:-1, + content:function(){ + if(!player.storage.jiehuo2){ + player.storage.jiehuo2=player.maxHp; + player.addSkill('jiehuo2'); + } + player.storage.jiehuo=true; + target.damage(Math.min(target.hp,player.storage.jiehuo2),'fire'); + } + }, + jiehuo2:{ + trigger:{player:'phaseUseEnd'}, + forced:true, + popup:false, + content:function(){ + player.die(); + } + }, + yuling:{ + unique:true, + locked:true, + group:['yuling1','yuling2','yuling3','yuling4','yuling5','yuling6'], + intro:{ + content:'time' + }, + // alter:true, + ai:{ + noh:true, + threaten:0.8, + effect:{ + target:function(card,player,target){ + if(card.name=='bingliang') return 0; + if(card.name=='lebu') return 1.5; + if(card.name=='guohe'){ + if(!target.countCards('e')) return 0; + return 0.5; + } + if(card.name=='liuxinghuoyu') return 0; + } + } + } + }, + yuling1:{ + trigger:{player:['phaseDrawBefore','phaseDiscardBefore']}, + priority:10, + forced:true, + popup:false, + check:function(){ + return false; + }, + content:function(){ + trigger.cancel(); + } + }, + yuling2:{ + trigger:{player:['loseEnd','drawEnd'],global:'gameDrawAfter'}, + check:function(event,player){ + return player.countCards('h')<2; + }, + priority:10, + forced:true, + filter:function(event,player){ + return player.countCards('h')<5; + }, + content:function(){ + player.draw(5-player.countCards('h')); + } + }, + yuling3:{ + trigger:{player:'gainEnd'}, + priority:10, + forced:true, + filter:function(event,player){ + return player.countCards('h')>5; + }, + check:function(event,player){ + return player.countCards('h')<2; + }, + content:function(){ + player.chooseToDiscard(true,player.countCards('h')-5); + } + }, + yuling4:{ + mod:{ + cardEnabled:function(card,player){ + if(_status.currentPhase!=player) return; + var num=2; + if(get.is.altered('yuling')) num=1; + if(player.countUsed()>=player.maxHp+num) return false; + } + } + }, + yuling5:{ + trigger:{player:['useCardAfter','phaseBegin']}, + silent:true, + content:function(){ + player.storage.yuling=player.maxHp+2-player.countUsed(); + } + }, + yuling6:{ + trigger:{player:'phaseAfter'}, + silent:true, + content:function(){ + delete player.storage.yuling; + } + }, + }, + translate:{ + gjqt_bailitusu:'百里屠苏', + gjqt_fengqingxue:'风晴雪', + gjqt_fanglansheng:'方兰生', + gjqt_xiangling:'襄铃', + gjqt_yinqianshang:'尹千觞', + gjqt_hongyu:'红玉', + gjqt_ouyangshaogong:'欧阳少恭', + gjqt_xunfang:'巽芳', + + gjqt_yuewuyi:'乐无异', + gjqt_wenrenyu:'闻人羽', + gjqt_xiayize:'夏夷则', + gjqt_aruan:'阿阮', + gjqt_xieyi:'谢衣', + gjqt_yanjiaxieyi:'偃甲谢衣', + gjqt_chuqi:'初七', + + gjqt_beiluo:'北洛', + gjqt_yunwuyue:'云无月', + gjqt_cenying:'岑缨', + + yunyou:'云游', + yunyou_info:'每两轮限一次,出牌阶段,你可以发现一张地图牌本局未使用过的地图牌并使用之', + xuanzhen:'玄阵', + // xuanzhen_bg:'阵', + xuanzhen_info:'每轮限一次,当你成为一名其他角色的卡牌惟一目标时,你可以发现一张牌代替此牌', + qingshu:'青书', + qingshu_info:'结束阶段,你可以令一名角色永久获得一个你使用过且不是当前地图的地图牌效果(每个地图最多发动一次)', + yanjiadan_heart:'偃甲蛋', + yanjiadan_heart_info:'可以当作紫阳丹、玉女元参或沙棠使用', + yanjiadan_diamond:'偃甲蛋', + yanjiadan_diamond_info:'可以当作流风散、舒筋散或神火飞鸦使用', + yanjiadan_club:'偃甲蛋', + yanjiadan_club_info:'可以当作天女散花、六骰格或锦里针使用', + yanjiadan_spade:'偃甲蛋', + yanjiadan_spade_info:'可以当作飞镖、乾坤镖或龙须钩使用', + lingyan:'灵偃', + lingyan_bg:'偃', + lingyan_info:'出牌阶段限一次,你可以将一张点数与武将牌上的牌均不同的手牌置于武将牌上,然后获得与其花色对应的一枚偃甲蛋', + xunjian:'寻剑', + xunjian_info:'锁定技,每当你使用或打出一张牌,若牌堆中有同名牌,你有X的机率获得之,X为你的“灵偃”牌数/13', + xunjian_old_info:'觉醒技,结束阶段,若你武将牌上有13张牌,你失去技能灵偃并获得技能通天', + tongtian:'通天', + tongtian_info:'锁定技,在你使用或打出一张牌后,若敌方角色手中有同名牌,你随机获得其中一张', + xianju:'仙居', + xianju_info:'锁定技,奇数游戏轮次开始时,你获得潜行直到下一轮开始;偶数游戏轮次开始时,你随机获得一张机关牌', + xuanci:'旋刺', + xuanci_info:'出牌阶段限一次,你可以将一张梅花牌当作飞镖使用;锁定技,你使用飞镖无距离限制,你使用飞镖后对目标结算后视为对目标使用一张杀', + humeng:'湖梦', + humeng_sub:'偃甲谢衣', + humeng_info:'觉醒技,当你使用过4种不同的偃甲蛋后,你获得替身偃甲谢衣;觉醒技,当你进入濒死状态时,你弃置所有牌,摸四张牌,变身为初七并激活偃甲谢衣', + yange:'魇歌', + yange_info:'游戏开始时,你获得数量等于敌方角色数的“魇”标记;准备阶段开始时,你可以移去一枚“魇”标记并变为一名其他存活角色的复制,此回合结束后你变回原角色', + woxue:'卧雪', + woxue_info:'每当你于回合外使用或打出一张牌,你可以视为对当前回合角色使用【白霜】', + lingnu:'灵怒', + lingnu_info:'锁定技,每当你造成一点伤害,你随机获得一个与杀相关的技能,技能在你行动3回合后消失(同一时间最多拥有3个以此法获得的技能)', + zhenying:'振影', + zhenying_info:'每当你使用或打出一张非转化的黑色牌(装备或延时锦囊牌除外),你可以获得一张此牌的“替身”;你使用或打出“替身”牌后需弃置一张牌(没有则不弃);当前回合结束后,“替身”牌消失', + cihong:'刺鸿', + cihong_bg:'鸿', + cihong_info:'每三轮限一次,结束阶段,你可以指定一名其他角色并可以依次选择:1. 弃置一张红色牌;2. 失去一点体力;3. 将武将牌翻至背面;每选择一项,视为对目标使用一张杀', + lianjing:'莲境', + lianjing_info:'每两轮限一次,回合结束后,你可以选择至多2名其他角色,将其他角色移出游戏,然后你与所选的角色依次进行一个回合', + zuiji:'醉饮', + zuiji_info:'出牌阶段,你可以将一张手牌或装备牌当作酒使用', + manwu:'曼舞', + manwu_info:'在一名角色的结束阶段,若其手牌数为全场最少或之一,你可以令其摸一张牌', + xfanghua:'芳华', + xfanghua_info:'在你成为红色牌的目标后,你可以回复一点体力', + yunyin:'云音', + yunyin_info:'结束阶段,你可以弃置一张与本回合使用过的卡牌花色均不相同的手牌,视为使用一张基本牌或普通锦囊牌', + shishui:'逝水', + shishui_info:'锁定技,每当你使用一张红色牌,你令目标流失一点体力', + duhun:'渡魂', + duhun_info:'濒死阶段,你可以与一名体力值不超过你的体力上限的角色拼点,若你赢,你失去一点体力上限并与该角色交换体力值;若你没赢,你立即死亡', + duhun_info_alter:'濒死阶段,你可以与一名体力值不超过你的体力上限的角色拼点,若你赢,你失去一点体力上限并将体力值回复至与该角色相同;若你没赢,你立即死亡', + chizhen:'驰阵', + chizhen_info:'出牌阶段开始时,你可以摸X张牌并弃置X张牌,若你弃置了杀,可以视为使用一张决斗(X为你已损失的体力值且至少为1)', + xidie:'戏蝶', + xidie2:'戏蝶', + xidie_info:'准备阶段,若你的手牌数大于体力值,可以弃置至多X张牌,并于结束阶段摸等量的牌,X为你的体力值与手牌数之差且不超过3', + meihu:'魅狐', + meihu2:'魅狐', + meihu_info:'当你受到伤害后,可令伤害来源交给你一张手牌', + jianwu:'剑舞', + jianwu_info:'锁定技,攻击范围不含你的角色无法闪避你的杀', + meiying:'魅影', + meiying_info:'一名其他角色的回合结束时,若其未于此回合内使用过指定另一名角色为目标的牌,你可以弃置一张红色牌视为对其使用一张杀', + zuizhan:'乱斩', + zuizhan_info:'每当你使用一张杀,可以摸一张牌,然后此杀随机增加一个额外目标', + xlqianhuan:'千幻', + xlqianhuan_info:'回合结束后,若你已受伤,你可以回复一点体力并将武将牌翻面。若你的武将牌背面朝上,你不能使用卡牌,也不能成为卡牌的目标', + fumo:'伏魔', + fumo_info:'每当你受到一次伤害,可以弃置两张颜色相同的手牌并对伤害来源造成一点雷电伤害', + fanyin:'梵音', + fanyin_info:'结束阶段,你可以令一名角色复原武将牌并移除判定区内的牌;若其体力值是全场最少的之一,其回复一点体力', + mingkong:'明空', + mingkong_info:'锁定技,若你没有手牌,你受到的伤害-1,然后伤害来源摸一张牌', + qinglan:'晴岚', + qinglan_info:'每当有一名角色即将受到属性伤害,你可以弃置一张牌令其防止此伤害,然后伤害来源摸一张牌并回复一点体力', + yuehua:'月华', + yuehua_info:'每当你于回合外使用、打出或弃置红色牌,你可以摸一张牌', + xuelu:'血戮', + xuelu_info:'结束阶段,若你已受伤,你可以弃置一张红色牌并对一名其他角色造成一点火焰伤害;若你已损失体力值不少于3,改为造成两点火焰伤害', + xuelu_info_alter:'结束阶段,若你已受伤,你可以弃置一张红色牌并对一名其他角色造成一点火焰伤害', + fanshi:'反噬', + fanshi_info:'锁定技,弃牌阶段结束时,若你本回合内造成过伤害,你流失一点体力并摸一张牌', + shahun:'煞魂', + shahun2:'煞魂', + shahun_info:'限定技,濒死阶段,你可以复原武将牌,弃置所有牌并摸三张牌,然后将体力回复至1;若如此做,你失去技能【反噬】,获得技能【绝境】,并于两回合后立即死亡', + shahun_info_alter:'限定技,濒死阶段,你可以复原武将牌,弃置所有牌并摸三张牌,然后将体力回复至1;若如此做,你失去技能【反噬】,获得技能【绝境】,并于两回合后立即死亡', + + yanjia:'偃甲', + yanjia_info:'出牌阶段,你可以将两张非特殊装备牌合成为一张强化装备', + xiuhua:'袖花', + xiuhua_info:'每当一件其他角色的装备因被替换或弃置进入弃牌堆,你可以获得之', + liuying:'流影', + liuying_info:'每当你使用一张杀结算完毕后,你可以指定一名本回合未成为过你的杀的目标的角色,并亮出牌堆顶的一张牌,若为黑色,你对该角色使用一张杀', + boyun:'拨云', + boyun1:'拨云', + boyun2:'拨云', + boyun_info:'在你的回合内,你可以弃置一张装备牌,并展示牌堆顶的一张牌,若其为装备牌,你须将其交给任意一张角色并对其造成一点伤害,否则你摸一张牌', + jizhan:'疾战', + jizhan_info:'出牌阶段限一次,你可以将移动到任意一名角色的前一位,视为对其使用了一张不计入出杀次数的杀', + qianjun:'千军', + qianjun_info:'每当你使用一张杀,你可以弃置一张牌,令距离目标1以内的所有角色成为额外目标', + xuanning:'玄凝', + xuanning1:'玄凝', + xuanning2:'玄凝', + liuguang:'流光', + yangming:'养命', + yangming2:'养命', + xuanning_info:'出牌阶段,你可以弃置一基本牌,获得至多3个玄凝标记。当你受到伤害时,你失去一枚玄凝标记,伤害来源随机弃置一张牌', + liuguang_info:'准备阶段,若你有玄凝标记,你可以弃置一枚玄凝标记,选择至多三名角色依次令其选择一项:弃置一张牌,或受到一点伤害,并终止流光结算', + yangming_info:'出牌阶段,你可以弃置一张红色牌,并在下个出牌阶段结束时令距离1以内的任意名角色回复一点体力,在此之前不可再次发动', + zhaolu:'朝露', + jiehuo:'劫火', + yuling:'御灵', + yuling1:'御灵', + yuling2:'御灵', + yuling3:'御灵', + yuling4:'御灵', + zhaolu_info:'锁定技,每隔X回合,你流失一点体力上限,每当你受到一点伤害或有人死亡,视为减少两个回合,X为现存角色数且至多为5', + jiehuo_info:'限定技,出牌阶段,你可以对一名其他角色造成两点火焰伤害,然后死亡', + yuling_info:'锁定技,你没有摸牌和弃牌阶段,你的手牌数始终为5,你在一个出牌阶段最多使用X+2张牌,X为你的体力上限', + yuling_info_alter:'锁定技,你没有摸牌和弃牌阶段,你的手牌数始终为5,你在一个出牌阶段最多使用X+1张牌,X为你的体力上限', + }, + }; +}); diff --git a/character/jiange.js b/character/jiange.js index 901272bb5..cad3400b3 100644 --- a/character/jiange.js +++ b/character/jiange.js @@ -1,578 +1,578 @@ -'use strict'; -game.import('character',function(lib,game,ui,get,ai,_status){ - return { - name:'jiange', - character:{ - jg_pangtong:['male','shu',3,['qiwu','tianyu']], - jg_huangyueying:['female','shu',3,['zhinang','jingmiao']], - jg_zhugeliang:['male','shu',3,['biantian','bazhen']], - jg_liubei:['male','shu',4,['jizhen','lingfeng']], - jg_xiahouyuan:['male','wei',4,['xinshensu','juechen']], - jg_caozhen:['male','wei',4,['chiying','jingfan']], - jg_zhanghe:['male','wei',4,['huodi','jueji']], - jg_simayi:['male','wei',5,['xuanlei','sfanshi','konghun']], - }, - skill:{ - sfanshi:{ - trigger:{player:'phaseEnd'}, - forced:true, - check:function(){ - return false; - }, - content:function(){ - player.loseHp(); - } - }, - konghun:{ - trigger:{player:'phaseUseBegin'}, - direct:true, - filter:function(event,player){ - if(game.players.length>=6){ - return player.hp<=2; - } - return player.hp<=1; - }, - content:function(){ - "step 0" - player.chooseTarget(get.prompt('konghun'),function(card,player,target){ - return player!=target; - },[1,Math.min(4,Math.floor((game.players.length-1)/2))]).ai=function(target){ - return get.damageEffect(target,player,player,'thunder')+1; - } - "step 1" - if(result.bool){ - event.targets=result.targets.slice(0); - player.logSkill('konghun',event.targets,'thunder'); - event.targets.sort(lib.sort.seat); - event.num=0; - } - else{ - event.finish(); - } - "step 2" - if(event.num0&&event.player!=player&&event.player.hp1||event.targets[0]!=player); - }, - silent:true, - content:function(){ - player.addTempSkill('huodi3'); - } - }, - huodi3:{}, - jingfan:{ - trigger:{player:'phaseUseEnd'}, - unique:true, - direct:true, - content:function(){ - "step 0" - var num=player.countUsed()-player.countCards('h'); - event.num=num; - if(num>0){ - player.draw(num); - } - "step 1" - if(event.num>0){ - player.chooseTarget('选择至多'+get.cnNumber(event.num)+'名角色令其进攻距离+1',[1,event.num],function(card,player,target){ - return player!=target; - }).ai=function(target){ - return get.attitude(player,target); - } - } - else{ - event.finish(); - } - "step 2" - if(result.bool&&result.targets){ - player.logSkill('jingfan',result.targets); - for(var i=0;i0; - }, - filter:function(event,player){ - if(event.num<=1) return false; - return true; - }, - priority:-11, - content:function(){ - trigger.num=1; - if(trigger.source){ - trigger.source.addTempSkill('chiying2','damageAfter'); - } - } - }, - chiying2:{ - trigger:{source:'damageEnd'}, - forced:true, - popup:false, - content:function(){ - player.draw(); - } - }, - juechen:{ - trigger:{player:'useCard'}, - filter:function(event,player){ - return event.card.name=='sha'; - }, - direct:true, - content:function(){ - "step 0" - player.chooseTarget(get.prompt('juechen'),function(card,player,target){ - return player!=target&&!trigger.targets.contains(target)&&target.countCards('he')>0; - }).set('autodelay',true).ai=function(target){ - return -get.attitude(player,target); - } - "step 1" - if(result.bool){ - player.logSkill('juechen',result.targets); - player.discardPlayerCard(true,result.targets[0],'he'); - } - } - }, - lingfeng:{ - trigger:{player:'phaseDrawBefore'}, - check:function(event,player){ - for(var i=0;i0; - }).ai=function(target){ - return -get.attitude(player,target); - } - } - "step 2" - if(result.bool&&result.targets&&result.targets.length){ - player.discardPlayerCard(result.targets[0],'he',true); - } - "step 3" - player.gain(event.cards); - player.$draw(event.cards); - game.delay(); - }, - ai:{ - threaten:1.1 - } - }, - biantian4:{ - trigger:{player:'dieBegin'}, - forced:true, - popup:false, - content:function(){ - for(var i=0;i0; - }, - content:function(){ - "step 0" - player.choosePlayerCard(trigger.player,get.prompt('jingmiao',trigger.player),'he'); - "step 1" - if(result.bool){ - player.logSkill('jingmiao',trigger.player); - trigger.player.discard(result.links); - } - }, - ai:{ - expose:0.2 - } - }, - zhinang:{ - trigger:{player:'phaseBegin'}, - frequent:true, - content:function(){ - "step 0" - event.cards=get.cards(3); - event.cards2=[]; - for(var i=0;iplayer.hp){ - if(target==player) return Math.max(1,att-2); - } - if(target==player) return att+5; - return att; - } - } - "step 2" - if(result&&result.targets&&result.targets.length){ - event.target=result.targets[0]; - } - if(event.cards2.length){ - event.target.gain(event.cards2,'gain2'); - } - }, - ai:{ - threaten:1.3 - } - }, - tianyu:{ - trigger:{player:'phaseEnd'}, - direct:true, - filter:function(event,player){ - for(var i=0;i=6){ + return player.hp<=2; + } + return player.hp<=1; + }, + content:function(){ + "step 0" + player.chooseTarget(get.prompt('konghun'),function(card,player,target){ + return player!=target; + },[1,Math.min(4,Math.floor((game.players.length-1)/2))]).ai=function(target){ + return get.damageEffect(target,player,player,'thunder')+1; + } + "step 1" + if(result.bool){ + event.targets=result.targets.slice(0); + player.logSkill('konghun',event.targets,'thunder'); + event.targets.sort(lib.sort.seat); + event.num=0; + } + else{ + event.finish(); + } + "step 2" + if(event.num0&&event.player!=player&&event.player.hp1||event.targets[0]!=player); + }, + silent:true, + content:function(){ + player.addTempSkill('huodi3'); + } + }, + huodi3:{}, + jingfan:{ + trigger:{player:'phaseUseEnd'}, + unique:true, + direct:true, + content:function(){ + "step 0" + var num=player.countUsed()-player.countCards('h'); + event.num=num; + if(num>0){ + player.draw(num); + } + "step 1" + if(event.num>0){ + player.chooseTarget('选择至多'+get.cnNumber(event.num)+'名角色令其进攻距离+1',[1,event.num],function(card,player,target){ + return player!=target; + }).ai=function(target){ + return get.attitude(player,target); + } + } + else{ + event.finish(); + } + "step 2" + if(result.bool&&result.targets){ + player.logSkill('jingfan',result.targets); + for(var i=0;i0; + }, + filter:function(event,player){ + if(event.num<=1) return false; + return true; + }, + priority:-11, + content:function(){ + trigger.num=1; + if(trigger.source){ + trigger.source.addTempSkill('chiying2','damageAfter'); + } + } + }, + chiying2:{ + trigger:{source:'damageEnd'}, + forced:true, + popup:false, + content:function(){ + player.draw(); + } + }, + juechen:{ + trigger:{player:'useCard'}, + filter:function(event,player){ + return event.card.name=='sha'; + }, + direct:true, + content:function(){ + "step 0" + player.chooseTarget(get.prompt('juechen'),function(card,player,target){ + return player!=target&&!trigger.targets.contains(target)&&target.countCards('he')>0; + }).set('autodelay',true).ai=function(target){ + return -get.attitude(player,target); + } + "step 1" + if(result.bool){ + player.logSkill('juechen',result.targets); + player.discardPlayerCard(true,result.targets[0],'he'); + } + } + }, + lingfeng:{ + trigger:{player:'phaseDrawBefore'}, + check:function(event,player){ + for(var i=0;i0; + }).ai=function(target){ + return -get.attitude(player,target); + } + } + "step 2" + if(result.bool&&result.targets&&result.targets.length){ + player.discardPlayerCard(result.targets[0],'he',true); + } + "step 3" + player.gain(event.cards); + player.$draw(event.cards); + game.delay(); + }, + ai:{ + threaten:1.1 + } + }, + biantian4:{ + trigger:{player:'dieBegin'}, + forced:true, + popup:false, + content:function(){ + for(var i=0;i0; + }, + content:function(){ + "step 0" + player.choosePlayerCard(trigger.player,get.prompt('jingmiao',trigger.player),'he'); + "step 1" + if(result.bool){ + player.logSkill('jingmiao',trigger.player); + trigger.player.discard(result.links); + } + }, + ai:{ + expose:0.2 + } + }, + zhinang:{ + trigger:{player:'phaseBegin'}, + frequent:true, + content:function(){ + "step 0" + event.cards=get.cards(3); + event.cards2=[]; + for(var i=0;iplayer.hp){ + if(target==player) return Math.max(1,att-2); + } + if(target==player) return att+5; + return att; + } + } + "step 2" + if(result&&result.targets&&result.targets.length){ + event.target=result.targets[0]; + } + if(event.cards2.length){ + event.target.gain(event.cards2,'gain2'); + } + }, + ai:{ + threaten:1.3 + } + }, + tianyu:{ + trigger:{player:'phaseEnd'}, + direct:true, + filter:function(event,player){ + for(var i=0;i0){ - return [0,num]; - } - if(target.hp==1&&!target.hasShan()) return; - return [1,num]; - } - } - } - } - }, - qianggu:{ - enable:'phaseUse', - usable:1, - filterCard:true, - selectCard:2, - position:'he', - check:function(card){ - return 8-get.value(card); - }, - filter:function(event,player){ - return player.countCards('he')>=2; - }, - content:function(){ - player.changeHujia(2); - player.addTempSkill('qianggu2',{player:'phaseBegin'}); - }, - ai:{ - result:{ - player:1 - }, - order:2.5 - } - }, - qianggu2:{ - trigger:{target:'useCardToBefore'}, - forced:true, - filter:function(event,player){ - return event.card.name=='sha'; - }, - mark:true, - intro:{ - content:'其他角色对你使用杀时需要弃置一张基本牌,否则杀对你无效' - }, - content:function(){ - "step 0" - var eff; - if(player.hasSkill('woliu2')){ - eff=-get.attitude(trigger.player,player); - } - else{ - eff=get.effect(player,trigger.card,trigger.player,trigger.player); - } - trigger.player.chooseToDiscard('强固:弃置一张基本牌,否则杀对'+get.translation(player)+'无效',function(card){ - return get.type(card)=='basic'; - }).set('ai',function(card){ - if(_status.event.eff>0){ - return 10-get.value(card); - } - return 0; - }).set('eff',eff); - "step 1" - if(result.bool==false){ - trigger.cancel(); - } - }, - ai:{ - effect:{ - target:function(card,player,target,current){ - if(card.name=='sha'){ - if(_status.event.name=='qianggu2') return; - if(get.attitude(player,target)>0) return; - var bs=player.getCards('h',{type:'basic'}); - if(bs.length<2) return 0; - if(player.hasSkill('jiu')||player.hasSkill('tianxianjiu')) return; - if(bs.length<=3&&player.countCards('h','sha')<=1){ - for(var i=0;i0; - }, - filterCard:true, - usable:1, - viewAs:{name:'jingleishan',nature:'thunder'}, - check:function(card){ - return 8-get.value(card) - }, - ai:{ - order:8, - expose:0.2, - threaten:1.2 - }, - mod:{ - playerEnabled:function(card,player,target){ - if(_status.event.skill=='dianji'&&get.distance(player,target)>2) return false; - } - } - }, - feitiao:{ - trigger:{player:'phaseUseBegin'}, - direct:true, - filter:function(event,player){ - return player.countCards('he')>0; - }, - content:function(){ - 'step 0' - var next=player.chooseCardTarget({ - prompt:get.prompt('feitiao'), - position:'he', - filterCard:true, - ai1:function(card){ - return 7-get.value(card); - }, - ai2:function(target){ - var att=get.attitude(player,target); - if(att>=0) return 0; - if(!target.countCards('he')) return -0.01; - var dist=get.distance(player,target); - if(dist>2){ - att-=2; - } - else if(dist==2){ - att--; - } - return -att; - }, - filterTarget:function(card,player,target){ - return player!=target; - } - }); - 'step 1' - if(result.bool){ - player.discard(result.cards); - var target=result.targets[0]; - player.logSkill('feitiao',target); - player.storage.feitiao2=target; - player.addTempSkill('feitiao2'); - target.randomDiscard(); - } - - } - }, - feitiao2:{ - mod:{ - globalFrom:function(from,to){ - if(to==from.storage.feitiao2) return -Infinity; - } - }, - mark:'character', - intro:{ - content:'与$的距离视为1直到回合结束' - }, - onremove:true - }, - zhencha:{ - init:function(player){ - player.storage.zhencha=true; - }, - mark:true, - intro:{ - content:function(storage,player){ - if(storage){ - return '每当你使用一张杀,你摸一张牌或回复一点体力'; - } - else if(player.hasSkill('bshaowei')&&player.storage.bshaowei){ - return '你的杀无视距离和防具、无数量限制且不可闪避;你不能闪避杀'; - } - else{ - return '无额外技能'; - } - } - }, - trigger:{player:'phaseEnd'}, - filter:function(event,player){ - if(player.hasSkill('zhencha2')) return false; - return !player.storage.zhencha; - }, - content:function(){ - player.storage.bshaowei=false; - player.storage.zhencha=true; - if(player.marks.zhencha){ - player.marks.zhencha.firstChild.innerHTML='侦'; - } - player.addTempSkill('zhencha2'); - }, - subSkill:{ - sha:{ - trigger:{player:'shaBegin'}, - direct:true, - filter:function(event,player){ - return player.storage.zhencha&&event.card&&event.card.name=='sha'; - }, - content:function(){ - player.chooseDrawRecover(get.prompt('zhencha')).logSkill='zhencha'; - } - } - }, - group:'zhencha_sha' - }, - bshaowei:{ - init:function(player){ - player.storage.bshaowei=false; - }, - trigger:{player:'phaseEnd'}, - filter:function(event,player){ - if(player.hasSkill('zhencha2')) return false; - return !player.storage.bshaowei; - }, - check:function(event,player){ - if(!player.hasShan()) return true; - if(!player.hasSha()) return false; - return Math.random()<0.5; - }, - content:function(){ - player.storage.bshaowei=true; - player.storage.zhencha=false; - if(player.marks.zhencha){ - player.marks.zhencha.firstChild.innerHTML='哨'; - } - player.addTempSkill('zhencha2'); - }, - subSkill:{ - sha:{ - mod:{ - targetInRange:function(card,player,target,now){ - if(card.name=='sha'&&player.storage.bshaowei) return true; - }, - cardUsable:function(card,player,num){ - if(card.name=='sha'&&player.storage.bshaowei) return Infinity; - } - }, - trigger:{target:'shaBegin',player:'shaBegin'}, - forced:true, - filter:function(event,player){ - return player.storage.bshaowei; - }, - check:function(){ - return false; - }, - content:function(){ - trigger.directHit=true; - }, - ai:{ - unequip:true, - skillTagFilter:function(player,tag,arg){ - if(!player.storage.bshaowei) return false; - if(arg&&arg.name=='sha') return true; - return false; - } - } - } - }, - group:'bshaowei_sha', - ai:{ - threaten:function(player,target){ - if(target.storage.bshaowei) return 1.7; - return 1; - } - } - }, - zhencha2:{}, - pingzhang:{ - trigger:{global:'damageBegin'}, - // alter:true, - intro:{ - content:function(storage,player){ - if(player.hasSkill('pingzhang2')){ - if(player.hasSkill('pingzhang3')){ - return '已对自已和其他角色发动屏障'; - } - else{ - return '已对自已发动屏障'; - } - } - else{ - return '已对其他角色发动屏障'; - } - }, - markcount:function(storage,player){ - if(player.hasSkill('pingzhang2')&&player.hasSkill('pingzhang3')){ - return 2; - } - return 1; - } - }, - filter:function(event,player){ - if(event.num<=0) return false; - var position=get.is.altered('pingzhang')?'h':'he'; - if(event.player==player){ - if(player.hasSkill('pingzhang2')) return false; - return player.countCards(position,{suit:'heart'}); - } - else{ - if(player.hasSkill('pingzhang3')) return false; - return player.countCards(position,{suit:'spade'}); - } - }, - direct:true, - content:function(){ - 'step 0' - var position=get.is.altered('pingzhang')?'h':'he'; - var suit=(player==trigger.player)?'heart':'spade'; - var next=player.chooseToDiscard(position,{suit:suit},get.prompt('pingzhang',trigger.player)); - next.ai=function(card){ - if(get.damageEffect(trigger.player,trigger.source,player)<0){ - return 8-get.value(card); - } - return 0; - } - next.logSkill=['pingzhang',trigger.player]; - 'step 1' - if(result.bool){ - trigger.num--; - if(player==trigger.player){ - player.addSkill('pingzhang2'); - } - else{ - player.addSkill('pingzhang3'); - } - player.markSkill('pingzhang'); - } - }, - group:['pingzhang_count'], - subSkill:{ - count:{ - trigger:{player:'phaseBegin'}, - silent:true, - content:function(){ - player.storage.pingzhang=0; - if(player.hasSkill('pingzhang2')){ - player.storage.pingzhang++; - player.removeSkill('pingzhang2'); - } - if(player.hasSkill('pingzhang3')){ - player.storage.pingzhang++; - player.removeSkill('pingzhang3'); - } - player.unmarkSkill('pingzhang'); - } - } - }, - ai:{ - expose:0.2, - threaten:1.5 - } - }, - pingzhang2:{}, - pingzhang3:{}, - liyong:{ - trigger:{player:'phaseDrawBegin'}, - forced:true, - filter:function(event,player){ - return player.storage.pingzhang>0; - }, - content:function(){ - trigger.num+=player.storage.pingzhang; - } - }, - liangou:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return target!=player; - }, - filterCard:true, - position:'he', - check:function(card){ - return 5-get.value(card); - }, - content:function(){ - 'step 0' - player.judge(function(card){ - return get.suit(card)!='heart'?1:-1; - }); - 'step 1' - if(result.bool){ - target.addTempSkill('liangou2'); - target.storage.liangou2=player; - } - }, - ai:{ - order:10, - expose:0.2, - result:{ - target:function(player,target){ - if(get.damageEffect(target,player,target)<0&&player.hasCard(function(card){ - return get.tag(card,'damage')?true:false; - })){ - return -1; - } - return 0; - } - } - } - }, - liangou2:{ - mod:{ - // cardEnabled:function(card,player){ - // return false; - // }, - // cardUsable:function(card,player){ - // return false; - // }, - // cardRespondable:function(card,player){ - // return false; - // }, - // cardSavable:function(card,player){ - // return false; - // }, - globalTo:function(from,to){ - if(from==to.storage.liangou2) return -Infinity; - } - }, - onremove:true, - trigger:{player:'damageBegin'}, - usable:1, - forced:true, - popup:false, - content:function(){ - trigger.num++; - }, - // ai:{ - // effect:{ - // target:function(card,player,target){ - // if(get.tag(card,'damage')) return [1,-2]; - // if(get.tag(card,'respond')) return [1,-1]; - // } - // } - // } - }, - xiyang:{ - trigger:{player:'phaseEnd'}, - filter:function(event,player){ - return !player.isTurnedOver()&&player.isDamaged(); - }, - check:function(event,player){ - return player.hp<=1; - }, - content:function(){ - 'step 0' - player.turnOver(); - 'step 1' - player.recover(2); - } - }, - qinru:{ - trigger:{player:'useCardToBegin'}, - filter:function(event,player){ - return event.card.name=='sha'&&event.target!=player&&event.target&&!event.target.hasSkill('fengyin'); - }, - logTarget:'target', - check:function(event,player){ - return get.attitude(player,event.target)<0; - }, - intro:{ - content:'players', - mark:function(dialog,storage,player){ - var one=[],two=[],three=[]; - for(var i=0;i1){ - player.storage.qinru_turn[i]--; - } - else{ - player.storage.qinru.splice(i,1); - player.storage.qinru_turn.splice(i,1); - i--; - } - } - if(!player.storage.qinru.length){ - player.unmarkSkill('qinru'); - } - else{ - player.updateMarks(); - } - } - } - }, - group:['qinru_count','qinru_die'] - }, - yinshen:{ - trigger:{player:'loseEnd'}, - forced:true, - filter:function(event,player){ - if(player.countCards('h',{type:'basic'})) return false; - for(var i=0;i0; - }, - content:function(){ - "step 0" - var next=player.chooseToDiscard(get.prompt('yinshen'),'he',{type:'equip'}); - next.logSkill='yinshen'; - next.ai=function(card){ - if(player.hp==1) return 8-get.value(card); - if(player.isZhu) return 7-get.value(card); - if(player.hp==2) return 6-get.value(card); - return 5-get.value(card); - }; - "step 1" - if(result.bool){ - player.tempHide(); - } - }, - }, - maichong:{ - trigger:{player:'useCard'}, - forced:true, - filter:function(event,player){ - if(!player.hasSkill('qinru')||!player.storage.qinru||!player.storage.qinru.length) return false; - if(get.type(event.card)=='trick'&&event.cards[0]&&event.cards[0]==event.card){ - for(var i=0;i0; - }, - filterTarget:function(card,player,target){ - return !target.hujia; - }, - filterCard:true, - position:'he', - check:function(card){ - var player=_status.event.player; - if(game.hasPlayer(function(current){ - return current.hp==1&&get.attitude(player,current)>2; - })){ - return 7-get.value(card); - } - return 5-get.value(card); - }, - content:function(){ - player.changeHujia(-1); - target.changeHujia(); - }, - ai:{ - order:5, - expose:0.2, - result:{ - target:function(player,target){ - return 1/Math.max(1,target.hp); - } - } - } - }, - maoding:{ - trigger:{player:'damageEnd',source:'damageEnd'}, - frequent:true, - filter:function(event,player){ - if(get.is.altered('maoding')&&event.source!=player) return false; - return true; - }, - // alter:true, - content:function(){ - var list=get.typeCard('hslingjian'); - if(!list.length){ - return; - } - player.gain(game.createCard(list.randomGet()),'gain2'); - }, - group:'maoding2', - ai:{ - threaten:1.5, - maixie_defend:true - } - }, - maoding2:{ - enable:'phaseUse', - filter:function(event,player){ - return player.countCards('h',{type:'hslingjian'})>1; - }, - filterCard:{type:'hslingjian'}, - filterTarget:function(card,player,target){ - return !target.hujia; - }, - selectCard:2, - // usable:1, - content:function(){ - target.changeHujia(); - }, - ai:{ - order:9, - result:{ - target:function(player,target){ - return 2/Math.max(1,Math.sqrt(target.hp)); - }, - }, - } - }, - paotai:{ - enable:'phaseUse', - intro:{ - content:function(storage){ - var num; - switch(storage){ - case 1:num=30;break; - case 2:num=60;break; - case 3:num=100;break; - } - return '结束阶段,有'+num+'%机率对一名随机敌人造成一点火焰伤害'; - } - }, - init:function(player){ - player.storage.paotai=0; - }, - filter:function(event,player){ - return player.countCards('h','sha')>0&&player.storage.paotai<3; - }, - filterCard:{name:'sha'}, - content:function(){ - player.storage.paotai++; - player.markSkill('paotai'); - }, - ai:{ - order:5, - threaten:1.5, - result:{ - player:1 - } - }, - group:['paotai2','paotai3'] - }, - paotai2:{ - trigger:{player:'phaseEnd'}, - forced:true, - filter:function(event,player){ - var num=0; - switch(player.storage.paotai){ - case 1:num=30;break; - case 2:num=60;break; - case 3:num=100;break; - } - return 100*Math.random()0&&event.num>0; - }, - content:function(){ - player.storage.paotai-=trigger.num; - if(player.storage.paotai<=0){ - player.storage.paotai=0; - player.unmarkSkill('paotai'); - } - else{ - player.updateMarks(); - } - } - }, - bfengshi:{ - trigger:{player:'shaBegin'}, - forced:true, - // alter:true, - check:function(event,player){ - return get.attitude(player,event.target)<=0; - }, - filter:function(event,player){ - if(player.hasSkill('bfengshi4')) return false; - var num=0.2; - if(get.is.altered('bfengshi')) num=0.15; - return Math.random()0; - }, - check:function(card){ - return 7-get.value(card); - }, - content:function(){ - 'step 0' - var targets=player.getEnemies(function(target){ - return target.countCards('he')>0; - }); - if(targets.length){ - event.targets=targets.randomGets(3); - event.targets.sort(lib.sort.seat); - player.line(event.targets,'green'); - } - 'step 1' - if(event.targets.length){ - var target=event.targets.shift(); - var he=target.getCards('he'); - if(he.length){ - target.addExpose(0.1); - target.discard(he.randomGet()); - } - event.redo(); - } - }, - ai:{ - order:10, - expose:0.3, - result:{ - player:1 - } - } - }, - aqianghua:{ - enable:'phaseUse', - usable:1, - // alter:true, - filter:function(event,player){ - return player.countCards('h')>=1; - }, - filterTarget:function(card,player,target){ - return target!=player; - }, - filterCard:true, - selectCard:-1, - discard:false, - prepare:'give', - content:function(){ - target.gain(cards); - if(!get.is.altered('aqianghua')) target.changeHujia(); - target.addSkill('aqianghua2'); - }, - ai:{ - threaten:1.5, - order:2.1, - result:{ - target:function(player,target){ - if(target.hasSkillTag('nogain')) return 0; - if(get.attitude(player,target)<3) return 0; - if(target.hasJudge('lebu')) return 0; - if(target.hasSkill('aqianghua2')) return 0.1; - return 1; - } - } - } - }, - aqianghua2:{ - trigger:{source:'damageBegin'}, - forced:true, - content:function(){ - trigger.num++; - player.unmarkSkill('aqianghua2'); - player.removeSkill('aqianghua2'); - }, - mark:true, - intro:{ - content:'下一次造成的伤害+1' - } - }, - shihuo:{ - trigger:{global:'damageEnd'}, - forced:true, - filter:function(event){ - return event.nature=='fire'; - }, - content:function(){ - player.draw(); - } - }, - shoujia:{ - enable:'phaseUse', - usable:1, - filter:function(event,player){ - return player.countCards('h')>0; - }, - filterCard:true, - check:function(card){ - return 6-get.value(card); - }, - discard:false, - prepare:'give2', - filterTarget:function(card,player,target){ - return target!=player&&!target.hasSkill('shoujia2'); - }, - content:function(){ - target.storage.shoujia=cards[0]; - target.storage.shoujia2=player; - target.addSkill('shoujia2'); - target.syncStorage('shoujia'); - }, - ai:{ - order:1, - expose:0.2, - threaten:1.4, - result:{ - target:-1 - } - } - }, - shoujia2:{ - mark:true, - trigger:{player:'useCardToBegin'}, - forced:true, - filter:function(event,player){ - return get.suit(event.card)==get.suit(player.storage.shoujia)&&event.target&&event.target!=player; - }, - content:function(){ - 'step 0' - player.showCards([player.storage.shoujia],get.translation(player)+'发动了【兽夹】'); - 'step 1' - player.storage.shoujia.discard(); - delete player.storage.shoujia; - delete player.storage.shoujia2; - player.removeSkill('shoujia2'); - game.addVideo('storage',player,['shoujia',null]); - game.addVideo('storage',player,['shoujia2',null]); - player.turnOver(true); - }, - intro:{ - mark:function(dialog,content,player){ - if(player.storage.shoujia2&&player.storage.shoujia2.isUnderControl(true)){ - dialog.add([player.storage.shoujia]); - } - else{ - return '已成为'+get.translation(player.storage.shoujia2)+'的兽夹目标'; - } - }, - content:function(content,player){ - if(player.storage.shoujia2&&player.storage.shoujia2.isUnderControl(true)){ - return get.translation(player.storage.shoujia); - } - return '已成为'+get.translation(player.storage.shoujia2)+'的兽夹目标'; - } - }, - group:'shoujia3' - }, - shoujia3:{ - trigger:{global:'damageEnd'}, - forced:true, - filter:function(event,player){ - return event.player==player.storage.shoujia2; - }, - content:function(){ - player.storage.shoujia.discard(); - player.$throw(player.storage.shoujia); - game.log(player.storage.shoujia,'被置入弃牌堆') - delete player.storage.shoujia; - delete player.storage.shoujia2; - player.removeSkill('shoujia2'); - game.addVideo('storage',player,['shoujia',null]); - game.addVideo('storage',player,['shoujia2',null]); - } - }, - liudan:{ - trigger:{player:'useCard'}, - check:function(event,player){ - return game.countPlayer(function(current){ - if(event.targets.contains(current)==false&¤t!=player&& - lib.filter.targetEnabled(event.card,player,current)){ - return get.effect(current,event.card,player,player); - } - })>=0; - }, - filter:function(event,player){ - if(event.card.name!='sha') return false; - return game.hasPlayer(function(current){ - return (event.targets.contains(current)==false&¤t!=player&& - lib.filter.targetEnabled(event.card,player,current)); - }); - }, - content:function(){ - var list=game.filterPlayer(function(current){ - return (trigger.targets.contains(current)==false&¤t!=player&& - lib.filter.targetEnabled(trigger.card,player,current)); - }); - if(list.length){ - var list2=[]; - for(var i=0;iplayer.countCards('h')&&!player.skipList.contains('phaseUse')&&!player.skipList.contains('phaseDiscard'); - }, - check:function(event,player){ - var nh=player.countCards('h'); - if(Math.min(5,player.hp)-nh>=2) return true; - return false; - }, - content:function(){ - var num=Math.min(5,player.hp)-player.countCards('h'); - var cards=[]; - while(num--){ - cards.push(game.createCard('sha')); - } - player.gain(cards,'gain2'); - player.skip('phaseUse'); - player.skip('phaseDiscard'); - } - }, - shanguang:{ - enable:'phaseUse', - usable:1, - filterCard:{suit:'diamond'}, - position:'he', - filter:function(event,player){ - return player.countCards('he',{suit:'diamond'})>0; - }, - filterTarget:function(card,player,target){ - return target!=player&&get.distance(player,target,'attack')<=1; - }, - check:function(card){ - if(card.name=='sha'&&_status.event.player.countCards('h','sha')<3) return 0; - return 6-get.value(card); - }, - content:function(){ - target.addTempSkill('shanguang2'); - }, - ai:{ - order:7.9, - result:{ - target:function(player,target){ - var nh=target.countCards('h'); - if(get.attitude(player,target)<0&&nh>=3&& - player.canUse('sha',target)&&player.countCards('h','sha')&& - get.effect(target,{name:'sha'},player,player)>0){ - return -nh-5; - } - return -nh; - } - } - } - }, - shanguang2:{ - mod:{ - cardEnabled:function(){ - return false; - }, - cardUsable:function(){ - return false; - }, - cardRespondable:function(){ - return false; - }, - cardSavable:function(){ - return false; - } - }, - ai:{ - effect:{ - target:function(card,player,target,current){ - if(get.tag(card,'respondShan')||get.tag(card,'respondSha')){ - if(current<0) return 1.5; - } - } - } - } - }, - baoxue:{ - enable:'phaseUse', - init:function(player){ - player.storage.baoxue=false; - }, - intro:{ - content:'limited' - }, - mark:true, - unique:true, - skillAnimation:true, - animationColor:'water', - line:'thunder', - filter:function(event,player){ - return !player.storage.baoxue&&player.countCards('he',{color:'black'})>0; - }, - filterTarget:function(card,player,target){ - return target!=player; - }, - selectTarget:function(){ - return [1,_status.event.player.countCards('he',{color:'black'})]; - }, - // alter:true, - delay:false, - contentBefore:function(){ - 'step 0' - game.delayx(); - 'step 1' - player.storage.baoxue=true; - player.awakenSkill('baoxue'); - player.showHandcards(); - player.discard(player.getCards('he',{color:'black'})); - }, - content:function(){ - 'step 0' - if(!get.is.altered('baoxue')){ - var he=target.getCards('he'); - if(he.length){ - target.discard(he.randomGet()); - } - } - 'step 1' - target.turnOver(true); - }, - contentAfter:function(){ - player.turnOver(true); - }, - ai:{ - order:function(skill,player){ - var num=game.countPlayer(function(current){ - return get.attitude(player,current)<0; - }); - var nh=player.countCards('he',{color:'black'}); - if(nh==1&&num>1) return 0; - if(nh>num) return 1; - return 11; - }, - result:{ - target:function(player,target){ - if(target.hasSkillTag('noturn')) return 0; - if(player.hasUnknown()) return 0; - return -1; - } - } - } - }, - mianzhen:{ - enable:'phaseUse', - usable:1, - filter:function(event,player){ - return player.countCards('he')>0; - }, - filterTarget:function(card,player,target){ - return target!=player&&!target.hasSkill('mianzhen2'); - }, - filterCard:true, - position:'he', - check:function(card){ - return 8-get.value(card); - }, - content:function(){ - 'step 0' - target.chooseToRespond({name:'shan'}); - 'step 1' - if(!result.bool) target.addSkill('mianzhen2'); - }, - ai:{ - order:2.2, - result:{ - target:function(player,target){ - return Math.min(-0.1,-1-target.countCards('h')+Math.sqrt(target.hp)/2); - } - } - } - }, - mianzhen2:{ - mark:true, - intro:{ - content:'不能使用或打出手牌直到受到伤害或下一回合结束' - }, - trigger:{player:['damageEnd','phaseEnd']}, - forced:true, - popup:false, - content:function(){ - player.removeSkill('mianzhen2'); - }, - mod:{ - cardEnabled:function(){ - return false; - }, - cardUsable:function(){ - return false; - }, - cardRespondable:function(){ - return false; - }, - cardSavable:function(){ - return false; - } - }, - ai:{ - threaten:0.6 - } - }, - zhiyuan:{ - trigger:{source:'damageBefore'}, - check:function(event,player){ - player.disableSkill('tmp','zhiyuan'); - var eff=get.damageEffect(event.player,player,player); - var att=get.attitude(player,event.player); - var bool=false; - if(att>0){ - if(eff<=0||event.player.hp0; - }, - content:function(){ - trigger.cancel(); - trigger.player.recover(trigger.num); - }, - ai:{ - effect:{ - player:function(card,player,target){ - if(get.tag(card,'damage')&&get.attitude(player,target)>0){ - if(target.hp==target.maxHp||get.recoverEffect(target,player,player)<=0) return 'zeroplayertarget'; - return [0,0,0,1]; - } - } - } - } - }, - duwen:{ - trigger:{source:'damageBegin'}, - check:function(event,player){ - return get.attitude(player,event.player)<=0; - }, - forced:true, - filter:function(event,player){ - return player.countCards('h')==event.player.countCards('h')&&event.notLink(); - }, - content:function(){ - trigger.num++; - }, - ai:{ - threaten:1.5 - }, - }, - duwen2:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&player.hp==event.player.hp&&event.notLink(); - }, - content:function(){ - player.draw(2); - } - }, - juji:{ - enable:'phaseUse', - usable:1, - position:'he', - filter:function(event,player){ - return player.countCards('he')>0; - }, - filterCard:function(card){ - var suit=get.suit(card); - for(var i=0;i0; - }, - check:function(card){ - if(ui.selected.cards.length>1) return 0; - return 5-get.value(card); - }, - selectCard:[1,4], - content:function(){ - var suits=[]; - for(var i=0;inum){ - min.length=0; - min.push(players[i]); - num=eff; - } - } - } - for(var i=0;i0) return 0; - if(min[i].countCards('h')<=1&&get.distance(player,min[i],'attack')<=1) return 0; - } - if(min.contains(target)) return -1; - return 0; - } - } - }, - }, - juji2:{ - ai:{ - effect:{ - player:function(card,player,target){ - if(card.name=='sha'&&target==player.storage.juji2) return [1,0,1,-1]; - } - } - }, - trigger:{player:'phaseAfter'}, - forced:true, - popup:false, - content:function(){ - player.unmarkSkill('juji2'); - player.removeSkill('juji2'); - delete player.storage.juji2; - }, - group:'juji3' - }, - juji3:{ - trigger:{player:'shaBegin'}, - forced:true, - filter:function(event,player){ - return event.target==player.storage.juji2; - }, - content:function(){ - trigger.directHit=true; - }, - mod:{ - globalFrom:function(from,to){ - if(to==from.storage.juji2) return -Infinity; - } - } - }, - dulei:{ - enable:'phaseUse', - filter:function(event,player){ - return !player.hasSkill('dulei2'); - }, - filterCard:true, - check:function(card){ - return 6-get.value(card); - }, - discard:false, - prepare:function(cards,player){ - player.$give(1,player,false); - }, - content:function(){ - player.storage.dulei=cards[0]; - player.addSkill('dulei2'); - player.syncStorage('dulei'); - }, - ai:{ - order:1, - result:{ - player:1 - } - } - }, - dulei2:{ - mark:true, - trigger:{target:'useCardToBegin'}, - forced:true, - filter:function(event,player){ - return event.player!=player&&get.suit(event.card)==get.suit(player.storage.dulei); - }, - content:function(){ - 'step 0' - player.showCards([player.storage.dulei],get.translation(player)+'发动了【诡雷】'); - 'step 1' - player.storage.dulei.discard(); - delete player.storage.dulei; - player.removeSkill('dulei2'); - game.addVideo('storage',player,['dulei',null]); - trigger.player.loseHp(); - 'step 2' - var he=trigger.player.getCards('he'); - if(he.length){ - trigger.player.discard(he.randomGet()); - } - }, - intro:{ - mark:function(dialog,content,player){ - if(player==game.me||player.isUnderControl()){ - dialog.add([player.storage.dulei]); - } - else{ - return '已发动诡雷'; - } - }, - content:function(content,player){ - if(player==game.me||player.isUnderControl()){ - return get.translation(player.storage.dulei); - } - return '已发动诡雷'; - } - } - }, - juji_old:{ - trigger:{player:'shaBegin'}, - forced:true, - filter:function(event,player){ - return get.distance(event.target,player,'attack')>1; - }, - content:function(){ - trigger.directHit=true; - }, - group:'juji2' - }, - juji2_old:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return target!=player; - }, - content:function(){ - target.addTempSkill('juji3',{player:'phaseEnd'}); - if(!target.storage.juji3){ - target.storage.juji3=[]; - } - target.storage.juji3.push(player); - }, - mod:{ - targetInRange:function(card,player,target){ - if(target.hasSkill('juji3')&&Array.isArray(target.storage.juji3)&&target.storage.juji3.contains(player)){ - return true; - } - } - } - }, - juji3_old:{ - mark:true, - intro:{ - nocount:true, - content:function(storage){ - return '对'+get.translation(storage)+'使用卡牌无视距离'; - } - }, - mod:{ - targetInRange:function(card,player,target){ - if(Array.isArray(player.storage.juji3)&&player.storage.juji3.contains(target)){ - return true; - } - } - } - }, - zhuagou:{ - enable:'phaseUse', - usable:1, - changeSeat:true, - filterTarget:function(card,player,target){ - return player!=target&&player.next!=target; - }, - filterCard:true, - check:function(card){ - return 4-get.value(card); - }, - content:function(){ - while(player.next!=target){ - game.swapSeat(player,player.next); - } - }, - ai:{ - order:5, - result:{ - player:function(player,target){ - var att=get.attitude(player,target); - if(target==player.previous&&att>0) return 1; - if(target==player.next.next&&get.attitude(player,player.next)<0) return 1; - return 0; - } - } - } - }, - bingqiang:{ - enable:'phaseUse', - position:'he', - filterCard:function(card){ - var color=get.color(card); - for(var i=0;imax){ - max=num; - } - if(num-min){ - if(get.color(card)=='red') return 5-get.value(card); - } - else{ - if(get.color(card)=='black') return 5-get.value(card); - } - return 0; - }, - changeTarget:function(player,targets){ - var target=targets[0]; - var add=game.filterPlayer(function(player){ - return get.distance(target,player,'pure')==1; - }); - for(var i=0;i0; - }, - content:function(){ - for(var i=0;i0; - }, - content:function(){ - 'step 0' - var goon=false; - var goon2=false; - var att=get.attitude(player,trigger.player); - if(att>0){ - if(trigger.player.hp==1) goon=true; - } - else{ - if(Math.random()<0.5) goon=true; - } - if(Math.random()<0.3) goon2=true; - player.chooseToDiscard([1,player.countCards('h')],'he',get.prompt('bingqiang',trigger.player)).set('logSkill',['bingqiang',trigger.player]).ai=function(card){ - if(ui.selected.cards.length) return 0; - if(goon) return 6-get.value(card); - if(goon2) return 4-get.value(card); - return 0; - } - 'step 1' - if(result.bool){ - var num=result.cards.length; - event.num=num; - player.chooseControl('选项一','选项二','选项三','选项四',function(){ - if(get.attitude(player,trigger.player)>0){ - if(Math.random()<0.7) return '选项一'; - return '选项三'; - } - else{ - if(Math.random()<0.7) return '选项四'; - return '选项二'; - } - }).set('prompt','冰墙

          选项一:防御距离+'+num+ - '

          选项二:防御距离-'+num+ - '

          选项三:进攻距离+'+num+ - '

          选项四:进攻距离-'+num+'
          '); - } - else{ - event.finish(); - } - 'step 2' - switch(result.control){ - case '选项一':{ - trigger.player.storage.bingqiang2=event.num; - trigger.player.addTempSkill('bingqiang2',{player:'phaseBegin'}); - break; - } - case '选项二':{ - trigger.player.storage.bingqiang3=event.num; - trigger.player.addTempSkill('bingqiang3',{player:'phaseBegin'}); - break; - } - case '选项三':{ - trigger.player.storage.bingqiang4=event.num; - trigger.player.addTempSkill('bingqiang4',{player:'phaseBegin'}); - break; - } - case '选项四':{ - trigger.player.storage.bingqiang5=event.num; - trigger.player.addTempSkill('bingqiang5',{player:'phaseBegin'}); - break; - } - } - }, - ai:{ - expose:0.1 - } - }, - bingqiang2:{ - mark:true, - intro:{ - content:'防御距离+#' - }, - mod:{ - globalTo:function(from,to,distance){ - if(typeof to.storage.bingqiang2=='number') return distance+to.storage.bingqiang2; - }, - } - }, - bingqiang3:{ - mark:true, - intro:{ - content:'防御距离-#' - }, - mod:{ - globalTo:function(from,to,distance){ - if(typeof to.storage.bingqiang3=='number') return distance-to.storage.bingqiang3; - }, - } - }, - bingqiang4:{ - mark:true, - intro:{ - content:'进攻距离+#' - }, - mod:{ - globalFrom:function(from,to,distance){ - if(typeof from.storage.bingqiang4=='number') return distance-from.storage.bingqiang4; - } - } - }, - bingqiang5:{ - mark:true, - intro:{ - content:'进攻距离-#' - }, - mod:{ - globalFrom:function(from,to,distance){ - if(typeof from.storage.bingqiang5=='number') return distance+from.storage.bingqiang5; - } - } - }, - shuangqiang:{ - trigger:{source:'damageBegin'}, - check:function(event,player){ - var att=get.attitude(player,event.player); - if(event.player.hp==1) return att>0; - return att<=0; - }, - logTarget:'player', - filter:function(event,player){ - return !event.player.isTurnedOver()&&event.num>0; - }, - content:function(){ - trigger.num--; - trigger.player.draw(); - trigger.player.turnOver(); - } - }, - jidong:{ - trigger:{global:'phaseEnd'}, - filter:function(event,player){ - return player.hp==1&&!player.isTurnedOver(); - }, - // alter:true, - content:function(){ - 'step 0' - player.turnOver(); - player.recover(2); - 'step 1' - if(player.isTurnedOver()&&!get.is.altered('jidong')){ - player.addTempSkill('jidong2',{player:'turnOverAfter'}); - } - }, - ai:{ - threaten:function(player,target){ - if(target.hp==1) return 2; - return 1; - } - } - }, - jidong2:{ - trigger:{player:'damageBefore'}, - forced:true, - content:function(){ - trigger.cancel(); - }, - ai:{ - nofire:true, - nothunder:true, - nodamage:true, - effect:{ - target:function(card,player,target,current){ - if(get.tag(card,'damage')) return [0,0]; - } - }, - }, - mod:{ - targetEnabled:function(card,player,target){ - if(player!=target) return false; - } - } - }, - chongzhuang:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event,player){ - return player.storage.jijia<=0&&event.num>0; - }, - popup:false, - unique:true, - content:function(){ - player.storage.jijia2+=trigger.num; - if(player.storage.jijia2>=4){ - player.storage.jijia=4; - player.storage.jijia2=0; - player.markSkill('jijia'); - if(lib.config.skill_animation_type!='off'){ - player.logSkill('chongzhuang'); - player.$skill('重装') - } - } - } - }, - tuijin:{ - enable:'phaseUse', - usable:1, - unique:true, - filter:function(event,player){ - if(player.storage.jijia>0){ - return game.hasPlayer(function(current){ - return get.distance(player,current)>1 - }); - } - return false; - }, - filterTarget:function(card,player,target){ - return target!=player&&get.distance(player,target)>1; - }, - content:function(){ - player.storage.tuijin2=target; - player.addTempSkill('tuijin2'); - }, - ai:{ - order:11, - result:{ - target:function(player,target){ - if(get.attitude(player,target)<0){ - if(get.distance(player,target)>2) return -1.5; - return -1; - } - return 0.3; - } - } - } - }, - tuijin2:{ - mod:{ - globalFrom:function(from,to){ - if(to==from.storage.tuijin2) return -Infinity; - } - }, - mark:'character', - intro:{ - content:'与$的距离视为1直到回合结束' - }, - onremove:true - }, - jijia:{ - mark:true, - unique:true, - init:function(player){ - player.storage.jijia=4; - player.storage.jijia2=0; - }, - intro:{ - content:'机甲体力值:#' - }, - mod:{ - maxHandcard:function(player,num){ - if(player.storage.jijia>0){ - return num+player.storage.jijia; - } - } - }, - trigger:{player:'changeHp'}, - forced:true, - popup:false, - filter:function(event,player){ - return player.storage.jijia>0&&event.parent.name=='damage'&&event.num<0; - }, - content:function(){ - player.hp-=trigger.num; - player.update(); - player.storage.jijia+=trigger.num; - if(player.storage.jijia<=0){ - player.unmarkSkill('jijia'); - } - else{ - player.updateMarks(); - } - }, - ai:{ - threaten:function(player,target){ - if(target.storage.jijia<=0) return 2; - return 1; - } - } - }, - zihui:{ - enable:'phaseUse', - filter:function(event,player){ - return player.storage.jijia>0; - }, - filterTarget:function(card,player,target){ - return target!=player&&get.distance(player,target)<=2; - }, - unique:true, - selectTarget:-1, - skillAnimation:true, - animationColor:'fire', - line:'fire', - // alter:true, - content:function(){ - 'step 0' - var num=player.storage.jijia; - if(get.is.altered('zihui')){ - num=Math.max(1,Math.min(num,target.countCards('he'))); - } - target.chooseToDiscard(num,'he','弃置'+get.cnNumber(num)+'张牌,或受到2点火焰伤害').ai=function(card){ - if(target.hasSkillTag('nofire')) return 0; - if(get.type(card)!='basic') return 11-get.value(card); - if(target.hp>4) return 7-get.value(card); - if(target.hp==4&&num>=3) return 7-get.value(card); - if(target.hp==3&&num>=4) return 7-get.value(card); - if(num>1) return 8-get.value(card); - return 10-get.value(card); - }; - 'step 1' - if(!result.bool){ - target.damage(2,'fire'); - } - if(target==targets[targets.length-1]){ - player.storage.jijia=0; - player.unmarkSkill('jijia'); - } - }, - ai:{ - order:2, - result:{ - player:function(player){ - var num=0; - var players=game.filterPlayer(); - for(var i=0;i2) continue; - var nh=players[i].countCards('h'); - var att=get.attitude(player,players[i]); - if(nh0){ - if(players[i].hp<=2){ - num-=2; - } - else{ - num-=1.5; - } - } - } - else if(nh==player.storage.jijia){ - if(att<0){ - num+=0.5; - } - else if(att>0){ - num-=0.5; - } - } - } - if(num>=2) return 1; - return 0; - } - } - } - }, - xiandan:{ - trigger:{player:'shaBegin'}, - direct:true, - content:function(){ - "step 0" - var dis=trigger.target.countCards('h','shan')||trigger.target.getEquip('bagua')||trigger.target.countCards('h')>2; - var att=get.attitude(player,trigger.target); - var next=player.chooseToDiscard(get.prompt('xiandan')); - next.ai=function(card){ - if(att) return 0; - if(dis) return 7-get.value(card); - return 0; - } - next.logSkill='xiandan'; - "step 1" - if(result.bool){ - if(get.color(result.cards[0])=='red'){ - trigger.directHit=true; - } - else{ - player.addTempSkill('xiandan2','shaAfter'); - } - } - } - }, - xiandan2:{ - trigger:{source:'damageBegin'}, - filter:function(event){ - return event.card&&event.card.name=='sha'&&event.notLink(); - }, - forced:true, - popup:false, - content:function(){ - trigger.num++; - } - }, - shouge:{ - trigger:{source:'dieAfter'}, - frequent:true, - content:function(){ - player.gain(game.createCard('zhiliaobo'),'gain2'); - } - }, - tuji:{ - mod:{ - globalFrom:function(from,to,distance){ - if(_status.currentPhase==from){ - return distance-from.countUsed(); - } - }, - }, - }, - mujing:{ - enable:['chooseToRespond','chooseToUse'], - filterCard:function(card){ - return get.color(card)=='black'; - }, - position:'he', - viewAs:{name:'sha'}, - viewAsFilter:function(player){ - if(!player.countCards('he',{color:'black'})) return false; - }, - prompt:'将一张黑色牌当杀使用或打出', - check:function(card){return 4-get.value(card)}, - ai:{ - skillTagFilter:function(player){ - if(!player.countCards('he',{color:'black'})) return false; - }, - respondSha:true, - }, - group:'mujing2' - }, - mujing2:{ - trigger:{player:'shaMiss'}, - forced:true, - popup:false, - filter:function(event){ - return !event.parent._mujinged; - }, - content:function(){ - trigger.parent._mujinged=true; - player.getStat().card.sha--; - } - }, - lichang:{ - trigger:{player:'phaseEnd'}, - direct:true, - filter:function(event,player){ - return player.countCards('he',{color:'red'})>0; - }, - content:function(){ - "step 0" - var next=player.chooseToDiscard(get.prompt('lichang'),'he',{color:'red'}); - next.logSkill='lichang'; - next.ai=function(card){ - return 6-get.value(card); - }; - "step 1" - if(result.bool){ - player.addSkill('lichang2'); - } - }, - }, - lichang2:{ - trigger:{player:'phaseBegin'}, - direct:true, - mark:true, - intro:{ - content:'下个准备阶段令一名距离1以内的角色回复一点体力或摸两张牌' - }, - content:function(){ - 'step 0' - player.chooseTarget(get.prompt('lichang'),function(card,player,target){ - return get.distance(player,target)<=1; - }).ai=function(target){ - var att=get.attitude(player,target); - if(att>0){ - if(target.hp==1&&target.maxHp>1) return att*2; - } - return att; - }; - player.removeSkill('lichang2'); - 'step 1' - if(result.bool){ - player.logSkill('lichang',result.targets); - result.targets[0].chooseDrawRecover(2,true); - } - } - }, - mujing_old:{ - trigger:{player:'useCardToBegin'}, - filter:function(event,player){ - return event.target&&event.target!=player&&get.distance(event.target,player,'attack')>1; - }, - direct:true, - content:function(){ - 'step 0' - player.discardPlayerCard(get.prompt('mujing'),trigger.target).logSkill=['mujing']; - 'step 1' - if(result.bool&&player.countCards('h')<=trigger.target.countCards('h')){ - player.draw(); - } - } - }, - zhanlong:{ - trigger:{player:'phaseBegin'}, - unique:true, - mark:true, - skillAnimation:true, - init:function(player){ - player.storage.zhanlong=false; - }, - check:function(event,player){ - if(player.hasJudge('lebu')) return false; - return true; - }, - filter:function(event,player){ - if(player.storage.zhanlong) return false; - if(player.countCards('he')==0) return false; - if(player.hp!=1) return false; - return true; - }, - content:function(){ - 'step 0' - player.discard(player.getCards('he')); - 'step 1' - player.addTempSkill('zhanlong2'); - player.awakenSkill('zhanlong'); - player.storage.zhanlong=true; - var cards=[]; - for(var i=0;i<3;i++){ - cards.push(game.createCard('sha')); - } - player.gain(cards,'gain2'); - }, - ai:{ - threaten:function(player,target){ - if(target.hp==1) return 3; - return 1; - }, - effect:{ - target:function(card,player,target){ - if(!target.hasFriend()) return; - if(get.tag(card,'damage')==1&&target.hp==2&&target.countCards('he')&& - !target.isTurnedOver()&&_status.currentPhase!=target){ - if(get.distance(_status.currentPhase,target,'absolute')<=2) return [0.5,1]; - return 0.8; - } - } - } - }, - intro:{ - content:'limited' - } - }, - zhanlong2:{ - mod:{ - cardUsable:function(card){ - if(card.name=='sha') return Infinity; - } - } - }, - feiren:{ - trigger:{source:'damageBegin'}, - forced:true, - // alter:true, - filter:function(event,player){ - return !get.is.altered('feiren')&&event.card&&event.card.name=='sha'&&get.suit(event.card)=='spade'&&event.notLink(); - }, - content:function(){ - trigger.num++; - }, - mod:{ - targetInRange:function(card){ - if(card.name=='sha') return true; - }, - selectTarget:function(card,player,range){ - if(card.name=='sha'&&range[1]!=-1&&get.suit(card)=='club'){ - range[1]++; - } - }, - }, - ai:{ - threaten:1.4 - } - }, - feiren3:{ - trigger:{player:'useCardAfter'}, - filter:function(event,player){ - if(event.parent.name=='feiren2') return false; - if(event.card.name!='sha') return false; - if(get.suit(event.card)!='spade') return false; - var card=game.createCard(event.card.name,event.card.suit,event.card.number,event.card.nature); - for(var i=0;i0){ - return 0; - } - return get.recoverEffect(target,player,target); - } - } - } - }, - xie2:{ - mark:true, - trigger:{global:'phaseEnd'}, - forced:true, - filter:function(event,player){ - if(player.storage.xie=='now'){ - return event.player==player; - } - var num=game.phaseNumber-player.storage.xie; - return num&&num%6==0; - }, - content:function(){ - if(player.storage.xie=='now'){ - player.storage.xie=game.phaseNumber; - } - player.recover(); - }, - intro:{ - content:function(storage,player){ - var str='每隔六回合回复一点体力,直到'+get.translation(storage)+'死亡'; - if(typeof player.storage.xie=='number'){ - var num=game.phaseNumber-player.storage.xie; - num=num%6; - if(num==0){ - str+='(下次生效于本回合)' - } - else{ - str+='(下次生效于'+(6-num)+'回合后)' - } - } - return str; - }, - onunmark:function(storage,player){ - delete player.storage.xie; - delete player.storage.xie2; - } - }, - group:['xie3','xie4'] - }, - xie3:{ - trigger:{global:'phaseBegin'}, - forced:true, - popup:false, - content:function(){ - var num=game.phaseNumber-player.storage.xie; - num=num%6; - if(num){ - num=6-num; - } - player.storage.xie2_markcount=num; - player.updateMarks(); - } - }, - xie4:{ - trigger:{global:'dieAfter'}, - forced:true, - popup:false, - filter:function(event,player){ - return event.player==player.storage.xie2; - }, - content:function(){ - game.log(player,'解除了','【谐】'); - player.removeSkill('xie2'); - } - }, - luan:{ - enable:'phaseUse', - unique:true, - filterTarget:function(card,player,target){ - return target!=player&&!target.hasSkill('luan2'); - }, - filter:function(event,player){ - return player.countCards('h',{suit:'spade'}); - }, - filterCard:{suit:'spade'}, - check:function(card){ - return 7-get.value(card); - }, - content:function(){ - var current=game.findPlayer(function(player){ - return player.hasSkill('luan2'); - }); - if(current){ - current.removeSkill('luan2'); - } - target.addSkill('luan2'); - // target.storage.luan='now'; - target.storage.luan2=player; - }, - ai:{ - expose:0.2, - order:9.1, - threaten:2, - result:{ - target:function(player,target){ - var current=game.findPlayer(function(player){ - return player.hasSkill('luan2'); - }); - if(current&&get.attitude(player,current)<0){ - return 0; - } - if(target.hp==1) return 0.5; - return -1; - } - } - } - }, - luan2:{ - mark:true, - intro:{ - content:'受到的伤害后流失一点体力,直到首次进入濒死状态' - }, - trigger:{player:'damageEnd'}, - forced:true, - content:function(){ - player.loseHp(); - }, - ai:{ - threaten:1.2 - }, - group:['luan3','luan4'] - }, - luan3:{ - trigger:{player:'dyingAfter'}, - forced:true, - popup:false, - content:function(){ - game.log(player,'解除了','【乱】'); - player.removeSkill('luan2'); - } - }, - luan2_old:{ - mark:true, - trigger:{global:'phaseEnd'}, - forced:true, - filter:function(event,player){ - if(player.storage.luan=='now'){ - return event.player==player; - } - var num=game.phaseNumber-player.storage.luan; - return num&&num%6==0; - }, - content:function(){ - if(player.storage.luan=='now'){ - player.storage.luan=game.phaseNumber; - } - player.loseHp(); - }, - intro:{ - content:function(storage,player){ - var str='每隔六回合失去一点体力,直到'+get.translation(storage)+'死亡'; - if(typeof player.storage.luan=='number'){ - var num=game.phaseNumber-player.storage.luan; - num=num%6; - if(num==0){ - str+='(下次生效于本回合)' - } - else{ - str+='(下次生效于'+(6-num)+'回合后)' - } - } - return str; - }, - onunmark:function(storage,player){ - delete player.storage.luan; - delete player.storage.luan2; - } - }, - group:['luan3','luan4'] - }, - luan3_old:{ - trigger:{global:'phaseBegin'}, - forced:true, - popup:false, - content:function(){ - var num=game.phaseNumber-player.storage.luan; - num=num%6; - if(num){ - num=6-num; - } - player.storage.luan2_markcount=num; - player.updateMarks(); - } - }, - luan4:{ - trigger:{global:'dieAfter'}, - forced:true, - popup:false, - filter:function(event,player){ - return event.player==player.storage.luan2; - }, - content:function(){ - game.log(player,'解除了','【乱】'); - player.removeSkill('luan2'); - } - }, - sheng:{ - enable:'phaseUse', - unique:true, - mark:true, - skillAnimation:true, - animationColor:'metal', - init:function(player){ - player.storage.sheng=false; - }, - filter:function(event,player){ - if(player.storage.sheng) return false; - return true; - }, - filterTarget:function(card,player,target){ - return target.isDamaged(); - }, - selectTarget:[1,Infinity], - contentBefore:function(){ - player.turnOver(); - player.addSkill('sheng2'); - player.awakenSkill('sheng'); - player.storage.sheng=true; - }, - content:function(){ - target.recover(); - }, - ai:{ - order:1, - result:{ - target:function(player,target){ - var eff=get.recoverEffect(target,player,target); - if(player.hp==1) return eff; - if(player.hasUnknown()) return 0; - var num1=0,num2=0,num3=0,players=game.filterPlayer(); - for(var i=0;i0){ - num1++; - if(players[i].isDamaged()){ - num2++; - if(players[i].hp<=1){ - num3++; - } - } - } - } - if(num1==num2) return eff; - if(num2==num1-1&&num3) return eff; - if(num3>=2) return eff; - return 0; - } - }, - }, - intro:{ - content:'limited' - } - }, - sheng2:{ - trigger:{player:'phaseBegin'}, - forced:true, - popup:false, - content:function(){ - player.removeSkill('sheng2'); - }, - mod:{ - targetEnabled:function(card,player,target){ - if(player!=target) return false; - } - } - }, - yihun:{ - trigger:{player:'phaseEnd'}, - direct:true, - filter:function(event,player){ - return player.countCards('he',{color:'black'})>0&&!player.hasSkill('yihun2'); - }, - content:function(){ - 'step 0' - var next=player.chooseCardTarget({ - prompt:get.prompt('yihun'), - position:'he', - filterCard:function(card,player){ - return get.color(card)=='black'&&lib.filter.cardDiscardable(card,player); - }, - ai1:function(card){ - return 7-get.value(card); - }, - ai2:function(target){ - var att=-get.attitude(player,target); - if(target==player.next){ - att/=10; - } - if(target==player.next.next){ - att/=2; - } - return att; - }, - filterTarget:function(card,player,target){ - return player!=target; - }, - }); - 'step 1' - if(result.bool){ - player.discard(result.cards); - player.logSkill('yihun',result.targets); - player.addSkill('yihun2'); - var target=result.targets[0] - player.storage.yihun2=target; - if(target&&(get.mode()!='guozhan')||!target.isUnseen()){ - player.markSkillCharacter('yihun2',target,'移魂','在'+get.translation(target)+'的下一准备阶段视为对其使用一张杀'); - } - } - }, - }, - yihun2:{ - trigger:{global:['phaseBegin','dieAfter']}, - forced:true, - filter:function(event,player){ - return event.player==player.storage.yihun2; - }, - content:function(){ - if(player.storage.yihun2.isIn()){ - player.useCard({name:'sha'},player.storage.yihun2); - } - player.removeSkill('yihun2'); - delete player.storage.yihun2; - }, - mod:{ - targetEnabled:function(){ - return false; - }, - cardEnabled:function(card,player){ - return false; - }, - } - }, - huoyu:{ - enable:'phaseUse', - unique:true, - mark:true, - skillAnimation:true, - animationColor:'fire', - init:function(player){ - player.storage.huoyu=false; - }, - filter:function(event,player){ - if(player.storage.huoyu) return false; - if(player.countCards('he',{color:'red'})<2) return false; - return true; - }, - filterTarget:function(card,player,target){ - return player.canUse('chiyuxi',target); - }, - filterCard:{color:'red'}, - selectCard:2, - position:'he', - check:function(card){ - return 7-get.value(card); - }, - selectTarget:-1, - multitarget:true, - multiline:true, - line:'fire', - content:function(){ - 'step 0' - targets.sort(lib.sort.seat); - player.awakenSkill('huoyu'); - player.storage.huoyu=true; - player.useCard({name:'chiyuxi'},targets).animate=false; - 'step 1' - player.useCard({name:'chiyuxi'},targets).animate=false; - }, - ai:{ - order:5, - result:{ - target:function(player,target){ - if(player.hasUnknown()) return 0; - return get.effect(target,{name:'chiyuxi'},player,target); - } - }, - }, - intro:{ - content:'limited' - } - }, - feidan:{ - trigger:{source:'damageAfter'}, - direct:true, - filter:function(event,player){ - if(player.countCards('he')==0) return false; - if(!event.card) return false; - if(event.card.name!='sha') return false; - return game.hasPlayer(function(current){ - return current!=event.player&&get.distance(event.player,current)<=1 - }); - }, - content:function(){ - "step 0" - var eff=0; - var targets=game.filterPlayer(function(current){ - if(current!=trigger.player&&get.distance(trigger.player,current)<=1){ - eff+=get.damageEffect(current,player,player); - return true; - } - }); - event.targets=targets; - player.chooseToDiscard(get.prompt('feidan',targets)).set('ai',function(card){ - if(eff>0) return 7-get.value(card); - return 0; - }).set('logSkill',['feidan',targets]); - "step 1" - if(result.bool){ - event.targets.sort(lib.sort.seat); - } - else{ - event.finish(); - } - "step 2" - if(event.targets.length){ - event.targets.shift().damage(); - event.redo(); - } - }, - mod:{ - targetInRange:function(card,player,target){ - if(card.name=='sha'){ - if(get.distance(player,target)<=1) return false; - return true; - } - } - } - }, - yuedong:{ - trigger:{player:'phaseUseEnd'}, - direct:true, - content:function(){ - 'step 0' - var num=1+player.storage.yuedong_num; - player.chooseTarget(get.prompt('yuedong'),[1,num],function(card,player,target){ - if(player.storage.yuedong_recover){ - return target.hp1) return att/5; - if(num2==1){ - if(num>1) return att/3; - return att/4; - } - return att*1.1; - } - return att; - }); - 'step 1' - if(result.bool){ - player.logSkill('yuedong',result.targets); - var eff=1+player.storage.yuedong_eff; - if(player.storage.yuedong_recover){ - result.targets.sort(lib.sort.seat); - for(var i=0;i1&&!player.storage.yuedong_recover; - }, - check:function(card){ - return 6-get.value(card); - }, - content:function(){ - player.storage.yuedong_recover=true; - }, - ai:{ - order:10.2, - result:{ - player:function(player){ - var num1=0,num2=0,players=game.filterPlayer(); - for(var i=0;i0){ - num2++; - if(players[i].hp<=2&&players[i].maxHp>2){ - num1++; - if(players[i].hp==1){ - num1++; - } - } - } - } - if(num1>=3){ - return 1; - } - return 0; - } - } - } - }, - kuoyin:{ - enable:'phaseUse', - filterCard:true, - selectCard:function(){ - if(get.is.altered('kuoyin')) return 1; - if(_status.event.player.storage.yuedong_eff) return 1; - if(_status.event.player.storage.yuedong_num) return 2; - return [1,2]; - }, - position:'he', - // alter:true, - filter:function(event,player){ - if(get.is.altered('kuoyin')&&player.storage.yuedong_num) return false; - if(player.storage.yuedong_eff&&player.storage.yuedong_num) return false; - return player.countCards('he')>0; - }, - check:function(card){ - var player=_status.event.player; - var num1=0,num2=0,players=game.filterPlayer(); - for(var i=0;i0){ - num2++; - if(players[i].hp<=2&&players[i].maxHp>2){ - num1++; - } - } - } - if(player.storage.yuedong_recover){ - if(num1>1&&!player.storage.yuedong_num){ - if(ui.selected.cards.length) return 0; - return 7-get.value(card); - } - return 0; - } - else{ - if(num2>1&&!player.storage.yuedong_num){ - if(ui.selected.cards.length) return 0; - return 7-get.value(card); - } - if(num2>2){ - return 6-get.value(card); - } - return 5-get.value(card); - } - }, - content:function(){ - if(cards.length==1){ - player.storage.yuedong_num+=2; - } - else{ - player.storage.yuedong_eff++; - } - }, - ai:{ - threaten:1.6, - order:10.1, - result:{ - player:1 - } - }, - group:'kuoyin2' - }, - kuoyin2:{ - trigger:{player:'phaseBegin'}, - silent:true, - content:function(){ - player.storage.yuedong_recover=false; - player.storage.yuedong_num=0; - player.storage.yuedong_eff=0; - } - }, - guangshu:{ - enable:'phaseUse', - check:function(card){ - var player=_status.event.player; - var suit=get.suit(card); - if(suit=='heart'){ - if(game.hasPlayer(function(current){ - return current.hp==1&&get.attitude(player,current)>0 - })); - } - else if(suit=='spade'){ - return 7-get.value(card); - } - return 6-get.value(card); - }, - filter:function(event,player){ - return player.countCards('he')>0; - }, - filterTarget:function(card,player,target){ - return !target.hasSkill('guangshu_heart')&& - !target.hasSkill('guangshu_spade')&& - !target.hasSkill('guangshu_club')&& - !target.hasSkill('guangshu_diamond'); - }, - filterCard:true, - position:'he', - content:function(){ - target.addSkill('guangshu_'+get.suit(cards[0])); - }, - ai:{ - expose:0.2, - threaten:1.6, - order:5, - result:{ - target:function(player,target){ - if(!ui.selected.cards.length) return 0; - switch(get.suit(ui.selected.cards[0])){ - case 'heart':if(target.hp==1) return 1;return 0.1; - case 'diamond':return 1+Math.sqrt(target.countCards('h')); - case 'club':return -target.countCards('h')-Math.sqrt(target.countCards('h','sha')); - case 'spade':return get.damageEffect(target,player,target,'thunder'); - default:return 0; - } - } - } - } - }, - guangshu_diamond:{ - mark:true, - intro:{ - content:'下次造成伤害时摸两张牌' - }, - trigger:{source:'damageEnd'}, - forced:true, - content:function(){ - player.draw(2); - player.removeSkill('guangshu_diamond'); - } - }, - guangshu_heart:{ - mark:true, - intro:{ - content:'下次受到伤害时回复一点体力' - }, - trigger:{player:'damageEnd'}, - priority:6, - forced:true, - content:function(){ - player.recover(); - player.removeSkill('guangshu_heart'); - } - }, - guangshu_club:{ - mark:true, - intro:{ - content:'无法使用杀直到下一回合结束' - }, - trigger:{player:'phaseEnd'}, - forced:true, - popup:false, - content:function(){ - player.removeSkill('guangshu_club'); - }, - mod:{ - cardEnabled:function(card){ - if(card.name=='sha') return false; - } - } - }, - guangshu_spade:{ - mark:true, - intro:{ - content:'下个结束阶段受到一点无来源的雷电伤害' - }, - trigger:{player:'phaseEnd'}, - forced:true, - content:function(){ - player.damage('thunder','nosource'); - player.removeSkill('guangshu_spade'); - } - }, - ziyu:{ - trigger:{global:'phaseEnd'}, - direct:true, - filter:function(event,player){ - if(get.is.altered('ziyu')) return game.phaseNumber%6==0; - return game.phaseNumber%4==0; - }, - // alter:true, - content:function(){ - player.chooseDrawRecover(get.prompt('ziyu')).logSkill='ziyu'; - } - }, - shouhu:{ - mod:{ - cardEnabled:function(card){ - if(card.name=='sha') return false; - }, - }, - enable:'phaseUse', - filter:function(event,player){ - return player.countCards('h','sha')>0; - }, - filterTarget:function(card,player,target){ - return target.hpplayer.hp&&player.countCards('h','lebu')==0)||get.distance(player,event.player)>1); - }, - // alter:true, - intro:{ - content:function(storage,player){ - var str=''; - if(player.storage.shanxian_h.length){ - if(player.isUnderControl(true)){ - str+='手牌区:'+get.translation(player.storage.shanxian_h); - } - else{ - str+='手牌区:'+(player.storage.shanxian_h.length)+'张牌'; - } - } - if(player.storage.shanxian_e.length){ - if(str.length) str+='、'; - if(player.isUnderControl(true)){ - str+='装备区:'+get.translation(player.storage.shanxian_e); - } - else{ - str+='装备区:'+(player.storage.shanxian_e.length)+'张牌'; - } - } - return str; - }, - mark:function(dialog,content,player){ - if(player.storage.shanxian_h.length){ - if(player.isUnderControl(true)){ - dialog.add('
          手牌区
          '); - dialog.addSmall(player.storage.shanxian_h); - } - else{ - dialog.add('
          手牌区:'+player.storage.shanxian_h.length+'张牌
          '); - } - } - if(player.storage.shanxian_e.length){ - if(player.isUnderControl(true)){ - dialog.add('
          装备区
          '); - dialog.addSmall(player.storage.shanxian_e); - } - else{ - dialog.add('
          装备区:'+player.storage.shanxian_e.length+'张牌
          '); - } - } - }, - }, - logTarget:'player', - content:function(){ - "step 0" - if(!get.is.altered('shanxian')){ - player.draw(false); - player.$draw(); - } - "step 1" - player.storage.shanxian_h=player.getCards('h'); - player.storage.shanxian_e=player.getCards('e'); - player.storage.shanxian_n=1; - player.syncStorage('shanxian_e'); - player.phase('shanxian'); - player.storage.shanxian=trigger.player; - player.removeSkill('shanxian2'); - player.markSkill('shanxian'); - "step 2" - player.turnOver(true); - delete player.storage.shanxian; - }, - mod:{ - targetInRange:function(card,player,target,now){ - if(target==player.storage.shanxian) return true; - }, - }, - ai:{ - expose:0.1, - effect:{ - target:function(card){ - if(card.name=='guiyoujie') return [0,0]; - } - } - } - }, - shanxian2:{ - trigger:{player:['gainBegin','loseBegin']}, - forced:true, - popup:false, - content:function(){ - player.removeSkill('shanxian2'); - } - }, - shanhui:{ - unique:true, - trigger:{player:'damageEnd',source:'damageEnd'}, - filter:function(event,player){ - return player.storage.shanxian_h&&player.storage.shanxian_e&& - player.storage.shanxian_n>0&&!player.hasSkill('shanxian2'); - }, - check:function(event,player){ - var n1=player.countCards('he'); - var n2=player.storage.shanxian_h.length+player.storage.shanxian_e.length; - if(n1player.storage.shanxian_h.length+player.storage.shanxian_e.length){ - player.recover(); - } - player.storage.shanxian_n--; - if(player.storage.shanxian_n<=0){ - delete player.storage.shanxian_h; - delete player.storage.shanxian_e; - delete player.storage.shanxian_n; - player.unmarkSkill('shanxian'); - } - else{ - player.addSkill('shanxian2'); - } - } - } - }, - translate:{ - woliu:'涡流', - woliu2:'涡流', - woliu_info:'结束阶段,你可以选择至多两名角色,当你或目标中的任意一名角色成为杀的目标时,其余角色也将被追加为目标,直到你死亡或下一回合开始', - qianggu:'强固', - qianggu_info:'出牌阶段限一次,你可以弃置两张牌并获得两点护甲,若如此做,直到你的下个回合开始,其他角色对你使用杀时需要弃置一张基本牌,否则杀对你无效', - qianggu2:'强固', - qianggu2_bg:'固', - qianggu2_info:'其他角色对你使用杀时需要弃置一张基本牌,否则杀对你无效', - pingzhang:'屏障', - pingzhang_info:'每轮各限一次,当你受到伤害时,你可以弃置一张红桃牌令伤害-1;当一名其他角色受到伤害时,你可以弃置一张黑桃牌令伤害-1', - pingzhang_info_alter:'每轮各限一次,当你受到伤害时,你可以弃置一张红桃手牌令伤害-1;当一名其他角色受到伤害时,你可以弃置一张黑桃手牌令伤害-1', - liyong:'力涌', - liyong_info:'锁定技,你摸牌阶段摸牌数+X,X为你上一轮发动屏障的次数', - dianji:'电击', - dianji_info:'出牌阶段限一次,你可以将一张手牌当作惊雷闪对距离2以内的角色使用', - feitiao:'飞跳', - feitiao2:'飞跳', - feitiao_info:'出牌阶段开始时,你可以弃置一张牌并指定一名角色,你与该角色的距离视为1直到回合结束,然后该角色随机弃置一张牌', - bshaowei:'哨卫', - bshaowei_info:'结束阶段,你可以切换至哨卫模式。当处于此模式时,你的杀无视距离和防具、无数量限制且不可闪避;你不能闪避杀', - zhencha:'侦查', - zhencha_info:'结束阶段,你可以切换至侦查模式。当处于此模式时,每当你使用一张杀,你摸一张牌或回复一点体力', - liangou:'链钩', - liangou_info:'出牌阶段限一次,你可以弃置一张牌,指定一名其他角色并进行一次判定,若结果不为红桃,该角色与你距离为1且受到的首次伤害+1直到回合结束', - xiyang:'吸氧', - xiyang_info:'结束阶段,若你武将牌正面朝上,你可以翻面并回复两点体力', - qinru:'侵入', - qinru_info:'每当你使用杀指定目标时,你可以令其进行一次判定,若结果不为红桃,该角色的非锁定技失效直到其下一回合结束', - yinshen:'隐身', - yinshen_info:'锁定技,每当你失去最后一张基本牌,你获得潜行直到下一回合开始', - yinshen_info_old:'结束阶段,你可以弃置一张装备牌并获得潜行直到下一回合开始', - maichong:'脉冲', - maichong_info:'锁定技,每当你使用一张普通锦囊牌,你令最近三回合内被你侵入过的角色各随机弃置一张牌', - maichong_info_alter:'准备阶段,你可以令最近两名被你侵入的角色各随机弃置一张牌', - lichang:'力场', - lichang2:'力场', - lichang_info:'结束阶段,你可以弃置一张红色牌,若如此做,你可以在下个准备阶段令一名距离1以内的角色回复一点体力或摸两张牌', - mengji:'猛击', - mengji_info:'锁定技,若你已发动重盾,当你没有护甲时,你的杀造成的伤害+1', - zhongdun:'重盾', - zhongdun_info:'游戏开始时,你获得8点护甲;出牌阶段限一次,你可以弃置一张牌并将一点护甲分给一名没有护甲的其他角色', - zhongdun_info_alter:'游戏开始时,你获得6点护甲;出牌阶段限一次,你可以弃置一张牌并将一点护甲分给一名没有护甲的其他角色', - paotai:'炮台', - paotai2:'炮台', - paotai_info:'出牌阶段,你可以弃置一张杀布置或升级一个炮台(最高3级);结束阶段,炮台有一定机率对一名随机敌人造成一点火焰伤害;每当你受到一点伤害,炮台降低一级', - maoding:'铆钉', - maoding2:'铆钉', - maoding_info:'每当你造成或受到一次伤害,你可以获得一个零件;出牌阶段,你可以弃置两张零件牌令一名没有护甲的角色获得一点护甲', - maoding_info_alter:'每当你造成一次伤害,你可以获得一个零件;出牌阶段,你可以弃置两张零件牌令一名没有护甲的角色获得一点护甲', - bfengshi:'风矢', - bfengshi2:'风矢', - bfengshi_info:'锁定技,在一合内每当你使用一张牌,你的攻击范围+1;你的首张杀增加20%的概率强制命中;你的首张杀造成伤害后增加20%的概率令伤害+1', - bfengshi_info_alter:'锁定技,在一合内每当你使用一张牌,你的攻击范围+1;你的首张杀增加15%的概率强制命中;你的首张杀造成伤害后增加15%的概率令伤害+1', - yinbo:'音波', - yinbo_info:'出牌阶段限一次,你可以弃置一张黑桃牌,然后随机弃置三名敌人各一张牌', - liudan:'榴弹', - liudan_info:'每当你使用一张杀,你可以令所有不是此杀目标的其他角色有50%概率成为此杀的额外目标', - shoujia:'兽夹', - shoujia2:'兽夹', - shoujia3:'兽夹', - shoujia_info:'出牌阶段限一次,你可以将一张牌背面朝上置于一名其他角色的武将牌上,当该角色使用一张与此牌花色相同的牌指定其他角色为目标时,移去此牌,该角色将武将牌翻至背面;当你受到伤害时,移去此牌', - shihuo:'嗜火', - shihuo_info:'锁定技,每当一名角色受到火焰伤害,你摸一张牌', - shanguang:'闪光', - shanguang_info:'出牌阶段限一次,你可以弃置一张方片牌令攻击范围内的一名其他角色本回合内不能使用或打出卡牌', - tiandan:'填弹', - tiandan_info:'摸牌阶段开始时,你可以跳过出牌和弃牌阶段,然后获得若干张杀直到你的手牌数等于你的体值(最多为5)', - shenqiang:'神枪', - shenqiang_info:'锁定技,每当你在出牌阶段使用杀造成伤害,本阶段内出杀次数上限+1', - mianzhen:'眠针', - mianzhen2:'眠针', - mianzhen_info:'出牌阶段限一次,你可以弃置一张牌并令一名其他角色打出一张闪,否则该角色不能使用或打出卡牌直到其受到伤害或下一回合结束', - aqianghua:'强化', - aqianghua2:'强化', - aqianghua_info:'出牌阶段限一次,你可以将你的全部手牌(至少一张)交给一名其他角色,该角色获得一点护甲且下一次造成的伤害+1', - aqianghua_info_alter:'出牌阶段限一次,你可以将你的全部手牌(至少一张)交给一名其他角色,该角色下一次造成的伤害+1', - zhiyuan:'支援', - zhiyuan_info:'每当你即将造成伤害,你可以防止此伤害,改为令目标回复等量的体力', - juji:'狙击', - juji2:'狙击', - juji3:'狙击', - juji_info:'出牌阶段限一次,你可以弃置任意张花色不同的牌并指定一名有手牌的其他角色,若该角色的手牌中含有与你弃置的牌花色相同的牌,则本回合内你与其距离为1且该角色不能闪避你的杀', - duwen:'毒吻', - duwen2:'毒吻', - duwen_info:'锁定技,当你造成伤害时,若你的手牌数与受伤害角色相等,此伤害+1', - zhuagou:'抓钩', - zhuagou_info:'出牌阶段限一次,你可以弃置一张手牌并将你的座位移到任意位置', - dulei:'诡雷', - dulei2:'诡雷', - dulei_info:'出牌阶段,若你武将牌上没有牌,你可以将一张牌背面朝上置于你的武将牌上,当一名角色使用与该牌花色相同的牌指定你为目标时,你展示并移去此牌,然后该角色失去一点体力并随机弃置一张牌', - shuangqiang:'霜枪', - shuangqiang_info:'每当你对一名未翻面的角色造成伤害,你可以令伤害-1,然后令受伤害角色翻面', - baoxue:'暴雪', - baoxue_info:'限定技,出牌阶段,若你未翻面,你可以展示并弃置你的所有黑色牌,然后令至多X名其他角色随机弃置一张牌并将武将牌翻至背面,X为你的弃牌数;结算后你将武将牌翻至背面', - baoxue_info_alter:'限定技,出牌阶段,你可以展示并弃置你的所有黑色牌,并选择等量其他角色将武将牌翻至背面,结算后你将武将牌翻至背面', - bingqiang:'冰墙', - bingqiang2:'冰墙', - bingqiang2_bg:'墙', - bingqiang3:'冰墙', - bingqiang3_bg:'墙', - bingqiang4:'冰墙', - bingqiang4_bg:'墙', - bingqiang5:'冰墙', - bingqiang5_bg:'障', - bingqiang_info:'出牌阶段,你可以弃置X张红色牌令一名角色和其相邻角色的防御离+X,或弃置X张黑色牌令一名角色和其相邻角色的进攻离-X,效果持续到你的下个回合开始', - jidong:'急冻', - jidong_info:'在一名角色的结束阶段,若你的体力值为1且未翻面,你可以翻面并回复两点体力,在你的武将牌翻至正面前,你防止所有伤害,也不能成为其他角色卡牌的目标', - jidong_info_alter:'在一名角色的结束阶段,若你的体力值为1,你可以翻面并回复两点体力', - jijia:'机甲', - jijia_info:'锁定技,游戏开始时,你获得一个体力为4的机甲;你的手牌上限为你和机甲的体力之和;你受到的伤害由机甲承担', - zihui:'自毁', - zihui_info:'出牌阶段,你可以令距离2以内的所有其他角色选择一项:弃置数量等同你机甲体力值的牌,或受到2点火焰伤害,并在结算完毕后摧毁你的机甲', - zihui_info_alter:'出牌阶段,你可以令距离2以内的所有其他角色选择一项:1. 弃置数量等同你机甲体力值的牌(不足则全弃,至少弃1张);2. 或受到2点火焰伤害,并在结算完毕后摧毁你的机甲', - tuijin:'推进', - tuijin2:'推进', - tuijin_info:'出牌阶段限一次,若你有机甲,你可以指定一名角色,本回合内视为与其距离为1', - chongzhuang:'重装', - chongzhuang_info:'在你失去机甲后,当你累计造成了4点伤害时,你重新获得机甲', - shouge:'收割', - shouge_info:'每当你杀死一名角色,你可以获得一张治疗波', - tuji:'突击', - tuji_info:'锁定技,在你的回合内,每当你使用一张牌,你的进攻距离+1', - mujing:'目镜', - mujing2:'目镜', - mujing_info:'你可以将一张黑色牌当作杀使用或打出;当你的杀被闪避后,此杀不计入出杀次数', - mujing_old_info:'每当你对攻击范围不含你的角色使用一张牌,你可以弃置目标一张牌;若你的手牌数不多于目标,你摸一张牌', - feiren:'飞刃', - feiren2:'飞刃', - feiren_info:'你的杀无视距离;你的黑桃杀造成的伤害+1,梅花杀可以额外指定一个目标', - feiren_info_alter:'你的杀无视距离;你的梅花杀可以额外指定一个目标', - zhanlong:'斩龙', - zhanlong_info:'限定技,准备阶段,若你体力值为1,你可以弃置所有牌(至少一张),然后将三张杀置入你的手牌,若如此做,你本回合使用杀无次数限制', - xie:'谐', - xie2:'谐', - xie_info:'出牌阶段,你可以弃置一张红桃手牌并指定一名角色,该角色自其下一回合开始每隔六回合回复一点体力,直到你死亡。同一时间只能对一人发动', - luan:'乱', - luan2:'乱', - luan_old_info:'出牌阶段,你可以弃置一张黑桃手牌并指定一名角色,该角色自其下一回合开始每隔六回合失去一点体力,直到你死亡。同一时间只能对一人发动', - luan_info:'出牌阶段,你可以弃置一张黑桃手牌并指定一名角色,该角色受到伤害后流失一点体力,直到你死亡或其首次进入濒死状态。同一时间只能对一人发动', - sheng:'圣', - sheng_info:'限定技,出牌阶段,你可以将你的武将牌翻面,然后令任意名角色回复一点体力,若如此做,你不能成为其他角色的卡牌目标直到下一回合开始', - xiandan:'霰弹', - xiandan_info:'每当你使用一张杀,你可以弃置一张红色牌令此杀不可闪避,或弃置一张黑色牌令此杀伤害+1', - yihun:'移魂', - yihun_info:'结束阶段,你可以弃置一张黑色牌并指定一名其他角色,你在该角色下一准备阶段视为对其使用一张杀;在此之前,你不能使用卡牌,也不能成为卡牌的目标', - feidan:'飞弹', - feidan_info:'你的杀只能对距离1以外的角色使用;每当你使用杀造成伤害后,你可以弃置一张牌对距离目标1以内的其他角色各造成一点伤害', - huoyu:'火雨', - huoyu_info:'限定技,出牌阶段,你可以弃置两张红色牌,视为使用两张炽羽袭', - yuedong:'乐动', - yuedong_info:'出牌阶段结束时,你可以令一名角色摸一张牌', - kuoyin:'扩音', - kuoyin_info:'出牌阶段,你可以弃置一张牌令本回合乐动的目标数改为3,或弃置两张牌令本回合乐动的摸牌量改为2', - kuoyin_info_alter:'出牌阶段,你可以弃置一张牌令本回合乐动的目标数改为3', - huhuan:'互换', - huhuan_info:'出牌阶段,你可以弃置两张牌令本回合乐动的摸牌效果改为回复等量体力', - guangshu:'光枢', - guangshu_heart:'光盾', - guangshu_spade:'光塔', - guangshu_club:'光井', - guangshu_diamond:'光流', - guangshu_info:'出牌阶段,你可以弃置一张牌,并指定一名角色,根据弃置牌的花色执行如下效果:♥该角色下次受到伤害时回复一点体力;♦︎该角色下次造成伤害时摸两张牌;♣该角色无法使用杀直到下一回合结束;♠该角色于下个结束阶段受到一点无来源的雷电伤害', - ziyu:'自愈', - ziyu_info:'在一名角色的结束阶段,你可以回复一点体力或摸一张牌,每隔四回合发动一次', - ziyu_info_alter:'在一名角色的结束阶段,你可以回复一点体力或摸一张牌,每隔六回合发动一次', - shouhu:'守护', - shouhu_info:'你不能使用杀;出牌阶段,你可以弃置一张杀令一名其他角色回复一点体力', - shanxian:'闪现', - shanxian_info:'在一名其他角色的回合开始前,若你的武将牌正面朝上,你可以摸一张牌并进行一个额外回合,并在回合结束后将武将牌翻至背面。若如此做,你对其使用卡牌无视距离直到回合结束。', - shanxian_info_alter:'在一名其他角色的回合开始前,若你的武将牌正面朝上,你可以进行一个额外回合,并在回合结束后将武将牌翻至背面。若如此做,你对其使用卡牌无视距离直到回合结束。', - shanhui:'闪回', - shanhui_info:'当你造成或受到伤害后,你可以将你的牌重置为上次发动闪现时的状态,若你的牌数因此而减少,你回复一点体力', - ow_liekong:'猎空', - ow_sishen:'死神', - ow_tianshi:'天使', - ow_falaozhiying:'法老之鹰', - ow_zhixuzhiguang:'秩序之光', - ow_luxiao:'卢西奥', - ow_shibing:'士兵76', - ow_yuanshi:'源氏', - ow_chanyata:'禅雅塔', - ow_dva:'DVA', - ow_mei:'小美', - ow_heibaihe:'黑百合', - ow_ana:'安娜', - ow_baolei:'堡垒', - ow_maikelei:'麦克雷', - ow_banzang:'半藏', - ow_kuangshu:'狂鼠', - ow_tuobiang:'托比昂', - ow_laiyinhate:'莱因哈特', - ow_luba:'路霸', - ow_wensidun:'温斯顿', - ow_zhaliya:'查莉娅', - ow_heiying:'黑影', - ow_orisa:'奥丽莎', - } - }; -}); +'use strict'; +game.import('character',function(lib,game,ui,get,ai,_status){ + return { + name:'ow', + character:{ + ow_liekong:['female','shu',3,['shanxian','shanhui']], + ow_sishen:['male','shu',4,['xiandan','yihun','shouge']], + ow_tianshi:['female','qun',3,['shouhu','ziyu','feiying']], + ow_falaozhiying:['female','shu',3,['feidan','huoyu','feiying']], + ow_zhixuzhiguang:['female','qun',3,['guangshu']], + ow_luxiao:['male','wu',3,['yuedong','kuoyin','huhuan']], + ow_shibing:['male','shu',4,['tuji','mujing','lichang']], + ow_yuanshi:['male','qun',3,['feiren','lianpo','zhanlong']], + ow_chanyata:['male','qun',3,['xie','luan','sheng']], + ow_dva:['female','shu',2,['jijia','tuijin','zihui','chongzhuang']], + ow_mei:['female','wei',3,['bingqiang','jidong','baoxue']], + ow_ana:['female','wei',3,['zhiyuan','mianzhen','aqianghua']], + ow_heibaihe:['female','qun',3,['juji','duwen','dulei']], + ow_maikelei:['male','shu',4,['shanguang','tiandan','shenqiang']], + ow_kuangshu:['male','shu',3,['liudan','shoujia','shihuo']], + + ow_tuobiang:['male','shu',3,['paotai','maoding']], + ow_baolei:['male','qun',4,['bshaowei','zhencha']], + ow_banzang:['male','qun',4,['bfengshi','yinbo']], + ow_laiyinhate:['male','qun',4,['zhongdun','mengji']], + ow_luba:['male','shu',4,['liangou','xiyang']], + ow_wensidun:['male','shu',4,['feitiao','dianji']], + ow_zhaliya:['female','wei',4,['pingzhang','liyong']], + + ow_heiying:['female','wei',3,['qinru','yinshen','maichong']], + ow_orisa:['female','wu',4,['qianggu','woliu']], + }, + characterIntro:{ + ow_orisa:'奥丽莎是用在努巴尼昙花一现的OR15防御机器人的零件组装而成的,她是这座城市的新一代守护者,但依然有很大的成长空间', + ow_liekong:'莉娜·奥克斯顿(代号:“猎空”)是守望先锋原型机试飞计划的最年轻成员。但在第一次试飞过程中,原型机的传送阵列出现故障,包括飞行员在内完全失踪。莉娜在几个月后再次出现,不过她身上的分子却无法和时间流同步。这种被称为“时间解离”的症状使她彻底变成了一个“活生生”的幽灵,时隐时现。直到一位名叫温斯顿的科学家设计出了“时间加速器”,一台可以让“猎空”维持在当前时间的装置。不仅如此,这一装置还让“猎空”有能力控制她自己的时间流,使她可以任意加速或减慢时间。有了这一全新的能力,她成了守望先锋最强大的特工之一。守望先锋解散后,“猎空”依旧选择为了正义而战,守护无辜。', + ow_sishen:'关于这个黑袍恐怖分子的传闻并不多,只知道大家都称他为“死神”。虽然没人知道他的真实身份和动机,但有一点是可以肯定的,他的出现意味着死亡。“死神”是一名极其不稳定、残暴、冷酷的雇佣兵,在世界各地犯下多起恐怖袭击案件。在过去的数十年间,他参与了许多武装冲突,但其本人却不属于任何组织。在多年的追踪后,“死神”神秘的面纱终于被慢慢揭开。据信,“死神”正在追杀前守望先锋特工并系统地逐一消灭。', + ow_tianshi:'齐格勒是瑞士一家顶尖医院的手术部门负责人。正是她在医学领域的成就,引起了守望先锋的注意。由于齐格勒的双亲都被战争夺走了生命,因此她从一开始就极其反对该组织通过军事手段进行维和。但最终,她意识到守望先锋给她提供了一个可以拯救更多人生命的机会。作为守望先锋医学研究部门的负责人,安吉拉致力于更好地在前线治疗受到致命伤的病员。尽管她对守望先锋做出了巨大的贡献,但齐格勒博士经常质疑她的上司以及守望先锋的长远目标。而当守望先锋解散之后,齐格勒博士便致力于帮助那些受战争波及的受难者。', + ow_falaozhiying:'本名法芮尔,来自一个军功卓越的军人世家,贡献与荣耀就是她最高的追求。在加入埃及军队后,她的坚韧和在战术上的天赋使她很快就晋升为了一名军官。作为一名果敢的领袖,任何在她手下服役的士兵都对她抱有绝对的忠诚。有了所有这些卓越的表现,法芮尔自然成了守望先锋最青睐的一名候选人。但在她正式加入之前,守望先锋就解散了。当她带着所有的荣誉退役后,法芮尔加入了一家名为“海力士国际安保”的私人安保公司,该公司的最重要的一笔订单就是负责保护吉萨高原地下的人工智能研究设施。尽管她对守望先锋的解散感到无比的遗憾,但她依旧梦想着能为正义而战并改变这个残破的世界。', + ow_zhixuzhiguang:'当被认为是为数不多可以成为光子建筑师的人才之后,年轻的塞特娅·法斯瓦尼离开了贫穷的家乡,成为了费斯卡光子建筑师学院的一员。她很快就成为了乌托邦最顶尖的光子建筑师。但费斯卡集团在塞特娅身上还是看到了更广阔的发展潜力。费斯卡集团称其为“秩序之光”,为了集团的利益和扩大在其他国家的影响力,将其派遣到全球执行秘密任务。虽然“秩序之光”相信她的所作所为是为了实现人性之“大善”,但有时候她也会怀疑她所希望实现的控制和秩序,是否真的是人类最需要的。', + ow_luxiao:'卢西奥•科雷亚•多斯桑托斯在里约热内卢长大。“智能危机”结束后,由于经济的一蹶不振导致这里变成了一个贫穷拥挤的贫民区。他想找到一个办法激发周围人的信心与活力:音乐。他开始在街边、社区派对进行表演,随着年龄的增长便开始了一系列传奇的地下演出。但当多国集团费斯卡集团计划重建城市的大部分地区时,卢西奥所在的社区陷入了混乱,人们失去了自由。卢西奥绝对不会容忍这一切。他偷走了费斯卡用来压迫人民的音波技术,反过来将人们团结在一起。最终,在一场暴动中,他们将费斯卡集团赶出了家园。卢西奥的领导能力让他在一夜之间成为了明星和社会正能量的象征。他的音乐在人们心中的地位如火箭般蹿升。随着影响力的不断扩大,卢西奥意识到他有机会可以改变这个世界,让这个世界变得更美好。', + ow_shibing:'被全球通缉的独行侠“士兵:76”独自一人发动了一场旨在查出守望先锋解散真相的战争。“士兵:76”在全球一系列针对金融机构、秘密集团和守望先锋基地的袭击活动中被曝光。尽管外界至今不清楚他的动机是什么,但有人认为他曾是一位守望先锋特工,决心查出守望先锋垮台的幕后黑手。', + ow_yuanshi:'岛田忍者家族大名最年轻的儿子,但对家族的非法生意毫无兴趣,被视为一个危险的累赘。在家族大名意外死亡后,他与哥哥半藏的矛盾激化,最终导致了一场生死对决,源氏也因此差点送命。后来被守望先锋救下,并被改造成机械忍者以摧毁他父亲的邪恶帝国。在完成任务后,源氏因无法接受自己的机械身躯,离开了守户先锋,并游历世界希望能找到自己存在的意义。数年之后,他遇到了智械僧侣禅雅塔,并且在这位僧侣的引导下,源氏体内的人类和机械体验终于融合在了一起。他开始明白,尽管他有一副机械身躯,但他的人类灵魂是完整的,他渐渐意识到自己的新形态是给予自己的恩赐和力量。', + ow_chanyata:'在“智能危机”结束之后,一群被放逐的智能机器人感受到了被其称为“灵魂觉醒”的升华之道,他们渐渐相信他们和人类一样,同样拥有灵魂。由神秘僧侣泰哈撒·孟达塔带领的这些僧侣开始寻找让人类和机器人重回曾经的和谐相处之道。他们最终被世人所接纳,并得到了全球数百万人的支持。但其中一位僧侣,禅雅塔并不赞同这一新道路。他认为要解决人类和机器人之间根深蒂固的问题,不能依靠循循善诱,而必须通过个体联系和互动。最终,他选择离开寺庙,游走世界,帮助那些他所遇到的人摆脱凡尘。但如果有必要,他也会为了保护无辜而拿起武器,无论人类还是机器人。', + ow_dva:'D.Va曾是一名职业玩家,而现在则利用自己的技巧驾驶一台尖端机甲保卫国家。随着智能机械不断进化,它最终干扰了MEKA的无人机控制网络,迫使军方派驾驶员驾驶这些机甲。由于难以找到合适的候选人,政府开始向那些拥有足以操控机甲尖端武器系统的必要反应和本能的国内职业玩家寻求帮助,其中就包括顶尖玩家之一的“D.Va”宋哈娜。作为一名为了获胜不惜一切代价的精英玩家,D.Va从来都不会对对手表现出丝毫的仁慈。D.Va将这次新任务视为一款全新的游戏,无所畏惧地和其他MEKA机甲冲向战场,随时准备保卫自己的国家。最近,她开始向她的粉丝直播战斗行动,而这也让她成为了世界巨星。', + ow_mei:'守望先锋为了查明全球不断升级的怪异气候现象的真正原因,在世界各个位置建立了一系列生态监测站。周美灵就是这一长久项目的成员之一。当她来到该项目的南极洲监控站时,一场突如其来的极地风暴摧毁了大部分设施并将这里与外界隔绝了开来。随着补给物资的不断消耗,科学家们进入了急冻状态希望能够撑到救援队抵达的那一天。但救援并没有抵达。几年后,当这些科学家的急冻舱最终被发现时,美是唯一的幸存者。此时,守望先锋已经解散,所有的生态监测站也都已经被废弃,他们之前收集的研究数据全部丢失。美最终决定独自继续她的工作。她带上了一台可穿戴式气候控制装置,游历世界,希望能够重新建立起生态监测网络,查出威胁着这个星球生态系统的真正原因。', + ow_ana:'守望先锋的创始成员之一,世界公认的顶级狙击手。智械危机结束后,安娜被晋升为了上尉。尽管身居要职,但已年过半百的安娜拒绝离开战场,依然亲临前线。直至在一次人质解救行动中,遭遇了一个叫做“黑百合”的黑爪特工,所有人都认为安娜死在了那场战斗中。但事实上,安娜活了下来,身受重伤并且失去了自己的右眼。在恢复期间,她感受到了战斗中生命的不可承受之重,因此决定就此隐居。然而随着时间一天天过去,看着自己家乡遭到的威胁愈演愈烈,她突然意识到自己依然有责任保护身边的亲人。在“征用”了守望先锋军械库中的装备后,安娜重新回到了这个世界,为了一个更安全、稳定的和平世界而战。', + ow_heibaihe:'“黑百合”在成为如今的杀手之前,曾与对抗恐怖组织“黑爪”的守望先锋探员杰哈·拉克瓦结婚。在多次刺杀杰哈无果之后,黑爪决定将目标转向他的妻子,艾米丽。黑爪特工绑架了艾米丽并对其进行了一项高强度神经重构计划。他们击垮了她的意志,抑制了其本身的人性,将其变成了一个潜伏特工。她最终被守望先锋探员找到并在确认无致命伤之后重新过上了以前的生活。两周之后,她杀死了睡梦中的杰哈,并回归了黑爪。', + ow_maikelei:'曾是美国西南部因非法军火交易而臭名昭著的“死局”帮中,最令人胆寒的一员,后被守望先锋逮捕。由于其枪法精湛且足智多谋,守望先锋给了他两个选择:在最高安全级别的监狱中度过余生,或加入守望先锋的秘密行动部队“暗影守望”。他选择了后者。尽管一开始他对守望先锋的理念嗤之以鼻,但他逐渐相信可以通过扫除世上的不公,来弥补自己过去犯下的罪行。后来,暗影守望内部出现了异样的气氛:废除守望先锋,独掌大权。麦克雷由于不想参与其中,于是便独自离开,销声匿迹。多年之后,他以雇佣兵的身份再次出现。尽管许多大小团体都想拉拢他,但他只为自己眼中的正义而战。', + ow_kuangshu:'由于澳大利亚智能中枢核心在遭到攻击后发生爆炸,这片地区现在变成了寸草不生的辐射荒地。但即便如此,还是有一群自称为“拾荒者”的人类生存了下来。他们在残骸中寻找一切还可利用的东西,渐渐形成了一个野蛮、危险的团体。“狂鼠”就是其中的一员。和其他人一样,他也受到了辐射,因此变成了一个痴迷于危险炸弹的疯子。当他在中枢废墟中发掘出一个极其珍贵的宝藏后,全世界都知道了这个疯子的名号。尽管几乎没人知道他到底发现了什么,但他身后总有数不清的赏金猎人、黑帮和投机分子想要杀掉他,直到他与“拾荒者”打手“路霸”达到了一个协议:只要今后找到的宝贝五五分成,“路霸”就会是“狂鼠”的私人保镖。', + ow_tuobiang:'托比昂是一个极其不信任智械的天才工程师,但他的同行都认为这只是他杞人忧天而已。但托比昂最担心的事情最终还是发生了,一场机器人对抗其人类发明者的“智能危机”在全球范围内爆发。由于其在工程学方面的天才造诣,守望先锋向他伸出了橄榄枝,并将其纳入了最早的守望先锋攻击部队,而他也证明了自己在终结这场危机中的关键价值。但在守望先锋解散后,托比昂设计出的许多武器被偷走并被藏在世界各地。出于对自己作品的责任心,托比昂便发誓不能让这些武器落入敌手,危害无辜的世人。', + ow_baolei:'“堡垒”系列在设计之初是被用于维和目的的,这一系列的机器单位拥有能够快速在突击和攻城模式之间转换的独特能力。但在“智能危机”期间,该系列却被用来对抗其人类发明者,成为了机器人叛军的中坚力量。随着后来危机的解除,几乎所有的“堡垒”系列机器人都被销毁或拆解。直到今天,“堡垒”依旧是当年那场可怕战争的代名词。但是有一台独特的“堡垒”机器人,在那场战争的决战中严重受损,因此被遗忘了数十年。直到有一天,它被意外地重新激活,它的战斗程序几乎全部受损,取而代之的是对自然世界及其住民的强烈好奇。好奇的“堡垒”于是在这个被战火蹂躏过的世界上,开始了探索和寻找自我价值的旅途。', + ow_banzang:'岛田家据传已有数百年的历史。以忍者为主要成员的岛田家,经过多年的发展,已经建立起一个以军火和非法物资交易为主的庞大黑道帝国。作为大名的长子,半藏注定要继承他的父亲统治岛田帝国。父亲过世后,家族长老就建议半藏帮助他那刚愎自用的弟弟源氏,以便两人携手管理岛田帝国。在遭到源氏拒绝后,半藏被迫亲手了结了自己的弟弟。半藏因此深受打击,他拒绝继承父亲的遗产并最终抛弃了自己的家族和所有辛苦换来的成果。现在,半藏四海为家,不断磨练着自己作为一名武士的技巧,希望终有一天能挽回自己的名誉并真正放下自己的过去。', + ow_laiyinhate:'莱因哈特•威尔海姆的行事作风就像一个属于过去的勇士,时刻铭记着骑士的信条:无畏、公正、勇敢。莱因哈特独特的道德观和舍小为大的信念,深得其上级的喜爱。他有话直说,因此成为了守望先锋最坚定的拥护者,同时在有必要时,也是守望先锋最刻薄的批评者,时刻提醒着其他人,守望先锋是一支正义之师。莱因哈特一直服役到将近六十岁,因此不得不面临强制退役。而守望先锋又深陷腐败和煽动叛乱之嫌的泥沼,莱因哈特只能眼睁睁地看着自己守护了一生的信念被吞噬。尽管守望先锋最终解散,但莱因哈特绝不会在世界陷入混乱之时袖手旁观。他再一次穿上了十字军战甲,立誓为正义而战,像曾经的白银骑士那样守护欧罗巴大陆无辜的人民,坚信光明的未来必将到来。', + ow_luba:'“路霸”马可曾是众多居住在澳洲内陆的居民之一。在智能危机爆发后,政府作出了一个极具争议的决定,将这里送给了差点摧毁整个国家的智能机器人,以求达成永久的和平协定。这一决定直接导致马可和大批居民被迫离开,因失去家园而怒不可遏的马可和其他人开始了一场暴力起义,最终,引发了一场大爆炸,导致周围数公里地区全部遭到辐射,只留下了变形的金属和残骸。为了在这种环境下生存下来,他戴上了面具、骑着破烂摩托开上了通往澳洲内陆的残破高速公路。一路上,他的人性一点一点地被消磨,马可最终消失了,而“路霸”就此诞生。', + ow_wensidun:'在“地平线”月球殖民地的居民中,有一群经过基因改造的大猩猩。其中一只大猩猩在接受了哈罗德·温斯顿博士团队的基因改造后,显示出了极快的脑部发育迹象,博士本人也将人类科学和创造力教给了这只猩猩。但当其他大猩猩发动叛乱,杀死了所有科学家并占领殖民地后,他的生活便再也回不到过去了。出于对哈罗德博士的爱戴,这只大猩猩决定继承他的姓氏:温斯顿,并设计出了一枚临时火箭逃往了地球。他找到了新家:守望先锋 —— 这是一个代表着他所憧憬的所有人性的组织。温斯顿终于有机会实现哈罗德博士生前不断教导他的英雄理念。但随着后来守望先锋的解散,温斯顿也隐居了起来,再一次与他报以厚望的世界失去联系,但他却从未放弃对英雄最终回归的期望。', + ow_zhaliya:'亚历山德拉·查莉娅诺娃是世界上最强壮的女运动员之一。然而就在世锦赛前夕,一直处于休眠状态的西伯利亚机器人控制中枢再度发动攻击,战火再次蔓延到了她的家乡。早已名利双收的亚历山德拉,毅然抛弃了一切,立即回到家乡加入了当地的防御部队。', + ow_heiying:'作为全世界最臭名昭著的黑客,“黑影”利用信息与情报操控权贵。早在她称自己为“黑影”之前,░░░░░░是千千万万在智械危机后变成孤儿的儿童之一。在家乡大部分基础设施都被摧毁的情况下,她依靠自己在黑客以及计算机方面的天赋活了下来。在黑客领域的一连串的胜利让░░░░░░对自己的实力过度自信,最终她在毫无防备的情况下,陷入了一张覆盖全球的阴谋网——并且也因此被人盯上了。由于自己的安全面临严重威胁,░░░░░░不得不删除关于自己的全部信息,从此销声匿迹。后来,她以“黑影”的身份再度出现,经过改造的她决心查出那张阴谋网背后的真相。', + }, + skill:{ + woliu:{ + trigger:{player:'phaseEnd'}, + direct:true, + unique:true, + forceunique:true, + content:function(){ + 'step 0' + player.chooseTarget(get.prompt('woliu'),lib.filter.notMe,[1,2]).ai=function(target){ + if(get.attitude(player,target)<0){ + return get.effect(target,{name:'sha'},player,player); + } + return 0; + } + 'step 1' + if(result.bool){ + player.logSkill('woliu',result.targets); + var list=[player].concat(result.targets); + for(var i=0;i0){ + return [0,num]; + } + if(target.hp==1&&!target.hasShan()) return; + return [1,num]; + } + } + } + } + }, + qianggu:{ + enable:'phaseUse', + usable:1, + filterCard:true, + selectCard:2, + position:'he', + check:function(card){ + return 8-get.value(card); + }, + filter:function(event,player){ + return player.countCards('he')>=2; + }, + content:function(){ + player.changeHujia(2); + player.addTempSkill('qianggu2',{player:'phaseBegin'}); + }, + ai:{ + result:{ + player:1 + }, + order:2.5 + } + }, + qianggu2:{ + trigger:{target:'useCardToBefore'}, + forced:true, + filter:function(event,player){ + return event.card.name=='sha'; + }, + mark:true, + intro:{ + content:'其他角色对你使用杀时需要弃置一张基本牌,否则杀对你无效' + }, + content:function(){ + "step 0" + var eff; + if(player.hasSkill('woliu2')){ + eff=-get.attitude(trigger.player,player); + } + else{ + eff=get.effect(player,trigger.card,trigger.player,trigger.player); + } + trigger.player.chooseToDiscard('强固:弃置一张基本牌,否则杀对'+get.translation(player)+'无效',function(card){ + return get.type(card)=='basic'; + }).set('ai',function(card){ + if(_status.event.eff>0){ + return 10-get.value(card); + } + return 0; + }).set('eff',eff); + "step 1" + if(result.bool==false){ + trigger.cancel(); + } + }, + ai:{ + effect:{ + target:function(card,player,target,current){ + if(card.name=='sha'){ + if(_status.event.name=='qianggu2') return; + if(get.attitude(player,target)>0) return; + var bs=player.getCards('h',{type:'basic'}); + if(bs.length<2) return 0; + if(player.hasSkill('jiu')||player.hasSkill('tianxianjiu')) return; + if(bs.length<=3&&player.countCards('h','sha')<=1){ + for(var i=0;i0; + }, + filterCard:true, + usable:1, + viewAs:{name:'jingleishan',nature:'thunder'}, + check:function(card){ + return 8-get.value(card) + }, + ai:{ + order:8, + expose:0.2, + threaten:1.2 + }, + mod:{ + playerEnabled:function(card,player,target){ + if(_status.event.skill=='dianji'&&get.distance(player,target)>2) return false; + } + } + }, + feitiao:{ + trigger:{player:'phaseUseBegin'}, + direct:true, + filter:function(event,player){ + return player.countCards('he')>0; + }, + content:function(){ + 'step 0' + var next=player.chooseCardTarget({ + prompt:get.prompt('feitiao'), + position:'he', + filterCard:true, + ai1:function(card){ + return 7-get.value(card); + }, + ai2:function(target){ + var att=get.attitude(player,target); + if(att>=0) return 0; + if(!target.countCards('he')) return -0.01; + var dist=get.distance(player,target); + if(dist>2){ + att-=2; + } + else if(dist==2){ + att--; + } + return -att; + }, + filterTarget:function(card,player,target){ + return player!=target; + } + }); + 'step 1' + if(result.bool){ + player.discard(result.cards); + var target=result.targets[0]; + player.logSkill('feitiao',target); + player.storage.feitiao2=target; + player.addTempSkill('feitiao2'); + target.randomDiscard(); + } + + } + }, + feitiao2:{ + mod:{ + globalFrom:function(from,to){ + if(to==from.storage.feitiao2) return -Infinity; + } + }, + mark:'character', + intro:{ + content:'与$的距离视为1直到回合结束' + }, + onremove:true + }, + zhencha:{ + init:function(player){ + player.storage.zhencha=true; + }, + mark:true, + intro:{ + content:function(storage,player){ + if(storage){ + return '每当你使用一张杀,你摸一张牌或回复一点体力'; + } + else if(player.hasSkill('bshaowei')&&player.storage.bshaowei){ + return '你的杀无视距离和防具、无数量限制且不可闪避;你不能闪避杀'; + } + else{ + return '无额外技能'; + } + } + }, + trigger:{player:'phaseEnd'}, + filter:function(event,player){ + if(player.hasSkill('zhencha2')) return false; + return !player.storage.zhencha; + }, + content:function(){ + player.storage.bshaowei=false; + player.storage.zhencha=true; + if(player.marks.zhencha){ + player.marks.zhencha.firstChild.innerHTML='侦'; + } + player.addTempSkill('zhencha2'); + }, + subSkill:{ + sha:{ + trigger:{player:'shaBegin'}, + direct:true, + filter:function(event,player){ + return player.storage.zhencha&&event.card&&event.card.name=='sha'; + }, + content:function(){ + player.chooseDrawRecover(get.prompt('zhencha')).logSkill='zhencha'; + } + } + }, + group:'zhencha_sha' + }, + bshaowei:{ + init:function(player){ + player.storage.bshaowei=false; + }, + trigger:{player:'phaseEnd'}, + filter:function(event,player){ + if(player.hasSkill('zhencha2')) return false; + return !player.storage.bshaowei; + }, + check:function(event,player){ + if(!player.hasShan()) return true; + if(!player.hasSha()) return false; + return Math.random()<0.5; + }, + content:function(){ + player.storage.bshaowei=true; + player.storage.zhencha=false; + if(player.marks.zhencha){ + player.marks.zhencha.firstChild.innerHTML='哨'; + } + player.addTempSkill('zhencha2'); + }, + subSkill:{ + sha:{ + mod:{ + targetInRange:function(card,player,target,now){ + if(card.name=='sha'&&player.storage.bshaowei) return true; + }, + cardUsable:function(card,player,num){ + if(card.name=='sha'&&player.storage.bshaowei) return Infinity; + } + }, + trigger:{target:'shaBegin',player:'shaBegin'}, + forced:true, + filter:function(event,player){ + return player.storage.bshaowei; + }, + check:function(){ + return false; + }, + content:function(){ + trigger.directHit=true; + }, + ai:{ + unequip:true, + skillTagFilter:function(player,tag,arg){ + if(!player.storage.bshaowei) return false; + if(arg&&arg.name=='sha') return true; + return false; + } + } + } + }, + group:'bshaowei_sha', + ai:{ + threaten:function(player,target){ + if(target.storage.bshaowei) return 1.7; + return 1; + } + } + }, + zhencha2:{}, + pingzhang:{ + trigger:{global:'damageBegin'}, + // alter:true, + intro:{ + content:function(storage,player){ + if(player.hasSkill('pingzhang2')){ + if(player.hasSkill('pingzhang3')){ + return '已对自已和其他角色发动屏障'; + } + else{ + return '已对自已发动屏障'; + } + } + else{ + return '已对其他角色发动屏障'; + } + }, + markcount:function(storage,player){ + if(player.hasSkill('pingzhang2')&&player.hasSkill('pingzhang3')){ + return 2; + } + return 1; + } + }, + filter:function(event,player){ + if(event.num<=0) return false; + var position=get.is.altered('pingzhang')?'h':'he'; + if(event.player==player){ + if(player.hasSkill('pingzhang2')) return false; + return player.countCards(position,{suit:'heart'}); + } + else{ + if(player.hasSkill('pingzhang3')) return false; + return player.countCards(position,{suit:'spade'}); + } + }, + direct:true, + content:function(){ + 'step 0' + var position=get.is.altered('pingzhang')?'h':'he'; + var suit=(player==trigger.player)?'heart':'spade'; + var next=player.chooseToDiscard(position,{suit:suit},get.prompt('pingzhang',trigger.player)); + next.ai=function(card){ + if(get.damageEffect(trigger.player,trigger.source,player)<0){ + return 8-get.value(card); + } + return 0; + } + next.logSkill=['pingzhang',trigger.player]; + 'step 1' + if(result.bool){ + trigger.num--; + if(player==trigger.player){ + player.addSkill('pingzhang2'); + } + else{ + player.addSkill('pingzhang3'); + } + player.markSkill('pingzhang'); + } + }, + group:['pingzhang_count'], + subSkill:{ + count:{ + trigger:{player:'phaseBegin'}, + silent:true, + content:function(){ + player.storage.pingzhang=0; + if(player.hasSkill('pingzhang2')){ + player.storage.pingzhang++; + player.removeSkill('pingzhang2'); + } + if(player.hasSkill('pingzhang3')){ + player.storage.pingzhang++; + player.removeSkill('pingzhang3'); + } + player.unmarkSkill('pingzhang'); + } + } + }, + ai:{ + expose:0.2, + threaten:1.5 + } + }, + pingzhang2:{}, + pingzhang3:{}, + liyong:{ + trigger:{player:'phaseDrawBegin'}, + forced:true, + filter:function(event,player){ + return player.storage.pingzhang>0; + }, + content:function(){ + trigger.num+=player.storage.pingzhang; + } + }, + liangou:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return target!=player; + }, + filterCard:true, + position:'he', + check:function(card){ + return 5-get.value(card); + }, + content:function(){ + 'step 0' + player.judge(function(card){ + return get.suit(card)!='heart'?1:-1; + }); + 'step 1' + if(result.bool){ + target.addTempSkill('liangou2'); + target.storage.liangou2=player; + } + }, + ai:{ + order:10, + expose:0.2, + result:{ + target:function(player,target){ + if(get.damageEffect(target,player,target)<0&&player.hasCard(function(card){ + return get.tag(card,'damage')?true:false; + })){ + return -1; + } + return 0; + } + } + } + }, + liangou2:{ + mod:{ + // cardEnabled:function(card,player){ + // return false; + // }, + // cardUsable:function(card,player){ + // return false; + // }, + // cardRespondable:function(card,player){ + // return false; + // }, + // cardSavable:function(card,player){ + // return false; + // }, + globalTo:function(from,to){ + if(from==to.storage.liangou2) return -Infinity; + } + }, + onremove:true, + trigger:{player:'damageBegin'}, + usable:1, + forced:true, + popup:false, + content:function(){ + trigger.num++; + }, + // ai:{ + // effect:{ + // target:function(card,player,target){ + // if(get.tag(card,'damage')) return [1,-2]; + // if(get.tag(card,'respond')) return [1,-1]; + // } + // } + // } + }, + xiyang:{ + trigger:{player:'phaseEnd'}, + filter:function(event,player){ + return !player.isTurnedOver()&&player.isDamaged(); + }, + check:function(event,player){ + return player.hp<=1; + }, + content:function(){ + 'step 0' + player.turnOver(); + 'step 1' + player.recover(2); + } + }, + qinru:{ + trigger:{player:'useCardToBegin'}, + filter:function(event,player){ + return event.card.name=='sha'&&event.target!=player&&event.target&&!event.target.hasSkill('fengyin'); + }, + logTarget:'target', + check:function(event,player){ + return get.attitude(player,event.target)<0; + }, + intro:{ + content:'players', + mark:function(dialog,storage,player){ + var one=[],two=[],three=[]; + for(var i=0;i1){ + player.storage.qinru_turn[i]--; + } + else{ + player.storage.qinru.splice(i,1); + player.storage.qinru_turn.splice(i,1); + i--; + } + } + if(!player.storage.qinru.length){ + player.unmarkSkill('qinru'); + } + else{ + player.updateMarks(); + } + } + } + }, + group:['qinru_count','qinru_die'] + }, + yinshen:{ + trigger:{player:'loseEnd'}, + forced:true, + filter:function(event,player){ + if(player.countCards('h',{type:'basic'})) return false; + for(var i=0;i0; + }, + content:function(){ + "step 0" + var next=player.chooseToDiscard(get.prompt('yinshen'),'he',{type:'equip'}); + next.logSkill='yinshen'; + next.ai=function(card){ + if(player.hp==1) return 8-get.value(card); + if(player.isZhu) return 7-get.value(card); + if(player.hp==2) return 6-get.value(card); + return 5-get.value(card); + }; + "step 1" + if(result.bool){ + player.tempHide(); + } + }, + }, + maichong:{ + trigger:{player:'useCard'}, + forced:true, + filter:function(event,player){ + if(!player.hasSkill('qinru')||!player.storage.qinru||!player.storage.qinru.length) return false; + if(get.type(event.card)=='trick'&&event.cards[0]&&event.cards[0]==event.card){ + for(var i=0;i0; + }, + filterTarget:function(card,player,target){ + return !target.hujia; + }, + filterCard:true, + position:'he', + check:function(card){ + var player=_status.event.player; + if(game.hasPlayer(function(current){ + return current.hp==1&&get.attitude(player,current)>2; + })){ + return 7-get.value(card); + } + return 5-get.value(card); + }, + content:function(){ + player.changeHujia(-1); + target.changeHujia(); + }, + ai:{ + order:5, + expose:0.2, + result:{ + target:function(player,target){ + return 1/Math.max(1,target.hp); + } + } + } + }, + maoding:{ + trigger:{player:'damageEnd',source:'damageEnd'}, + frequent:true, + filter:function(event,player){ + if(get.is.altered('maoding')&&event.source!=player) return false; + return true; + }, + // alter:true, + content:function(){ + var list=get.typeCard('hslingjian'); + if(!list.length){ + return; + } + player.gain(game.createCard(list.randomGet()),'gain2'); + }, + group:'maoding2', + ai:{ + threaten:1.5, + maixie_defend:true + } + }, + maoding2:{ + enable:'phaseUse', + filter:function(event,player){ + return player.countCards('h',{type:'hslingjian'})>1; + }, + filterCard:{type:'hslingjian'}, + filterTarget:function(card,player,target){ + return !target.hujia; + }, + selectCard:2, + // usable:1, + content:function(){ + target.changeHujia(); + }, + ai:{ + order:9, + result:{ + target:function(player,target){ + return 2/Math.max(1,Math.sqrt(target.hp)); + }, + }, + } + }, + paotai:{ + enable:'phaseUse', + intro:{ + content:function(storage){ + var num; + switch(storage){ + case 1:num=30;break; + case 2:num=60;break; + case 3:num=100;break; + } + return '结束阶段,有'+num+'%机率对一名随机敌人造成一点火焰伤害'; + } + }, + init:function(player){ + player.storage.paotai=0; + }, + filter:function(event,player){ + return player.countCards('h','sha')>0&&player.storage.paotai<3; + }, + filterCard:{name:'sha'}, + content:function(){ + player.storage.paotai++; + player.markSkill('paotai'); + }, + ai:{ + order:5, + threaten:1.5, + result:{ + player:1 + } + }, + group:['paotai2','paotai3'] + }, + paotai2:{ + trigger:{player:'phaseEnd'}, + forced:true, + filter:function(event,player){ + var num=0; + switch(player.storage.paotai){ + case 1:num=30;break; + case 2:num=60;break; + case 3:num=100;break; + } + return 100*Math.random()0&&event.num>0; + }, + content:function(){ + player.storage.paotai-=trigger.num; + if(player.storage.paotai<=0){ + player.storage.paotai=0; + player.unmarkSkill('paotai'); + } + else{ + player.updateMarks(); + } + } + }, + bfengshi:{ + trigger:{player:'shaBegin'}, + forced:true, + // alter:true, + check:function(event,player){ + return get.attitude(player,event.target)<=0; + }, + filter:function(event,player){ + if(player.hasSkill('bfengshi4')) return false; + var num=0.2; + if(get.is.altered('bfengshi')) num=0.15; + return Math.random()0; + }, + check:function(card){ + return 7-get.value(card); + }, + content:function(){ + 'step 0' + var targets=player.getEnemies(function(target){ + return target.countCards('he')>0; + }); + if(targets.length){ + event.targets=targets.randomGets(3); + event.targets.sort(lib.sort.seat); + player.line(event.targets,'green'); + } + 'step 1' + if(event.targets.length){ + var target=event.targets.shift(); + var he=target.getCards('he'); + if(he.length){ + target.addExpose(0.1); + target.discard(he.randomGet()); + } + event.redo(); + } + }, + ai:{ + order:10, + expose:0.3, + result:{ + player:1 + } + } + }, + aqianghua:{ + enable:'phaseUse', + usable:1, + // alter:true, + filter:function(event,player){ + return player.countCards('h')>=1; + }, + filterTarget:function(card,player,target){ + return target!=player; + }, + filterCard:true, + selectCard:-1, + discard:false, + prepare:'give', + content:function(){ + target.gain(cards); + if(!get.is.altered('aqianghua')) target.changeHujia(); + target.addSkill('aqianghua2'); + }, + ai:{ + threaten:1.5, + order:2.1, + result:{ + target:function(player,target){ + if(target.hasSkillTag('nogain')) return 0; + if(get.attitude(player,target)<3) return 0; + if(target.hasJudge('lebu')) return 0; + if(target.hasSkill('aqianghua2')) return 0.1; + return 1; + } + } + } + }, + aqianghua2:{ + trigger:{source:'damageBegin'}, + forced:true, + content:function(){ + trigger.num++; + player.unmarkSkill('aqianghua2'); + player.removeSkill('aqianghua2'); + }, + mark:true, + intro:{ + content:'下一次造成的伤害+1' + } + }, + shihuo:{ + trigger:{global:'damageEnd'}, + forced:true, + filter:function(event){ + return event.nature=='fire'; + }, + content:function(){ + player.draw(); + } + }, + shoujia:{ + enable:'phaseUse', + usable:1, + filter:function(event,player){ + return player.countCards('h')>0; + }, + filterCard:true, + check:function(card){ + return 6-get.value(card); + }, + discard:false, + prepare:'give2', + filterTarget:function(card,player,target){ + return target!=player&&!target.hasSkill('shoujia2'); + }, + content:function(){ + target.storage.shoujia=cards[0]; + target.storage.shoujia2=player; + target.addSkill('shoujia2'); + target.syncStorage('shoujia'); + }, + ai:{ + order:1, + expose:0.2, + threaten:1.4, + result:{ + target:-1 + } + } + }, + shoujia2:{ + mark:true, + trigger:{player:'useCardToBegin'}, + forced:true, + filter:function(event,player){ + return get.suit(event.card)==get.suit(player.storage.shoujia)&&event.target&&event.target!=player; + }, + content:function(){ + 'step 0' + player.showCards([player.storage.shoujia],get.translation(player)+'发动了【兽夹】'); + 'step 1' + player.storage.shoujia.discard(); + delete player.storage.shoujia; + delete player.storage.shoujia2; + player.removeSkill('shoujia2'); + game.addVideo('storage',player,['shoujia',null]); + game.addVideo('storage',player,['shoujia2',null]); + player.turnOver(true); + }, + intro:{ + mark:function(dialog,content,player){ + if(player.storage.shoujia2&&player.storage.shoujia2.isUnderControl(true)){ + dialog.add([player.storage.shoujia]); + } + else{ + return '已成为'+get.translation(player.storage.shoujia2)+'的兽夹目标'; + } + }, + content:function(content,player){ + if(player.storage.shoujia2&&player.storage.shoujia2.isUnderControl(true)){ + return get.translation(player.storage.shoujia); + } + return '已成为'+get.translation(player.storage.shoujia2)+'的兽夹目标'; + } + }, + group:'shoujia3' + }, + shoujia3:{ + trigger:{global:'damageEnd'}, + forced:true, + filter:function(event,player){ + return event.player==player.storage.shoujia2; + }, + content:function(){ + player.storage.shoujia.discard(); + player.$throw(player.storage.shoujia); + game.log(player.storage.shoujia,'被置入弃牌堆') + delete player.storage.shoujia; + delete player.storage.shoujia2; + player.removeSkill('shoujia2'); + game.addVideo('storage',player,['shoujia',null]); + game.addVideo('storage',player,['shoujia2',null]); + } + }, + liudan:{ + trigger:{player:'useCard'}, + check:function(event,player){ + return game.countPlayer(function(current){ + if(event.targets.contains(current)==false&¤t!=player&& + lib.filter.targetEnabled(event.card,player,current)){ + return get.effect(current,event.card,player,player); + } + })>=0; + }, + filter:function(event,player){ + if(event.card.name!='sha') return false; + return game.hasPlayer(function(current){ + return (event.targets.contains(current)==false&¤t!=player&& + lib.filter.targetEnabled(event.card,player,current)); + }); + }, + content:function(){ + var list=game.filterPlayer(function(current){ + return (trigger.targets.contains(current)==false&¤t!=player&& + lib.filter.targetEnabled(trigger.card,player,current)); + }); + if(list.length){ + var list2=[]; + for(var i=0;iplayer.countCards('h')&&!player.skipList.contains('phaseUse')&&!player.skipList.contains('phaseDiscard'); + }, + check:function(event,player){ + var nh=player.countCards('h'); + if(Math.min(5,player.hp)-nh>=2) return true; + return false; + }, + content:function(){ + var num=Math.min(5,player.hp)-player.countCards('h'); + var cards=[]; + while(num--){ + cards.push(game.createCard('sha')); + } + player.gain(cards,'gain2'); + player.skip('phaseUse'); + player.skip('phaseDiscard'); + } + }, + shanguang:{ + enable:'phaseUse', + usable:1, + filterCard:{suit:'diamond'}, + position:'he', + filter:function(event,player){ + return player.countCards('he',{suit:'diamond'})>0; + }, + filterTarget:function(card,player,target){ + return target!=player&&get.distance(player,target,'attack')<=1; + }, + check:function(card){ + if(card.name=='sha'&&_status.event.player.countCards('h','sha')<3) return 0; + return 6-get.value(card); + }, + content:function(){ + target.addTempSkill('shanguang2'); + }, + ai:{ + order:7.9, + result:{ + target:function(player,target){ + var nh=target.countCards('h'); + if(get.attitude(player,target)<0&&nh>=3&& + player.canUse('sha',target)&&player.countCards('h','sha')&& + get.effect(target,{name:'sha'},player,player)>0){ + return -nh-5; + } + return -nh; + } + } + } + }, + shanguang2:{ + mod:{ + cardEnabled:function(){ + return false; + }, + cardUsable:function(){ + return false; + }, + cardRespondable:function(){ + return false; + }, + cardSavable:function(){ + return false; + } + }, + ai:{ + effect:{ + target:function(card,player,target,current){ + if(get.tag(card,'respondShan')||get.tag(card,'respondSha')){ + if(current<0) return 1.5; + } + } + } + } + }, + baoxue:{ + enable:'phaseUse', + init:function(player){ + player.storage.baoxue=false; + }, + intro:{ + content:'limited' + }, + mark:true, + unique:true, + skillAnimation:true, + animationColor:'water', + line:'thunder', + filter:function(event,player){ + return !player.storage.baoxue&&player.countCards('he',{color:'black'})>0; + }, + filterTarget:function(card,player,target){ + return target!=player; + }, + selectTarget:function(){ + return [1,_status.event.player.countCards('he',{color:'black'})]; + }, + // alter:true, + delay:false, + contentBefore:function(){ + 'step 0' + game.delayx(); + 'step 1' + player.storage.baoxue=true; + player.awakenSkill('baoxue'); + player.showHandcards(); + player.discard(player.getCards('he',{color:'black'})); + }, + content:function(){ + 'step 0' + if(!get.is.altered('baoxue')){ + var he=target.getCards('he'); + if(he.length){ + target.discard(he.randomGet()); + } + } + 'step 1' + target.turnOver(true); + }, + contentAfter:function(){ + player.turnOver(true); + }, + ai:{ + order:function(skill,player){ + var num=game.countPlayer(function(current){ + return get.attitude(player,current)<0; + }); + var nh=player.countCards('he',{color:'black'}); + if(nh==1&&num>1) return 0; + if(nh>num) return 1; + return 11; + }, + result:{ + target:function(player,target){ + if(target.hasSkillTag('noturn')) return 0; + if(player.hasUnknown()) return 0; + return -1; + } + } + } + }, + mianzhen:{ + enable:'phaseUse', + usable:1, + filter:function(event,player){ + return player.countCards('he')>0; + }, + filterTarget:function(card,player,target){ + return target!=player&&!target.hasSkill('mianzhen2'); + }, + filterCard:true, + position:'he', + check:function(card){ + return 8-get.value(card); + }, + content:function(){ + 'step 0' + target.chooseToRespond({name:'shan'}); + 'step 1' + if(!result.bool) target.addSkill('mianzhen2'); + }, + ai:{ + order:2.2, + result:{ + target:function(player,target){ + return Math.min(-0.1,-1-target.countCards('h')+Math.sqrt(target.hp)/2); + } + } + } + }, + mianzhen2:{ + mark:true, + intro:{ + content:'不能使用或打出手牌直到受到伤害或下一回合结束' + }, + trigger:{player:['damageEnd','phaseEnd']}, + forced:true, + popup:false, + content:function(){ + player.removeSkill('mianzhen2'); + }, + mod:{ + cardEnabled:function(){ + return false; + }, + cardUsable:function(){ + return false; + }, + cardRespondable:function(){ + return false; + }, + cardSavable:function(){ + return false; + } + }, + ai:{ + threaten:0.6 + } + }, + zhiyuan:{ + trigger:{source:'damageBefore'}, + check:function(event,player){ + player.disableSkill('tmp','zhiyuan'); + var eff=get.damageEffect(event.player,player,player); + var att=get.attitude(player,event.player); + var bool=false; + if(att>0){ + if(eff<=0||event.player.hp0; + }, + content:function(){ + trigger.cancel(); + trigger.player.recover(trigger.num); + }, + ai:{ + effect:{ + player:function(card,player,target){ + if(get.tag(card,'damage')&&get.attitude(player,target)>0){ + if(target.hp==target.maxHp||get.recoverEffect(target,player,player)<=0) return 'zeroplayertarget'; + return [0,0,0,1]; + } + } + } + } + }, + duwen:{ + trigger:{source:'damageBegin'}, + check:function(event,player){ + return get.attitude(player,event.player)<=0; + }, + forced:true, + filter:function(event,player){ + return player.countCards('h')==event.player.countCards('h')&&event.notLink(); + }, + content:function(){ + trigger.num++; + }, + ai:{ + threaten:1.5 + }, + }, + duwen2:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&player.hp==event.player.hp&&event.notLink(); + }, + content:function(){ + player.draw(2); + } + }, + juji:{ + enable:'phaseUse', + usable:1, + position:'he', + filter:function(event,player){ + return player.countCards('he')>0; + }, + filterCard:function(card){ + var suit=get.suit(card); + for(var i=0;i0; + }, + check:function(card){ + if(ui.selected.cards.length>1) return 0; + return 5-get.value(card); + }, + selectCard:[1,4], + content:function(){ + var suits=[]; + for(var i=0;inum){ + min.length=0; + min.push(players[i]); + num=eff; + } + } + } + for(var i=0;i0) return 0; + if(min[i].countCards('h')<=1&&get.distance(player,min[i],'attack')<=1) return 0; + } + if(min.contains(target)) return -1; + return 0; + } + } + }, + }, + juji2:{ + ai:{ + effect:{ + player:function(card,player,target){ + if(card.name=='sha'&&target==player.storage.juji2) return [1,0,1,-1]; + } + } + }, + trigger:{player:'phaseAfter'}, + forced:true, + popup:false, + content:function(){ + player.unmarkSkill('juji2'); + player.removeSkill('juji2'); + delete player.storage.juji2; + }, + group:'juji3' + }, + juji3:{ + trigger:{player:'shaBegin'}, + forced:true, + filter:function(event,player){ + return event.target==player.storage.juji2; + }, + content:function(){ + trigger.directHit=true; + }, + mod:{ + globalFrom:function(from,to){ + if(to==from.storage.juji2) return -Infinity; + } + } + }, + dulei:{ + enable:'phaseUse', + filter:function(event,player){ + return !player.hasSkill('dulei2'); + }, + filterCard:true, + check:function(card){ + return 6-get.value(card); + }, + discard:false, + prepare:function(cards,player){ + player.$give(1,player,false); + }, + content:function(){ + player.storage.dulei=cards[0]; + player.addSkill('dulei2'); + player.syncStorage('dulei'); + }, + ai:{ + order:1, + result:{ + player:1 + } + } + }, + dulei2:{ + mark:true, + trigger:{target:'useCardToBegin'}, + forced:true, + filter:function(event,player){ + return event.player!=player&&get.suit(event.card)==get.suit(player.storage.dulei); + }, + content:function(){ + 'step 0' + player.showCards([player.storage.dulei],get.translation(player)+'发动了【诡雷】'); + 'step 1' + player.storage.dulei.discard(); + delete player.storage.dulei; + player.removeSkill('dulei2'); + game.addVideo('storage',player,['dulei',null]); + trigger.player.loseHp(); + 'step 2' + var he=trigger.player.getCards('he'); + if(he.length){ + trigger.player.discard(he.randomGet()); + } + }, + intro:{ + mark:function(dialog,content,player){ + if(player==game.me||player.isUnderControl()){ + dialog.add([player.storage.dulei]); + } + else{ + return '已发动诡雷'; + } + }, + content:function(content,player){ + if(player==game.me||player.isUnderControl()){ + return get.translation(player.storage.dulei); + } + return '已发动诡雷'; + } + } + }, + juji_old:{ + trigger:{player:'shaBegin'}, + forced:true, + filter:function(event,player){ + return get.distance(event.target,player,'attack')>1; + }, + content:function(){ + trigger.directHit=true; + }, + group:'juji2' + }, + juji2_old:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return target!=player; + }, + content:function(){ + target.addTempSkill('juji3',{player:'phaseEnd'}); + if(!target.storage.juji3){ + target.storage.juji3=[]; + } + target.storage.juji3.push(player); + }, + mod:{ + targetInRange:function(card,player,target){ + if(target.hasSkill('juji3')&&Array.isArray(target.storage.juji3)&&target.storage.juji3.contains(player)){ + return true; + } + } + } + }, + juji3_old:{ + mark:true, + intro:{ + nocount:true, + content:function(storage){ + return '对'+get.translation(storage)+'使用卡牌无视距离'; + } + }, + mod:{ + targetInRange:function(card,player,target){ + if(Array.isArray(player.storage.juji3)&&player.storage.juji3.contains(target)){ + return true; + } + } + } + }, + zhuagou:{ + enable:'phaseUse', + usable:1, + changeSeat:true, + filterTarget:function(card,player,target){ + return player!=target&&player.next!=target; + }, + filterCard:true, + check:function(card){ + return 4-get.value(card); + }, + content:function(){ + while(player.next!=target){ + game.swapSeat(player,player.next); + } + }, + ai:{ + order:5, + result:{ + player:function(player,target){ + var att=get.attitude(player,target); + if(target==player.previous&&att>0) return 1; + if(target==player.next.next&&get.attitude(player,player.next)<0) return 1; + return 0; + } + } + } + }, + bingqiang:{ + enable:'phaseUse', + position:'he', + filterCard:function(card){ + var color=get.color(card); + for(var i=0;imax){ + max=num; + } + if(num-min){ + if(get.color(card)=='red') return 5-get.value(card); + } + else{ + if(get.color(card)=='black') return 5-get.value(card); + } + return 0; + }, + changeTarget:function(player,targets){ + var target=targets[0]; + var add=game.filterPlayer(function(player){ + return get.distance(target,player,'pure')==1; + }); + for(var i=0;i0; + }, + content:function(){ + for(var i=0;i0; + }, + content:function(){ + 'step 0' + var goon=false; + var goon2=false; + var att=get.attitude(player,trigger.player); + if(att>0){ + if(trigger.player.hp==1) goon=true; + } + else{ + if(Math.random()<0.5) goon=true; + } + if(Math.random()<0.3) goon2=true; + player.chooseToDiscard([1,player.countCards('h')],'he',get.prompt('bingqiang',trigger.player)).set('logSkill',['bingqiang',trigger.player]).ai=function(card){ + if(ui.selected.cards.length) return 0; + if(goon) return 6-get.value(card); + if(goon2) return 4-get.value(card); + return 0; + } + 'step 1' + if(result.bool){ + var num=result.cards.length; + event.num=num; + player.chooseControl('选项一','选项二','选项三','选项四',function(){ + if(get.attitude(player,trigger.player)>0){ + if(Math.random()<0.7) return '选项一'; + return '选项三'; + } + else{ + if(Math.random()<0.7) return '选项四'; + return '选项二'; + } + }).set('prompt','冰墙

          选项一:防御距离+'+num+ + '

          选项二:防御距离-'+num+ + '

          选项三:进攻距离+'+num+ + '

          选项四:进攻距离-'+num+'
          '); + } + else{ + event.finish(); + } + 'step 2' + switch(result.control){ + case '选项一':{ + trigger.player.storage.bingqiang2=event.num; + trigger.player.addTempSkill('bingqiang2',{player:'phaseBegin'}); + break; + } + case '选项二':{ + trigger.player.storage.bingqiang3=event.num; + trigger.player.addTempSkill('bingqiang3',{player:'phaseBegin'}); + break; + } + case '选项三':{ + trigger.player.storage.bingqiang4=event.num; + trigger.player.addTempSkill('bingqiang4',{player:'phaseBegin'}); + break; + } + case '选项四':{ + trigger.player.storage.bingqiang5=event.num; + trigger.player.addTempSkill('bingqiang5',{player:'phaseBegin'}); + break; + } + } + }, + ai:{ + expose:0.1 + } + }, + bingqiang2:{ + mark:true, + intro:{ + content:'防御距离+#' + }, + mod:{ + globalTo:function(from,to,distance){ + if(typeof to.storage.bingqiang2=='number') return distance+to.storage.bingqiang2; + }, + } + }, + bingqiang3:{ + mark:true, + intro:{ + content:'防御距离-#' + }, + mod:{ + globalTo:function(from,to,distance){ + if(typeof to.storage.bingqiang3=='number') return distance-to.storage.bingqiang3; + }, + } + }, + bingqiang4:{ + mark:true, + intro:{ + content:'进攻距离+#' + }, + mod:{ + globalFrom:function(from,to,distance){ + if(typeof from.storage.bingqiang4=='number') return distance-from.storage.bingqiang4; + } + } + }, + bingqiang5:{ + mark:true, + intro:{ + content:'进攻距离-#' + }, + mod:{ + globalFrom:function(from,to,distance){ + if(typeof from.storage.bingqiang5=='number') return distance+from.storage.bingqiang5; + } + } + }, + shuangqiang:{ + trigger:{source:'damageBegin'}, + check:function(event,player){ + var att=get.attitude(player,event.player); + if(event.player.hp==1) return att>0; + return att<=0; + }, + logTarget:'player', + filter:function(event,player){ + return !event.player.isTurnedOver()&&event.num>0; + }, + content:function(){ + trigger.num--; + trigger.player.draw(); + trigger.player.turnOver(); + } + }, + jidong:{ + trigger:{global:'phaseEnd'}, + filter:function(event,player){ + return player.hp==1&&!player.isTurnedOver(); + }, + // alter:true, + content:function(){ + 'step 0' + player.turnOver(); + player.recover(2); + 'step 1' + if(player.isTurnedOver()&&!get.is.altered('jidong')){ + player.addTempSkill('jidong2',{player:'turnOverAfter'}); + } + }, + ai:{ + threaten:function(player,target){ + if(target.hp==1) return 2; + return 1; + } + } + }, + jidong2:{ + trigger:{player:'damageBefore'}, + forced:true, + content:function(){ + trigger.cancel(); + }, + ai:{ + nofire:true, + nothunder:true, + nodamage:true, + effect:{ + target:function(card,player,target,current){ + if(get.tag(card,'damage')) return [0,0]; + } + }, + }, + mod:{ + targetEnabled:function(card,player,target){ + if(player!=target) return false; + } + } + }, + chongzhuang:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event,player){ + return player.storage.jijia<=0&&event.num>0; + }, + popup:false, + unique:true, + content:function(){ + player.storage.jijia2+=trigger.num; + if(player.storage.jijia2>=4){ + player.storage.jijia=4; + player.storage.jijia2=0; + player.markSkill('jijia'); + if(lib.config.skill_animation_type!='off'){ + player.logSkill('chongzhuang'); + player.$skill('重装') + } + } + } + }, + tuijin:{ + enable:'phaseUse', + usable:1, + unique:true, + filter:function(event,player){ + if(player.storage.jijia>0){ + return game.hasPlayer(function(current){ + return get.distance(player,current)>1 + }); + } + return false; + }, + filterTarget:function(card,player,target){ + return target!=player&&get.distance(player,target)>1; + }, + content:function(){ + player.storage.tuijin2=target; + player.addTempSkill('tuijin2'); + }, + ai:{ + order:11, + result:{ + target:function(player,target){ + if(get.attitude(player,target)<0){ + if(get.distance(player,target)>2) return -1.5; + return -1; + } + return 0.3; + } + } + } + }, + tuijin2:{ + mod:{ + globalFrom:function(from,to){ + if(to==from.storage.tuijin2) return -Infinity; + } + }, + mark:'character', + intro:{ + content:'与$的距离视为1直到回合结束' + }, + onremove:true + }, + jijia:{ + mark:true, + unique:true, + init:function(player){ + player.storage.jijia=4; + player.storage.jijia2=0; + }, + intro:{ + content:'机甲体力值:#' + }, + mod:{ + maxHandcard:function(player,num){ + if(player.storage.jijia>0){ + return num+player.storage.jijia; + } + } + }, + trigger:{player:'changeHp'}, + forced:true, + popup:false, + filter:function(event,player){ + return player.storage.jijia>0&&event.parent.name=='damage'&&event.num<0; + }, + content:function(){ + player.hp-=trigger.num; + player.update(); + player.storage.jijia+=trigger.num; + if(player.storage.jijia<=0){ + player.unmarkSkill('jijia'); + } + else{ + player.updateMarks(); + } + }, + ai:{ + threaten:function(player,target){ + if(target.storage.jijia<=0) return 2; + return 1; + } + } + }, + zihui:{ + enable:'phaseUse', + filter:function(event,player){ + return player.storage.jijia>0; + }, + filterTarget:function(card,player,target){ + return target!=player&&get.distance(player,target)<=2; + }, + unique:true, + selectTarget:-1, + skillAnimation:true, + animationColor:'fire', + line:'fire', + // alter:true, + content:function(){ + 'step 0' + var num=player.storage.jijia; + if(get.is.altered('zihui')){ + num=Math.max(1,Math.min(num,target.countCards('he'))); + } + target.chooseToDiscard(num,'he','弃置'+get.cnNumber(num)+'张牌,或受到2点火焰伤害').ai=function(card){ + if(target.hasSkillTag('nofire')) return 0; + if(get.type(card)!='basic') return 11-get.value(card); + if(target.hp>4) return 7-get.value(card); + if(target.hp==4&&num>=3) return 7-get.value(card); + if(target.hp==3&&num>=4) return 7-get.value(card); + if(num>1) return 8-get.value(card); + return 10-get.value(card); + }; + 'step 1' + if(!result.bool){ + target.damage(2,'fire'); + } + if(target==targets[targets.length-1]){ + player.storage.jijia=0; + player.unmarkSkill('jijia'); + } + }, + ai:{ + order:2, + result:{ + player:function(player){ + var num=0; + var players=game.filterPlayer(); + for(var i=0;i2) continue; + var nh=players[i].countCards('h'); + var att=get.attitude(player,players[i]); + if(nh0){ + if(players[i].hp<=2){ + num-=2; + } + else{ + num-=1.5; + } + } + } + else if(nh==player.storage.jijia){ + if(att<0){ + num+=0.5; + } + else if(att>0){ + num-=0.5; + } + } + } + if(num>=2) return 1; + return 0; + } + } + } + }, + xiandan:{ + trigger:{player:'shaBegin'}, + direct:true, + content:function(){ + "step 0" + var dis=trigger.target.countCards('h','shan')||trigger.target.getEquip('bagua')||trigger.target.countCards('h')>2; + var att=get.attitude(player,trigger.target); + var next=player.chooseToDiscard(get.prompt('xiandan')); + next.ai=function(card){ + if(att) return 0; + if(dis) return 7-get.value(card); + return 0; + } + next.logSkill='xiandan'; + "step 1" + if(result.bool){ + if(get.color(result.cards[0])=='red'){ + trigger.directHit=true; + } + else{ + player.addTempSkill('xiandan2','shaAfter'); + } + } + } + }, + xiandan2:{ + trigger:{source:'damageBegin'}, + filter:function(event){ + return event.card&&event.card.name=='sha'&&event.notLink(); + }, + forced:true, + popup:false, + content:function(){ + trigger.num++; + } + }, + shouge:{ + trigger:{source:'dieAfter'}, + frequent:true, + content:function(){ + player.gain(game.createCard('zhiliaobo'),'gain2'); + } + }, + tuji:{ + mod:{ + globalFrom:function(from,to,distance){ + if(_status.currentPhase==from){ + return distance-from.countUsed(); + } + }, + }, + }, + mujing:{ + enable:['chooseToRespond','chooseToUse'], + filterCard:function(card){ + return get.color(card)=='black'; + }, + position:'he', + viewAs:{name:'sha'}, + viewAsFilter:function(player){ + if(!player.countCards('he',{color:'black'})) return false; + }, + prompt:'将一张黑色牌当杀使用或打出', + check:function(card){return 4-get.value(card)}, + ai:{ + skillTagFilter:function(player){ + if(!player.countCards('he',{color:'black'})) return false; + }, + respondSha:true, + }, + group:'mujing2' + }, + mujing2:{ + trigger:{player:'shaMiss'}, + forced:true, + popup:false, + filter:function(event){ + return !event.parent._mujinged; + }, + content:function(){ + trigger.parent._mujinged=true; + player.getStat().card.sha--; + } + }, + lichang:{ + trigger:{player:'phaseEnd'}, + direct:true, + filter:function(event,player){ + return player.countCards('he',{color:'red'})>0; + }, + content:function(){ + "step 0" + var next=player.chooseToDiscard(get.prompt('lichang'),'he',{color:'red'}); + next.logSkill='lichang'; + next.ai=function(card){ + return 6-get.value(card); + }; + "step 1" + if(result.bool){ + player.addSkill('lichang2'); + } + }, + }, + lichang2:{ + trigger:{player:'phaseBegin'}, + direct:true, + mark:true, + intro:{ + content:'下个准备阶段令一名距离1以内的角色回复一点体力或摸两张牌' + }, + content:function(){ + 'step 0' + player.chooseTarget(get.prompt('lichang'),function(card,player,target){ + return get.distance(player,target)<=1; + }).ai=function(target){ + var att=get.attitude(player,target); + if(att>0){ + if(target.hp==1&&target.maxHp>1) return att*2; + } + return att; + }; + player.removeSkill('lichang2'); + 'step 1' + if(result.bool){ + player.logSkill('lichang',result.targets); + result.targets[0].chooseDrawRecover(2,true); + } + } + }, + mujing_old:{ + trigger:{player:'useCardToBegin'}, + filter:function(event,player){ + return event.target&&event.target!=player&&get.distance(event.target,player,'attack')>1; + }, + direct:true, + content:function(){ + 'step 0' + player.discardPlayerCard(get.prompt('mujing'),trigger.target).logSkill=['mujing']; + 'step 1' + if(result.bool&&player.countCards('h')<=trigger.target.countCards('h')){ + player.draw(); + } + } + }, + zhanlong:{ + trigger:{player:'phaseBegin'}, + unique:true, + mark:true, + skillAnimation:true, + init:function(player){ + player.storage.zhanlong=false; + }, + check:function(event,player){ + if(player.hasJudge('lebu')) return false; + return true; + }, + filter:function(event,player){ + if(player.storage.zhanlong) return false; + if(player.countCards('he')==0) return false; + if(player.hp!=1) return false; + return true; + }, + content:function(){ + 'step 0' + player.discard(player.getCards('he')); + 'step 1' + player.addTempSkill('zhanlong2'); + player.awakenSkill('zhanlong'); + player.storage.zhanlong=true; + var cards=[]; + for(var i=0;i<3;i++){ + cards.push(game.createCard('sha')); + } + player.gain(cards,'gain2'); + }, + ai:{ + threaten:function(player,target){ + if(target.hp==1) return 3; + return 1; + }, + effect:{ + target:function(card,player,target){ + if(!target.hasFriend()) return; + if(get.tag(card,'damage')==1&&target.hp==2&&target.countCards('he')&& + !target.isTurnedOver()&&_status.currentPhase!=target){ + if(get.distance(_status.currentPhase,target,'absolute')<=2) return [0.5,1]; + return 0.8; + } + } + } + }, + intro:{ + content:'limited' + } + }, + zhanlong2:{ + mod:{ + cardUsable:function(card){ + if(card.name=='sha') return Infinity; + } + } + }, + feiren:{ + trigger:{source:'damageBegin'}, + forced:true, + // alter:true, + filter:function(event,player){ + return !get.is.altered('feiren')&&event.card&&event.card.name=='sha'&&get.suit(event.card)=='spade'&&event.notLink(); + }, + content:function(){ + trigger.num++; + }, + mod:{ + targetInRange:function(card){ + if(card.name=='sha') return true; + }, + selectTarget:function(card,player,range){ + if(card.name=='sha'&&range[1]!=-1&&get.suit(card)=='club'){ + range[1]++; + } + }, + }, + ai:{ + threaten:1.4 + } + }, + feiren3:{ + trigger:{player:'useCardAfter'}, + filter:function(event,player){ + if(event.parent.name=='feiren2') return false; + if(event.card.name!='sha') return false; + if(get.suit(event.card)!='spade') return false; + var card=game.createCard(event.card.name,event.card.suit,event.card.number,event.card.nature); + for(var i=0;i0){ + return 0; + } + return get.recoverEffect(target,player,target); + } + } + } + }, + xie2:{ + mark:true, + trigger:{global:'phaseEnd'}, + forced:true, + filter:function(event,player){ + if(player.storage.xie=='now'){ + return event.player==player; + } + var num=game.phaseNumber-player.storage.xie; + return num&&num%6==0; + }, + content:function(){ + if(player.storage.xie=='now'){ + player.storage.xie=game.phaseNumber; + } + player.recover(); + }, + intro:{ + content:function(storage,player){ + var str='每隔六回合回复一点体力,直到'+get.translation(storage)+'死亡'; + if(typeof player.storage.xie=='number'){ + var num=game.phaseNumber-player.storage.xie; + num=num%6; + if(num==0){ + str+='(下次生效于本回合)' + } + else{ + str+='(下次生效于'+(6-num)+'回合后)' + } + } + return str; + }, + onunmark:function(storage,player){ + delete player.storage.xie; + delete player.storage.xie2; + } + }, + group:['xie3','xie4'] + }, + xie3:{ + trigger:{global:'phaseBegin'}, + forced:true, + popup:false, + content:function(){ + var num=game.phaseNumber-player.storage.xie; + num=num%6; + if(num){ + num=6-num; + } + player.storage.xie2_markcount=num; + player.updateMarks(); + } + }, + xie4:{ + trigger:{global:'dieAfter'}, + forced:true, + popup:false, + filter:function(event,player){ + return event.player==player.storage.xie2; + }, + content:function(){ + game.log(player,'解除了','【谐】'); + player.removeSkill('xie2'); + } + }, + luan:{ + enable:'phaseUse', + unique:true, + filterTarget:function(card,player,target){ + return target!=player&&!target.hasSkill('luan2'); + }, + filter:function(event,player){ + return player.countCards('h',{suit:'spade'}); + }, + filterCard:{suit:'spade'}, + check:function(card){ + return 7-get.value(card); + }, + content:function(){ + var current=game.findPlayer(function(player){ + return player.hasSkill('luan2'); + }); + if(current){ + current.removeSkill('luan2'); + } + target.addSkill('luan2'); + // target.storage.luan='now'; + target.storage.luan2=player; + }, + ai:{ + expose:0.2, + order:9.1, + threaten:2, + result:{ + target:function(player,target){ + var current=game.findPlayer(function(player){ + return player.hasSkill('luan2'); + }); + if(current&&get.attitude(player,current)<0){ + return 0; + } + if(target.hp==1) return 0.5; + return -1; + } + } + } + }, + luan2:{ + mark:true, + intro:{ + content:'受到的伤害后流失一点体力,直到首次进入濒死状态' + }, + trigger:{player:'damageEnd'}, + forced:true, + content:function(){ + player.loseHp(); + }, + ai:{ + threaten:1.2 + }, + group:['luan3','luan4'] + }, + luan3:{ + trigger:{player:'dyingAfter'}, + forced:true, + popup:false, + content:function(){ + game.log(player,'解除了','【乱】'); + player.removeSkill('luan2'); + } + }, + luan2_old:{ + mark:true, + trigger:{global:'phaseEnd'}, + forced:true, + filter:function(event,player){ + if(player.storage.luan=='now'){ + return event.player==player; + } + var num=game.phaseNumber-player.storage.luan; + return num&&num%6==0; + }, + content:function(){ + if(player.storage.luan=='now'){ + player.storage.luan=game.phaseNumber; + } + player.loseHp(); + }, + intro:{ + content:function(storage,player){ + var str='每隔六回合失去一点体力,直到'+get.translation(storage)+'死亡'; + if(typeof player.storage.luan=='number'){ + var num=game.phaseNumber-player.storage.luan; + num=num%6; + if(num==0){ + str+='(下次生效于本回合)' + } + else{ + str+='(下次生效于'+(6-num)+'回合后)' + } + } + return str; + }, + onunmark:function(storage,player){ + delete player.storage.luan; + delete player.storage.luan2; + } + }, + group:['luan3','luan4'] + }, + luan3_old:{ + trigger:{global:'phaseBegin'}, + forced:true, + popup:false, + content:function(){ + var num=game.phaseNumber-player.storage.luan; + num=num%6; + if(num){ + num=6-num; + } + player.storage.luan2_markcount=num; + player.updateMarks(); + } + }, + luan4:{ + trigger:{global:'dieAfter'}, + forced:true, + popup:false, + filter:function(event,player){ + return event.player==player.storage.luan2; + }, + content:function(){ + game.log(player,'解除了','【乱】'); + player.removeSkill('luan2'); + } + }, + sheng:{ + enable:'phaseUse', + unique:true, + mark:true, + skillAnimation:true, + animationColor:'metal', + init:function(player){ + player.storage.sheng=false; + }, + filter:function(event,player){ + if(player.storage.sheng) return false; + return true; + }, + filterTarget:function(card,player,target){ + return target.isDamaged(); + }, + selectTarget:[1,Infinity], + contentBefore:function(){ + player.turnOver(); + player.addSkill('sheng2'); + player.awakenSkill('sheng'); + player.storage.sheng=true; + }, + content:function(){ + target.recover(); + }, + ai:{ + order:1, + result:{ + target:function(player,target){ + var eff=get.recoverEffect(target,player,target); + if(player.hp==1) return eff; + if(player.hasUnknown()) return 0; + var num1=0,num2=0,num3=0,players=game.filterPlayer(); + for(var i=0;i0){ + num1++; + if(players[i].isDamaged()){ + num2++; + if(players[i].hp<=1){ + num3++; + } + } + } + } + if(num1==num2) return eff; + if(num2==num1-1&&num3) return eff; + if(num3>=2) return eff; + return 0; + } + }, + }, + intro:{ + content:'limited' + } + }, + sheng2:{ + trigger:{player:'phaseBegin'}, + forced:true, + popup:false, + content:function(){ + player.removeSkill('sheng2'); + }, + mod:{ + targetEnabled:function(card,player,target){ + if(player!=target) return false; + } + } + }, + yihun:{ + trigger:{player:'phaseEnd'}, + direct:true, + filter:function(event,player){ + return player.countCards('he',{color:'black'})>0&&!player.hasSkill('yihun2'); + }, + content:function(){ + 'step 0' + var next=player.chooseCardTarget({ + prompt:get.prompt('yihun'), + position:'he', + filterCard:function(card,player){ + return get.color(card)=='black'&&lib.filter.cardDiscardable(card,player); + }, + ai1:function(card){ + return 7-get.value(card); + }, + ai2:function(target){ + var att=-get.attitude(player,target); + if(target==player.next){ + att/=10; + } + if(target==player.next.next){ + att/=2; + } + return att; + }, + filterTarget:function(card,player,target){ + return player!=target; + }, + }); + 'step 1' + if(result.bool){ + player.discard(result.cards); + player.logSkill('yihun',result.targets); + player.addSkill('yihun2'); + var target=result.targets[0] + player.storage.yihun2=target; + if(target&&(get.mode()!='guozhan')||!target.isUnseen()){ + player.markSkillCharacter('yihun2',target,'移魂','在'+get.translation(target)+'的下一准备阶段视为对其使用一张杀'); + } + } + }, + }, + yihun2:{ + trigger:{global:['phaseBegin','dieAfter']}, + forced:true, + filter:function(event,player){ + return event.player==player.storage.yihun2; + }, + content:function(){ + if(player.storage.yihun2.isIn()){ + player.useCard({name:'sha'},player.storage.yihun2); + } + player.removeSkill('yihun2'); + delete player.storage.yihun2; + }, + mod:{ + targetEnabled:function(){ + return false; + }, + cardEnabled:function(card,player){ + return false; + }, + } + }, + huoyu:{ + enable:'phaseUse', + unique:true, + mark:true, + skillAnimation:true, + animationColor:'fire', + init:function(player){ + player.storage.huoyu=false; + }, + filter:function(event,player){ + if(player.storage.huoyu) return false; + if(player.countCards('he',{color:'red'})<2) return false; + return true; + }, + filterTarget:function(card,player,target){ + return player.canUse('chiyuxi',target); + }, + filterCard:{color:'red'}, + selectCard:2, + position:'he', + check:function(card){ + return 7-get.value(card); + }, + selectTarget:-1, + multitarget:true, + multiline:true, + line:'fire', + content:function(){ + 'step 0' + targets.sort(lib.sort.seat); + player.awakenSkill('huoyu'); + player.storage.huoyu=true; + player.useCard({name:'chiyuxi'},targets).animate=false; + 'step 1' + player.useCard({name:'chiyuxi'},targets).animate=false; + }, + ai:{ + order:5, + result:{ + target:function(player,target){ + if(player.hasUnknown()) return 0; + return get.effect(target,{name:'chiyuxi'},player,target); + } + }, + }, + intro:{ + content:'limited' + } + }, + feidan:{ + trigger:{source:'damageAfter'}, + direct:true, + filter:function(event,player){ + if(player.countCards('he')==0) return false; + if(!event.card) return false; + if(event.card.name!='sha') return false; + return game.hasPlayer(function(current){ + return current!=event.player&&get.distance(event.player,current)<=1 + }); + }, + content:function(){ + "step 0" + var eff=0; + var targets=game.filterPlayer(function(current){ + if(current!=trigger.player&&get.distance(trigger.player,current)<=1){ + eff+=get.damageEffect(current,player,player); + return true; + } + }); + event.targets=targets; + player.chooseToDiscard(get.prompt('feidan',targets)).set('ai',function(card){ + if(eff>0) return 7-get.value(card); + return 0; + }).set('logSkill',['feidan',targets]); + "step 1" + if(result.bool){ + event.targets.sort(lib.sort.seat); + } + else{ + event.finish(); + } + "step 2" + if(event.targets.length){ + event.targets.shift().damage(); + event.redo(); + } + }, + mod:{ + targetInRange:function(card,player,target){ + if(card.name=='sha'){ + if(get.distance(player,target)<=1) return false; + return true; + } + } + } + }, + yuedong:{ + trigger:{player:'phaseUseEnd'}, + direct:true, + content:function(){ + 'step 0' + var num=1+player.storage.yuedong_num; + player.chooseTarget(get.prompt('yuedong'),[1,num],function(card,player,target){ + if(player.storage.yuedong_recover){ + return target.hp1) return att/5; + if(num2==1){ + if(num>1) return att/3; + return att/4; + } + return att*1.1; + } + return att; + }); + 'step 1' + if(result.bool){ + player.logSkill('yuedong',result.targets); + var eff=1+player.storage.yuedong_eff; + if(player.storage.yuedong_recover){ + result.targets.sort(lib.sort.seat); + for(var i=0;i1&&!player.storage.yuedong_recover; + }, + check:function(card){ + return 6-get.value(card); + }, + content:function(){ + player.storage.yuedong_recover=true; + }, + ai:{ + order:10.2, + result:{ + player:function(player){ + var num1=0,num2=0,players=game.filterPlayer(); + for(var i=0;i0){ + num2++; + if(players[i].hp<=2&&players[i].maxHp>2){ + num1++; + if(players[i].hp==1){ + num1++; + } + } + } + } + if(num1>=3){ + return 1; + } + return 0; + } + } + } + }, + kuoyin:{ + enable:'phaseUse', + filterCard:true, + selectCard:function(){ + if(get.is.altered('kuoyin')) return 1; + if(_status.event.player.storage.yuedong_eff) return 1; + if(_status.event.player.storage.yuedong_num) return 2; + return [1,2]; + }, + position:'he', + // alter:true, + filter:function(event,player){ + if(get.is.altered('kuoyin')&&player.storage.yuedong_num) return false; + if(player.storage.yuedong_eff&&player.storage.yuedong_num) return false; + return player.countCards('he')>0; + }, + check:function(card){ + var player=_status.event.player; + var num1=0,num2=0,players=game.filterPlayer(); + for(var i=0;i0){ + num2++; + if(players[i].hp<=2&&players[i].maxHp>2){ + num1++; + } + } + } + if(player.storage.yuedong_recover){ + if(num1>1&&!player.storage.yuedong_num){ + if(ui.selected.cards.length) return 0; + return 7-get.value(card); + } + return 0; + } + else{ + if(num2>1&&!player.storage.yuedong_num){ + if(ui.selected.cards.length) return 0; + return 7-get.value(card); + } + if(num2>2){ + return 6-get.value(card); + } + return 5-get.value(card); + } + }, + content:function(){ + if(cards.length==1){ + player.storage.yuedong_num+=2; + } + else{ + player.storage.yuedong_eff++; + } + }, + ai:{ + threaten:1.6, + order:10.1, + result:{ + player:1 + } + }, + group:'kuoyin2' + }, + kuoyin2:{ + trigger:{player:'phaseBegin'}, + silent:true, + content:function(){ + player.storage.yuedong_recover=false; + player.storage.yuedong_num=0; + player.storage.yuedong_eff=0; + } + }, + guangshu:{ + enable:'phaseUse', + check:function(card){ + var player=_status.event.player; + var suit=get.suit(card); + if(suit=='heart'){ + if(game.hasPlayer(function(current){ + return current.hp==1&&get.attitude(player,current)>0 + })); + } + else if(suit=='spade'){ + return 7-get.value(card); + } + return 6-get.value(card); + }, + filter:function(event,player){ + return player.countCards('he')>0; + }, + filterTarget:function(card,player,target){ + return !target.hasSkill('guangshu_heart')&& + !target.hasSkill('guangshu_spade')&& + !target.hasSkill('guangshu_club')&& + !target.hasSkill('guangshu_diamond'); + }, + filterCard:true, + position:'he', + content:function(){ + target.addSkill('guangshu_'+get.suit(cards[0])); + }, + ai:{ + expose:0.2, + threaten:1.6, + order:5, + result:{ + target:function(player,target){ + if(!ui.selected.cards.length) return 0; + switch(get.suit(ui.selected.cards[0])){ + case 'heart':if(target.hp==1) return 1;return 0.1; + case 'diamond':return 1+Math.sqrt(target.countCards('h')); + case 'club':return -target.countCards('h')-Math.sqrt(target.countCards('h','sha')); + case 'spade':return get.damageEffect(target,player,target,'thunder'); + default:return 0; + } + } + } + } + }, + guangshu_diamond:{ + mark:true, + intro:{ + content:'下次造成伤害时摸两张牌' + }, + trigger:{source:'damageEnd'}, + forced:true, + content:function(){ + player.draw(2); + player.removeSkill('guangshu_diamond'); + } + }, + guangshu_heart:{ + mark:true, + intro:{ + content:'下次受到伤害时回复一点体力' + }, + trigger:{player:'damageEnd'}, + priority:6, + forced:true, + content:function(){ + player.recover(); + player.removeSkill('guangshu_heart'); + } + }, + guangshu_club:{ + mark:true, + intro:{ + content:'无法使用杀直到下一回合结束' + }, + trigger:{player:'phaseEnd'}, + forced:true, + popup:false, + content:function(){ + player.removeSkill('guangshu_club'); + }, + mod:{ + cardEnabled:function(card){ + if(card.name=='sha') return false; + } + } + }, + guangshu_spade:{ + mark:true, + intro:{ + content:'下个结束阶段受到一点无来源的雷电伤害' + }, + trigger:{player:'phaseEnd'}, + forced:true, + content:function(){ + player.damage('thunder','nosource'); + player.removeSkill('guangshu_spade'); + } + }, + ziyu:{ + trigger:{global:'phaseEnd'}, + direct:true, + filter:function(event,player){ + if(get.is.altered('ziyu')) return game.phaseNumber%6==0; + return game.phaseNumber%4==0; + }, + // alter:true, + content:function(){ + player.chooseDrawRecover(get.prompt('ziyu')).logSkill='ziyu'; + } + }, + shouhu:{ + mod:{ + cardEnabled:function(card){ + if(card.name=='sha') return false; + }, + }, + enable:'phaseUse', + filter:function(event,player){ + return player.countCards('h','sha')>0; + }, + filterTarget:function(card,player,target){ + return target.hpplayer.hp&&player.countCards('h','lebu')==0)||get.distance(player,event.player)>1); + }, + // alter:true, + intro:{ + content:function(storage,player){ + var str=''; + if(player.storage.shanxian_h.length){ + if(player.isUnderControl(true)){ + str+='手牌区:'+get.translation(player.storage.shanxian_h); + } + else{ + str+='手牌区:'+(player.storage.shanxian_h.length)+'张牌'; + } + } + if(player.storage.shanxian_e.length){ + if(str.length) str+='、'; + if(player.isUnderControl(true)){ + str+='装备区:'+get.translation(player.storage.shanxian_e); + } + else{ + str+='装备区:'+(player.storage.shanxian_e.length)+'张牌'; + } + } + return str; + }, + mark:function(dialog,content,player){ + if(player.storage.shanxian_h.length){ + if(player.isUnderControl(true)){ + dialog.add('
          手牌区
          '); + dialog.addSmall(player.storage.shanxian_h); + } + else{ + dialog.add('
          手牌区:'+player.storage.shanxian_h.length+'张牌
          '); + } + } + if(player.storage.shanxian_e.length){ + if(player.isUnderControl(true)){ + dialog.add('
          装备区
          '); + dialog.addSmall(player.storage.shanxian_e); + } + else{ + dialog.add('
          装备区:'+player.storage.shanxian_e.length+'张牌
          '); + } + } + }, + }, + logTarget:'player', + content:function(){ + "step 0" + if(!get.is.altered('shanxian')){ + player.draw(false); + player.$draw(); + } + "step 1" + player.storage.shanxian_h=player.getCards('h'); + player.storage.shanxian_e=player.getCards('e'); + player.storage.shanxian_n=1; + player.syncStorage('shanxian_e'); + player.phase('shanxian'); + player.storage.shanxian=trigger.player; + player.removeSkill('shanxian2'); + player.markSkill('shanxian'); + "step 2" + player.turnOver(true); + delete player.storage.shanxian; + }, + mod:{ + targetInRange:function(card,player,target,now){ + if(target==player.storage.shanxian) return true; + }, + }, + ai:{ + expose:0.1, + effect:{ + target:function(card){ + if(card.name=='guiyoujie') return [0,0]; + } + } + } + }, + shanxian2:{ + trigger:{player:['gainBegin','loseBegin']}, + forced:true, + popup:false, + content:function(){ + player.removeSkill('shanxian2'); + } + }, + shanhui:{ + unique:true, + trigger:{player:'damageEnd',source:'damageEnd'}, + filter:function(event,player){ + return player.storage.shanxian_h&&player.storage.shanxian_e&& + player.storage.shanxian_n>0&&!player.hasSkill('shanxian2'); + }, + check:function(event,player){ + var n1=player.countCards('he'); + var n2=player.storage.shanxian_h.length+player.storage.shanxian_e.length; + if(n1player.storage.shanxian_h.length+player.storage.shanxian_e.length){ + player.recover(); + } + player.storage.shanxian_n--; + if(player.storage.shanxian_n<=0){ + delete player.storage.shanxian_h; + delete player.storage.shanxian_e; + delete player.storage.shanxian_n; + player.unmarkSkill('shanxian'); + } + else{ + player.addSkill('shanxian2'); + } + } + } + }, + translate:{ + woliu:'涡流', + woliu2:'涡流', + woliu_info:'结束阶段,你可以选择至多两名角色,当你或目标中的任意一名角色成为杀的目标时,其余角色也将被追加为目标,直到你死亡或下一回合开始', + qianggu:'强固', + qianggu_info:'出牌阶段限一次,你可以弃置两张牌并获得两点护甲,若如此做,直到你的下个回合开始,其他角色对你使用杀时需要弃置一张基本牌,否则杀对你无效', + qianggu2:'强固', + qianggu2_bg:'固', + qianggu2_info:'其他角色对你使用杀时需要弃置一张基本牌,否则杀对你无效', + pingzhang:'屏障', + pingzhang_info:'每轮各限一次,当你受到伤害时,你可以弃置一张红桃牌令伤害-1;当一名其他角色受到伤害时,你可以弃置一张黑桃牌令伤害-1', + pingzhang_info_alter:'每轮各限一次,当你受到伤害时,你可以弃置一张红桃手牌令伤害-1;当一名其他角色受到伤害时,你可以弃置一张黑桃手牌令伤害-1', + liyong:'力涌', + liyong_info:'锁定技,你摸牌阶段摸牌数+X,X为你上一轮发动屏障的次数', + dianji:'电击', + dianji_info:'出牌阶段限一次,你可以将一张手牌当作惊雷闪对距离2以内的角色使用', + feitiao:'飞跳', + feitiao2:'飞跳', + feitiao_info:'出牌阶段开始时,你可以弃置一张牌并指定一名角色,你与该角色的距离视为1直到回合结束,然后该角色随机弃置一张牌', + bshaowei:'哨卫', + bshaowei_info:'结束阶段,你可以切换至哨卫模式。当处于此模式时,你的杀无视距离和防具、无数量限制且不可闪避;你不能闪避杀', + zhencha:'侦查', + zhencha_info:'结束阶段,你可以切换至侦查模式。当处于此模式时,每当你使用一张杀,你摸一张牌或回复一点体力', + liangou:'链钩', + liangou_info:'出牌阶段限一次,你可以弃置一张牌,指定一名其他角色并进行一次判定,若结果不为红桃,该角色与你距离为1且受到的首次伤害+1直到回合结束', + xiyang:'吸氧', + xiyang_info:'结束阶段,若你武将牌正面朝上,你可以翻面并回复两点体力', + qinru:'侵入', + qinru_info:'每当你使用杀指定目标时,你可以令其进行一次判定,若结果不为红桃,该角色的非锁定技失效直到其下一回合结束', + yinshen:'隐身', + yinshen_info:'锁定技,每当你失去最后一张基本牌,你获得潜行直到下一回合开始', + yinshen_info_old:'结束阶段,你可以弃置一张装备牌并获得潜行直到下一回合开始', + maichong:'脉冲', + maichong_info:'锁定技,每当你使用一张普通锦囊牌,你令最近三回合内被你侵入过的角色各随机弃置一张牌', + maichong_info_alter:'准备阶段,你可以令最近两名被你侵入的角色各随机弃置一张牌', + lichang:'力场', + lichang2:'力场', + lichang_info:'结束阶段,你可以弃置一张红色牌,若如此做,你可以在下个准备阶段令一名距离1以内的角色回复一点体力或摸两张牌', + mengji:'猛击', + mengji_info:'锁定技,若你已发动重盾,当你没有护甲时,你的杀造成的伤害+1', + zhongdun:'重盾', + zhongdun_info:'游戏开始时,你获得8点护甲;出牌阶段限一次,你可以弃置一张牌并将一点护甲分给一名没有护甲的其他角色', + zhongdun_info_alter:'游戏开始时,你获得6点护甲;出牌阶段限一次,你可以弃置一张牌并将一点护甲分给一名没有护甲的其他角色', + paotai:'炮台', + paotai2:'炮台', + paotai_info:'出牌阶段,你可以弃置一张杀布置或升级一个炮台(最高3级);结束阶段,炮台有一定机率对一名随机敌人造成一点火焰伤害;每当你受到一点伤害,炮台降低一级', + maoding:'铆钉', + maoding2:'铆钉', + maoding_info:'每当你造成或受到一次伤害,你可以获得一个零件;出牌阶段,你可以弃置两张零件牌令一名没有护甲的角色获得一点护甲', + maoding_info_alter:'每当你造成一次伤害,你可以获得一个零件;出牌阶段,你可以弃置两张零件牌令一名没有护甲的角色获得一点护甲', + bfengshi:'风矢', + bfengshi2:'风矢', + bfengshi_info:'锁定技,在一合内每当你使用一张牌,你的攻击范围+1;你的首张杀增加20%的概率强制命中;你的首张杀造成伤害后增加20%的概率令伤害+1', + bfengshi_info_alter:'锁定技,在一合内每当你使用一张牌,你的攻击范围+1;你的首张杀增加15%的概率强制命中;你的首张杀造成伤害后增加15%的概率令伤害+1', + yinbo:'音波', + yinbo_info:'出牌阶段限一次,你可以弃置一张黑桃牌,然后随机弃置三名敌人各一张牌', + liudan:'榴弹', + liudan_info:'每当你使用一张杀,你可以令所有不是此杀目标的其他角色有50%概率成为此杀的额外目标', + shoujia:'兽夹', + shoujia2:'兽夹', + shoujia3:'兽夹', + shoujia_info:'出牌阶段限一次,你可以将一张牌背面朝上置于一名其他角色的武将牌上,当该角色使用一张与此牌花色相同的牌指定其他角色为目标时,移去此牌,该角色将武将牌翻至背面;当你受到伤害时,移去此牌', + shihuo:'嗜火', + shihuo_info:'锁定技,每当一名角色受到火焰伤害,你摸一张牌', + shanguang:'闪光', + shanguang_info:'出牌阶段限一次,你可以弃置一张方片牌令攻击范围内的一名其他角色本回合内不能使用或打出卡牌', + tiandan:'填弹', + tiandan_info:'摸牌阶段开始时,你可以跳过出牌和弃牌阶段,然后获得若干张杀直到你的手牌数等于你的体值(最多为5)', + shenqiang:'神枪', + shenqiang_info:'锁定技,每当你在出牌阶段使用杀造成伤害,本阶段内出杀次数上限+1', + mianzhen:'眠针', + mianzhen2:'眠针', + mianzhen_info:'出牌阶段限一次,你可以弃置一张牌并令一名其他角色打出一张闪,否则该角色不能使用或打出卡牌直到其受到伤害或下一回合结束', + aqianghua:'强化', + aqianghua2:'强化', + aqianghua_info:'出牌阶段限一次,你可以将你的全部手牌(至少一张)交给一名其他角色,该角色获得一点护甲且下一次造成的伤害+1', + aqianghua_info_alter:'出牌阶段限一次,你可以将你的全部手牌(至少一张)交给一名其他角色,该角色下一次造成的伤害+1', + zhiyuan:'支援', + zhiyuan_info:'每当你即将造成伤害,你可以防止此伤害,改为令目标回复等量的体力', + juji:'狙击', + juji2:'狙击', + juji3:'狙击', + juji_info:'出牌阶段限一次,你可以弃置任意张花色不同的牌并指定一名有手牌的其他角色,若该角色的手牌中含有与你弃置的牌花色相同的牌,则本回合内你与其距离为1且该角色不能闪避你的杀', + duwen:'毒吻', + duwen2:'毒吻', + duwen_info:'锁定技,当你造成伤害时,若你的手牌数与受伤害角色相等,此伤害+1', + zhuagou:'抓钩', + zhuagou_info:'出牌阶段限一次,你可以弃置一张手牌并将你的座位移到任意位置', + dulei:'诡雷', + dulei2:'诡雷', + dulei_info:'出牌阶段,若你武将牌上没有牌,你可以将一张牌背面朝上置于你的武将牌上,当一名角色使用与该牌花色相同的牌指定你为目标时,你展示并移去此牌,然后该角色失去一点体力并随机弃置一张牌', + shuangqiang:'霜枪', + shuangqiang_info:'每当你对一名未翻面的角色造成伤害,你可以令伤害-1,然后令受伤害角色翻面', + baoxue:'暴雪', + baoxue_info:'限定技,出牌阶段,若你未翻面,你可以展示并弃置你的所有黑色牌,然后令至多X名其他角色随机弃置一张牌并将武将牌翻至背面,X为你的弃牌数;结算后你将武将牌翻至背面', + baoxue_info_alter:'限定技,出牌阶段,你可以展示并弃置你的所有黑色牌,并选择等量其他角色将武将牌翻至背面,结算后你将武将牌翻至背面', + bingqiang:'冰墙', + bingqiang2:'冰墙', + bingqiang2_bg:'墙', + bingqiang3:'冰墙', + bingqiang3_bg:'墙', + bingqiang4:'冰墙', + bingqiang4_bg:'墙', + bingqiang5:'冰墙', + bingqiang5_bg:'障', + bingqiang_info:'出牌阶段,你可以弃置X张红色牌令一名角色和其相邻角色的防御离+X,或弃置X张黑色牌令一名角色和其相邻角色的进攻离-X,效果持续到你的下个回合开始', + jidong:'急冻', + jidong_info:'在一名角色的结束阶段,若你的体力值为1且未翻面,你可以翻面并回复两点体力,在你的武将牌翻至正面前,你防止所有伤害,也不能成为其他角色卡牌的目标', + jidong_info_alter:'在一名角色的结束阶段,若你的体力值为1,你可以翻面并回复两点体力', + jijia:'机甲', + jijia_info:'锁定技,游戏开始时,你获得一个体力为4的机甲;你的手牌上限为你和机甲的体力之和;你受到的伤害由机甲承担', + zihui:'自毁', + zihui_info:'出牌阶段,你可以令距离2以内的所有其他角色选择一项:弃置数量等同你机甲体力值的牌,或受到2点火焰伤害,并在结算完毕后摧毁你的机甲', + zihui_info_alter:'出牌阶段,你可以令距离2以内的所有其他角色选择一项:1. 弃置数量等同你机甲体力值的牌(不足则全弃,至少弃1张);2. 或受到2点火焰伤害,并在结算完毕后摧毁你的机甲', + tuijin:'推进', + tuijin2:'推进', + tuijin_info:'出牌阶段限一次,若你有机甲,你可以指定一名角色,本回合内视为与其距离为1', + chongzhuang:'重装', + chongzhuang_info:'在你失去机甲后,当你累计造成了4点伤害时,你重新获得机甲', + shouge:'收割', + shouge_info:'每当你杀死一名角色,你可以获得一张治疗波', + tuji:'突击', + tuji_info:'锁定技,在你的回合内,每当你使用一张牌,你的进攻距离+1', + mujing:'目镜', + mujing2:'目镜', + mujing_info:'你可以将一张黑色牌当作杀使用或打出;当你的杀被闪避后,此杀不计入出杀次数', + mujing_old_info:'每当你对攻击范围不含你的角色使用一张牌,你可以弃置目标一张牌;若你的手牌数不多于目标,你摸一张牌', + feiren:'飞刃', + feiren2:'飞刃', + feiren_info:'你的杀无视距离;你的黑桃杀造成的伤害+1,梅花杀可以额外指定一个目标', + feiren_info_alter:'你的杀无视距离;你的梅花杀可以额外指定一个目标', + zhanlong:'斩龙', + zhanlong_info:'限定技,准备阶段,若你体力值为1,你可以弃置所有牌(至少一张),然后将三张杀置入你的手牌,若如此做,你本回合使用杀无次数限制', + xie:'谐', + xie2:'谐', + xie_info:'出牌阶段,你可以弃置一张红桃手牌并指定一名角色,该角色自其下一回合开始每隔六回合回复一点体力,直到你死亡。同一时间只能对一人发动', + luan:'乱', + luan2:'乱', + luan_old_info:'出牌阶段,你可以弃置一张黑桃手牌并指定一名角色,该角色自其下一回合开始每隔六回合失去一点体力,直到你死亡。同一时间只能对一人发动', + luan_info:'出牌阶段,你可以弃置一张黑桃手牌并指定一名角色,该角色受到伤害后流失一点体力,直到你死亡或其首次进入濒死状态。同一时间只能对一人发动', + sheng:'圣', + sheng_info:'限定技,出牌阶段,你可以将你的武将牌翻面,然后令任意名角色回复一点体力,若如此做,你不能成为其他角色的卡牌目标直到下一回合开始', + xiandan:'霰弹', + xiandan_info:'每当你使用一张杀,你可以弃置一张红色牌令此杀不可闪避,或弃置一张黑色牌令此杀伤害+1', + yihun:'移魂', + yihun_info:'结束阶段,你可以弃置一张黑色牌并指定一名其他角色,你在该角色下一准备阶段视为对其使用一张杀;在此之前,你不能使用卡牌,也不能成为卡牌的目标', + feidan:'飞弹', + feidan_info:'你的杀只能对距离1以外的角色使用;每当你使用杀造成伤害后,你可以弃置一张牌对距离目标1以内的其他角色各造成一点伤害', + huoyu:'火雨', + huoyu_info:'限定技,出牌阶段,你可以弃置两张红色牌,视为使用两张炽羽袭', + yuedong:'乐动', + yuedong_info:'出牌阶段结束时,你可以令一名角色摸一张牌', + kuoyin:'扩音', + kuoyin_info:'出牌阶段,你可以弃置一张牌令本回合乐动的目标数改为3,或弃置两张牌令本回合乐动的摸牌量改为2', + kuoyin_info_alter:'出牌阶段,你可以弃置一张牌令本回合乐动的目标数改为3', + huhuan:'互换', + huhuan_info:'出牌阶段,你可以弃置两张牌令本回合乐动的摸牌效果改为回复等量体力', + guangshu:'光枢', + guangshu_heart:'光盾', + guangshu_spade:'光塔', + guangshu_club:'光井', + guangshu_diamond:'光流', + guangshu_info:'出牌阶段,你可以弃置一张牌,并指定一名角色,根据弃置牌的花色执行如下效果:♥该角色下次受到伤害时回复一点体力;♦︎该角色下次造成伤害时摸两张牌;♣该角色无法使用杀直到下一回合结束;♠该角色于下个结束阶段受到一点无来源的雷电伤害', + ziyu:'自愈', + ziyu_info:'在一名角色的结束阶段,你可以回复一点体力或摸一张牌,每隔四回合发动一次', + ziyu_info_alter:'在一名角色的结束阶段,你可以回复一点体力或摸一张牌,每隔六回合发动一次', + shouhu:'守护', + shouhu_info:'你不能使用杀;出牌阶段,你可以弃置一张杀令一名其他角色回复一点体力', + shanxian:'闪现', + shanxian_info:'在一名其他角色的回合开始前,若你的武将牌正面朝上,你可以摸一张牌并进行一个额外回合,并在回合结束后将武将牌翻至背面。若如此做,你对其使用卡牌无视距离直到回合结束。', + shanxian_info_alter:'在一名其他角色的回合开始前,若你的武将牌正面朝上,你可以进行一个额外回合,并在回合结束后将武将牌翻至背面。若如此做,你对其使用卡牌无视距离直到回合结束。', + shanhui:'闪回', + shanhui_info:'当你造成或受到伤害后,你可以将你的牌重置为上次发动闪现时的状态,若你的牌数因此而减少,你回复一点体力', + ow_liekong:'猎空', + ow_sishen:'死神', + ow_tianshi:'天使', + ow_falaozhiying:'法老之鹰', + ow_zhixuzhiguang:'秩序之光', + ow_luxiao:'卢西奥', + ow_shibing:'士兵76', + ow_yuanshi:'源氏', + ow_chanyata:'禅雅塔', + ow_dva:'DVA', + ow_mei:'小美', + ow_heibaihe:'黑百合', + ow_ana:'安娜', + ow_baolei:'堡垒', + ow_maikelei:'麦克雷', + ow_banzang:'半藏', + ow_kuangshu:'狂鼠', + ow_tuobiang:'托比昂', + ow_laiyinhate:'莱因哈特', + ow_luba:'路霸', + ow_wensidun:'温斯顿', + ow_zhaliya:'查莉娅', + ow_heiying:'黑影', + ow_orisa:'奥丽莎', + } + }; +}); diff --git a/character/rank.js b/character/rank.js index a8f8b9255..d3e4bcada 100644 --- a/character/rank.js +++ b/character/rank.js @@ -1,709 +1,709 @@ -window.noname_character_rank={ - s:[ - 'swd_muyun', - 'swd_zhaoyun', - 'swd_septem', - 'hs_sthrall', - 'hs_malorne', - 'swd_duguningke', - 'swd_guyue', - 'swd_murongshi', - 'swd_cheyun', - 'swd_tuobayuer', - 'swd_yuxiaoxue', - 'gjqt_bailitusu', - 'swd_huanglei', - 'hs_medivh', - 'pal_yuejinzhao', - 'gjqt_beiluo', - 'gjqt_xieyi', - 'swd_xuanyuanjianxian', - ], - ap:[ - 'hs_hajiasha', - 'gjqt_yunwuyue', - 'gjqt_cenying', - 'gw_yioufeisisp', - 'pal_liumengli', - 'pal_yuntianhe', - 'pal_lixiaoyao', - 'swd_huanyuanzhi', - 'pal_xiahoujinxuan', - 'swd_huiyan', - 'ow_dva', - 'ow_mei', - 'ow_yuanshi', - 'swd_yuwentuo', - 'pal_xuanxiao', - 'swd_jipeng', - 'lusu', - 'old_yuanshu', - 'xunyu', - 'pal_murongziying', - 'swd_jiliang', - 'swd_shuijing', - 'shen_caocao', - 'hs_neptulon', - 'gjqt_aruan', - 'swd_muyue', - 'swd_qi', - 'zhangliao', - 'pal_zixuan', - 'swd_tuwei', - 'hs_xsylvanas', - 'hs_malygos', - 'hs_alakir', - 'ow_luxiao', - 'gjqt_ouyangshaogong', - 'ow_liekong', - 'ow_ana', - 'hs_aya', - 'hs_tyrande', - 'swd_shuwaner', - 'pal_yueqi', - 'hs_ashamoer', - 'gw_diandian', - 'ns_nanhua', - 're_sunshangxiang', - ], - a:[ - 'hs_siwangxianzhi', - 'hs_xukongzhiying', - 'hs_tuoqi', - 'mtg_lilianna', - 'mtg_jiesi', - 'mtg_jiding', - 'ns_yanliang', - 'ns_caocaosp', - 'ns_caocao', - 'gw_yisilinni', - 'liuqi', - 'ow_zhaliya', - 'gw_shasixiwusi', - 'gw_kairuisi', - 'gw_kanbi', - 'gw_nvyemo', - 'gw_linjing', - 'lifeng', - 'gw_meizi', - 'gw_aimin', - 'gw_puxila', - 'gw_bulanwang', - 'gw_kaxier', - 'gw_zhangyujushou', - 'xushi', - 'wuxian', - 'pal_tangyurou', - 'gw_luobo', - 'gw_aigeleisi', - 'gw_gaier', - 'pal_mingxiu', - 'pal_luozhaoyan', - 'pal_xia', - 'hs_yashaji', - 'hs_pengpeng', - 'hs_manyututeng', - 'hs_amala', - 'hs_kaituozhe', - 'hs_yinggencao', - 'hs_laila', - 'swd_kendi', - 'hs_enzoth', - 'hs_sapphiron', - 'gw_airuiting', - 'gw_dagong', - 'gw_fulisi', - 'gw_xili', - 'gw_yenaifa', - 'gw_yioufeisi', - 'gw_jieluote', - 'gw_telisi', - 'gw_luoqi', - 'gw_enxier', - 'gw_aisinie', - 'gw_falanxisika', - 'gw_haluo', - 'hs_khadgar', - 'swd_sikongyu', - 'swd_huzhongxian', - 'swd_anka', - 'swd_kangnalishi', - 'liuxie', - 'shen_lvbu', - 'liufeng', - 'zhangxingcai', - 'shen_lvmeng', - 'swd_xiarou', - 'hs_morgl', - 're_diaochan', - 'diy_zaozhirenjun', - 'ow_heiying', - 'pal_longkui', - 'pal_nangonghuang', - 'pal_xingxuan', - 'hs_walian', - 'hs_laxiao', - 'hs_fandral', - 'sundeng', - 'hs_xialikeer', - 'hs_sainaliusi', - 'hs_lrhonin', - 'hs_ysera', - 'yxs_diaochan', - 'liuzan', - 'lingcao', - 'hs_trueheart', - 'swd_wangsiyue', - 'swd_lanyin', - 'swd_hengai', - 're_huangyueying', - 'hs_bchillmaw', - 'shen_zhugeliang', - 'gjqt_fanglansheng', - 'hs_zhishigushu', - 'hs_zhanzhenggushu', - 'gjqt_xiayize', - 'hs_yngvar', - 're_huanggai', - 'hs_antonidas', - 'chenlin', - 'swd_chenjingchou', - 'hs_anduin', - 'yxs_yangyuhuan', - 'caoang', - 'swd_shanxiaoxiao', - 'gjqt_fengqingxue', - 'swd_ziqiao', - 'pal_jingtian', - 'yxs_wuzetian', - 'yxs_caocao', - 'bulianshi', - 'sp_pangtong', - 'liubiao', - 'zhongyao', - 'liuchen', - 'yxs_guiguzi', - 'hs_shanlingjuren', - 'hs_bannabusi', - 'zhangrang', - 'hs_lafamu', - 'ow_chanyata', - 'ow_tianshi', - 'ow_maikelei', - 'ow_tuobiang', - 'ow_banzang', - 'ow_laiyinhate', - 'wanglang', - 'huangzhong', - 'pal_wangpengxu', - 'yxs_luobinhan', - 'hs_alleria', - 're_lusu', - 'hs_fuding', - 'wangji', - 're_luxun', - 'pal_muchanglan', - 'yxs_luzhishen', - 'hs_jaina', - 're_lidian', - 'hs_kalimosi', - 'hs_zhihuanhua', - 'xizhicai', - 'maliang', - 'hs_yelinlonghou', - 'sp_liuqi', - 'hs_heifengqishi', - 'jikang', - 'ns_lvmeng', - 'ns_simazhao', - 'ns_guanlu', - 'ns_jinke', - 'ns_wangyun', - 'zuoci', - 're_liubei', - 'guansuo', - ], - am:[ - 'gw_saqiya', - 'gw_zhuoertan', - 'qinmi', - 'ns_duangui', - 'diaochan', - 'gw_lanbote', - 'gw_fenghuang', - 'ns_zuoci', - 'ns_huangzu', - 'ns_xinxianying', - 'ns_shenpei', - 'xuezong', - 'caiyong', - 'hs_barnes', - 'hs_yangyanwageli', - 'hs_aiqinvyao', - 'gjqt_chuqi', - 'gjqt_yanjiaxieyi', - 'beimihu', - 'hs_taisi', - 'caocao', - 'gw_oudimu', - 'guanyinping', - 'dongyun', - 'gw_xigedelifa', - 'gw_laomaotou', - 'gw_haizhiyezhu', - 'gw_nitelila', - 'gw_bierna', - 'gw_fuertaisite', - 'gw_hengsaite', - 'gw_kuite', - 'sunqian', - 'pal_anu', - 'gw_mieren', - 'gw_sanhanya', - 'pal_xianqing', - 'gw_shanhu', - 'gw_huoge', - 'taoqian', - 'pal_jushifang', - 'hs_fachaotuteng', - 'huangfusong', - 'hs_shizugui', - 'hs_shuiwenxuejia', - 'hs_pyros', - 'swd_xiyan', - 'swd_xiaohuanglong', - 'ow_orisa', - 'ow_baolei', - 'ow_wensidun', - 'gw_gaier', - 'gw_laduoweide', - 'gw_kaerweite', - 'yxs_napolun', - 'hs_aedwin', - 'hs_wujiyuansu', - 'ow_kuangshu', - 'old_wangyi', - 'hs_shaku', - 're_zhangliao', - 'zhoutai', - 'zhugejin', - 'hs_totemic', - 'swd_duanmeng', - 'pal_wenhui', - 'gjqt_xunfang', - 'ow_shibing', - 'hs_blingtron', - 'hs_kcthun', - 'ow_zhixuzhiguang', - 'ow_falaozhiying', - 'hs_bolvar', - 'sp_caiwenji', - 'yxs_yingzheng', - 'caorui', - 'manchong', - 'yxs_xiangyu', - 'swd_linyue', - 'swd_fuyan', - 'pal_xuejian', - 'swd_maixing', - 'xunyou', - 're_daqiao', - 'swd_fengyu', - 'swd_xuanyuanjiantong', - 'zhugeke', - 'pal_changqing', - 'gjqt_xiangling', - 'diy_zhenji', - 'gjqt_yuewuyi', - 'hs_magni', - 're_zhouyu', - 'dengai', - 'zhonghui', - 'hs_ronghejuren', - 'hs_wvelen', - 'swd_yuchiyanhong', - 'pal_hanlingsha', - 'diy_liuyan', - 'diy_zhouyu', - 'swd_jiuyou', - 'swd_duopeng', - 'swd_kama', - 'swd_yuli', - 'swd_rongshuang', - 'zhanghe', - 'zhangzhang', - 'pal_zhaoliner', - 'caozhi', - 'caochong', - 'xin_fazheng', - 'wuguotai', - 'chengong', - 'hs_siwangzhiyi', - 'hs_bilanyoulong', - 'yxs_direnjie', - 'yxs_sunwu', - 'zhanglu', - 'yxs_luban', - 'yxs_huamulan', - 'hs_jiaziruila', - 'hs_brann', - 'hs_kchromaggus', - 'sunru', - 'yxs_zhangsanfeng', - 'yxs_nandinggeer', - 'hs_kazhakusi', - 'swd_yeyaxi', - 'dianwei', - 'swd_weida', - 'yxs_libai', - 'hs_yogg', - 'swd_nicole', - 'yxs_wangzhaojun', - 'hs_alextrasza', - 'yxs_yangguang', - 're_sunquan', - 'wangyi', - 'diy_caiwenji', - 'hs_mijiaojisi', - 'hs_mojinbaozi', - 'xuhuang', - 'liuye', - 'sunshangxiang', - 'ns_yuji', - 'ns_yujisp', - 'ns_zhangxiu', - 'ns_masu', - 'ns_sunjian', - 're_zhugeliang', - 're_zhenji', - 'chengyu', - ], - bp:[ - 'hs_duyaxinshi', - 'xiahouyuan', - 'ns_huamulan', - 'sunquan', - 'ns_zhugeliang', - 'ns_zhangbao', - 'ns_yangyi', - 'gw_feilafanruide', - 'tangzi', - 'hs_fengjianhuanfengzhe', - 'zhenji', - 'gw_qigaiwang', - 'quyi', - 'wangyun', - 'pal_xiaoman', - 'pal_jiangyunfan', - 'pal_longyou', - 'gw_aokeweisite', - 'pal_wangxiaohu', - 'pal_shenqishuang', - 'pal_sumei', - 'hs_tgolem', - 'hs_huolituteng', - 'hs_selajin', - 'hs_hemite', - 'zoushi', - 'swd_zhiyin', - 'hs_hudunren', - 'hs_ruanniguai', - 'diy_lukang', - 'yxs_meixi', - 'yxs_lanlinwang', - 'jianyong', - 'jiling', - 'zhangren', - 'yanbaihu', - 'sunziliufang', - 'shen_zhouyu', - 'yj_jushou', - 'swd_zidashu', - 'taishici', - 'caifuren', - 'yxs_kaisa', - 'sunluyu', - 'liyan', - 'pal_jiangcheng', - 'zhugeguo', - 'xiaoqiao', - 'sp_zhangjiao', - 'hs_tanghangu', - 'ow_heibaihe', - 'ow_luba', - 'pal_leiyuange', - 'dongbai', - 'swd_moye', - 'guohuanghou', - 'yxs_zhaoyong', - 'litong', - 'old_quancong', - 'mizhu', - 'hs_hallazeal', - 'liuyu', - 'buzhi', - 'chess_zhangliao', - 'chess_sunshangxiang', - 'chess_huangzhong', - 'chess_taishici', - 'chess_diaochan', - 'ow_sishen', - 'hs_nate', - 'hs_finley', - 'hs_yelise', - 'hs_loatheb', - 'hs_xuanzhuanjijia', - 'swd_quxian', - 'yxs_zhuyuanzhang', - 'yxs_jinke', - 'gongsunyuan', - 'guotufengji', - 'shixie', - 'yxs_mozi', - 'hs_xuefashi', - 'xiahoushi', - 'zhangsong', - 'yxs_yuefei', - 'yxs_fuermosi', - 'yxs_xiaoqiao', - 'zhuhuan', - 'yxs_aijiyanhou', - 'yxs_bole', - 're_machao', - 're_guanyu', - 'yuanshao', - 're_yuanshao', - 'hs_waleera', - 'gjqt_yinqianshang', - 'gjqt_hongyu', - 'gjqt_wenrenyu', - 'shen_zhaoyun', - 'swd_shangzhang', - 'swd_situqiang', - 'hs_malfurion', - 'swd_haidapang', - 'hs_wuther', - 'sp_dongzhuo', - 'jiangwei', - 'liubei', - 'mateng', - 'wutugu', - 'swd_chunyuheng', - 'hetaihou', - 'swd_fengtianling', - 'kongrong', - 'swd_qiner', - 'sp_diaochan', - 'swd_jiangziya', - 'liushan', - 'zhugedan', - 'sp_zhaoyun', - 're_huatuo', - 'swd_jiangwu', - 'sp_jiangwei', - 'zhugeliang', - 'swd_huyue', - 'swd_zhuoshanzhu', - 'swd_shaowei', - 'swd_hanlong', - 'swd_hupo', - 'caopi', - 'jiaxu', - 'zhangchunhua', - 'xushu', - 'xin_xushu', - 'lingtong', - 'chenqun', - 'guyong', - 'diy_xuhuang', - 'hs_jgarrosh', - 'guanping', - 'sunxiu', - 'quancong', - 'yxs_luocheng', - 'caoren', - 'zumao', - 'sp_ganning', - 'sp_lvmeng', - 'swd_lilian', - 'hs_lreno', - 'hs_zhouzhuo', - 'hs_liadrin', - 'hs_anomalus', - 'mifuren', - 'hanba', - 'sunjian', - 'pangtong', - 'caochun', - 'diy_liuzan', - 'diy_yangyi', - 're_yuanshu', - 'yuanshu', - 're_guojia', - 'cuiyan', - 'sp_zhugeliang', - 'hs_guldan', - 're_ganning', - 'caiwenji', - 're_xushu', - 'hs_lrexxar', - 'huatuo', - 'sunhao', - 'swd_jiting', - 'hs_nozdormu', - 'zhoucang', - 'hs_shifazhe', - 'huanghao', - 'luzhi', - ], - b:[ - 'hs_yelinchulong', - 'xinxianying', - 'caojie', - 'hs_shirencao', - 'sp_daqiao', - 'hs_jiawodun', - 'yxs_weizhongxian', - 'cenhun', - 'panzhangmazhong', - 'jsp_guanyu', - 'wenpin', - 'diy_liufu', - 're_xuzhu', - 're_simayi', - 'yxs_mingchenghuanghou', - 'diy_tianyu', - 'old_zhuran', - 'old_lingtong', - 'sp_jiaxu', - 'sp_liubei', - 'zhuling', - 'xin_liru', - 'weiyan', - 'sp_xiahoudun', - 'jsp_huangyueying', - 'sp_zhangfei', - 'yxs_lishimin', - 'daxiaoqiao', - 'pal_linyueru', - 'zhuran', - 'fuhuanghou', - 'xin_masu', - 'yxs_chengjisihan', - 'masu', - 'handang', - 'swd_youzhao', - 'swd_fu', - 'yxs_chengyaojin', - 'yxs_yujix', - 'swd_jialanduo', - 'kanze', - 'mazhong', - 'zhaoxiang', - 'heqi', - 'sp_machao', - 're_zhaoyun', - 'fuwan', - 'hs_jinglinglong', - 'ganfuren', - 'hs_huzhixiannv', - 'sp_sunshangxiang', - 'jiangqing', - 'shen_simayi', - 'swd_hanluo', - 'swd_zhanggao', - 'simalang', - 'sp_caoren', - 'daqiao', - 'swd_luchengxuan', - 'dongzhuo', - 'fazheng', - 'yufan', - 'guanzhang', - 'diy_yuji', - 'yuejin', - 'gaoshun', - 'chengpu', - 'caozhen', - 'wuyi', - 'hanhaoshihuan', - 'caoxiu', - 'zhangyi', - 'yxs_baosi', - 'lingju', - 'xin_yujin', - 'diy_feishi', - 'yxs_lvzhi', - 'madai', - 'sunce', - 'huangyueying', - 'guojia', - 'jiangfei', - 'xiahouba', - 'yxs_tangbohu', - 'caozhang', - 'pangde', - ], - bm:[ - 'ns_nanhua_left', - 'ns_nanhua_right', - 'ns_wenchou', - 'zangba', - 'diy_xizhenxihong', - 'tadun', - 'guohuai', - 'sunluban', - 'zhouyu', - 'dingfeng', - 'mayunlu', - 'shen_guanyu', - 're_caocao', - 're_lvbu', - 'chendong', - 'simayi', - 'ganning', - 'luxun', - 'zhangjiao', - 'zhurong', - 'jsp_zhaoyun', - 'tianfeng', - 'old_zhonghui', - 'xusheng', - 'liru', - 're_zhangfei', - 'yxs_goujian', - 'yangxiu', - 're_lvmeng', - 're_xiahoudun', - 'zhangliang', - 'sp_pangde', - 'yanwen', - ], - c:[ - 'old_xusheng', - 'old_caozhen', - 'old_caoxiu', - 'old_madai', - 'zhuzhi', - 'liaohua', - 'zhaoyun', - 'machao', - 're_gongsunzan', - 'caohong', - 'lvbu', - 'yujin', - ], - d:[ - 'menghuo', - 'huaxiong', - 'panfeng', - 'guanyu', - 'xuzhu', - 'lvmeng', - 'huanggai', - 'xiahoudun', - 'zhangbao', - 'zhangfei', - 'old_huaxiong', - ], -}; +window.noname_character_rank={ + s:[ + 'swd_muyun', + 'swd_zhaoyun', + 'swd_septem', + 'hs_sthrall', + 'hs_malorne', + 'swd_duguningke', + 'swd_guyue', + 'swd_murongshi', + 'swd_cheyun', + 'swd_tuobayuer', + 'swd_yuxiaoxue', + 'gjqt_bailitusu', + 'swd_huanglei', + 'hs_medivh', + 'pal_yuejinzhao', + 'gjqt_beiluo', + 'gjqt_xieyi', + 'swd_xuanyuanjianxian', + ], + ap:[ + 'hs_hajiasha', + 'gjqt_yunwuyue', + 'gjqt_cenying', + 'gw_yioufeisisp', + 'pal_liumengli', + 'pal_yuntianhe', + 'pal_lixiaoyao', + 'swd_huanyuanzhi', + 'pal_xiahoujinxuan', + 'swd_huiyan', + 'ow_dva', + 'ow_mei', + 'ow_yuanshi', + 'swd_yuwentuo', + 'pal_xuanxiao', + 'swd_jipeng', + 'lusu', + 'old_yuanshu', + 'xunyu', + 'pal_murongziying', + 'swd_jiliang', + 'swd_shuijing', + 'shen_caocao', + 'hs_neptulon', + 'gjqt_aruan', + 'swd_muyue', + 'swd_qi', + 'zhangliao', + 'pal_zixuan', + 'swd_tuwei', + 'hs_xsylvanas', + 'hs_malygos', + 'hs_alakir', + 'ow_luxiao', + 'gjqt_ouyangshaogong', + 'ow_liekong', + 'ow_ana', + 'hs_aya', + 'hs_tyrande', + 'swd_shuwaner', + 'pal_yueqi', + 'hs_ashamoer', + 'gw_diandian', + 'ns_nanhua', + 're_sunshangxiang', + ], + a:[ + 'hs_siwangxianzhi', + 'hs_xukongzhiying', + 'hs_tuoqi', + 'mtg_lilianna', + 'mtg_jiesi', + 'mtg_jiding', + 'ns_yanliang', + 'ns_caocaosp', + 'ns_caocao', + 'gw_yisilinni', + 'liuqi', + 'ow_zhaliya', + 'gw_shasixiwusi', + 'gw_kairuisi', + 'gw_kanbi', + 'gw_nvyemo', + 'gw_linjing', + 'lifeng', + 'gw_meizi', + 'gw_aimin', + 'gw_puxila', + 'gw_bulanwang', + 'gw_kaxier', + 'gw_zhangyujushou', + 'xushi', + 'wuxian', + 'pal_tangyurou', + 'gw_luobo', + 'gw_aigeleisi', + 'gw_gaier', + 'pal_mingxiu', + 'pal_luozhaoyan', + 'pal_xia', + 'hs_yashaji', + 'hs_pengpeng', + 'hs_manyututeng', + 'hs_amala', + 'hs_kaituozhe', + 'hs_yinggencao', + 'hs_laila', + 'swd_kendi', + 'hs_enzoth', + 'hs_sapphiron', + 'gw_airuiting', + 'gw_dagong', + 'gw_fulisi', + 'gw_xili', + 'gw_yenaifa', + 'gw_yioufeisi', + 'gw_jieluote', + 'gw_telisi', + 'gw_luoqi', + 'gw_enxier', + 'gw_aisinie', + 'gw_falanxisika', + 'gw_haluo', + 'hs_khadgar', + 'swd_sikongyu', + 'swd_huzhongxian', + 'swd_anka', + 'swd_kangnalishi', + 'liuxie', + 'shen_lvbu', + 'liufeng', + 'zhangxingcai', + 'shen_lvmeng', + 'swd_xiarou', + 'hs_morgl', + 're_diaochan', + 'diy_zaozhirenjun', + 'ow_heiying', + 'pal_longkui', + 'pal_nangonghuang', + 'pal_xingxuan', + 'hs_walian', + 'hs_laxiao', + 'hs_fandral', + 'sundeng', + 'hs_xialikeer', + 'hs_sainaliusi', + 'hs_lrhonin', + 'hs_ysera', + 'yxs_diaochan', + 'liuzan', + 'lingcao', + 'hs_trueheart', + 'swd_wangsiyue', + 'swd_lanyin', + 'swd_hengai', + 're_huangyueying', + 'hs_bchillmaw', + 'shen_zhugeliang', + 'gjqt_fanglansheng', + 'hs_zhishigushu', + 'hs_zhanzhenggushu', + 'gjqt_xiayize', + 'hs_yngvar', + 're_huanggai', + 'hs_antonidas', + 'chenlin', + 'swd_chenjingchou', + 'hs_anduin', + 'yxs_yangyuhuan', + 'caoang', + 'swd_shanxiaoxiao', + 'gjqt_fengqingxue', + 'swd_ziqiao', + 'pal_jingtian', + 'yxs_wuzetian', + 'yxs_caocao', + 'bulianshi', + 'sp_pangtong', + 'liubiao', + 'zhongyao', + 'liuchen', + 'yxs_guiguzi', + 'hs_shanlingjuren', + 'hs_bannabusi', + 'zhangrang', + 'hs_lafamu', + 'ow_chanyata', + 'ow_tianshi', + 'ow_maikelei', + 'ow_tuobiang', + 'ow_banzang', + 'ow_laiyinhate', + 'wanglang', + 'huangzhong', + 'pal_wangpengxu', + 'yxs_luobinhan', + 'hs_alleria', + 're_lusu', + 'hs_fuding', + 'wangji', + 're_luxun', + 'pal_muchanglan', + 'yxs_luzhishen', + 'hs_jaina', + 're_lidian', + 'hs_kalimosi', + 'hs_zhihuanhua', + 'xizhicai', + 'maliang', + 'hs_yelinlonghou', + 'sp_liuqi', + 'hs_heifengqishi', + 'jikang', + 'ns_lvmeng', + 'ns_simazhao', + 'ns_guanlu', + 'ns_jinke', + 'ns_wangyun', + 'zuoci', + 're_liubei', + 'guansuo', + ], + am:[ + 'gw_saqiya', + 'gw_zhuoertan', + 'qinmi', + 'ns_duangui', + 'diaochan', + 'gw_lanbote', + 'gw_fenghuang', + 'ns_zuoci', + 'ns_huangzu', + 'ns_xinxianying', + 'ns_shenpei', + 'xuezong', + 'caiyong', + 'hs_barnes', + 'hs_yangyanwageli', + 'hs_aiqinvyao', + 'gjqt_chuqi', + 'gjqt_yanjiaxieyi', + 'beimihu', + 'hs_taisi', + 'caocao', + 'gw_oudimu', + 'guanyinping', + 'dongyun', + 'gw_xigedelifa', + 'gw_laomaotou', + 'gw_haizhiyezhu', + 'gw_nitelila', + 'gw_bierna', + 'gw_fuertaisite', + 'gw_hengsaite', + 'gw_kuite', + 'sunqian', + 'pal_anu', + 'gw_mieren', + 'gw_sanhanya', + 'pal_xianqing', + 'gw_shanhu', + 'gw_huoge', + 'taoqian', + 'pal_jushifang', + 'hs_fachaotuteng', + 'huangfusong', + 'hs_shizugui', + 'hs_shuiwenxuejia', + 'hs_pyros', + 'swd_xiyan', + 'swd_xiaohuanglong', + 'ow_orisa', + 'ow_baolei', + 'ow_wensidun', + 'gw_gaier', + 'gw_laduoweide', + 'gw_kaerweite', + 'yxs_napolun', + 'hs_aedwin', + 'hs_wujiyuansu', + 'ow_kuangshu', + 'old_wangyi', + 'hs_shaku', + 're_zhangliao', + 'zhoutai', + 'zhugejin', + 'hs_totemic', + 'swd_duanmeng', + 'pal_wenhui', + 'gjqt_xunfang', + 'ow_shibing', + 'hs_blingtron', + 'hs_kcthun', + 'ow_zhixuzhiguang', + 'ow_falaozhiying', + 'hs_bolvar', + 'sp_caiwenji', + 'yxs_yingzheng', + 'caorui', + 'manchong', + 'yxs_xiangyu', + 'swd_linyue', + 'swd_fuyan', + 'pal_xuejian', + 'swd_maixing', + 'xunyou', + 're_daqiao', + 'swd_fengyu', + 'swd_xuanyuanjiantong', + 'zhugeke', + 'pal_changqing', + 'gjqt_xiangling', + 'diy_zhenji', + 'gjqt_yuewuyi', + 'hs_magni', + 're_zhouyu', + 'dengai', + 'zhonghui', + 'hs_ronghejuren', + 'hs_wvelen', + 'swd_yuchiyanhong', + 'pal_hanlingsha', + 'diy_liuyan', + 'diy_zhouyu', + 'swd_jiuyou', + 'swd_duopeng', + 'swd_kama', + 'swd_yuli', + 'swd_rongshuang', + 'zhanghe', + 'zhangzhang', + 'pal_zhaoliner', + 'caozhi', + 'caochong', + 'xin_fazheng', + 'wuguotai', + 'chengong', + 'hs_siwangzhiyi', + 'hs_bilanyoulong', + 'yxs_direnjie', + 'yxs_sunwu', + 'zhanglu', + 'yxs_luban', + 'yxs_huamulan', + 'hs_jiaziruila', + 'hs_brann', + 'hs_kchromaggus', + 'sunru', + 'yxs_zhangsanfeng', + 'yxs_nandinggeer', + 'hs_kazhakusi', + 'swd_yeyaxi', + 'dianwei', + 'swd_weida', + 'yxs_libai', + 'hs_yogg', + 'swd_nicole', + 'yxs_wangzhaojun', + 'hs_alextrasza', + 'yxs_yangguang', + 're_sunquan', + 'wangyi', + 'diy_caiwenji', + 'hs_mijiaojisi', + 'hs_mojinbaozi', + 'xuhuang', + 'liuye', + 'sunshangxiang', + 'ns_yuji', + 'ns_yujisp', + 'ns_zhangxiu', + 'ns_masu', + 'ns_sunjian', + 're_zhugeliang', + 're_zhenji', + 'chengyu', + ], + bp:[ + 'hs_duyaxinshi', + 'xiahouyuan', + 'ns_huamulan', + 'sunquan', + 'ns_zhugeliang', + 'ns_zhangbao', + 'ns_yangyi', + 'gw_feilafanruide', + 'tangzi', + 'hs_fengjianhuanfengzhe', + 'zhenji', + 'gw_qigaiwang', + 'quyi', + 'wangyun', + 'pal_xiaoman', + 'pal_jiangyunfan', + 'pal_longyou', + 'gw_aokeweisite', + 'pal_wangxiaohu', + 'pal_shenqishuang', + 'pal_sumei', + 'hs_tgolem', + 'hs_huolituteng', + 'hs_selajin', + 'hs_hemite', + 'zoushi', + 'swd_zhiyin', + 'hs_hudunren', + 'hs_ruanniguai', + 'diy_lukang', + 'yxs_meixi', + 'yxs_lanlinwang', + 'jianyong', + 'jiling', + 'zhangren', + 'yanbaihu', + 'sunziliufang', + 'shen_zhouyu', + 'yj_jushou', + 'swd_zidashu', + 'taishici', + 'caifuren', + 'yxs_kaisa', + 'sunluyu', + 'liyan', + 'pal_jiangcheng', + 'zhugeguo', + 'xiaoqiao', + 'sp_zhangjiao', + 'hs_tanghangu', + 'ow_heibaihe', + 'ow_luba', + 'pal_leiyuange', + 'dongbai', + 'swd_moye', + 'guohuanghou', + 'yxs_zhaoyong', + 'litong', + 'old_quancong', + 'mizhu', + 'hs_hallazeal', + 'liuyu', + 'buzhi', + 'chess_zhangliao', + 'chess_sunshangxiang', + 'chess_huangzhong', + 'chess_taishici', + 'chess_diaochan', + 'ow_sishen', + 'hs_nate', + 'hs_finley', + 'hs_yelise', + 'hs_loatheb', + 'hs_xuanzhuanjijia', + 'swd_quxian', + 'yxs_zhuyuanzhang', + 'yxs_jinke', + 'gongsunyuan', + 'guotufengji', + 'shixie', + 'yxs_mozi', + 'hs_xuefashi', + 'xiahoushi', + 'zhangsong', + 'yxs_yuefei', + 'yxs_fuermosi', + 'yxs_xiaoqiao', + 'zhuhuan', + 'yxs_aijiyanhou', + 'yxs_bole', + 're_machao', + 're_guanyu', + 'yuanshao', + 're_yuanshao', + 'hs_waleera', + 'gjqt_yinqianshang', + 'gjqt_hongyu', + 'gjqt_wenrenyu', + 'shen_zhaoyun', + 'swd_shangzhang', + 'swd_situqiang', + 'hs_malfurion', + 'swd_haidapang', + 'hs_wuther', + 'sp_dongzhuo', + 'jiangwei', + 'liubei', + 'mateng', + 'wutugu', + 'swd_chunyuheng', + 'hetaihou', + 'swd_fengtianling', + 'kongrong', + 'swd_qiner', + 'sp_diaochan', + 'swd_jiangziya', + 'liushan', + 'zhugedan', + 'sp_zhaoyun', + 're_huatuo', + 'swd_jiangwu', + 'sp_jiangwei', + 'zhugeliang', + 'swd_huyue', + 'swd_zhuoshanzhu', + 'swd_shaowei', + 'swd_hanlong', + 'swd_hupo', + 'caopi', + 'jiaxu', + 'zhangchunhua', + 'xushu', + 'xin_xushu', + 'lingtong', + 'chenqun', + 'guyong', + 'diy_xuhuang', + 'hs_jgarrosh', + 'guanping', + 'sunxiu', + 'quancong', + 'yxs_luocheng', + 'caoren', + 'zumao', + 'sp_ganning', + 'sp_lvmeng', + 'swd_lilian', + 'hs_lreno', + 'hs_zhouzhuo', + 'hs_liadrin', + 'hs_anomalus', + 'mifuren', + 'hanba', + 'sunjian', + 'pangtong', + 'caochun', + 'diy_liuzan', + 'diy_yangyi', + 're_yuanshu', + 'yuanshu', + 're_guojia', + 'cuiyan', + 'sp_zhugeliang', + 'hs_guldan', + 're_ganning', + 'caiwenji', + 're_xushu', + 'hs_lrexxar', + 'huatuo', + 'sunhao', + 'swd_jiting', + 'hs_nozdormu', + 'zhoucang', + 'hs_shifazhe', + 'huanghao', + 'luzhi', + ], + b:[ + 'hs_yelinchulong', + 'xinxianying', + 'caojie', + 'hs_shirencao', + 'sp_daqiao', + 'hs_jiawodun', + 'yxs_weizhongxian', + 'cenhun', + 'panzhangmazhong', + 'jsp_guanyu', + 'wenpin', + 'diy_liufu', + 're_xuzhu', + 're_simayi', + 'yxs_mingchenghuanghou', + 'diy_tianyu', + 'old_zhuran', + 'old_lingtong', + 'sp_jiaxu', + 'sp_liubei', + 'zhuling', + 'xin_liru', + 'weiyan', + 'sp_xiahoudun', + 'jsp_huangyueying', + 'sp_zhangfei', + 'yxs_lishimin', + 'daxiaoqiao', + 'pal_linyueru', + 'zhuran', + 'fuhuanghou', + 'xin_masu', + 'yxs_chengjisihan', + 'masu', + 'handang', + 'swd_youzhao', + 'swd_fu', + 'yxs_chengyaojin', + 'yxs_yujix', + 'swd_jialanduo', + 'kanze', + 'mazhong', + 'zhaoxiang', + 'heqi', + 'sp_machao', + 're_zhaoyun', + 'fuwan', + 'hs_jinglinglong', + 'ganfuren', + 'hs_huzhixiannv', + 'sp_sunshangxiang', + 'jiangqing', + 'shen_simayi', + 'swd_hanluo', + 'swd_zhanggao', + 'simalang', + 'sp_caoren', + 'daqiao', + 'swd_luchengxuan', + 'dongzhuo', + 'fazheng', + 'yufan', + 'guanzhang', + 'diy_yuji', + 'yuejin', + 'gaoshun', + 'chengpu', + 'caozhen', + 'wuyi', + 'hanhaoshihuan', + 'caoxiu', + 'zhangyi', + 'yxs_baosi', + 'lingju', + 'xin_yujin', + 'diy_feishi', + 'yxs_lvzhi', + 'madai', + 'sunce', + 'huangyueying', + 'guojia', + 'jiangfei', + 'xiahouba', + 'yxs_tangbohu', + 'caozhang', + 'pangde', + ], + bm:[ + 'ns_nanhua_left', + 'ns_nanhua_right', + 'ns_wenchou', + 'zangba', + 'diy_xizhenxihong', + 'tadun', + 'guohuai', + 'sunluban', + 'zhouyu', + 'dingfeng', + 'mayunlu', + 'shen_guanyu', + 're_caocao', + 're_lvbu', + 'chendong', + 'simayi', + 'ganning', + 'luxun', + 'zhangjiao', + 'zhurong', + 'jsp_zhaoyun', + 'tianfeng', + 'old_zhonghui', + 'xusheng', + 'liru', + 're_zhangfei', + 'yxs_goujian', + 'yangxiu', + 're_lvmeng', + 're_xiahoudun', + 'zhangliang', + 'sp_pangde', + 'yanwen', + ], + c:[ + 'old_xusheng', + 'old_caozhen', + 'old_caoxiu', + 'old_madai', + 'zhuzhi', + 'liaohua', + 'zhaoyun', + 'machao', + 're_gongsunzan', + 'caohong', + 'lvbu', + 'yujin', + ], + d:[ + 'menghuo', + 'huaxiong', + 'panfeng', + 'guanyu', + 'xuzhu', + 'lvmeng', + 'huanggai', + 'xiahoudun', + 'zhangbao', + 'zhangfei', + 'old_huaxiong', + ], +}; diff --git a/character/xiake.js b/character/xiake.js index 6ef2cc00d..7d15e350a 100644 --- a/character/xiake.js +++ b/character/xiake.js @@ -1,125 +1,125 @@ -'use strict'; -game.import('character',function(lib,game,ui,get,ai,_status){ - return { - name:'xiake', - character:{ - // xk_dongfangweiming:['male','shu',4,[]], - xk_guyuexuan:['male','qun',4,['rouquan','gzhenji']], - xk_jinji:['male','shu',4,['zhongzhan','lianpo']], - // xk_shenxiangyun:['female','wei',3,['zhenjiu']], - xk_fujianhan:['male','qun',4,['zuijian','zitong']], - }, - skill:{ - zhongzhan:{ - trigger:{source:'damageBegin'}, - logTarget:'player', - check:function(event,player){ - if(get.damageEffect(event.player,player,player)>0&& - get.attitude(player,event.player)<0){ - return player.hp>event.player.hp&&player.hp>=2; - } - return false; - }, - content:function(){ - player.loseHp(); - trigger.num++; - } - }, - rouquan:{ - mod:{ - selectTarget:function(card,player,range){ - if(card.name=='sha'&&!player.getEquip(1)&&range[1]!=-1) range[1]=Infinity; - } - }, - enable:'phaseUse', - position:'e', - filter:function(event,player){ - return player.countCards('e')>0; - }, - filterCard:true, - prompt:'将要重铸的牌置入弃牌堆并摸一张牌', - discard:false, - delay:0.5, - check:function(card,player){ - var val=get.equipValue(card); - var player=_status.event.player; - var cards=player.getCards('h',{subtype:get.subtype(card)}); - for(var i=0;i=val){ - return 1; - } - } - return 0; - }, - prepare:function(cards,player){ - player.$throw(cards,1000); - }, - content:function(){ - "step 0" - player.draw(); - "step 1" - for(var i=0;i0&& + get.attitude(player,event.player)<0){ + return player.hp>event.player.hp&&player.hp>=2; + } + return false; + }, + content:function(){ + player.loseHp(); + trigger.num++; + } + }, + rouquan:{ + mod:{ + selectTarget:function(card,player,range){ + if(card.name=='sha'&&!player.getEquip(1)&&range[1]!=-1) range[1]=Infinity; + } + }, + enable:'phaseUse', + position:'e', + filter:function(event,player){ + return player.countCards('e')>0; + }, + filterCard:true, + prompt:'将要重铸的牌置入弃牌堆并摸一张牌', + discard:false, + delay:0.5, + check:function(card,player){ + var val=get.equipValue(card); + var player=_status.event.player; + var cards=player.getCards('h',{subtype:get.subtype(card)}); + for(var i=0;i=val){ + return 1; + } + } + return 0; + }, + prepare:function(cards,player){ + player.$throw(cards,1000); + }, + content:function(){ + "step 0" + player.draw(); + "step 1" + for(var i=0;i=3&&player.countUsed()>player.hp; - }, - content:function(){ - 'step 0' - player.awakenSkill('lingquan'); - player.draw(3); - player.addSkill('shuiyun'); - 'step 1' - game.createTrigger('phaseEnd','shuiyun',player,trigger); - }, - }, - shenwu:{ - trigger:{global:'phaseEnd'}, - forced:true, - skillAnimation:true, - animationColor:'water', - unique:true, - filter:function(event,player){ - return player.storage.shuiyun_count>=3; - }, - content:function(){ - player.awakenSkill('shenwu'); - player.gainMaxHp(); - player.recover(); - player.addSkill('huimeng'); - } - }, - qiongguang:{ - trigger:{player:'phaseDiscardEnd'}, - filter:function(event,player){ - return event.cards&&event.cards.length>1 - }, - content:function(){ - 'step 0' - event.targets=player.getEnemies().sortBySeat(); - 'step 1' - if(event.targets.length){ - player.line(event.targets.shift().getDebuff(false).addExpose(0.1),'green'); - event.redo(); - } - 'step 2' - game.delay(); - }, - ai:{ - threaten:2, - expose:0.2, - effect:{ - player:function(card,player){ - if(_status.currentPhase!=player) return; - if(_status.event.name!='chooseToUse'||_status.event.player!=player) return; - var num=player.needsToDiscard(); - if(num>2||num==1) return; - if(get.type(card)=='basic'&&num!=2) return; - if(get.tag(card,'gain')) return; - if(get.value(card,player,'raw')>=7) return; - if(player.hp<=2) return; - if(!player.hasSkill('jilue')||player.storage.renjie==0){ - return 'zeroplayertarget'; - } - } - } - } - }, - txianqu:{ - trigger:{source:'damageBefore'}, - logTarget:'player', - filter:function(event,player){ - if(player.hasSkill('txianqu2')) return false; - var evt=event.getParent('phaseUse'); - if(evt&&evt.player==player) return true; - return false; - }, - check:function(event,player){ - var target=event.player; - if(get.attitude(player,target)>=0||get.damageEffect(target,player,player)<=0) return true; - if(target.hp>player.hp&&player.isDamaged()) return true; - return false; - }, - content:function(){ - trigger.cancel(); - player.draw(2); - player.recover(); - player.addTempSkill('txianqu2'); - }, - ai:{ - jueqing:true, - skillTagFilter:function(player,tag,arg){ - if(!arg) return false; - if(player.hasSkill('txianqu2')) return false; - if(get.attitude(player,arg)>0) return false; - var evt=_status.event.getParent('phaseUse'); - if(evt&&evt.player==player) return true; - return false; - }, - effect:{ - player:function(card,player,target){ - if(get.tag(card,'damage')&&get.attitude(player,target)>0){ - if(player.hp==player.maxHp||get.recoverEffect(player,player,player)<=0) return 'zeroplayertarget'; - return [0,0,0,0.5]; - } - } - } - } - }, - txianqu2:{}, - xunying:{ - trigger:{player:'shaAfter'}, - direct:true, - filter:function(event,player){ - return player.canUse('sha',event.target)&&player.hasSha()&&event.target.isIn(); - }, - content:function(){ - "step 0" - if(player.hasSkill('jiu')){ - player.removeSkill('jiu'); - event.jiu=true; - } - player.chooseToUse(get.prompt('xunying'),{name:'sha'},trigger.target,-1).logSkill='xunying'; - "step 1" - if(result.bool); - else if(event.jiu){ - player.addSkill('jiu'); - } - } - }, - liefeng:{ - trigger:{player:'useCard'}, - forced:true, - popup:false, - filter:function(event,player){ - return _status.currentPhase==player&&[2,3,4].contains(player.countUsed()); - }, - content:function(){ - var skill; - switch(player.countUsed()){ - case 2:skill='yanzhan';break; - case 3:skill='tianjian';break; - case 4:skill='yufeng';break; - } - if(skill&&!player.hasSkill(skill)){ - player.addTempSkill(skill); - player.popup(skill); - game.log(player,'获得了','【'+get.translation(skill)+'】'); - if(skill=='yufeng'){ - var nh=player.countCards('h'); - if(nh<2){ - player.draw(2-nh); - player.addSkill('counttrigger'); - if(!player.storage.counttrigger){ - player.storage.counttrigger={}; - } - player.storage.counttrigger.yufeng=1; - } - } - } - }, - ai:{ - effect:{ - player:function(card,player){ - if(_status.currentPhase!=player) return; - if(get.type(card)=='basic') return; - if(get.tag(card,'gain')) return; - if(get.value(card,player,'raw')>=7) return; - if(player.hp<=2) return; - if(player.needsToDiscard()) return; - if(player.countUsed()>=2) return; - return 'zeroplayertarget'; - } - } - } - }, - yuexing:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return target!=player; - }, - content:function(){ - player.storage.yuexing2=target; - player.addTempSkill('yuexing2'); - target.storage.yuexing2=player; - target.addTempSkill('yuexing2'); - }, - ai:{ - order:function(){ - var player=_status.event.player; - if(player.hasSkill('minsha')) return 6.5; - return 2; - }, - result:{ - target:function(player,target){ - if(player.hasSkill('minsha')&&player.countCards('he')>=3&& - target.hp>1&&get.damageEffect(target,player,player,'thunder')>0){ - var num1=game.countPlayer(function(current){ - if(get.distance(target,current)<=1&¤t!=player&¤t!=target){ - return -get.sgn(get.attitude(player,current)); - } - }); - var num2=game.countPlayer(function(current){ - if(get.distance(player,current)<=1&¤t!=player&¤t!=target){ - return -get.sgn(get.attitude(player,current)); - } - }); - - if(num2>=num1) return 0; - return 2*(num2-num1); - } - return -_status.event.getRand(); - } - } - } - }, - yuexing2:{ - mark:'character', - intro:{ - content:'到其他角色的距离基数与$交换' - }, - onremove:true, - mod:{ - globalFrom:function(from,to,distance){ - if(from.storage.yuexing2){ - var dist1=get.distance(from,to,'pure'); - var dist2=get.distance(from.storage.yuexing2,to,'pure'); - return distance-dist1+dist2; - } - } - } - }, - minsha:{ - enable:'phaseUse', - usable:1, - filterCard:true, - selectCard:2, - position:'he', - filter:function(event,player){ - return player.countCards('he')>=2; - }, - filterTarget:function(card,player,target){ - return target!=player&&target.hp>1; - }, - line:'thunder', - check:function(card){ - return 8-get.value(card); - }, - content:function(){ - 'step 0' - target.damage('thunder'); - 'step 1' - event.targets=game.filterPlayer(function(current){ - return get.distance(target,current)<=1&¤t!=target&¤t!=player; - }).sortBySeat(target); - 'step 2' - if(event.targets.length){ - event.targets.shift().randomDiscard(false); - event.redo(); - } - }, - ai:{ - order:6, - result:{ - player:function(player,target){ - if(get.damageEffect(target,player,player,'thunder')>0){ - if(target==player.storage.yuexing2){ - return 10; - } - var num=1+game.countPlayer(function(current){ - if(get.distance(target,current)<=1&¤t!=player&¤t!=target){ - return -get.sgn(get.attitude(player,current)); - } - }); - if(target.hp==1){ - num+=2; - } - if(target.hp1&&player.countCards('h','sha')==1){ - return 'zeroplayertarget'; - } - } - }, - } - }, - xiaoyue2:{ - mod:{ - cardRespondable:function(card,player){ - if(_status.event.getParent(4).name=='xiaoyue'&&get.suit(card)!='heart') return false; - } - } - }, - huanlei:{ - trigger:{player:'damageEnd'}, - check:function(event,player){ - return get.damageEffect(event.source,player,player,'thunder')>0; - }, - filter:function(event,player){ - return event.source&&event.source.isIn()&&event.source.hp>player.hp; - }, - logTarget:'source', - content:function(){ - 'step 0' - trigger.source.damage('thunder'); - 'step 1' - trigger.source.draw(); - } - }, - anwugu:{ - trigger:{source:'damageEnd'}, - check:function(event,player){ - return get.attitude(player,event.player)<0; - }, - filter:function(event,player){ - if(event._notrigger.contains(event.player)) return false; - return event.player!=player&&event.player.isIn()&&!event.player.hasSkill('anwugu2'); - }, - logTarget:'player', - content:function(){ - trigger.player.addSkill('anwugu2'); - } - }, - anwugu2:{ - mod:{ - cardEnabled:function(card,player){ - if(_status.currentPhase!=player) return; - if(player.countUsed()>=player.storage.anwugu2) return false; - }, - maxHandcard:function(player,num){ - return num-1; - } - }, - mark:true, - intro:{ - content:'手牌上限-1,每回合最多使用$张牌(剩余$回合)' - }, - init:function(player){ - player.storage.anwugu2=3; - }, - trigger:{player:'phaseAfter'}, - silent:true, - onremove:true, - content:function(){ - player.storage.anwugu2--; - if(player.storage.anwugu2<=0){ - // player.loseHp(); - player.removeSkill('anwugu2'); - } - else{ - player.updateMarks(); - } - } - }, - xtanxi:{ - enable:'phaseUse', - usable:1, - filterCard:true, - check:function(card){ - var enemies=_status.event.player.getEnemies(); - var num1=0,num2=0; - for(var i=0;i=3) {num1+=0.5;num2+=0.5;} - } - var rand=_status.event.getRand(); - if(num1>=1&&num2>=1){ - if(card.name=='shan') return rand+=0.4; - if(card.name=='sha') return rand; - } - else if(num1>=1){ - if(card.name=='sha') return rand; - } - else if(num2>=1){ - if(card.name=='shan') return rand; - } - return 0; - }, - content:function(){ - player.addExpose(0.1); - var targets=player.getEnemies(); - for(var i=0;i0; - }, - logTarget:'player', - line:'fire', - ai:{ - expose:0.2, - threaten:1.3, - }, - content:function(){ - trigger.player.damage('fire'); - }, - }, - guijin:{ - round:3, - enable:'phaseUse', - delay:0, - content:function(){ - 'step 0' - event.cards=get.cards(4); - 'step 1' - if(event.cards.length){ - var more=false,remain=false,nomore=false; - if(event.cards.length>=3){ - for(var i=0;i=8){ - more=true; - } - if(event.cards.length>=4&&value<6){ - if(remain===false){ - remain=value; - } - else{ - remain=Math.min(remain,value); - } - } - } - } - if(remain===false){ - remain=0; - } - if(!more&&!game.hasPlayer(function(current){ - return get.attitude(player,current)<0&&!current.skipList.contains('phaseDraw'); - })){ - var num=0; - for(var i=0;i=12){ - more=true; - } - else{ - nomore=true; - } - } - player.chooseCardButton('归烬',event.cards,[1,event.cards.length]).ai=function(button){ - if(nomore) return 0; - if(more){ - return get.value(button.link,player,'raw')-remain; - } - else{ - if(ui.selected.buttons.length) return 0; - return 8-get.value(button.link,player,'raw'); - } - } - } - else{ - event.goto(4); - } - 'step 2' - if(result.bool){ - for(var i=0;i1){ - if(target==player&&target.needsToDiscard(result.links.length)>1){ - return att/5; - } - return att; - } - else{ - if(target.skipList.contains('phaseDraw')) return att/5; - return -att; - } - } - } - else{ - event.goto(4); - } - 'step 3' - if(result.targets.length){ - result.targets[0].gain(event.togive,'draw'); - result.targets[0].skip('phaseDraw'); - result.targets[0].addTempSkill('guijin2',{player:'phaseBegin'}); - game.log(result.targets[0],'获得了'+get.cnNumber(event.togive.length)+'张','#g“归烬”牌'); - player.line(result.targets[0],'green'); - event.goto(1); - } - 'step 4' - while(event.cards.length){ - ui.cardPile.insertBefore(event.cards.pop(),ui.cardPile.firstChild); - } - }, - ai:{ - order:1, - result:{ - player:function(player){ - if(game.roundNumber==1&&player.hasUnknown()) return 0; - return 1; - } - } - } - }, - guijin2:{ - mark:true, - intro:{ - content:'跳过下一个摸牌阶段' - }, - ai:{ - effect:{ - target:function(card,player,target){ - if(card.name=='bingliang'||card.name=='caomu') return 0; - } - } - } - }, - chengxin:{ - round:4, - enable:'chooseToUse', - filter:function(event,player){ - return event.type=='dying'; - }, - filterTarget:function(card,player,target){ - return target==_status.event.dying; - }, - selectTarget:-1, - content:function(){ - target.recover(1-target.hp); - target.addTempSkill('chengxin2',{player:'phaseAfter'}); - }, - ai:{ - order:6, - threaten:1.4, - skillTagFilter:function(player){ - if(4-(game.roundNumber-player.storage.chengxin_roundcount)>0) return false; - if(!_status.event.dying) return false; - }, - save:true, - result:{ - target:3 - }, - } - }, - chengxin2:{ - trigger:{player:'damageBefore'}, - mark:true, - forced:true, - content:function(){ - trigger.cancel(); - }, - ai:{ - nofire:true, - nothunder:true, - nodamage:true, - effect:{ - target:function(card,player,target,current){ - if(get.tag(card,'damage')) return [0,0]; - } - }, - }, - intro:{ - content:'防止一切伤害' - } - }, - tianwu:{ - trigger:{player:'useCardToBegin'}, - filter:function(event,player){ - if(get.is.altered('tianwu')&&player.hasSkill('tianwu2')) return false; - return event.targets&&event.targets.length==1&&player.getEnemies().contains(event.target); - }, - // alter:true, - frequent:true, - content:function(){ - trigger.target.getDebuff(); - player.addTempSkill('tianwu2'); - } - }, - tianwu2:{}, - shiying:{ - trigger:{global:'dieBefore'}, - skillAnimation:'epic', - animationColor:'water', - unique:true, - init:function(player){ - player.storage.shiying=false; - }, - mark:true, - intro:{ - content:'limited' - }, - check:function(event,player){ - return get.attitude(player,event.player)>=3; - }, - filter:function(event,player){ - return !player.storage.shiying&&event.player!=player; - }, - logTarget:'player', - content:function(){ - 'step 0' - trigger.cancel(); - player.awakenSkill('shiying'); - player.storage.shiying=true; - - player.maxHp=3; - player.hp=3; - trigger.player.maxHp=3; - trigger.player.hp=3; - - player.clearSkills(); - trigger.player.clearSkills(); - 'step 1' - var hs=player.getCards('hej'); - player.$throw(hs); - player.lose(player.getCards('hej'))._triggered=null; - 'step 2' - var hs=trigger.player.getCards('hej'); - trigger.player.$throw(hs); - trigger.player.lose(trigger.player.getCards('hej'))._triggered=null; - 'step 3' - game.asyncDraw([player,trigger.player],3); - }, - ai:{ - threaten:1.5 - } - }, - liguang:{ - trigger:{player:'phaseEnd'}, - filter:function(event,player){ - if(!player.canMoveCard()) return false; - if(!game.hasPlayer(function(current){ - return current.countCards('ej'); - })){ - return false; - } - return player.countCards('h')>0; - }, - direct:true, - content:function(){ - "step 0" - player.chooseToDiscard(get.prompt('liguang'),'弃置一张手牌并移动场上的一张牌',lib.filter.cardDiscardable).set('ai',function(card){ - if(!_status.event.check) return 0; - return 7-get.useful(card); - }).set('check',player.canMoveCard(true)).set('logSkill','liguang'); - "step 1" - if(result.bool){ - player.moveCard(true); - } - else{ - event.finish(); - } - }, - ai:{ - expose:0.2, - threaten:1.3 - } - }, - xiepan:{ - trigger:{player:'loseEnd'}, - direct:true, - filter:function(event,player){ - if(player.countCards('h',{type:'basic'})) return false; - if(!player.countCards('h')) return false; - for(var i=0;i1){ - list=list.randomGets(player.storage.yujia); - for(var i=0;i0&&event.num>0; - }, - content:function(){ - trigger.cancel(); - player.draw(2*trigger.num); - }, - group:'tanhua_remove', - subSkill:{ - remove:{ - trigger:{player:'dying'}, - priority:10, - forced:true, - content:function(){ - player.recover(); - player.removeSkill('tanhua'); - } - } - } - }, - yingfeng:{ - trigger:{player:'useCardAfter'}, - filter:function(event,player){ - if(event.card.name!='sha') return false; - if(event.parent.name=='yingfeng') return false; - var enemies=player.getEnemies(); - return game.hasPlayer(function(current){ - return enemies.contains(current)&&!event.targets.contains(current)&&player.canUse('sha',current,false); - }); - }, - forced:true, - content:function(){ - var enemies=player.getEnemies(); - enemies.remove(trigger.targets); - if(enemies.length){ - player.useCard({name:'sha'},enemies.randomGet().addExpose(0.2)); - } - }, - }, - ywuhun:{ - trigger:{player:'phaseBefore'}, - forced:true, - // alter:true, - filter:function(event){ - return event.parent.name!='ywuhun'; - }, - intro:{ - content:'回合结束后,场上及牌堆中的牌将恢复到回合前的状态' - }, - video:function(player,data){ - for(var i in data){ - var current=game.playerMap[i]; - current.node.handcards1.innerHTML=''; - current.node.handcards2.innerHTML=''; - current.node.equips.innerHTML=''; - current.node.judges.innerHTML=''; - current.directgain(get.infoCards(data[i].h)); - var es=get.infoCards(data[i].e); - for(var j=0;j0){ - num++; - if(get.attitude(player,target)>0){ - hef/=1.5; - if(get.tag(hs[i],'damage')){ - damaged=true; - } - } - eff+=hef; - } - } - if(!player.needsToDiscard(-num)){ - return eff; - } - return 0; - }; - 'step 1' - if(result.bool){ - event.target=result.targets[0]; - var num=0; - player.chooseCard([1,Infinity],'按顺序选择对'+get.translation(result.targets)+'使用的牌',function(card){ - return lib.filter.targetEnabled2(card,player,event.target); - }).ai=function(card){ - if(get.effect(event.target,card,player,player)>0){ - if(get.attitude(player,event.target)>0&&get.tag(card,'damage')){ - for(var i=0;i2&& - get.recoverEffect(players[i],player,player)>0){ - if(players[i].hp==1){ - if(player.hp=3) return 1; - return 0; - } - }, - }, - intro:{ - content:'limited' - } - }, - binxin:{ - trigger:{global:'phaseEnd'}, - check:function(event,player){ - return get.attitude(player,event.player)>0; - }, - filter:function(event,player){ - return event.player.hp==1; - }, - logTarget:'player', - content:function(){ - trigger.player.changeHujia(); - }, - ai:{ - expose:0.1 - } - }, - qixia:{ - trigger:{player:['useCardAfter','respondAfter']}, - silent:true, - init:function(player){ - player.storage.qixia=[]; - }, - // mark:true, - intro:{ - content:function(storage){ - if(!storage.length){ - return '未使用或打出过有花色的牌'; - } - else{ - var str='已使用过'+get.translation(storage[0]+'2'); - for(var i=1;i=4; - }, - content:function(){ - player.insertPhase(); - player.storage.qixia.length=0; - player.syncStorage('qixia'); - player.unmarkSkill('qixia'); - } - } - } - }, - qixia_old:{ - trigger:{global:'damageAfter'}, - direct:true, - filter:function(event,player){ - return !player.hasSkill('qixia2')&&event.source!=player&&event.player.isIn()&&player.countCards('he',{color:'red'}); - }, - content:function(){ - 'step 0' - player.chooseToDiscard('he',get.prompt('qixia',trigger.player),{color:'red'}).set('logSkill',['qixia',trigger.player]).ai=function(card){ - if(get.attitude(player,trigger.player)>0){ - if(trigger.player.hp==1){ - return 10-get.value(card); - } - return 8-get.value(card); - } - } - 'step 1' - if(result.bool){ - player.addTempSkill('qixia2'); - trigger.player.draw(2); - if(trigger.player.hp==1&&!trigger.player.hujia){ - trigger.player.changeHujia(); - } - } - }, - ai:{ - threaten:1.8 - } - }, - qixia2:{}, - jianzhen:{ - trigger:{player:'shaAfter'}, - forced:true, - filter:function(event,player){ - return event.target.isIn()&&game.hasPlayer(function(current){ - return current.canUse('sha',event.target,false)&¤t!=player; - }); - }, - content:function(){ - 'step 0' - event.targets=game.filterPlayer(function(current){ - return current.canUse('sha',trigger.target,false)&¤t!=player; - }); - event.targets.sortBySeat(trigger.player); - 'step 1' - if(event.targets.length){ - event.current=event.targets.shift(); - if(event.current.hasSha()){ - event.current.chooseToUse({name:'sha'},'是否对'+get.translation(trigger.target)+'使用一张杀?',trigger.target,-1); - } - else{ - event.redo(); - } - } - else{ - event.finish(); - } - 'step 2' - if (!result.bool){ - event.goto(1); - } - }, - ai:{ - expose:0.2, - threaten:1.4 - }, - }, - husha:{ - trigger:{player:'phaseEnd'}, - direct:true, - filter:function(event,player){ - if(player.storage.husha==1){ - return game.hasPlayer(function(current){ - return player.canUse('sha',current,false); - }) - } - else{ - return player.storage.husha>0; - } - }, - content:function(){ - 'step 0' - var list=[]; - if(game.hasPlayer(function(current){ - return player.canUse('sha',current,false); - })){ - list.push('移去1枚虎煞标记,视为使用一张杀'); - } - if(player.storage.husha>1){ - list.push('移去2枚虎煞标记,视为使用一张南蛮入侵'); - if(player.storage.husha>2){ - list.push('移去3枚虎煞标记,视为对除你之外的角色使用一张元素毁灭'); - } - } - player.chooseControl('cancel2',function(){ - if(player.storage.husha>2){ - var num1=game.countPlayer(function(current){ - if(current!=player&&player.canUse('yuansuhuimie',current)){ - return get.sgn(get.effect(current,{name:'yuansuhuimie'},player,player)); - } - }); - var num2=game.countPlayer(function(current){ - if(current!=player&&player.canUse('yuansuhuimie',current)){ - return get.effect(current,{name:'yuansuhuimie'},player,player); - } - }); - if(num1>0&&num2>0) return '选项三'; - } - if(player.storage.husha>1){ - var num=game.countPlayer(function(current){ - if(current!=player&&player.canUse('nanman',current)){ - return get.sgn(get.effect(current,{name:'nanman'},player,player)); - } - }); - if(num>0) return '选项二'; - } - if(game.hasPlayer(function(current){ - return player.canUse('sha',current,false)&&get.effect(current,{name:'sha'},player,player)>0; - })){ - return '选项一'; - } - return 'cancel2'; - }).set('prompt',get.prompt('husha')).set('choiceList',list); - 'step 1' - if(result.control!='cancel2'){ - player.logSkill('husha'); - if(result.control=='选项一'){ - event.sha=true; - player.storage.husha--; - player.chooseTarget('选择出杀的目标',true,function(card,player,target){ - return player.canUse('sha',target,false); - }).ai=function(target){ - return get.effect(target,{name:'sha'},player,player); - } - } - else if(result.control=='选项二'){ - var list=game.filterPlayer(function(current){ - return player.canUse('nanman',current); - }); - player.storage.husha-=2; - list.sortBySeat(); - player.useCard({name:'nanman'},list); - } - else{ - var list=game.filterPlayer(function(current){ - return player.canUse('yuansuhuimie',current); - }); - player.storage.husha-=3; - list.remove(player); - list.sortBySeat(); - player.useCard({name:'yuansuhuimie'},list); - } - if(!player.storage.husha){ - player.unmarkSkill('husha'); - } - else{ - player.syncStorage('husha'); - player.updateMarks(); - } - } - 'step 2' - if(event.sha&&result.targets&&result.targets.length){ - player.useCard({name:'sha'},result.targets[0]); - } - }, - init:function(player){ - player.storage.husha=0; - }, - intro:{ - content:'mark' - }, - group:'husha_count', - subSkill:{ - count:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event,player){ - if(player.storage.husha<3){ - var evt=event.getParent('phaseUse'); - return evt&&evt.player==player; - } - return false; - }, - content:function(){ - player.storage.husha+=trigger.num; - if(player.storage.husha>3){ - player.storage.husha=3; - } - player.markSkill('husha'); - player.syncStorage('husha'); - player.updateMarks(); - } - } - } - }, - fenshi:{ - unique:true, - skillAnimation:true, - animationColor:'fire', - trigger:{player:'dyingAfter'}, - forced:true, - mark:true, - derivation:'longhuo', - intro:{ - content:'limited' - }, - content:function(){ - player.awakenSkill('fenshi'); - player.changeHujia(2); - player.draw(2); - player.addSkill('longhuo'); - }, - }, - longhuo:{ - unique:true, - trigger:{player:'phaseEnd'}, - check:function(event,player){ - if(player.hp==1&&player.hujia==0) return false; - var num=game.countPlayer(function(current){ - var eff=get.sgn(get.damageEffect(current,player,player,'fire')); - if(current.hp==1&¤t.hujia==0) eff*=1.5; - return eff; - }); - return num>0; - }, - content:function(){ - 'step 0' - event.targets=get.players(lib.sort.seat); - 'step 1' - if(event.targets.length){ - var current=event.targets.shift(); - if(current.isIn()){ - player.line(current,'fire'); - current.damage('fire'); - event.redo(); - } - } - } - }, - yanzhan:{ - enable:'phaseUse', - viewAs:{name:'sha',nature:'fire'}, - usable:1, - position:'he', - viewAsFilter:function(player){ - if(!player.countCards('he',{color:'red'})) return false; - }, - filterCard:{color:'red'}, - check:function(card){ - if(get.suit(card)=='heart') return 7-get.value(card); - return 5-get.value(card); - }, - onuse:function(result){ - if(result.targets){ - for(var i=0;i2||nh==0)){ - return false; - } - } - } - }, - feixia:{ - enable:'phaseUse', - usable:1, - filterCard:{color:'red'}, - position:'he', - filter:function(event,player){ - return player.countCards('he',{color:'red'})>0; - }, - check:function(card){ - return 7-get.value(card); - }, - content:function(){ - var targets=player.getEnemies(); - if(targets.length){ - var target=targets.randomGet(); - target.addExpose(0.2); - player.useCard({name:'sha'},target,false); - } - }, - ai:{ - order:2.9, - result:{ - player:1 - } - } - }, - lueying:{ - trigger:{player:'shaBegin'}, - filter:function(event,player){ - if(event.target.countCards('he')>0){ - return game.hasPlayer(function(current){ - return current!=player&¤t!=event.target&¤t.countCards('he'); - }); - } - return false; - }, - logTarget:'target', - usable:1, - content:function(){ - 'step 0' - var card=trigger.target.getCards('he').randomGet(); - player.gain(card,trigger.target); - if(get.position(card)=='e'){ - trigger.target.$give(card,player); - } - else{ - trigger.target.$giveAuto(card,player); - } - 'step 1' - if(game.hasPlayer(function(current){ - return current!=player&¤t!=trigger.target&¤t.countCards('he'); - })){ - trigger.target.chooseTarget(function(card,player,target){ - return target!=player&&target!=_status.event.parent.player&&target.countCards('he')>0; - },'选择一名角色并令'+get.translation(player)+'弃置其一张牌').ai=function(target){ - return -get.attitude(_status.event.player,target); - }; - } - else{ - event.finish(); - } - 'step 2' - if(result.bool){ - trigger.target.line(result.targets[0],'green'); - player.discardPlayerCard(result.targets[0],true,'he'); - } - }, - ai:{ - threaten:1.5, - expose:0.2, - } - }, - tianjian:{ - enable:'phaseUse', - viewAs:{name:'wanjian'}, - filterCard:{name:'sha'}, - filter:function(event,player){ - return player.countCards('h','sha')>0; - }, - // alter:true, - usable:1, - group:'tianjian_discard', - subSkill:{ - discard:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event){ - if(event._notrigger.contains(event.player)) return false; - if(get.is.altered('tianjian')) return false; - return event.parent.skill=='tianjian'&&event.player.countCards('he'); - }, - popup:false, - content:function(){ - trigger.player.discard(trigger.player.getCards('he').randomGet()); - } - } - } - }, - feng:{ - unique:true, - init:function(player){ - player.storage.feng=0; - }, - mark:true, - intro:{ - content:'已累计摸#次牌' - }, - trigger:{player:'drawBegin'}, - forced:true, - popup:false, - priority:5, - content:function(){ - if(player.storage.feng<2){ - player.storage.feng++; - } - else{ - trigger.num++; - player.storage.feng=0; - player.logSkill('feng'); - } - player.updateMarks(); - } - }, - ya:{ - unique:true, - init:function(player){ - player.storage.ya=0; - }, - mark:true, - intro:{ - content:'已累计受到#次伤害' - }, - trigger:{player:'damageBegin'}, - filter:function(event,player){ - if(player.storage.ya==2) return event.num>0; - return true; - }, - forced:true, - popup:false, - content:function(){ - if(player.storage.ya<2){ - player.storage.ya++; - } - else if(trigger.num>0){ - trigger.num--; - player.storage.ya=0; - player.logSkill('ya'); - } - player.updateMarks(); - } - }, - song:{ - unique:true, - init:function(player){ - player.storage.song=0; - }, - mark:true, - intro:{ - content:'已累计造成#次伤害' - }, - trigger:{source:'damageBegin'}, - forced:true, - popup:false, - content:function(){ - if(player.storage.song<2){ - player.storage.song++; - } - else{ - trigger.num++; - player.storage.song=0; - player.logSkill('song'); - } - player.updateMarks(); - } - }, - longxiang:{ - trigger:{player:'shaBegin'}, - filter:function(event,player){ - return event.target.countCards('h')>player.countCards('h'); - }, - check:function(event,player){ - return get.attitude(player,event.target)<0; - }, - logTarget:'target', - content:function(){ - var hs=trigger.target.getCards('h'); - trigger.target.discard(hs.randomGets(hs.length-player.countCards('h'))); - } - }, - huxi:{ - enable:'chooseToUse', - viewAs:{name:'sha'}, - precontent:function(){ - 'step 0' - player.loseHp(); - 'step 1' - player.changeHujia(); - }, - filterCard:function(){return false}, - selectCard:-1, - prompt:'失去一点体力并获得一点护甲,视为使用一张杀', - ai:{ - order:function(){ - var player=_status.event.player; - if(player.hp<=2) return 0; - return 2; - }, - skillTagFilter:function(player,tag,arg){ - if(arg!='use') return false; - }, - respondSha:true, - } - }, - xuanmo:{ - enable:'phaseUse', - usable:1, - filterCard:function(card){ - var type=get.type(card,'trick'); - return type=='basic'||type=='equip'||type=='trick'; - }, - check:function(card){ - return 8-get.value(card); - }, - filter:function(event,player){ - return player.countCards('h')>0; - }, - discard:false, - prepare:'throw', - content:function(){ - game.log(player,'将',cards,'置于牌堆顶'); - ui.cardPile.insertBefore(cards[0],ui.cardPile.firstChild); - var list=get.inpile(get.type(cards[0],'trick'),'trick').randomGets(2); - for(var i=0;i0){ - return att+1/Math.sqrt(1+target.countCards('h')); - } - return 0; - }; - 'step 2' - if(result.bool){ - player.line(result.targets[0],'green'); - result.targets[0].draw(); - event.targets.push(result.targets[0]); - if(event.targets.length==game.players.length){ - event.finish(); - } - else{ - player.chooseTarget('令一名角色获得一点护甲',function(card,player,target){ - return !event.targets.contains(target); - }).ai=function(target){ - var att=get.attitude(player,target); - if(att>0){ - return att+1/Math.sqrt(1+target.hp); - } - return 0; - }; - } - } - else{ - event.finish(); - } - 'step 3' - if(result.bool){ - player.line(result.targets[0],'green'); - result.targets[0].changeHujia(); - game.delay(); - event.targets.push(result.targets[0]); - if(event.targets.length==game.players.length){ - event.finish(); - } - else{ - player.chooseTarget('令一名角色装备一件随机装备',function(card,player,target){ - return !event.targets.contains(target); - }).ai=function(target){ - var att=get.attitude(player,target); - if(att>0&&!target.getEquip(5)){ - return att; - } - return 0; - }; - } - } - else{ - event.finish(); - } - 'step 4' - if(result.bool){ - player.line(result.targets[0],'green'); - game.delay(); - var list=[]; - for(var i=0;i0){ - return att+1/Math.sqrt(1+target.hp); - } - return 0; - }; - } - } - else{ - event.finish(); - } - 'step 5' - if(result.bool){ - player.line(result.targets[0],'green'); - game.delay(); - result.targets[0].tempHide(); - } - } - }, - sheling:{ - trigger:{global:['useCardAfter','respondAfter','discardAfter']}, - filter:function(event,player){ - if(player!=_status.currentPhase||player==event.player) return false; - if(event.cards){ - for(var i=0;i0&&player.countCards('h')>0; - }, - check:function(event,player){ - if(player.isUnseen()) return false; - if(get.attitude(player,event.player)>=0) return false; - var hs=player.getCards('h'); - if(hs.length=10&&val<=6) return true; - if(hs[i].number>=8&&val<=3) return true; - } - return false; - }, - logTarget:'player', - content:function(){ - 'step 0' - player.chooseToCompare(trigger.player); - 'step 1' - if(result.bool){ - player.draw(2); - } - else{ - event.finish(); - } - 'step 2' - player.chooseCard('将两张牌置于牌堆顶(先选择的在上)',2,'he',true); - 'step 3' - if(result.bool){ - player.lose(result.cards,ui.special); - event.cards=result.cards; - } - else{ - event.finish(); - } - 'step 4' - game.delay(); - var nodes=[]; - for(var i=0;i=0;i--){ - ui.cardPile.insertBefore(event.cards[i],ui.cardPile.firstChild); - } - }, - ai:{ - mingzhi:false, - expose:0.2 - } - }, - zhangmu:{ - trigger:{player:['chooseToRespondBegin','chooseToUseBegin']}, - filter:function(event,player){ - if(event.responded) return false; - if(!event.filterCard({name:'shan'},player,event)) return false; - return player.countCards('h','shan')>0; - }, - direct:true, - usable:1, - content:function(){ - "step 0" - var goon=(get.damageEffect(player,trigger.player,player)<=0); - player.chooseCard(get.prompt('zhangmu'),{name:'shan'}).ai=function(){ - return goon?1:0; - } - "step 1" - if(result.bool){ - player.logSkill('zhangmu'); - player.showCards(result.cards); - trigger.untrigger(); - trigger.responded=true; - trigger.result={bool:true,card:{name:'shan'}} - player.addSkill('zhangmu_ai'); - } - else{ - player.storage.counttrigger.zhangmu--; - } - }, - ai:{ - respondShan:true, - effect:{ - target:function(card,player,target,effect){ - if(get.tag(card,'respondShan')&&effect<0){ - if(target.hasSkill('zhangmu_ai')) return 0; - if(target.countCards('h')>=2) return 0.5; - } - } - } - } - }, - zhangmu_ai:{ - trigger:{player:'loseAfter'}, - silent:true, - filter:function(event,player){ - return player.countCards('h','shan')==0; - }, - content:function(){ - player.removeSkill('zhangmu_ai'); - } - }, - leiyu:{ - trigger:{player:'phaseEnd'}, - direct:true, - filter:function(event,player){ - if(!player.countCards('h',{color:'black'})) return false; - if(player.storage.leiyu){ - for(var i=0;i0){ - num2++; - } - else if(eff<0){ - num2--; - } - } - var next=player.chooseToDiscard(get.prompt('leiyu',player.storage.leiyu),{color:'black'}); - next.ai=function(card){ - if(num>0&&num2>=2){ - return 7-get.value(card); - } - return 0; - }; - next.logSkill=['leiyu',player.storage.leiyu]; - 'step 1' - if(result.bool){ - player.storage.leiyu.sort(lib.sort.seat); - player.useCard({name:'jingleishan',nature:'thunder'},player.storage.leiyu).animate=false; - } - }, - group:['leiyu2','leiyu4'], - ai:{ - threaten:1.3 - } - }, - leiyu2:{ - trigger:{player:'phaseUseBegin'}, - silent:true, - content:function(){ - player.storage.leiyu=[]; - } - }, - leiyu3:{ - trigger:{source:'dieAfter'}, - forced:true, - popup:false, - filter:function(event,player){ - return player.storage.leiyu2?true:false; - }, - content:function(){ - player.recover(); - delete player.storage.leiyu2; - } - }, - leiyu4:{ - trigger:{player:'useCardToBegin'}, - silent:true, - filter:function(event,player){ - return _status.currentPhase==player&&Array.isArray(player.storage.leiyu)&&event.target&&event.target!=player; - }, - content:function(){ - player.storage.leiyu.add(trigger.target); - } - }, - feizhua:{ - trigger:{player:'useCard'}, - filter:function(event,player){ - if(event.card.name!='sha') return false; - if(event.targets.length!=1) return false; - var target=event.targets[0]; - var players=game.filterPlayer(function(current){ - return get.distance(target,current,'pure')==1; - }); - for(var i=0;i0; - }, - content:function(){ - "step 0" - var target=trigger.targets[0]; - var players=game.filterPlayer(function(current){ - return get.distance(target,current,'pure')==1; - }); - for(var i=0;i0; - }, - filterCard:{name:'sha'}, - filterTarget:function(card,player,target){ - return target!=player; - }, - prepare:'give', - discard:false, - content:function(){ - target.gain(cards,player); - if(!player.hasSkill('diewu2')){ - player.draw(); - player.addTempSkill('diewu2'); - } - }, - ai:{ - order:2, - expose:0.2, - result:{ - target:function(player,target){ - if(!player.hasSkill('diewu2')) return 1; - return 0; - } - } - } - }, - diewu2:{}, - lingyu:{ - trigger:{player:'phaseEnd'}, - direct:true, - filter:function(event,player){ - return game.hasPlayer(function(current){ - return current!=player&¤t.isDamaged(); - }); - }, - content:function(){ - 'step 0' - player.chooseTarget('灵愈:令一名其他角色回复一点体力',function(card,player,target){ - return target!=player&&target.hp0; - }, - logTarget:'player', - content:function(){ - trigger.player.draw(); - }, - ai:{ - mingzhi:false, - threaten:2, - expose:0.2, - } - }, - xuanyan:{ - // trigger:{source:'damageBefore'}, - // forced:true, - // priority:5, - // check:function(event,player){ - // return player.hp>3; - // }, - // filter:function(event){ - // return event.card&&get.color(event.card)=='red'; - // }, - // content:function(){ - // trigger.nature='fire'; - // }, - group:['xuanyan2','xuanyan3'] - }, - xuanyan2:{ - trigger:{source:'damageBegin'}, - forced:true, - filter:function(event){ - return event.nature=='fire'&&event.notLink(); - }, - content:function(){ - trigger.num++; - } - }, - xuanyan3:{ - trigger:{source:'damageEnd'}, - forced:true, - popup:false, - filter:function(event){ - return event.nature=='fire'; - }, - content:function(){ - player.loseHp(); - } - }, - ningbin:{ - trigger:{player:'damageEnd'}, - forced:true, - filter:function(event){ - return event.nature=='thunder'; - }, - content:function(){ - player.recover(); - }, - ai:{ - effect:{ - target:function(card,player,target){ - if(get.tag(card,'thunderDamage')){ - if(target.hp<=1||!target.hasSkill('xfenxin')) return [0,0]; - return [0,1.5]; - } - } - } - }, - }, - xfenxin:{ - trigger:{player:'changeHp'}, - forced:true, - filter:function(event){ - return event.num!=0; - }, - // alter:true, - content:function(){ - if(get.is.altered('xfenxin')){ - player.draw(); - } - else{ - player.draw(Math.abs(trigger.num)); - } - }, - ai:{ - effect:{ - target:function(card){ - if(get.tag(card,'thunderDamage')) return; - if(get.tag(card,'damage')||get.tag(card,'recover')){ - return [1,0.2]; - } - } - } - }, - group:'xfenxin2' - }, - xfenxin2:{ - trigger:{source:'dieAfter'}, - forced:true, - filter:function(){ - return !get.is.altered('xfenxin'); - }, - content:function(){ - player.gainMaxHp(); - player.recover(); - } - }, - luanjian:{ - enable:'phaseUse', - filterCard:{name:'sha'}, - selectCard:2, - check:function(card){ - var num=0; - var player=_status.event.player; - var players=game.filterPlayer(); - for(var i=0;i0){ - num++; - if(num>1) return 8-get.value(card); - } - } - return 0; - }, - viewAs:{name:'sha'}, - selectTarget:[1,Infinity], - filterTarget:function(card,player,target){ - return lib.filter.targetEnabled({name:'sha'},player,target); - }, - ai:{ - order:function(){ - return get.order({name:'sha'})+0.1; - }, - effect:{ - player:function(card,player){ - if(_status.currentPhase!=player) return; - if(card.name=='sha'&&player.countCards('h','sha')<2&&!player.needsToDiscard()){ - var num=0; - var player=_status.event.player; - var players=game.filterPlayer(); - for(var i=0;i1) return 'zeroplayertarget'; - } - } - } - } - }, - }, - group:'luanjian2' - }, - luanjian2:{ - trigger:{source:'damageBegin'}, - forced:true, - popup:false, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&event.parent.skill=='luanjian'; - }, - content:function(){ - if(Math.random()<0.5) trigger.num++; - } - }, - ctianfu:{ - enable:'phaseUse', - filter:function(event,player){ - return player.countCards('h','shan')>0; - }, - usable:1, - filterCard:{name:'shan'}, - discard:false, - prepare:'give', - filterTarget:function(card,player,target){ - return target!=player&&!target.hasSkill('ctianfu2'); - }, - check:function(card){ - if(_status.event.player.hp>=3) return 8-get.value(card); - return 7-get.value(card); - }, - content:function(){ - target.storage.ctianfu2=cards[0]; - target.storage.ctianfu3=player; - game.addVideo('storage',target,['ctianfu2',get.cardInfo(cards[0]),'card']); - target.addSkill('ctianfu2'); - }, - ai:{ - order:2, - result:{ - target:function(player,target){ - var att=get.attitude(player,target); - if(att>=0) return 0; - return get.damageEffect(target,player,target,'thunder'); - } - }, - expose:0.2 - } - }, - ctianfu2:{ - trigger:{source:'damageAfter'}, - forced:true, - mark:'card', - filter:function(event,player){ - return player.storage.ctianfu2&&player.storage.ctianfu3; - }, - content:function(){ - "step 0" - if(player.storage.ctianfu3&&player.storage.ctianfu3.isAlive()){ - player.damage(player.storage.ctianfu3); - player.storage.ctianfu3.line(player,'thunder'); - } - else{ - player.damage('nosource'); - } - "step 1" - var he=player.getCards('he'); - if(he.length){ - player.discard(he.randomGet()); - } - "step 2" - player.$throw(player.storage.ctianfu2); - player.storage.ctianfu2.discard(); - delete player.storage.ctianfu2; - delete player.storage.ctianfu3; - player.removeSkill('ctianfu2'); - }, - group:'ctianfu3', - intro:{ - content:'card' - } - }, - ctianfu3:{ - trigger:{player:'dieBegin'}, - forced:true, - popup:false, - content:function(){ - player.storage.ctianfu2.discard(); - delete player.storage.ctianfu2; - delete player.storage.ctianfu3; - player.removeSkill('ctianfu2'); - } - }, - shuiyun:{ - trigger:{player:'phaseEnd'}, - direct:true, - init:function(player){ - player.storage.shuiyun=[]; - player.storage.shuiyun_count=0; - }, - // alter:true, - filter:function(event,player){ - if(player.storage.shuiyun.length>=3) return false; - if(player.storage.shuiyun.length>=2&&get.is.altered('shuiyun')) return false; - var types=[]; - for(var i=0;i0; - }, - filterTarget:function(card,player,target){ - return target==_status.event.dying; - }, - delay:0, - selectTarget:-1, - content:function(){ - "step 0" - player.chooseCardButton(get.translation('shuiyun'),player.storage.shuiyun,true); - "step 1" - if(result.bool){ - player.storage.shuiyun.remove(result.links[0]); - if(!player.storage.shuiyun.length){ - player.unmarkSkill('shuiyun'); - } - player.$throw(result.links); - result.links[0].discard(); - target.recover(); - if(typeof player.storage.shuiyun_count=='number'){ - player.storage.shuiyun_count++; - } - player.syncStorage('shuiyun'); - } - else{ - event.finish(); - } - }, - ai:{ - order:6, - skillTagFilter:function(player){ - return player.storage.shuiyun.length>0; - }, - save:true, - result:{ - target:3 - }, - threaten:1.6 - } - }, - wangyou:{ - trigger:{global:'phaseEnd'}, - unique:true, - gainable:true, - direct:true, - filter:function(event,player){ - if(!player.countCards('he')) return false; - if(player==event.player) return false; - return game.hasPlayer(function(current){ - return current.hasSkill('wangyou3'); - }); - }, - content:function(){ - "step 0" - var targets=[]; - var num=0; - var players=game.filterPlayer(); - for(var i=0;i0) num++; - else if(att<0) num--; - targets.push(players[i]); - } - } - event.targets=targets; - var next=player.chooseToDiscard(get.prompt('wangyou',targets),'he'); - next.logSkill=['wangyou',event.targets]; - next.ai=function(card){ - if(num<=0) return 0; - switch(num){ - case 1:return 5-get.value(card); - case 2:return 7-get.value(card); - default:return 8-get.value(card); - } - } - "step 1" - if(result.bool){ - event.targets.sort(lib.sort.seat); - game.asyncDraw(event.targets); - } - else{ - event.finish(); - } - }, - ai:{ - expose:0.1, - threaten:1.2 - }, - group:'wangyou2' - }, - wangyou2:{ - trigger:{global:'damageEnd'}, - silent:true, - filter:function(event){ - return event.player.isAlive(); - }, - content:function(){ - trigger.player.addTempSkill('wangyou3'); - } - }, - wangyou3:{}, - changnian:{ - forbid:['boss'], - trigger:{player:'dieBegin'}, - direct:true, - unique:true, - derivation:'changnian2', - content:function(){ - "step 0" - player.chooseTarget(get.prompt('changnian'),function(card,player,target){ - return player!=target; - }).ai=function(target){ - return get.attitude(player,target); - }; - "step 1" - if(result.bool){ - var cards=player.getCards('hej'); - var target=result.targets[0]; - // if(player.storage.shuiyun&&player.storage.shuiyun.length){ - // target.gainMaxHp(); - // target.recover(player.storage.shuiyun.length); - // cards=cards.concat(player.storage.shuiyun); - // player.storage.shuiyun.length=0; - // } - player.$give(cards,target); - target.gain(cards); - target.addSkill('changnian2'); - player.logSkill('changnian',target); - target.marks.changnian=target.markCharacter(player,{ - name:'长念', - content:'
          【追思】
          锁定技,结束阶段,你摸一张牌
          ' - }); - game.addVideo('markCharacter',target,{ - name:'长念', - content:'
          【追思】
          锁定技,结束阶段,你摸一张牌
          ', - id:'changnian', - target:player.dataset.position - }); - } - }, - ai:{ - threaten:0.8 - } - }, - changnian2:{ - trigger:{player:'phaseEnd'}, - forced:true, - nopop:true, - content:function(){ - player.draw(); - }, - }, - sajin:{ - enable:'phaseUse', - filterTarget:function(card,player,target){ - return target.hpplayer.hp){ - return 7-get.value(card); - } - return 4-get.value(card); - }, - content:function(){ - "step 0" - var color=get.color(cards[0]); - target.judge(function(card){ - return get.color(card)==color?1:0; - }); - "step 1" - if(result.bool){ - target.recover(); - } - }, - ai:{ - order:3, - result:{ - target:function(player,target){ - return get.recoverEffect(target); - } - }, - threaten:1.5 - } - }, - jtjubao:{ - trigger:{global:'discardAfter'}, - filter:function(event,player){ - if(player.hasSkill('jtjubao2')) return false; - if(event.player==player) return false; - if(_status.currentPhase==player) return false; - for(var i=0;i0){ - return [1,3]; - } - } - } - } - } - } - }, - xshuangren:{ - trigger:{player:['loseEnd']}, - filter:function(event,player){ - if(!player.equiping) return false; - for(var i=0;i0; - }, - content:function(){ - "step 0" - player.chooseTarget([1,trigger.cards.length],get.prompt('qijian'),function(card,player,target){ - return player.canUse({name:'sha'},target,false); - }).ai=function(target){ - return get.effect(target,{name:'sha'},player); - }; - "step 1" - if(result.bool){ - player.logSkill('qijian'); - player.useCard({name:'sha'},result.targets); - } - }, - }, - shenmu:{ - trigger:{global:'dying'}, - priority:6, - filter:function(event,player){ - return event.player.hp<=0&&player.countCards('h',{color:'red'}); - }, - check:function(event,player){ - if(get.attitude(player,event.player)<=0) return false; - var cards=player.getCards('h',{color:'red'}); - for(var i=0;i7&&cards.length>2) return false; - } - }, - content:function(){ - "step 0" - player.showHandcards(); - "step 1" - var cards=player.getCards('h',{color:'red'}); - event.num=cards.length; - player.discard(cards); - "step 2" - trigger.player.recover(); - trigger.player.draw(event.num); - }, - ai:{ - threaten:1.6, - expose:0.2 - } - }, - qianfang:{ - trigger:{player:'phaseBegin'}, - direct:true, - filter:function(event,player){ - return player.storage.xuanning&&player.countCards('he')+player.storage.xuanning>=3; - }, - // alter:true, - content:function(){ - "step 0" - // trigger.cancel(); - var ainum=0; - var num=3-player.storage.xuanning; - var players=game.filterPlayer(); - event.targets=[]; - for(var i=0;i=0){ - switch(num){ - case 1:return 8-get.value(card); - case 2:return 6-get.value(card); - case 3:return 4-get.value(card); - } - } - return -1; - } - next.logSkill='qianfang'; - event.logged=true; - } - else{ - player.chooseBool(get.prompt2('qianfang')).ai=function(){ - return ainum>=0; - } - } - "step 1" - if(result.bool){ - player.storage.xuanning=0; - player.unmarkSkill('xuanning'); - if(!event.logged){ - player.logSkill('qianfang'); - } - player.useCard({name:'wanjian'},'qianfang',event.targets); - } - else{ - event.finish(); - } - }, - ai:{ - expose:0.1, - threaten:1.5 - }, - group:'qianfang_draw', - subSkill:{ - draw:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event,player){ - if(event._notrigger.contains(event.player)) return false; - if(!event.player.isEnemiesOf(player)) return false; - return event.parent.skill=='qianfang'; - }, - popup:false, - content:function(){ - player.draw(); - } - } - } - }, - qianfang2:{ - trigger:{player:'phaseDrawBegin'}, - forced:true, - popup:false, - content:function(){ - trigger.num++; - } - }, - poyun:{ - trigger:{source:'damageEnd'}, - // alter:true, - filter:function(event,player){ - if(event._notrigger.contains(event.player)) return false; - return player.storage.xuanning>0&&event.player.countCards('he')>0; - }, - direct:true, - content:function(){ - "step 0" - player.discardPlayerCard(trigger.player,'he',get.prompt('poyun',trigger.player),[1,get.is.altered('poyun')?1:2]).logSkill=['poyun',trigger.player]; - "step 1" - if(result.bool){ - player.storage.xuanning--; - if(!player.storage.xuanning){ - player.unmarkSkill('xuanning'); - } - player.syncStorage('xuanning'); - } - }, - ai:{ - threaten:1.3 - } - }, - poyun2:{ - trigger:{source:'damageEnd'}, - forced:true, - popup:false, - filter:function(event,player){ - return player.storage.poyun?true:false; - }, - content:function(){ - player.draw(); - player.storage.poyun=false; - player.removeSkill('poyun2'); - } - }, - poyun3:{}, - zhuyue:{ - enable:'phaseUse', - // alter:true, - filter:function(event,player){ - if(get.is.altered('zhuyue')){ - return player.hasCard(function(card){ - return get.color(card)=='black'&&get.type(card)!='basic'; - }); - } - return player.countCards('h',{type:'basic'})0; - }, - usable:1, - locked:false, - check:function(card){ - return 7-get.value(card); - }, - multitarget:true, - multiline:true, - content:function(){ - 'step 0' - targets.sort(lib.sort.seat); - var target=targets[0]; - var cs=target.getCards('he'); - if(cs.length){ - target.discard(cs.randomGet()); - } - player.storage.zhuyue.add(target); - if(targets.length<2){ - event.finish(); - } - 'step 1' - var target=targets[1]; - var cs=target.getCards('he'); - if(cs.length){ - target.discard(cs.randomGet()); - } - player.storage.zhuyue.add(target); - }, - ai:{ - result:{ - target:function(player,target){ - if(!target.countCards('he')) return -0.2; - return -1; - } - }, - order:10, - threaten:1.2, - exoise:0.2 - }, - mod:{ - targetInRange:function(card,player,target){ - if(card.name=='sha'&&player.storage.zhuyue&&player.storage.zhuyue.contains(target)){ - return true; - } - }, - selectTarget:function(card,player,range){ - if(card.name=='sha'&&player.storage.zhuyue&&player.storage.zhuyue.length){ - range[1]=-1; - range[0]=-1; - } - }, - playerEnabled:function(card,player,target){ - if(card.name=='sha'&&player.storage.zhuyue&&player.storage.zhuyue.length&&!player.storage.zhuyue.contains(target)){ - return false; - } - } - }, - intro:{ - content:'players' - }, - group:'zhuyue2' - }, - zhuyue2:{ - trigger:{player:'phaseUseEnd'}, - silent:true, - content:function(){ - player.storage.zhuyue.length=0; - } - }, - longxi:{ - trigger:{player:['chooseToRespondBegin','chooseToUseBegin']}, - forced:true, - popup:false, - max:2, - filter:function(event,player){ - return _status.currentPhase!=player; - }, - priority:101, - content:function(){ - var cards=[]; - var max=Math.min(ui.cardPile.childNodes.length,lib.skill.longxi.max); - for(var i=0;i=2; - }, - check:function(card){ - return 8-get.value(card); - }, - filterCard:function(card){ - return get.color(card)=='red'; - }, - selectCard:2, - filterTarget:function(card,player,target){ - return player!=target&&target.hp>=player.hp; - }, - intro:{ - content:'limited' - }, - line:'fire', - content:function(){ - "step 0" - player.storage.guanri=true; - player.loseHp(); - "step 1" - target.damage(2,'fire'); - "step 2" - if(target.isAlive()){ - target.discard(target.getCards('e')); - } - }, - ai:{ - order:1, - result:{ - target:function(player,target){ - var eff=get.damageEffect(target,player,target,'fire'); - if(player.hp>2) return eff; - if(player.hp==2&&target.hp==2) return eff; - return 0; - } - }, - expose:0.5 - } - }, - tianxian:{ - mod:{ - targetInRange:function(card,player,target,now){ - if(card.name=='sha') return true; - }, - selectTarget:function(card,player,range){ - if(card.name=='sha'&&range[1]!=-1) range[1]=Infinity; - } - }, - priority:5.5, - trigger:{player:'useCardToBefore'}, - filter:function(event){ - return event.card.name=='sha'; - }, - forced:true, - check:function(){ - return false; - }, - content:function(){ - "step 0" - trigger.target.judge(function(card){ - return get.color(card)=='black'?1:0; - }); - "step 1" - if(result.bool){ - trigger.cancel(); - } - } - }, - runxin:{ - trigger:{player:['useCard','respondEnd']}, - direct:true, - filter:function(event){ - if(get.suit(event.card)=='heart'){ - return game.hasPlayer(function(current){ - return current.isDamaged(); - }); - } - return false; - }, - content:function(){ - "step 0" - var noneed=(trigger.card.name=='tao'&&trigger.targets[0]==player&&player.hp==player.maxHp-1); - player.chooseTarget(get.prompt('runxin'),function(card,player,target){ - return target.hp0){ - if(noneed&&player==target){ - num=0.5; - } - else if(target.hp==1){ - num+=3; - } - else if(target.hp==2){ - num+=1; - } - } - return num; - } - "step 1" - if(result.bool){ - player.logSkill('runxin',result.targets); - result.targets[0].recover(); - } - }, - ai:{ - expose:0.3, - threaten:1.5 - } - }, - zhimeng:{ - trigger:{player:'phaseEnd'}, - direct:true, - locked:true, - unique:true, - gainable:true, - // alter:true, - group:'zhimeng3', - content:function(){ - "step 0" - player.chooseTarget(get.prompt('zhimeng'),function(card,player,target){ - return player!=target; - }).ai=function(target){ - var num=get.attitude(player,target); - if(num>0){ - if(player==target){ - num++; - } - if(target.hp==1){ - num+=3; - } - if(target.hp==2){ - num+=1; - } - } - return num; - } - "step 1" - if(result.bool){ - var target=result.targets[0]; - if(get.is.altered('zhimeng')){ - target.draw(); - } - else{ - var card=get.cards()[0]; - target.$draw(card); - target.storage.zhimeng2=card; - game.addVideo('storage',target,['zhimeng2',get.cardInfo(card),'card']); - target.addSkill('zhimeng2'); - } - player.logSkill('zhimeng',target); - } - }, - ai:{ - expose:0.2 - } - }, - zhimeng2:{ - intro:{ - content:'card', - onunmark:function(storage,player){ - delete player.storage.zhimeng2; - } - }, - mark:'card', - trigger:{target:'useCardToBegin'}, - frequent:true, - filter:function(event,player){ - return player.storage.zhimeng2&&get.type(event.card,'trick')==get.type(player.storage.zhimeng2,'trick'); - }, - content:function(){ - player.draw(); - }, - ai:{ - effect:{ - target:function(card,player,target){ - if(target.storage.zhimeng2&&get.type(card,'trick')==get.type(target.storage.zhimeng2,'trick')){ - return [1,0.5]; - } - } - } - } - }, - zhimeng3:{ - trigger:{player:['phaseBegin','dieBegin']}, - silent:true, - content:function(){ - "step 0" - event.players=game.filterPlayer(); - event.num=0; - "step 1" - if(event.num1){ - return 11-get.equipValue(card); - } - if(player.countCards('h')0; - }, - ai:{ - shihuifen:true, - skillTagFilter:function(player){ - return player.countCards('he',{color:'black'})>0; - } - } - }, - tuoqiao_old:{ - filter:function(event,player){ - return game.players.length>3&&(event.player==player.next||event.player==player.previous); - }, - check:function(event,player){ - return get.effect(player,event.card,event.player,player)<0 - }, - changeSeat:true, - trigger:{target:'useCardToBefore'}, - content:function(){ - if(trigger.player==player.next){ - game.swapSeat(player,player.previous); - } - else if(trigger.player==player.previous){ - game.swapSeat(player,player.next); - } - else{ - return; - } - trigger.cancel(); - // player.popup('xiaoyao'); - }, - ai:{ - effect:{ - target:function(card,player,target,current){ - if(target==player.next||target==player.previous) return 0.1; - } - } - } - }, - tianjian_old:{ - enable:'phaseUse', - usable:1, - changeSeat:true, - filterTarget:function(card,player,target){ - return player!=target&&player.next!=target; - }, - filterCard:true, - check:function(card){ - return 4-get.value(card); - }, - content:function(){ - while(player.next!=target){ - game.swapSeat(player,player.next); - } - }, - ai:{ - order:5, - result:{ - player:function(player,target){ - var att=get.attitude(player,target); - if(target==player.previous&&att>0) return 1; - if(target==player.next.next&&get.attitude(player,player.next)<0) return 1; - return 0; - } - } - } - }, - huimeng:{ - trigger:{player:'recoverAfter'}, - frequent:true, - content:function(){ - player.draw(2); - }, - ai:{ - threaten:0.8 - } - }, - tianshe:{ - group:['tianshe2'], - trigger:{player:'damageBefore'}, - filter:function(event){ - if(event.nature) return true; - return false; - }, - forced:true, - content:function(){ - trigger.cancel(); - }, - ai:{ - nofire:true, - nothunder:true, - effect:{ - target:function(card,player,target,current){ - if(card.name=='tiesuo') return 0; - if(get.tag(card,'fireDamage')) return 0; - if(get.tag(card,'thunderDamage')) return 0; - } - } - } - }, - tianshe2:{ - trigger:{source:'damageAfter'}, - filter:function(event,player){ - if(event.nature&&player.hp=3&&player.countUsed()>player.hp; + }, + content:function(){ + 'step 0' + player.awakenSkill('lingquan'); + player.draw(3); + player.addSkill('shuiyun'); + 'step 1' + game.createTrigger('phaseEnd','shuiyun',player,trigger); + }, + }, + shenwu:{ + trigger:{global:'phaseEnd'}, + forced:true, + skillAnimation:true, + animationColor:'water', + unique:true, + filter:function(event,player){ + return player.storage.shuiyun_count>=3; + }, + content:function(){ + player.awakenSkill('shenwu'); + player.gainMaxHp(); + player.recover(); + player.addSkill('huimeng'); + } + }, + qiongguang:{ + trigger:{player:'phaseDiscardEnd'}, + filter:function(event,player){ + return event.cards&&event.cards.length>1 + }, + content:function(){ + 'step 0' + event.targets=player.getEnemies().sortBySeat(); + 'step 1' + if(event.targets.length){ + player.line(event.targets.shift().getDebuff(false).addExpose(0.1),'green'); + event.redo(); + } + 'step 2' + game.delay(); + }, + ai:{ + threaten:2, + expose:0.2, + effect:{ + player:function(card,player){ + if(_status.currentPhase!=player) return; + if(_status.event.name!='chooseToUse'||_status.event.player!=player) return; + var num=player.needsToDiscard(); + if(num>2||num==1) return; + if(get.type(card)=='basic'&&num!=2) return; + if(get.tag(card,'gain')) return; + if(get.value(card,player,'raw')>=7) return; + if(player.hp<=2) return; + if(!player.hasSkill('jilue')||player.storage.renjie==0){ + return 'zeroplayertarget'; + } + } + } + } + }, + txianqu:{ + trigger:{source:'damageBefore'}, + logTarget:'player', + filter:function(event,player){ + if(player.hasSkill('txianqu2')) return false; + var evt=event.getParent('phaseUse'); + if(evt&&evt.player==player) return true; + return false; + }, + check:function(event,player){ + var target=event.player; + if(get.attitude(player,target)>=0||get.damageEffect(target,player,player)<=0) return true; + if(target.hp>player.hp&&player.isDamaged()) return true; + return false; + }, + content:function(){ + trigger.cancel(); + player.draw(2); + player.recover(); + player.addTempSkill('txianqu2'); + }, + ai:{ + jueqing:true, + skillTagFilter:function(player,tag,arg){ + if(!arg) return false; + if(player.hasSkill('txianqu2')) return false; + if(get.attitude(player,arg)>0) return false; + var evt=_status.event.getParent('phaseUse'); + if(evt&&evt.player==player) return true; + return false; + }, + effect:{ + player:function(card,player,target){ + if(get.tag(card,'damage')&&get.attitude(player,target)>0){ + if(player.hp==player.maxHp||get.recoverEffect(player,player,player)<=0) return 'zeroplayertarget'; + return [0,0,0,0.5]; + } + } + } + } + }, + txianqu2:{}, + xunying:{ + trigger:{player:'shaAfter'}, + direct:true, + filter:function(event,player){ + return player.canUse('sha',event.target)&&player.hasSha()&&event.target.isIn(); + }, + content:function(){ + "step 0" + if(player.hasSkill('jiu')){ + player.removeSkill('jiu'); + event.jiu=true; + } + player.chooseToUse(get.prompt('xunying'),{name:'sha'},trigger.target,-1).logSkill='xunying'; + "step 1" + if(result.bool); + else if(event.jiu){ + player.addSkill('jiu'); + } + } + }, + liefeng:{ + trigger:{player:'useCard'}, + forced:true, + popup:false, + filter:function(event,player){ + return _status.currentPhase==player&&[2,3,4].contains(player.countUsed()); + }, + content:function(){ + var skill; + switch(player.countUsed()){ + case 2:skill='yanzhan';break; + case 3:skill='tianjian';break; + case 4:skill='yufeng';break; + } + if(skill&&!player.hasSkill(skill)){ + player.addTempSkill(skill); + player.popup(skill); + game.log(player,'获得了','【'+get.translation(skill)+'】'); + if(skill=='yufeng'){ + var nh=player.countCards('h'); + if(nh<2){ + player.draw(2-nh); + player.addSkill('counttrigger'); + if(!player.storage.counttrigger){ + player.storage.counttrigger={}; + } + player.storage.counttrigger.yufeng=1; + } + } + } + }, + ai:{ + effect:{ + player:function(card,player){ + if(_status.currentPhase!=player) return; + if(get.type(card)=='basic') return; + if(get.tag(card,'gain')) return; + if(get.value(card,player,'raw')>=7) return; + if(player.hp<=2) return; + if(player.needsToDiscard()) return; + if(player.countUsed()>=2) return; + return 'zeroplayertarget'; + } + } + } + }, + yuexing:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return target!=player; + }, + content:function(){ + player.storage.yuexing2=target; + player.addTempSkill('yuexing2'); + target.storage.yuexing2=player; + target.addTempSkill('yuexing2'); + }, + ai:{ + order:function(){ + var player=_status.event.player; + if(player.hasSkill('minsha')) return 6.5; + return 2; + }, + result:{ + target:function(player,target){ + if(player.hasSkill('minsha')&&player.countCards('he')>=3&& + target.hp>1&&get.damageEffect(target,player,player,'thunder')>0){ + var num1=game.countPlayer(function(current){ + if(get.distance(target,current)<=1&¤t!=player&¤t!=target){ + return -get.sgn(get.attitude(player,current)); + } + }); + var num2=game.countPlayer(function(current){ + if(get.distance(player,current)<=1&¤t!=player&¤t!=target){ + return -get.sgn(get.attitude(player,current)); + } + }); + + if(num2>=num1) return 0; + return 2*(num2-num1); + } + return -_status.event.getRand(); + } + } + } + }, + yuexing2:{ + mark:'character', + intro:{ + content:'到其他角色的距离基数与$交换' + }, + onremove:true, + mod:{ + globalFrom:function(from,to,distance){ + if(from.storage.yuexing2){ + var dist1=get.distance(from,to,'pure'); + var dist2=get.distance(from.storage.yuexing2,to,'pure'); + return distance-dist1+dist2; + } + } + } + }, + minsha:{ + enable:'phaseUse', + usable:1, + filterCard:true, + selectCard:2, + position:'he', + filter:function(event,player){ + return player.countCards('he')>=2; + }, + filterTarget:function(card,player,target){ + return target!=player&&target.hp>1; + }, + line:'thunder', + check:function(card){ + return 8-get.value(card); + }, + content:function(){ + 'step 0' + target.damage('thunder'); + 'step 1' + event.targets=game.filterPlayer(function(current){ + return get.distance(target,current)<=1&¤t!=target&¤t!=player; + }).sortBySeat(target); + 'step 2' + if(event.targets.length){ + event.targets.shift().randomDiscard(false); + event.redo(); + } + }, + ai:{ + order:6, + result:{ + player:function(player,target){ + if(get.damageEffect(target,player,player,'thunder')>0){ + if(target==player.storage.yuexing2){ + return 10; + } + var num=1+game.countPlayer(function(current){ + if(get.distance(target,current)<=1&¤t!=player&¤t!=target){ + return -get.sgn(get.attitude(player,current)); + } + }); + if(target.hp==1){ + num+=2; + } + if(target.hp1&&player.countCards('h','sha')==1){ + return 'zeroplayertarget'; + } + } + }, + } + }, + xiaoyue2:{ + mod:{ + cardRespondable:function(card,player){ + if(_status.event.getParent(4).name=='xiaoyue'&&get.suit(card)!='heart') return false; + } + } + }, + huanlei:{ + trigger:{player:'damageEnd'}, + check:function(event,player){ + return get.damageEffect(event.source,player,player,'thunder')>0; + }, + filter:function(event,player){ + return event.source&&event.source.isIn()&&event.source.hp>player.hp; + }, + logTarget:'source', + content:function(){ + 'step 0' + trigger.source.damage('thunder'); + 'step 1' + trigger.source.draw(); + } + }, + anwugu:{ + trigger:{source:'damageEnd'}, + check:function(event,player){ + return get.attitude(player,event.player)<0; + }, + filter:function(event,player){ + if(event._notrigger.contains(event.player)) return false; + return event.player!=player&&event.player.isIn()&&!event.player.hasSkill('anwugu2'); + }, + logTarget:'player', + content:function(){ + trigger.player.addSkill('anwugu2'); + } + }, + anwugu2:{ + mod:{ + cardEnabled:function(card,player){ + if(_status.currentPhase!=player) return; + if(player.countUsed()>=player.storage.anwugu2) return false; + }, + maxHandcard:function(player,num){ + return num-1; + } + }, + mark:true, + intro:{ + content:'手牌上限-1,每回合最多使用$张牌(剩余$回合)' + }, + init:function(player){ + player.storage.anwugu2=3; + }, + trigger:{player:'phaseAfter'}, + silent:true, + onremove:true, + content:function(){ + player.storage.anwugu2--; + if(player.storage.anwugu2<=0){ + // player.loseHp(); + player.removeSkill('anwugu2'); + } + else{ + player.updateMarks(); + } + } + }, + xtanxi:{ + enable:'phaseUse', + usable:1, + filterCard:true, + check:function(card){ + var enemies=_status.event.player.getEnemies(); + var num1=0,num2=0; + for(var i=0;i=3) {num1+=0.5;num2+=0.5;} + } + var rand=_status.event.getRand(); + if(num1>=1&&num2>=1){ + if(card.name=='shan') return rand+=0.4; + if(card.name=='sha') return rand; + } + else if(num1>=1){ + if(card.name=='sha') return rand; + } + else if(num2>=1){ + if(card.name=='shan') return rand; + } + return 0; + }, + content:function(){ + player.addExpose(0.1); + var targets=player.getEnemies(); + for(var i=0;i0; + }, + logTarget:'player', + line:'fire', + ai:{ + expose:0.2, + threaten:1.3, + }, + content:function(){ + trigger.player.damage('fire'); + }, + }, + guijin:{ + round:3, + enable:'phaseUse', + delay:0, + content:function(){ + 'step 0' + event.cards=get.cards(4); + 'step 1' + if(event.cards.length){ + var more=false,remain=false,nomore=false; + if(event.cards.length>=3){ + for(var i=0;i=8){ + more=true; + } + if(event.cards.length>=4&&value<6){ + if(remain===false){ + remain=value; + } + else{ + remain=Math.min(remain,value); + } + } + } + } + if(remain===false){ + remain=0; + } + if(!more&&!game.hasPlayer(function(current){ + return get.attitude(player,current)<0&&!current.skipList.contains('phaseDraw'); + })){ + var num=0; + for(var i=0;i=12){ + more=true; + } + else{ + nomore=true; + } + } + player.chooseCardButton('归烬',event.cards,[1,event.cards.length]).ai=function(button){ + if(nomore) return 0; + if(more){ + return get.value(button.link,player,'raw')-remain; + } + else{ + if(ui.selected.buttons.length) return 0; + return 8-get.value(button.link,player,'raw'); + } + } + } + else{ + event.goto(4); + } + 'step 2' + if(result.bool){ + for(var i=0;i1){ + if(target==player&&target.needsToDiscard(result.links.length)>1){ + return att/5; + } + return att; + } + else{ + if(target.skipList.contains('phaseDraw')) return att/5; + return -att; + } + } + } + else{ + event.goto(4); + } + 'step 3' + if(result.targets.length){ + result.targets[0].gain(event.togive,'draw'); + result.targets[0].skip('phaseDraw'); + result.targets[0].addTempSkill('guijin2',{player:'phaseBegin'}); + game.log(result.targets[0],'获得了'+get.cnNumber(event.togive.length)+'张','#g“归烬”牌'); + player.line(result.targets[0],'green'); + event.goto(1); + } + 'step 4' + while(event.cards.length){ + ui.cardPile.insertBefore(event.cards.pop(),ui.cardPile.firstChild); + } + }, + ai:{ + order:1, + result:{ + player:function(player){ + if(game.roundNumber==1&&player.hasUnknown()) return 0; + return 1; + } + } + } + }, + guijin2:{ + mark:true, + intro:{ + content:'跳过下一个摸牌阶段' + }, + ai:{ + effect:{ + target:function(card,player,target){ + if(card.name=='bingliang'||card.name=='caomu') return 0; + } + } + } + }, + chengxin:{ + round:4, + enable:'chooseToUse', + filter:function(event,player){ + return event.type=='dying'; + }, + filterTarget:function(card,player,target){ + return target==_status.event.dying; + }, + selectTarget:-1, + content:function(){ + target.recover(1-target.hp); + target.addTempSkill('chengxin2',{player:'phaseAfter'}); + }, + ai:{ + order:6, + threaten:1.4, + skillTagFilter:function(player){ + if(4-(game.roundNumber-player.storage.chengxin_roundcount)>0) return false; + if(!_status.event.dying) return false; + }, + save:true, + result:{ + target:3 + }, + } + }, + chengxin2:{ + trigger:{player:'damageBefore'}, + mark:true, + forced:true, + content:function(){ + trigger.cancel(); + }, + ai:{ + nofire:true, + nothunder:true, + nodamage:true, + effect:{ + target:function(card,player,target,current){ + if(get.tag(card,'damage')) return [0,0]; + } + }, + }, + intro:{ + content:'防止一切伤害' + } + }, + tianwu:{ + trigger:{player:'useCardToBegin'}, + filter:function(event,player){ + if(get.is.altered('tianwu')&&player.hasSkill('tianwu2')) return false; + return event.targets&&event.targets.length==1&&player.getEnemies().contains(event.target); + }, + // alter:true, + frequent:true, + content:function(){ + trigger.target.getDebuff(); + player.addTempSkill('tianwu2'); + } + }, + tianwu2:{}, + shiying:{ + trigger:{global:'dieBefore'}, + skillAnimation:'epic', + animationColor:'water', + unique:true, + init:function(player){ + player.storage.shiying=false; + }, + mark:true, + intro:{ + content:'limited' + }, + check:function(event,player){ + return get.attitude(player,event.player)>=3; + }, + filter:function(event,player){ + return !player.storage.shiying&&event.player!=player; + }, + logTarget:'player', + content:function(){ + 'step 0' + trigger.cancel(); + player.awakenSkill('shiying'); + player.storage.shiying=true; + + player.maxHp=3; + player.hp=3; + trigger.player.maxHp=3; + trigger.player.hp=3; + + player.clearSkills(); + trigger.player.clearSkills(); + 'step 1' + var hs=player.getCards('hej'); + player.$throw(hs); + player.lose(player.getCards('hej'))._triggered=null; + 'step 2' + var hs=trigger.player.getCards('hej'); + trigger.player.$throw(hs); + trigger.player.lose(trigger.player.getCards('hej'))._triggered=null; + 'step 3' + game.asyncDraw([player,trigger.player],3); + }, + ai:{ + threaten:1.5 + } + }, + liguang:{ + trigger:{player:'phaseEnd'}, + filter:function(event,player){ + if(!player.canMoveCard()) return false; + if(!game.hasPlayer(function(current){ + return current.countCards('ej'); + })){ + return false; + } + return player.countCards('h')>0; + }, + direct:true, + content:function(){ + "step 0" + player.chooseToDiscard(get.prompt('liguang'),'弃置一张手牌并移动场上的一张牌',lib.filter.cardDiscardable).set('ai',function(card){ + if(!_status.event.check) return 0; + return 7-get.useful(card); + }).set('check',player.canMoveCard(true)).set('logSkill','liguang'); + "step 1" + if(result.bool){ + player.moveCard(true); + } + else{ + event.finish(); + } + }, + ai:{ + expose:0.2, + threaten:1.3 + } + }, + xiepan:{ + trigger:{player:'loseEnd'}, + direct:true, + filter:function(event,player){ + if(player.countCards('h',{type:'basic'})) return false; + if(!player.countCards('h')) return false; + for(var i=0;i1){ + list=list.randomGets(player.storage.yujia); + for(var i=0;i0&&event.num>0; + }, + content:function(){ + trigger.cancel(); + player.draw(2*trigger.num); + }, + group:'tanhua_remove', + subSkill:{ + remove:{ + trigger:{player:'dying'}, + priority:10, + forced:true, + content:function(){ + player.recover(); + player.removeSkill('tanhua'); + } + } + } + }, + yingfeng:{ + trigger:{player:'useCardAfter'}, + filter:function(event,player){ + if(event.card.name!='sha') return false; + if(event.parent.name=='yingfeng') return false; + var enemies=player.getEnemies(); + return game.hasPlayer(function(current){ + return enemies.contains(current)&&!event.targets.contains(current)&&player.canUse('sha',current,false); + }); + }, + forced:true, + content:function(){ + var enemies=player.getEnemies(); + enemies.remove(trigger.targets); + if(enemies.length){ + player.useCard({name:'sha'},enemies.randomGet().addExpose(0.2)); + } + }, + }, + ywuhun:{ + trigger:{player:'phaseBefore'}, + forced:true, + // alter:true, + filter:function(event){ + return event.parent.name!='ywuhun'; + }, + intro:{ + content:'回合结束后,场上及牌堆中的牌将恢复到回合前的状态' + }, + video:function(player,data){ + for(var i in data){ + var current=game.playerMap[i]; + current.node.handcards1.innerHTML=''; + current.node.handcards2.innerHTML=''; + current.node.equips.innerHTML=''; + current.node.judges.innerHTML=''; + current.directgain(get.infoCards(data[i].h)); + var es=get.infoCards(data[i].e); + for(var j=0;j0){ + num++; + if(get.attitude(player,target)>0){ + hef/=1.5; + if(get.tag(hs[i],'damage')){ + damaged=true; + } + } + eff+=hef; + } + } + if(!player.needsToDiscard(-num)){ + return eff; + } + return 0; + }; + 'step 1' + if(result.bool){ + event.target=result.targets[0]; + var num=0; + player.chooseCard([1,Infinity],'按顺序选择对'+get.translation(result.targets)+'使用的牌',function(card){ + return lib.filter.targetEnabled2(card,player,event.target); + }).ai=function(card){ + if(get.effect(event.target,card,player,player)>0){ + if(get.attitude(player,event.target)>0&&get.tag(card,'damage')){ + for(var i=0;i2&& + get.recoverEffect(players[i],player,player)>0){ + if(players[i].hp==1){ + if(player.hp=3) return 1; + return 0; + } + }, + }, + intro:{ + content:'limited' + } + }, + binxin:{ + trigger:{global:'phaseEnd'}, + check:function(event,player){ + return get.attitude(player,event.player)>0; + }, + filter:function(event,player){ + return event.player.hp==1; + }, + logTarget:'player', + content:function(){ + trigger.player.changeHujia(); + }, + ai:{ + expose:0.1 + } + }, + qixia:{ + trigger:{player:['useCardAfter','respondAfter']}, + silent:true, + init:function(player){ + player.storage.qixia=[]; + }, + // mark:true, + intro:{ + content:function(storage){ + if(!storage.length){ + return '未使用或打出过有花色的牌'; + } + else{ + var str='已使用过'+get.translation(storage[0]+'2'); + for(var i=1;i=4; + }, + content:function(){ + player.insertPhase(); + player.storage.qixia.length=0; + player.syncStorage('qixia'); + player.unmarkSkill('qixia'); + } + } + } + }, + qixia_old:{ + trigger:{global:'damageAfter'}, + direct:true, + filter:function(event,player){ + return !player.hasSkill('qixia2')&&event.source!=player&&event.player.isIn()&&player.countCards('he',{color:'red'}); + }, + content:function(){ + 'step 0' + player.chooseToDiscard('he',get.prompt('qixia',trigger.player),{color:'red'}).set('logSkill',['qixia',trigger.player]).ai=function(card){ + if(get.attitude(player,trigger.player)>0){ + if(trigger.player.hp==1){ + return 10-get.value(card); + } + return 8-get.value(card); + } + } + 'step 1' + if(result.bool){ + player.addTempSkill('qixia2'); + trigger.player.draw(2); + if(trigger.player.hp==1&&!trigger.player.hujia){ + trigger.player.changeHujia(); + } + } + }, + ai:{ + threaten:1.8 + } + }, + qixia2:{}, + jianzhen:{ + trigger:{player:'shaAfter'}, + forced:true, + filter:function(event,player){ + return event.target.isIn()&&game.hasPlayer(function(current){ + return current.canUse('sha',event.target,false)&¤t!=player; + }); + }, + content:function(){ + 'step 0' + event.targets=game.filterPlayer(function(current){ + return current.canUse('sha',trigger.target,false)&¤t!=player; + }); + event.targets.sortBySeat(trigger.player); + 'step 1' + if(event.targets.length){ + event.current=event.targets.shift(); + if(event.current.hasSha()){ + event.current.chooseToUse({name:'sha'},'是否对'+get.translation(trigger.target)+'使用一张杀?',trigger.target,-1); + } + else{ + event.redo(); + } + } + else{ + event.finish(); + } + 'step 2' + if (!result.bool){ + event.goto(1); + } + }, + ai:{ + expose:0.2, + threaten:1.4 + }, + }, + husha:{ + trigger:{player:'phaseEnd'}, + direct:true, + filter:function(event,player){ + if(player.storage.husha==1){ + return game.hasPlayer(function(current){ + return player.canUse('sha',current,false); + }) + } + else{ + return player.storage.husha>0; + } + }, + content:function(){ + 'step 0' + var list=[]; + if(game.hasPlayer(function(current){ + return player.canUse('sha',current,false); + })){ + list.push('移去1枚虎煞标记,视为使用一张杀'); + } + if(player.storage.husha>1){ + list.push('移去2枚虎煞标记,视为使用一张南蛮入侵'); + if(player.storage.husha>2){ + list.push('移去3枚虎煞标记,视为对除你之外的角色使用一张元素毁灭'); + } + } + player.chooseControl('cancel2',function(){ + if(player.storage.husha>2){ + var num1=game.countPlayer(function(current){ + if(current!=player&&player.canUse('yuansuhuimie',current)){ + return get.sgn(get.effect(current,{name:'yuansuhuimie'},player,player)); + } + }); + var num2=game.countPlayer(function(current){ + if(current!=player&&player.canUse('yuansuhuimie',current)){ + return get.effect(current,{name:'yuansuhuimie'},player,player); + } + }); + if(num1>0&&num2>0) return '选项三'; + } + if(player.storage.husha>1){ + var num=game.countPlayer(function(current){ + if(current!=player&&player.canUse('nanman',current)){ + return get.sgn(get.effect(current,{name:'nanman'},player,player)); + } + }); + if(num>0) return '选项二'; + } + if(game.hasPlayer(function(current){ + return player.canUse('sha',current,false)&&get.effect(current,{name:'sha'},player,player)>0; + })){ + return '选项一'; + } + return 'cancel2'; + }).set('prompt',get.prompt('husha')).set('choiceList',list); + 'step 1' + if(result.control!='cancel2'){ + player.logSkill('husha'); + if(result.control=='选项一'){ + event.sha=true; + player.storage.husha--; + player.chooseTarget('选择出杀的目标',true,function(card,player,target){ + return player.canUse('sha',target,false); + }).ai=function(target){ + return get.effect(target,{name:'sha'},player,player); + } + } + else if(result.control=='选项二'){ + var list=game.filterPlayer(function(current){ + return player.canUse('nanman',current); + }); + player.storage.husha-=2; + list.sortBySeat(); + player.useCard({name:'nanman'},list); + } + else{ + var list=game.filterPlayer(function(current){ + return player.canUse('yuansuhuimie',current); + }); + player.storage.husha-=3; + list.remove(player); + list.sortBySeat(); + player.useCard({name:'yuansuhuimie'},list); + } + if(!player.storage.husha){ + player.unmarkSkill('husha'); + } + else{ + player.syncStorage('husha'); + player.updateMarks(); + } + } + 'step 2' + if(event.sha&&result.targets&&result.targets.length){ + player.useCard({name:'sha'},result.targets[0]); + } + }, + init:function(player){ + player.storage.husha=0; + }, + intro:{ + content:'mark' + }, + group:'husha_count', + subSkill:{ + count:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event,player){ + if(player.storage.husha<3){ + var evt=event.getParent('phaseUse'); + return evt&&evt.player==player; + } + return false; + }, + content:function(){ + player.storage.husha+=trigger.num; + if(player.storage.husha>3){ + player.storage.husha=3; + } + player.markSkill('husha'); + player.syncStorage('husha'); + player.updateMarks(); + } + } + } + }, + fenshi:{ + unique:true, + skillAnimation:true, + animationColor:'fire', + trigger:{player:'dyingAfter'}, + forced:true, + mark:true, + derivation:'longhuo', + intro:{ + content:'limited' + }, + content:function(){ + player.awakenSkill('fenshi'); + player.changeHujia(2); + player.draw(2); + player.addSkill('longhuo'); + }, + }, + longhuo:{ + unique:true, + trigger:{player:'phaseEnd'}, + check:function(event,player){ + if(player.hp==1&&player.hujia==0) return false; + var num=game.countPlayer(function(current){ + var eff=get.sgn(get.damageEffect(current,player,player,'fire')); + if(current.hp==1&¤t.hujia==0) eff*=1.5; + return eff; + }); + return num>0; + }, + content:function(){ + 'step 0' + event.targets=get.players(lib.sort.seat); + 'step 1' + if(event.targets.length){ + var current=event.targets.shift(); + if(current.isIn()){ + player.line(current,'fire'); + current.damage('fire'); + event.redo(); + } + } + } + }, + yanzhan:{ + enable:'phaseUse', + viewAs:{name:'sha',nature:'fire'}, + usable:1, + position:'he', + viewAsFilter:function(player){ + if(!player.countCards('he',{color:'red'})) return false; + }, + filterCard:{color:'red'}, + check:function(card){ + if(get.suit(card)=='heart') return 7-get.value(card); + return 5-get.value(card); + }, + onuse:function(result){ + if(result.targets){ + for(var i=0;i2||nh==0)){ + return false; + } + } + } + }, + feixia:{ + enable:'phaseUse', + usable:1, + filterCard:{color:'red'}, + position:'he', + filter:function(event,player){ + return player.countCards('he',{color:'red'})>0; + }, + check:function(card){ + return 7-get.value(card); + }, + content:function(){ + var targets=player.getEnemies(); + if(targets.length){ + var target=targets.randomGet(); + target.addExpose(0.2); + player.useCard({name:'sha'},target,false); + } + }, + ai:{ + order:2.9, + result:{ + player:1 + } + } + }, + lueying:{ + trigger:{player:'shaBegin'}, + filter:function(event,player){ + if(event.target.countCards('he')>0){ + return game.hasPlayer(function(current){ + return current!=player&¤t!=event.target&¤t.countCards('he'); + }); + } + return false; + }, + logTarget:'target', + usable:1, + content:function(){ + 'step 0' + var card=trigger.target.getCards('he').randomGet(); + player.gain(card,trigger.target); + if(get.position(card)=='e'){ + trigger.target.$give(card,player); + } + else{ + trigger.target.$giveAuto(card,player); + } + 'step 1' + if(game.hasPlayer(function(current){ + return current!=player&¤t!=trigger.target&¤t.countCards('he'); + })){ + trigger.target.chooseTarget(function(card,player,target){ + return target!=player&&target!=_status.event.parent.player&&target.countCards('he')>0; + },'选择一名角色并令'+get.translation(player)+'弃置其一张牌').ai=function(target){ + return -get.attitude(_status.event.player,target); + }; + } + else{ + event.finish(); + } + 'step 2' + if(result.bool){ + trigger.target.line(result.targets[0],'green'); + player.discardPlayerCard(result.targets[0],true,'he'); + } + }, + ai:{ + threaten:1.5, + expose:0.2, + } + }, + tianjian:{ + enable:'phaseUse', + viewAs:{name:'wanjian'}, + filterCard:{name:'sha'}, + filter:function(event,player){ + return player.countCards('h','sha')>0; + }, + // alter:true, + usable:1, + group:'tianjian_discard', + subSkill:{ + discard:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event){ + if(event._notrigger.contains(event.player)) return false; + if(get.is.altered('tianjian')) return false; + return event.parent.skill=='tianjian'&&event.player.countCards('he'); + }, + popup:false, + content:function(){ + trigger.player.discard(trigger.player.getCards('he').randomGet()); + } + } + } + }, + feng:{ + unique:true, + init:function(player){ + player.storage.feng=0; + }, + mark:true, + intro:{ + content:'已累计摸#次牌' + }, + trigger:{player:'drawBegin'}, + forced:true, + popup:false, + priority:5, + content:function(){ + if(player.storage.feng<2){ + player.storage.feng++; + } + else{ + trigger.num++; + player.storage.feng=0; + player.logSkill('feng'); + } + player.updateMarks(); + } + }, + ya:{ + unique:true, + init:function(player){ + player.storage.ya=0; + }, + mark:true, + intro:{ + content:'已累计受到#次伤害' + }, + trigger:{player:'damageBegin'}, + filter:function(event,player){ + if(player.storage.ya==2) return event.num>0; + return true; + }, + forced:true, + popup:false, + content:function(){ + if(player.storage.ya<2){ + player.storage.ya++; + } + else if(trigger.num>0){ + trigger.num--; + player.storage.ya=0; + player.logSkill('ya'); + } + player.updateMarks(); + } + }, + song:{ + unique:true, + init:function(player){ + player.storage.song=0; + }, + mark:true, + intro:{ + content:'已累计造成#次伤害' + }, + trigger:{source:'damageBegin'}, + forced:true, + popup:false, + content:function(){ + if(player.storage.song<2){ + player.storage.song++; + } + else{ + trigger.num++; + player.storage.song=0; + player.logSkill('song'); + } + player.updateMarks(); + } + }, + longxiang:{ + trigger:{player:'shaBegin'}, + filter:function(event,player){ + return event.target.countCards('h')>player.countCards('h'); + }, + check:function(event,player){ + return get.attitude(player,event.target)<0; + }, + logTarget:'target', + content:function(){ + var hs=trigger.target.getCards('h'); + trigger.target.discard(hs.randomGets(hs.length-player.countCards('h'))); + } + }, + huxi:{ + enable:'chooseToUse', + viewAs:{name:'sha'}, + precontent:function(){ + 'step 0' + player.loseHp(); + 'step 1' + player.changeHujia(); + }, + filterCard:function(){return false}, + selectCard:-1, + prompt:'失去一点体力并获得一点护甲,视为使用一张杀', + ai:{ + order:function(){ + var player=_status.event.player; + if(player.hp<=2) return 0; + return 2; + }, + skillTagFilter:function(player,tag,arg){ + if(arg!='use') return false; + }, + respondSha:true, + } + }, + xuanmo:{ + enable:'phaseUse', + usable:1, + filterCard:function(card){ + var type=get.type(card,'trick'); + return type=='basic'||type=='equip'||type=='trick'; + }, + check:function(card){ + return 8-get.value(card); + }, + filter:function(event,player){ + return player.countCards('h')>0; + }, + discard:false, + prepare:'throw', + content:function(){ + game.log(player,'将',cards,'置于牌堆顶'); + ui.cardPile.insertBefore(cards[0],ui.cardPile.firstChild); + var list=get.inpile(get.type(cards[0],'trick'),'trick').randomGets(2); + for(var i=0;i0){ + return att+1/Math.sqrt(1+target.countCards('h')); + } + return 0; + }; + 'step 2' + if(result.bool){ + player.line(result.targets[0],'green'); + result.targets[0].draw(); + event.targets.push(result.targets[0]); + if(event.targets.length==game.players.length){ + event.finish(); + } + else{ + player.chooseTarget('令一名角色获得一点护甲',function(card,player,target){ + return !event.targets.contains(target); + }).ai=function(target){ + var att=get.attitude(player,target); + if(att>0){ + return att+1/Math.sqrt(1+target.hp); + } + return 0; + }; + } + } + else{ + event.finish(); + } + 'step 3' + if(result.bool){ + player.line(result.targets[0],'green'); + result.targets[0].changeHujia(); + game.delay(); + event.targets.push(result.targets[0]); + if(event.targets.length==game.players.length){ + event.finish(); + } + else{ + player.chooseTarget('令一名角色装备一件随机装备',function(card,player,target){ + return !event.targets.contains(target); + }).ai=function(target){ + var att=get.attitude(player,target); + if(att>0&&!target.getEquip(5)){ + return att; + } + return 0; + }; + } + } + else{ + event.finish(); + } + 'step 4' + if(result.bool){ + player.line(result.targets[0],'green'); + game.delay(); + var list=[]; + for(var i=0;i0){ + return att+1/Math.sqrt(1+target.hp); + } + return 0; + }; + } + } + else{ + event.finish(); + } + 'step 5' + if(result.bool){ + player.line(result.targets[0],'green'); + game.delay(); + result.targets[0].tempHide(); + } + } + }, + sheling:{ + trigger:{global:['useCardAfter','respondAfter','discardAfter']}, + filter:function(event,player){ + if(player!=_status.currentPhase||player==event.player) return false; + if(event.cards){ + for(var i=0;i0&&player.countCards('h')>0; + }, + check:function(event,player){ + if(player.isUnseen()) return false; + if(get.attitude(player,event.player)>=0) return false; + var hs=player.getCards('h'); + if(hs.length=10&&val<=6) return true; + if(hs[i].number>=8&&val<=3) return true; + } + return false; + }, + logTarget:'player', + content:function(){ + 'step 0' + player.chooseToCompare(trigger.player); + 'step 1' + if(result.bool){ + player.draw(2); + } + else{ + event.finish(); + } + 'step 2' + player.chooseCard('将两张牌置于牌堆顶(先选择的在上)',2,'he',true); + 'step 3' + if(result.bool){ + player.lose(result.cards,ui.special); + event.cards=result.cards; + } + else{ + event.finish(); + } + 'step 4' + game.delay(); + var nodes=[]; + for(var i=0;i=0;i--){ + ui.cardPile.insertBefore(event.cards[i],ui.cardPile.firstChild); + } + }, + ai:{ + mingzhi:false, + expose:0.2 + } + }, + zhangmu:{ + trigger:{player:['chooseToRespondBegin','chooseToUseBegin']}, + filter:function(event,player){ + if(event.responded) return false; + if(!event.filterCard({name:'shan'},player,event)) return false; + return player.countCards('h','shan')>0; + }, + direct:true, + usable:1, + content:function(){ + "step 0" + var goon=(get.damageEffect(player,trigger.player,player)<=0); + player.chooseCard(get.prompt('zhangmu'),{name:'shan'}).ai=function(){ + return goon?1:0; + } + "step 1" + if(result.bool){ + player.logSkill('zhangmu'); + player.showCards(result.cards); + trigger.untrigger(); + trigger.responded=true; + trigger.result={bool:true,card:{name:'shan'}} + player.addSkill('zhangmu_ai'); + } + else{ + player.storage.counttrigger.zhangmu--; + } + }, + ai:{ + respondShan:true, + effect:{ + target:function(card,player,target,effect){ + if(get.tag(card,'respondShan')&&effect<0){ + if(target.hasSkill('zhangmu_ai')) return 0; + if(target.countCards('h')>=2) return 0.5; + } + } + } + } + }, + zhangmu_ai:{ + trigger:{player:'loseAfter'}, + silent:true, + filter:function(event,player){ + return player.countCards('h','shan')==0; + }, + content:function(){ + player.removeSkill('zhangmu_ai'); + } + }, + leiyu:{ + trigger:{player:'phaseEnd'}, + direct:true, + filter:function(event,player){ + if(!player.countCards('h',{color:'black'})) return false; + if(player.storage.leiyu){ + for(var i=0;i0){ + num2++; + } + else if(eff<0){ + num2--; + } + } + var next=player.chooseToDiscard(get.prompt('leiyu',player.storage.leiyu),{color:'black'}); + next.ai=function(card){ + if(num>0&&num2>=2){ + return 7-get.value(card); + } + return 0; + }; + next.logSkill=['leiyu',player.storage.leiyu]; + 'step 1' + if(result.bool){ + player.storage.leiyu.sort(lib.sort.seat); + player.useCard({name:'jingleishan',nature:'thunder'},player.storage.leiyu).animate=false; + } + }, + group:['leiyu2','leiyu4'], + ai:{ + threaten:1.3 + } + }, + leiyu2:{ + trigger:{player:'phaseUseBegin'}, + silent:true, + content:function(){ + player.storage.leiyu=[]; + } + }, + leiyu3:{ + trigger:{source:'dieAfter'}, + forced:true, + popup:false, + filter:function(event,player){ + return player.storage.leiyu2?true:false; + }, + content:function(){ + player.recover(); + delete player.storage.leiyu2; + } + }, + leiyu4:{ + trigger:{player:'useCardToBegin'}, + silent:true, + filter:function(event,player){ + return _status.currentPhase==player&&Array.isArray(player.storage.leiyu)&&event.target&&event.target!=player; + }, + content:function(){ + player.storage.leiyu.add(trigger.target); + } + }, + feizhua:{ + trigger:{player:'useCard'}, + filter:function(event,player){ + if(event.card.name!='sha') return false; + if(event.targets.length!=1) return false; + var target=event.targets[0]; + var players=game.filterPlayer(function(current){ + return get.distance(target,current,'pure')==1; + }); + for(var i=0;i0; + }, + content:function(){ + "step 0" + var target=trigger.targets[0]; + var players=game.filterPlayer(function(current){ + return get.distance(target,current,'pure')==1; + }); + for(var i=0;i0; + }, + filterCard:{name:'sha'}, + filterTarget:function(card,player,target){ + return target!=player; + }, + prepare:'give', + discard:false, + content:function(){ + target.gain(cards,player); + if(!player.hasSkill('diewu2')){ + player.draw(); + player.addTempSkill('diewu2'); + } + }, + ai:{ + order:2, + expose:0.2, + result:{ + target:function(player,target){ + if(!player.hasSkill('diewu2')) return 1; + return 0; + } + } + } + }, + diewu2:{}, + lingyu:{ + trigger:{player:'phaseEnd'}, + direct:true, + filter:function(event,player){ + return game.hasPlayer(function(current){ + return current!=player&¤t.isDamaged(); + }); + }, + content:function(){ + 'step 0' + player.chooseTarget('灵愈:令一名其他角色回复一点体力',function(card,player,target){ + return target!=player&&target.hp0; + }, + logTarget:'player', + content:function(){ + trigger.player.draw(); + }, + ai:{ + mingzhi:false, + threaten:2, + expose:0.2, + } + }, + xuanyan:{ + // trigger:{source:'damageBefore'}, + // forced:true, + // priority:5, + // check:function(event,player){ + // return player.hp>3; + // }, + // filter:function(event){ + // return event.card&&get.color(event.card)=='red'; + // }, + // content:function(){ + // trigger.nature='fire'; + // }, + group:['xuanyan2','xuanyan3'] + }, + xuanyan2:{ + trigger:{source:'damageBegin'}, + forced:true, + filter:function(event){ + return event.nature=='fire'&&event.notLink(); + }, + content:function(){ + trigger.num++; + } + }, + xuanyan3:{ + trigger:{source:'damageEnd'}, + forced:true, + popup:false, + filter:function(event){ + return event.nature=='fire'; + }, + content:function(){ + player.loseHp(); + } + }, + ningbin:{ + trigger:{player:'damageEnd'}, + forced:true, + filter:function(event){ + return event.nature=='thunder'; + }, + content:function(){ + player.recover(); + }, + ai:{ + effect:{ + target:function(card,player,target){ + if(get.tag(card,'thunderDamage')){ + if(target.hp<=1||!target.hasSkill('xfenxin')) return [0,0]; + return [0,1.5]; + } + } + } + }, + }, + xfenxin:{ + trigger:{player:'changeHp'}, + forced:true, + filter:function(event){ + return event.num!=0; + }, + // alter:true, + content:function(){ + if(get.is.altered('xfenxin')){ + player.draw(); + } + else{ + player.draw(Math.abs(trigger.num)); + } + }, + ai:{ + effect:{ + target:function(card){ + if(get.tag(card,'thunderDamage')) return; + if(get.tag(card,'damage')||get.tag(card,'recover')){ + return [1,0.2]; + } + } + } + }, + group:'xfenxin2' + }, + xfenxin2:{ + trigger:{source:'dieAfter'}, + forced:true, + filter:function(){ + return !get.is.altered('xfenxin'); + }, + content:function(){ + player.gainMaxHp(); + player.recover(); + } + }, + luanjian:{ + enable:'phaseUse', + filterCard:{name:'sha'}, + selectCard:2, + check:function(card){ + var num=0; + var player=_status.event.player; + var players=game.filterPlayer(); + for(var i=0;i0){ + num++; + if(num>1) return 8-get.value(card); + } + } + return 0; + }, + viewAs:{name:'sha'}, + selectTarget:[1,Infinity], + filterTarget:function(card,player,target){ + return lib.filter.targetEnabled({name:'sha'},player,target); + }, + ai:{ + order:function(){ + return get.order({name:'sha'})+0.1; + }, + effect:{ + player:function(card,player){ + if(_status.currentPhase!=player) return; + if(card.name=='sha'&&player.countCards('h','sha')<2&&!player.needsToDiscard()){ + var num=0; + var player=_status.event.player; + var players=game.filterPlayer(); + for(var i=0;i1) return 'zeroplayertarget'; + } + } + } + } + }, + }, + group:'luanjian2' + }, + luanjian2:{ + trigger:{source:'damageBegin'}, + forced:true, + popup:false, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&event.parent.skill=='luanjian'; + }, + content:function(){ + if(Math.random()<0.5) trigger.num++; + } + }, + ctianfu:{ + enable:'phaseUse', + filter:function(event,player){ + return player.countCards('h','shan')>0; + }, + usable:1, + filterCard:{name:'shan'}, + discard:false, + prepare:'give', + filterTarget:function(card,player,target){ + return target!=player&&!target.hasSkill('ctianfu2'); + }, + check:function(card){ + if(_status.event.player.hp>=3) return 8-get.value(card); + return 7-get.value(card); + }, + content:function(){ + target.storage.ctianfu2=cards[0]; + target.storage.ctianfu3=player; + game.addVideo('storage',target,['ctianfu2',get.cardInfo(cards[0]),'card']); + target.addSkill('ctianfu2'); + }, + ai:{ + order:2, + result:{ + target:function(player,target){ + var att=get.attitude(player,target); + if(att>=0) return 0; + return get.damageEffect(target,player,target,'thunder'); + } + }, + expose:0.2 + } + }, + ctianfu2:{ + trigger:{source:'damageAfter'}, + forced:true, + mark:'card', + filter:function(event,player){ + return player.storage.ctianfu2&&player.storage.ctianfu3; + }, + content:function(){ + "step 0" + if(player.storage.ctianfu3&&player.storage.ctianfu3.isAlive()){ + player.damage(player.storage.ctianfu3); + player.storage.ctianfu3.line(player,'thunder'); + } + else{ + player.damage('nosource'); + } + "step 1" + var he=player.getCards('he'); + if(he.length){ + player.discard(he.randomGet()); + } + "step 2" + player.$throw(player.storage.ctianfu2); + player.storage.ctianfu2.discard(); + delete player.storage.ctianfu2; + delete player.storage.ctianfu3; + player.removeSkill('ctianfu2'); + }, + group:'ctianfu3', + intro:{ + content:'card' + } + }, + ctianfu3:{ + trigger:{player:'dieBegin'}, + forced:true, + popup:false, + content:function(){ + player.storage.ctianfu2.discard(); + delete player.storage.ctianfu2; + delete player.storage.ctianfu3; + player.removeSkill('ctianfu2'); + } + }, + shuiyun:{ + trigger:{player:'phaseEnd'}, + direct:true, + init:function(player){ + player.storage.shuiyun=[]; + player.storage.shuiyun_count=0; + }, + // alter:true, + filter:function(event,player){ + if(player.storage.shuiyun.length>=3) return false; + if(player.storage.shuiyun.length>=2&&get.is.altered('shuiyun')) return false; + var types=[]; + for(var i=0;i0; + }, + filterTarget:function(card,player,target){ + return target==_status.event.dying; + }, + delay:0, + selectTarget:-1, + content:function(){ + "step 0" + player.chooseCardButton(get.translation('shuiyun'),player.storage.shuiyun,true); + "step 1" + if(result.bool){ + player.storage.shuiyun.remove(result.links[0]); + if(!player.storage.shuiyun.length){ + player.unmarkSkill('shuiyun'); + } + player.$throw(result.links); + result.links[0].discard(); + target.recover(); + if(typeof player.storage.shuiyun_count=='number'){ + player.storage.shuiyun_count++; + } + player.syncStorage('shuiyun'); + } + else{ + event.finish(); + } + }, + ai:{ + order:6, + skillTagFilter:function(player){ + return player.storage.shuiyun.length>0; + }, + save:true, + result:{ + target:3 + }, + threaten:1.6 + } + }, + wangyou:{ + trigger:{global:'phaseEnd'}, + unique:true, + gainable:true, + direct:true, + filter:function(event,player){ + if(!player.countCards('he')) return false; + if(player==event.player) return false; + return game.hasPlayer(function(current){ + return current.hasSkill('wangyou3'); + }); + }, + content:function(){ + "step 0" + var targets=[]; + var num=0; + var players=game.filterPlayer(); + for(var i=0;i0) num++; + else if(att<0) num--; + targets.push(players[i]); + } + } + event.targets=targets; + var next=player.chooseToDiscard(get.prompt('wangyou',targets),'he'); + next.logSkill=['wangyou',event.targets]; + next.ai=function(card){ + if(num<=0) return 0; + switch(num){ + case 1:return 5-get.value(card); + case 2:return 7-get.value(card); + default:return 8-get.value(card); + } + } + "step 1" + if(result.bool){ + event.targets.sort(lib.sort.seat); + game.asyncDraw(event.targets); + } + else{ + event.finish(); + } + }, + ai:{ + expose:0.1, + threaten:1.2 + }, + group:'wangyou2' + }, + wangyou2:{ + trigger:{global:'damageEnd'}, + silent:true, + filter:function(event){ + return event.player.isAlive(); + }, + content:function(){ + trigger.player.addTempSkill('wangyou3'); + } + }, + wangyou3:{}, + changnian:{ + forbid:['boss'], + trigger:{player:'dieBegin'}, + direct:true, + unique:true, + derivation:'changnian2', + content:function(){ + "step 0" + player.chooseTarget(get.prompt('changnian'),function(card,player,target){ + return player!=target; + }).ai=function(target){ + return get.attitude(player,target); + }; + "step 1" + if(result.bool){ + var cards=player.getCards('hej'); + var target=result.targets[0]; + // if(player.storage.shuiyun&&player.storage.shuiyun.length){ + // target.gainMaxHp(); + // target.recover(player.storage.shuiyun.length); + // cards=cards.concat(player.storage.shuiyun); + // player.storage.shuiyun.length=0; + // } + player.$give(cards,target); + target.gain(cards); + target.addSkill('changnian2'); + player.logSkill('changnian',target); + target.marks.changnian=target.markCharacter(player,{ + name:'长念', + content:'
          【追思】
          锁定技,结束阶段,你摸一张牌
          ' + }); + game.addVideo('markCharacter',target,{ + name:'长念', + content:'
          【追思】
          锁定技,结束阶段,你摸一张牌
          ', + id:'changnian', + target:player.dataset.position + }); + } + }, + ai:{ + threaten:0.8 + } + }, + changnian2:{ + trigger:{player:'phaseEnd'}, + forced:true, + nopop:true, + content:function(){ + player.draw(); + }, + }, + sajin:{ + enable:'phaseUse', + filterTarget:function(card,player,target){ + return target.hpplayer.hp){ + return 7-get.value(card); + } + return 4-get.value(card); + }, + content:function(){ + "step 0" + var color=get.color(cards[0]); + target.judge(function(card){ + return get.color(card)==color?1:0; + }); + "step 1" + if(result.bool){ + target.recover(); + } + }, + ai:{ + order:3, + result:{ + target:function(player,target){ + return get.recoverEffect(target); + } + }, + threaten:1.5 + } + }, + jtjubao:{ + trigger:{global:'discardAfter'}, + filter:function(event,player){ + if(player.hasSkill('jtjubao2')) return false; + if(event.player==player) return false; + if(_status.currentPhase==player) return false; + for(var i=0;i0){ + return [1,3]; + } + } + } + } + } + } + }, + xshuangren:{ + trigger:{player:['loseEnd']}, + filter:function(event,player){ + if(!player.equiping) return false; + for(var i=0;i0; + }, + content:function(){ + "step 0" + player.chooseTarget([1,trigger.cards.length],get.prompt('qijian'),function(card,player,target){ + return player.canUse({name:'sha'},target,false); + }).ai=function(target){ + return get.effect(target,{name:'sha'},player); + }; + "step 1" + if(result.bool){ + player.logSkill('qijian'); + player.useCard({name:'sha'},result.targets); + } + }, + }, + shenmu:{ + trigger:{global:'dying'}, + priority:6, + filter:function(event,player){ + return event.player.hp<=0&&player.countCards('h',{color:'red'}); + }, + check:function(event,player){ + if(get.attitude(player,event.player)<=0) return false; + var cards=player.getCards('h',{color:'red'}); + for(var i=0;i7&&cards.length>2) return false; + } + }, + content:function(){ + "step 0" + player.showHandcards(); + "step 1" + var cards=player.getCards('h',{color:'red'}); + event.num=cards.length; + player.discard(cards); + "step 2" + trigger.player.recover(); + trigger.player.draw(event.num); + }, + ai:{ + threaten:1.6, + expose:0.2 + } + }, + qianfang:{ + trigger:{player:'phaseBegin'}, + direct:true, + filter:function(event,player){ + return player.storage.xuanning&&player.countCards('he')+player.storage.xuanning>=3; + }, + // alter:true, + content:function(){ + "step 0" + // trigger.cancel(); + var ainum=0; + var num=3-player.storage.xuanning; + var players=game.filterPlayer(); + event.targets=[]; + for(var i=0;i=0){ + switch(num){ + case 1:return 8-get.value(card); + case 2:return 6-get.value(card); + case 3:return 4-get.value(card); + } + } + return -1; + } + next.logSkill='qianfang'; + event.logged=true; + } + else{ + player.chooseBool(get.prompt2('qianfang')).ai=function(){ + return ainum>=0; + } + } + "step 1" + if(result.bool){ + player.storage.xuanning=0; + player.unmarkSkill('xuanning'); + if(!event.logged){ + player.logSkill('qianfang'); + } + player.useCard({name:'wanjian'},'qianfang',event.targets); + } + else{ + event.finish(); + } + }, + ai:{ + expose:0.1, + threaten:1.5 + }, + group:'qianfang_draw', + subSkill:{ + draw:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event,player){ + if(event._notrigger.contains(event.player)) return false; + if(!event.player.isEnemiesOf(player)) return false; + return event.parent.skill=='qianfang'; + }, + popup:false, + content:function(){ + player.draw(); + } + } + } + }, + qianfang2:{ + trigger:{player:'phaseDrawBegin'}, + forced:true, + popup:false, + content:function(){ + trigger.num++; + } + }, + poyun:{ + trigger:{source:'damageEnd'}, + // alter:true, + filter:function(event,player){ + if(event._notrigger.contains(event.player)) return false; + return player.storage.xuanning>0&&event.player.countCards('he')>0; + }, + direct:true, + content:function(){ + "step 0" + player.discardPlayerCard(trigger.player,'he',get.prompt('poyun',trigger.player),[1,get.is.altered('poyun')?1:2]).logSkill=['poyun',trigger.player]; + "step 1" + if(result.bool){ + player.storage.xuanning--; + if(!player.storage.xuanning){ + player.unmarkSkill('xuanning'); + } + player.syncStorage('xuanning'); + } + }, + ai:{ + threaten:1.3 + } + }, + poyun2:{ + trigger:{source:'damageEnd'}, + forced:true, + popup:false, + filter:function(event,player){ + return player.storage.poyun?true:false; + }, + content:function(){ + player.draw(); + player.storage.poyun=false; + player.removeSkill('poyun2'); + } + }, + poyun3:{}, + zhuyue:{ + enable:'phaseUse', + // alter:true, + filter:function(event,player){ + if(get.is.altered('zhuyue')){ + return player.hasCard(function(card){ + return get.color(card)=='black'&&get.type(card)!='basic'; + }); + } + return player.countCards('h',{type:'basic'})0; + }, + usable:1, + locked:false, + check:function(card){ + return 7-get.value(card); + }, + multitarget:true, + multiline:true, + content:function(){ + 'step 0' + targets.sort(lib.sort.seat); + var target=targets[0]; + var cs=target.getCards('he'); + if(cs.length){ + target.discard(cs.randomGet()); + } + player.storage.zhuyue.add(target); + if(targets.length<2){ + event.finish(); + } + 'step 1' + var target=targets[1]; + var cs=target.getCards('he'); + if(cs.length){ + target.discard(cs.randomGet()); + } + player.storage.zhuyue.add(target); + }, + ai:{ + result:{ + target:function(player,target){ + if(!target.countCards('he')) return -0.2; + return -1; + } + }, + order:10, + threaten:1.2, + exoise:0.2 + }, + mod:{ + targetInRange:function(card,player,target){ + if(card.name=='sha'&&player.storage.zhuyue&&player.storage.zhuyue.contains(target)){ + return true; + } + }, + selectTarget:function(card,player,range){ + if(card.name=='sha'&&player.storage.zhuyue&&player.storage.zhuyue.length){ + range[1]=-1; + range[0]=-1; + } + }, + playerEnabled:function(card,player,target){ + if(card.name=='sha'&&player.storage.zhuyue&&player.storage.zhuyue.length&&!player.storage.zhuyue.contains(target)){ + return false; + } + } + }, + intro:{ + content:'players' + }, + group:'zhuyue2' + }, + zhuyue2:{ + trigger:{player:'phaseUseEnd'}, + silent:true, + content:function(){ + player.storage.zhuyue.length=0; + } + }, + longxi:{ + trigger:{player:['chooseToRespondBegin','chooseToUseBegin']}, + forced:true, + popup:false, + max:2, + filter:function(event,player){ + return _status.currentPhase!=player; + }, + priority:101, + content:function(){ + var cards=[]; + var max=Math.min(ui.cardPile.childNodes.length,lib.skill.longxi.max); + for(var i=0;i=2; + }, + check:function(card){ + return 8-get.value(card); + }, + filterCard:function(card){ + return get.color(card)=='red'; + }, + selectCard:2, + filterTarget:function(card,player,target){ + return player!=target&&target.hp>=player.hp; + }, + intro:{ + content:'limited' + }, + line:'fire', + content:function(){ + "step 0" + player.storage.guanri=true; + player.loseHp(); + "step 1" + target.damage(2,'fire'); + "step 2" + if(target.isAlive()){ + target.discard(target.getCards('e')); + } + }, + ai:{ + order:1, + result:{ + target:function(player,target){ + var eff=get.damageEffect(target,player,target,'fire'); + if(player.hp>2) return eff; + if(player.hp==2&&target.hp==2) return eff; + return 0; + } + }, + expose:0.5 + } + }, + tianxian:{ + mod:{ + targetInRange:function(card,player,target,now){ + if(card.name=='sha') return true; + }, + selectTarget:function(card,player,range){ + if(card.name=='sha'&&range[1]!=-1) range[1]=Infinity; + } + }, + priority:5.5, + trigger:{player:'useCardToBefore'}, + filter:function(event){ + return event.card.name=='sha'; + }, + forced:true, + check:function(){ + return false; + }, + content:function(){ + "step 0" + trigger.target.judge(function(card){ + return get.color(card)=='black'?1:0; + }); + "step 1" + if(result.bool){ + trigger.cancel(); + } + } + }, + runxin:{ + trigger:{player:['useCard','respondEnd']}, + direct:true, + filter:function(event){ + if(get.suit(event.card)=='heart'){ + return game.hasPlayer(function(current){ + return current.isDamaged(); + }); + } + return false; + }, + content:function(){ + "step 0" + var noneed=(trigger.card.name=='tao'&&trigger.targets[0]==player&&player.hp==player.maxHp-1); + player.chooseTarget(get.prompt('runxin'),function(card,player,target){ + return target.hp0){ + if(noneed&&player==target){ + num=0.5; + } + else if(target.hp==1){ + num+=3; + } + else if(target.hp==2){ + num+=1; + } + } + return num; + } + "step 1" + if(result.bool){ + player.logSkill('runxin',result.targets); + result.targets[0].recover(); + } + }, + ai:{ + expose:0.3, + threaten:1.5 + } + }, + zhimeng:{ + trigger:{player:'phaseEnd'}, + direct:true, + locked:true, + unique:true, + gainable:true, + // alter:true, + group:'zhimeng3', + content:function(){ + "step 0" + player.chooseTarget(get.prompt('zhimeng'),function(card,player,target){ + return player!=target; + }).ai=function(target){ + var num=get.attitude(player,target); + if(num>0){ + if(player==target){ + num++; + } + if(target.hp==1){ + num+=3; + } + if(target.hp==2){ + num+=1; + } + } + return num; + } + "step 1" + if(result.bool){ + var target=result.targets[0]; + if(get.is.altered('zhimeng')){ + target.draw(); + } + else{ + var card=get.cards()[0]; + target.$draw(card); + target.storage.zhimeng2=card; + game.addVideo('storage',target,['zhimeng2',get.cardInfo(card),'card']); + target.addSkill('zhimeng2'); + } + player.logSkill('zhimeng',target); + } + }, + ai:{ + expose:0.2 + } + }, + zhimeng2:{ + intro:{ + content:'card', + onunmark:function(storage,player){ + delete player.storage.zhimeng2; + } + }, + mark:'card', + trigger:{target:'useCardToBegin'}, + frequent:true, + filter:function(event,player){ + return player.storage.zhimeng2&&get.type(event.card,'trick')==get.type(player.storage.zhimeng2,'trick'); + }, + content:function(){ + player.draw(); + }, + ai:{ + effect:{ + target:function(card,player,target){ + if(target.storage.zhimeng2&&get.type(card,'trick')==get.type(target.storage.zhimeng2,'trick')){ + return [1,0.5]; + } + } + } + } + }, + zhimeng3:{ + trigger:{player:['phaseBegin','dieBegin']}, + silent:true, + content:function(){ + "step 0" + event.players=game.filterPlayer(); + event.num=0; + "step 1" + if(event.num1){ + return 11-get.equipValue(card); + } + if(player.countCards('h')0; + }, + ai:{ + shihuifen:true, + skillTagFilter:function(player){ + return player.countCards('he',{color:'black'})>0; + } + } + }, + tuoqiao_old:{ + filter:function(event,player){ + return game.players.length>3&&(event.player==player.next||event.player==player.previous); + }, + check:function(event,player){ + return get.effect(player,event.card,event.player,player)<0 + }, + changeSeat:true, + trigger:{target:'useCardToBefore'}, + content:function(){ + if(trigger.player==player.next){ + game.swapSeat(player,player.previous); + } + else if(trigger.player==player.previous){ + game.swapSeat(player,player.next); + } + else{ + return; + } + trigger.cancel(); + // player.popup('xiaoyao'); + }, + ai:{ + effect:{ + target:function(card,player,target,current){ + if(target==player.next||target==player.previous) return 0.1; + } + } + } + }, + tianjian_old:{ + enable:'phaseUse', + usable:1, + changeSeat:true, + filterTarget:function(card,player,target){ + return player!=target&&player.next!=target; + }, + filterCard:true, + check:function(card){ + return 4-get.value(card); + }, + content:function(){ + while(player.next!=target){ + game.swapSeat(player,player.next); + } + }, + ai:{ + order:5, + result:{ + player:function(player,target){ + var att=get.attitude(player,target); + if(target==player.previous&&att>0) return 1; + if(target==player.next.next&&get.attitude(player,player.next)<0) return 1; + return 0; + } + } + } + }, + huimeng:{ + trigger:{player:'recoverAfter'}, + frequent:true, + content:function(){ + player.draw(2); + }, + ai:{ + threaten:0.8 + } + }, + tianshe:{ + group:['tianshe2'], + trigger:{player:'damageBefore'}, + filter:function(event){ + if(event.nature) return true; + return false; + }, + forced:true, + content:function(){ + trigger.cancel(); + }, + ai:{ + nofire:true, + nothunder:true, + effect:{ + target:function(card,player,target,current){ + if(card.name=='tiesuo') return 0; + if(get.tag(card,'fireDamage')) return 0; + if(get.tag(card,'thunderDamage')) return 0; + } + } + } + }, + tianshe2:{ + trigger:{source:'damageAfter'}, + filter:function(event,player){ + if(event.nature&&player.hp1; - }, - content:function (){ - "step 0" - player.chooseTarget('选择【门神】的目标',lib.translate.yxs_menshen_info,true,function(card,player,target){ - return target!=player; - }).set('ai',function(target){ - return get.attitude(player,target); - }); - "step 1" - if(result.bool){ - var target=result.targets[0]; - player.line(target,'green'); - game.log(target,'成为了','【门神】','的目标'); - target.storage.yxs_menshen2=player; - target.addSkill('yxs_menshen2'); - } - else { - event.finish(); - } - }, - ai:{ - expose:0.5, - }, - }, - - yxs_menshen2:{ - audio:2, - mark:'character', - intro:{ - content:'当你成为【杀】或【决斗】的目标后,改为$成为目标' - }, - nopop:true, - priority:15, - trigger:{ - target:["shaBegin","juedouBegin"], - }, - forced:true, - popup:false, - filter:function(event,player){ - return player.isAlive(); - }, - content:function (){ - var target=player.storage.yxs_menshen2; - trigger.player.line(target,'green'); - trigger.targets.remove(player); - trigger.targets.push(target); - trigger.target = target; - }, - }, - - guimian:{ - trigger:{source:'damageEnd'}, - forced:true, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&_status.currentPhase==player; - }, - content:function(){ - player.getStat().card.sha--; - } - }, - lyuxue:{ - trigger:{source:'damageEnd'}, - forced:true, - logTarget:'player', - filter:function(event,player){ - if(event._notrigger.contains(event.player)) return false; - return event.player.isIn()&&!event.player.hasSkill('lyuxue2'); - }, - content:function(){ - trigger.player.addSkill('lyuxue2'); - }, - subSkill:{ - clear:{ - trigger:{player:['phaseBegin','dieBegin']}, - forced:true, - filter:function(event,player){ - var num=game.countPlayer(function(current){ - return current.hasSkill('lyuxue2'); - }); - if(!num) return false; - if(event.name=='die') return true; - return num>=Math.floor(game.countPlayer()/2); - }, - content:function(){ - 'step 0' - var list=game.filterPlayer(function(current){ - return current.hasSkill('lyuxue2'); - }); - list.sortBySeat(); - event.list=list; - player.line(list,'green'); - 'step 1' - if(event.list.length){ - event.list.shift().removeSkill('lyuxue2'); - event.redo(); - } - } - } - }, - group:'lyuxue_clear', - }, - lyuxue2:{ - mark:true, - intro:{ - content:'已获得浴血标记' - }, - onremove:function(player){ - player.loseHp(); - } - }, - yaoji:{ - trigger:{player:'damageEnd'}, - filter:function(event,player){ - return event.source&&event.source.isIn()&&event.source!=player&&!event.source.hasJudge('lebu'); - }, - check:function(event,player){ - return get.attitude(player,event.source)<=0; - }, - logTarget:'source', - content:function(){ - var card=game.createCard('lebu'); - trigger.source.addJudge(card); - trigger.source.$draw(card); - game.delay(); - }, - ai:{ - maixie_defend:true, - } - }, - liebo:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return Math.abs(target.countCards('h')-player.countCards('h'))<=1; - }, - content:function(){ - player.swapHandcards(target); - }, - ai:{ - order:function(){ - var player=_status.event.player; - if(player.hasCard(function(card){ - return get.value(card)>=8; - })){ - return 0; - } - var nh=player.countCards('h'); - if(game.hasPlayer(function(current){ - return get.attitude(player,current)<=0&¤t.countCards('h')==nh+1; - })){ - return 9; - } - return 1; - }, - result:{ - player:function(player,target){ - var att=get.attitude(player,target); - if(att>0) return 0; - if(player.hasCard(function(card){ - return get.value(card)>=8; - })){ - return 0; - } - var n1=target.countCards('h'),n2=player.countCards('h'); - var num=0; - if(n1-n2==1){ - num=1; - } - if(player.countCards('h','du')){ - if(n1==n2) num=0.5; - else num=0.1; - } - if(att==0){ - num/=2; - } - return num; - } - } - } - }, - zhuxin:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return target!=player&&target.countCards('h'); - }, - filter:function(event,player){ - return player.countCards('h'); - }, - content:function(){ - 'step 0' - player.chooseToCompare(target); - 'step 1' - if(result.bool){ - target.damage(); - } - }, - ai:{ - order:8, - result:{ - target:function(player,target){ - return get.damageEffect(target,player,target); - } - } - } - }, - wlianhuan:{ - trigger:{source:'damageBegin'}, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&player.countCards('e'); - }, - direct:true, - content:function(){ - 'step 0' - var next=player.chooseToDiscard('e',get.prompt('wlianhuan',trigger.player),'弃置一张装备区内的牌使伤害+1'); - next.ai=function(card){ - if(get.attitude(player,trigger.player)<0){ - return 7-get.value(card); - } - return 0; - } - next.logSkill=['wlianhuan',trigger.player]; - 'step 1' - if(result.bool){ - trigger.num++; - } - } - }, - huli:{ - enable:'phaseUse', - filterCard:{suit:'heart'}, - filterTarget:function(card,player,target){ - return get.distance(player,target)<=1&&lib.filter.cardEnabled({name:'tao'},target,target); - }, - check:function(card){ - return 8-get.value(card); - }, - discard:false, - filter:function(event,player){ - if(player.countCards('h',{suit:'heart'})){ - return true; - } - return false; - }, - prepare:'throw', - content:function(){ - player.useCard({name:'tao'},cards,targets[0]).animate=false; - }, - ai:{ - order:9.5, - result:{ - target:function(player,target){ - return get.recoverEffect(target,player,target); - } - }, - threaten:1.6 - } - }, - yixin:{ - skillAnimation:true, - unique:true, - mark:true, - init:function(player){ - player.storage.yixin=false; - }, - enable:'phaseUse', - filter:function(event,player){ - return !player.storage.yixin&&player.countCards('he')>2; - }, - intro:{ - content:'limited' - }, - filterTarget:function(card,player,target){ - return target.isDamaged(); - }, - filterCard:true, - position:'he', - selectCard:2, - check:function(card){ - return 7-get.value(card); - }, - content:function(){ - player.awakenSkill('yixin'); - player.storage.yixin=true; - var num=Math.min(4,target.maxHp-target.hp); - target.recover(num); - if(num<4){ - target.draw(4-num); - } - }, - ai:{ - order:9.6, - result:{ - target:function(player,target){ - if(target.hp==1&&target.maxHp>=3){ - return get.recoverEffect(target,player,target); - } - return 0; - } - } - } - }, - xianqu:{ - mod:{ - targetEnabled:function(card){ - if(card.name=='sha'&&card.number<8) return false; - } - }, - }, - zbudao:{ - trigger:{player:'phaseDrawBegin'}, - //check:function(event,player){ - // if(player.hasFriend()) return true; - // return false; - //}, - content:function(){ - trigger.num++; - player.addTempSkill('zbudao2','phaseDrawAfter'); - }, - ai:{ - threaten:1.3 - }, - }, - zbudao2:{ - trigger:{player:'phaseDrawEnd'}, - forced:true, - popup:false, - filter:function(event){ - return event.cards&&event.cards.length; - }, - content:function(){ - 'step 0' - event.cards=trigger.cards.slice(0); - player.chooseCardTarget({ - filterCard:function(card){ - return _status.event.getParent().cards.contains(card); - }, - selectCard:1, - filterTarget:function(card,player,target){ - return player!=target; - }, - ai1:function(card){ - if(ui.selected.cards.length>0) return -1; - if(card.name=='du') return 20; - return (_status.event.player.countCards('h')-_status.event.player.hp); - }, - ai2:function(target){ - var att=get.attitude(_status.event.player,target); - if(ui.selected.cards.length&&ui.selected.cards[0].name=='du'){ - return 1-att; - } - return att-4; - }, - //forced:true, - prompt:'将获得的一张牌交给一名其他角色,或点取消' - }); - "step 1" - if(result.bool){ - player.line(result.targets,'green'); - result.targets[0].gain(result.cards,player); - player.$give(result.cards.length,result.targets[0]); - game.delay(0.7); - } - } - }, - taiji:{ - trigger:{player:['useCard','respond']}, - filter:function(event,player){ - return event.card.name=='shan'&&player.hasSha(); - }, - direct:true, - content:function(){ - player.chooseToUse({name:'sha'},'太极:是否使用一张杀?').logSkill='taiji'; - }, - }, - fengliu:{ - trigger:{player:'phaseDrawBegin'}, - filter:function(event,player){ - return game.hasPlayer(function(current){ - return current.sex=='female'; - }); - }, - forced:true, - content:function(){ - var num=game.countPlayer(function(current){ - return current.sex=='female'; - }); - if(num>2) num=2; - trigger.num+=num; - }, - ai:{ - threaten:function(){ - var num=game.countPlayer(function(current){ - return current.sex=='female'; - }); - switch(num){ - case 0:return 1; - case 1:return 1.3; - default:return 2; - } - } - }, - }, - luobi:{ - trigger:{player:'phaseEnd'}, - filter:function(event,player){ - return player.isDamaged(); - }, - content:function(){ - "step 0" - player.draw(player.maxHp-player.hp); - "step 1" - event.cards=result; - "step 2" - player.chooseCardTarget({ - filterCard:function(card){ - return _status.event.getParent().cards.contains(card); - }, - selectCard:[1,event.cards.length], - filterTarget:function(card,player,target){ - return player!=target; - }, - ai1:function(card){ - if(ui.selected.cards.length>0) return -1; - if(card.name=='du') return 20; - return (_status.event.player.countCards('h')-_status.event.player.hp); - }, - ai2:function(target){ - var att=get.attitude(_status.event.player,target); - if(ui.selected.cards.length&&ui.selected.cards[0].name=='du'){ - return 1-att; - } - return att-4; - }, - prompt:'请选择要送人的卡牌' - }); - "step 3" - if(result.bool){ - player.line(result.targets,'green'); - result.targets[0].gain(result.cards,player); - player.$give(result.cards.length,result.targets[0]); - for(var i=0;i0){ - return game.hasPlayer(function(current){ - return current.group!='qun'&¤t!=player; - }); - } - return false; - }, - content:function(){ - 'step 0' - player.chooseTarget(get.prompt('yaoyi'),[1,2],function(card,player,target){ - return target.countCards('h')&&target.group!='qun'&&target!=player; - }).set('ai',function(target){ - return 0.5-get.attitude(_status.event.player,target); - }); - 'step 1' - if(result.bool){ - player.logSkill('yaoyi',result.targets); - event.targets=result.targets; - } - else{ - event.finish(); - } - 'step 2' - if(event.targets&&event.targets.length){ - event.target=event.targets.shift(); - event.target.chooseCard('交给'+get.translation(player)+'一张手牌',true).ai=function(card){ - return -get.value(card); - } - } - else{ - event.finish(); - } - 'step 3' - if(result.bool&&result.cards&&result.cards.length){ - event.target.$give(1,player); - player.gain(result.cards,event.target); - } - event.goto(2); - }, - ai:{ - maixie:true, - maixie_hp:true, - expose:0.2, - effect:{ - target:function(card,player,target){ - if(get.tag(card,'damage')){ - if(player.hasSkillTag('jueqing',false,target)) return [1,-2]; - if(!target.hasFriend()) return; - var players=game.filterPlayer(); - for(var i=0;i=4) return [1,get.tag(card,'damage')*2]; - if(target.hp==3) return [1,get.tag(card,'damage')*1.5]; - if(target.hp==2) return [1,get.tag(card,'damage')*0.5]; - } - } - } - } - } - } - }, - shiqin:{ - trigger:{global:'dying'}, - priority:9, - filter:function(event,player){ - return event.player!=player&&event.player.hp<=0&&event.player.group=='qun'; - }, - check:function(event,player){ - return get.attitude(player,event.player)<0; - }, - forced:true, - logTarget:'player', - content:function(){ - 'step 0' - game.delayx(); - trigger.player.die(); - 'step 1' - if(!trigger.player.isAlive()){ - trigger.cancel(true); - } - } - }, - zyhufu:{ - trigger:{player:'phaseDrawBegin'}, - filter:function(event,player){ - return !player.getEquip(2); - }, - forced:true, - content:function(){ - trigger.num++; - }, - ai:{ - threaten:1.3 - }, - mod:{ - maxHandcard:function(player,num){ - if(player.getEquip(2)) return num+5; - } - } - }, - hanbei:{ - trigger:{player:'shaBegin'}, - forced:true, - filter:function(event,player){ - if(player.getEquip(3)||player.getEquip(4)) return true; - return false; - }, - content:function(){ - trigger.directHit=true; - } - }, - sheshu:{ - trigger:{player:'shaBegin'}, - forced:true, - filter:function(event,player){ - return event.target.hp>=3; - }, - content:function(){ - trigger.directHit=true; - }, - mod:{ - targetInRange:function(card){ - if(card.name=='sha') return true; - }, - }, - }, - lguiyin:{ - unique:true, - forceunique:true, - enable:'phaseUse', - filter:function(event,player){ - return !player.hasSkill('tongyu_guiyin')&&!player.getStat('damage'); - }, - derivation:['lzhangyi','jimin','tongyu'], - content:function(){ - player.draw(); - player.setAvatar('yxs_luobinhan','yxs_handingdun'); - player.removeSkill('lguiyin'); - player.removeSkill('sheshu'); - player.removeSkill('xiadao'); - player.addSkill('jimin'); - player.addSkill('lzhangyi'); - player.addSkill('tongyu'); - player.addTempSkill('tongyu_guiyin'); - }, - ai:{ - order:function(){ - if(_status.event.player.hp==1) return 9; - return 0.5; - }, - result:{ - player:function(player){ - if(player.hp0&&!player.hasSkill('tongyu_guiyin'); - }, - filterCard:true, - position:'he', - check:function(card){ - return 7-get.value(card); - }, - content:function(){ - player.setAvatar('yxs_luobinhan','yxs_luobinhan'); - player.addSkill('lguiyin'); - player.addSkill('sheshu'); - player.addSkill('xiadao'); - player.removeSkill('jimin'); - player.removeSkill('lzhangyi'); - player.removeSkill('tongyu'); - player.addTempSkill('tongyu_guiyin'); - }, - ai:{ - order:9, - result:{ - player:function(player){ - if(player.hasFriend()) return 1; - return 0; - } - } - } - }, - tongyu_guiyin:{}, - zhijie:{ - enable:'phaseUse', - usable:1, - viewAsFilter:function(player){ - return player.countCards('h',{suit:'heart'})>0; - }, - viewAs:{name:'wuzhong'}, - filterCard:{suit:'heart'}, - check:function(card){ - return 8-get.value(card); - } - }, - dili:{ - trigger:{player:'phaseDrawBegin'}, - forced:true, - filter:function(event,player){ - return player.hp=player.maxHp-1) return [0,0]; - } - } - } - }, - kuangchan:{ - ai:{ - neg:true - }, - init:function(player){ - if(player.isZhu){ - player.maxHp--; - player.update(); - } - } - }, - chujia:{ - enable:'phaseUse', - filterCard:function(card){ - if(ui.selected.cards.length){ - return get.color(card)==get.color(ui.selected.cards[0]); - } - return true; - }, - complexCard:true, - usable:1, - selectCard:2, - check:function(card){ - return 6-get.value(card); - }, - filterTarget:function(card,player,target){ - return target.hptarget.hp){ - target.draw(target.maxHp-target.hp); - } - }, - ai:{ - order:2, - result:{ - target:function(player,target){ - var num=target.maxHp-target.hp; - if(num>2) return num; - return 0; - } - } - } - }, - baihe:{ - enable:'phaseUse', - usable:1, - filterCard:true, - position:'he', - filterTarget:true, - content:function(){ - 'step 0' - if(target.isLinked()){ - target.link(); - } - else{ - target.link(); - target.draw(); - event.finish(); - } - 'step 1' - if(target.countCards('h')){ - target.chooseToDiscard('h',true); - } - }, - check:function(card){ - return 8-get.value(card); - }, - ai:{ - order:1, - result:{ - player:function(player,target){ - if(!player.hasSkill('xiushen')) return 0; - if(target.isLinked()) return 0; - if(game.hasPlayer(function(current){ - return current.isLinked(); - })){ - return 0; - } - return 1; - } - } - } - }, - yinyang:{ - enable:'phaseUse', - usable:1, - filterCard:true, - selectCard:2, - filterTarget:true, - selectTarget:3, - content:function(){ - target.link(); - }, - check:function(card){ - return 6-get.value(card); - }, - ai:{ - order:2, - result:{ - target:function(player,target){ - if(target.isLinked()) return 1; - return -1; - } - } - } - }, - xiushen:{ - trigger:{player:'phaseUseEnd'}, - forced:true, - filter:function(event,player){ - return game.hasPlayer(function(current){ - return current.isLinked(); - }); - }, - content:function(){ - player.draw(2); - }, - ai:{ - threaten:1.6 - } - }, - jiean:{ - trigger:{source:'damageEnd'}, - frequent:true, - filter:function(event){ - if(event._notrigger.contains(event.player)) return false; - return event.player.isAlive()&&event.parent.name=='yanyi'&&event.player.hp0) return -1; - return (_status.event.player.countCards('h')-_status.event.player.hp); - }, - ai2:function(target){ - return get.attitude(_status.event.player,target)-4; - }, - prompt:'请选择要送人的卡牌' - }); - "step 3" - if(result.bool){ - result.targets[0].gain(result.cards,player); - player.$give(result.cards.length,result.targets[0]); - for(var i=0;i0; - }, - content:function(){ - "step 0" - player.chooseControl('heart2','diamond2','club2','spade2').ai=function(event){ - switch(Math.floor(Math.random()*5)){ - case 0:return 'heart2'; - case 1:case 4:return 'diamond2'; - case 2:return 'club2'; - case 3:return 'spade2'; - } - }; - "step 1" - game.log(player,'选择了'+get.translation(result.control)); - event.choice=result.control.slice(0,result.control.length-1); - target.popup(result.control); - target.showHandcards(); - "step 2" - if(target.countCards('h',{suit:event.choice})){ - target.damage(); - } - }, - ai:{ - result:{ - target:function(player,target){ - return get.damageEffect(target,player,target); - } - } - } - }, - wumu:{ - mod:{ - targetInRange:function(card,player){ - if(card.name=='sha'&&get.color(card)=='black') return true; - }, - cardUsable:function(card){ - if(card.name=='sha'&&get.color(card)=='red') return Infinity; - } - }, - trigger:{player:'useCard'}, - filter:function(event,player){ - return event.card.name=='sha'&&get.color(event.card)=='red'; - }, - forced:true, - content:function(){ - if(player.stat[player.stat.length-1].card.sha>0){ - player.stat[player.stat.length-1].card.sha--; - } - }, - }, - ysheshen:{ - inherit:'yiji' - }, - sanbanfu:{ - trigger:{player:'shaBegin'}, - filter:function(event,player){ - if(player.storage.sanbanfu||player.storage.sanbanfu2) return false; - return !event.directHit; - }, - check:function(event,player){ - if(get.attitude(player,event.target)>=0) return false; - if(event.target.getEquip('bagua')) return false; - if(event.target.hasSkillTag('respondShan')&&event.target.countCards('h')>=3) return false; - return true; - }, - logTarget:'target', - content:function(){ - "step 0" - var next=trigger.target.chooseToRespond({name:'shan'}); - next.autochoose=lib.filter.autoRespondShan; - next.ai=function(card){ - return get.unuseful2(card); - }; - player.storage.sanbanfu=false; - player.storage.sanbanfu2=false; - "step 1" - if(result.bool==false){ - trigger.untrigger(); - trigger.directHit=true; - player.storage.sanbanfu2=true; - } - else{ - player.storage.sanbanfu=true; - } - }, - group:['sanbanfu2','sanbanfu3'] - }, - sanbanfu2:{ - trigger:{player:'shaAfter'}, - silent:true, - content:function(){ - if(player.storage.sanbanfu) player.damage(trigger.target); - delete player.storage.sanbanfu; - delete player.storage.sanbanfu2; - } - }, - sanbanfu3:{ - trigger:{source:'damageBegin'}, - silent:true, - filter:function(event,player){ - return event.card&&event.card.name=='sha'&&player.storage.sanbanfu2; - }, - content:function(){ - trigger.num++; - } - }, - bingsheng:{ - enable:'phaseUse', - usable:1, - filterCard:function(card){ - if(ui.selected.cards.length){ - return get.suit(card)!=get.suit(ui.selected.cards[0]); - } - return true; - }, - complexCard:true, - selectCard:2, - check:function(card){ - return 8-get.value(card); - }, - filterTarget:function(card,player,target){ - if(target.hp==Infinity) return false; - if(target.hp>player.hp) return true; - if(target.hp2){ - num=2; - } - if(num<-2){ - num=-2; - } - if(num>0){ - target.damage(num); - } - else if(num<0&&target.hptarget.maxHp){ - num=player.hp-target.maxHp; - } - else{ - num=player.hp-target.hp; - } - if(target.hp==1&&num){ - return num+1; - } - return num; - } - } - } - }, - taolue:{ - mod:{ - maxHandcard:function(player,num){ - return num+1; - } - }, - }, - shentan:{ - enable:'phaseUse', - usable:1, - filterCard:true, - filterTarget:function(card,player,target){ - return target.countCards('h')>0&&get.distance(player,target)<=2; - }, - check:function(card){ - return 7-get.value(card); - }, - position:'he', - content:function(){ - "step 0" - var hs=target.getCards('h'); - if(hs.length){ - event.card=hs.randomGet(); - player.gain(event.card,target); - target.$giveAuto(event.card,player); - } - else{ - event.finish(); - } - "step 1" - var source=target; - player.chooseTarget('选择一个目标送出'+get.translation(event.card),function(card,player,target){ - return target!=player; - }).ai=function(target){ - var att=get.attitude(player,target); - if(att>3&&player.countCards('h')>target.countCards('h')){ - return att; - } - return 0; - } - "step 2" - if(result.bool){ - result.targets[0].gain(card,player); - player.$give(1,result.targets[0]); - player.line(result.targets,'green'); - game.delay(); - } - }, - ai:{ - order:9, - result:{ - target:-1, - player:function(player,target){ - if(get.attitude(player,target)>0){ - return 0; - } - return 1; - } - }, - }, - }, - hanqiang:{ - mod:{ - attackFrom:function(from,to,distance){ - if(!from.getEquip(1)) return distance-1 - } - } - }, - biaoqi:{ - trigger:{player:'shaBegin'}, - forced:true, - content:function(){ - var range=player.getAttackRange(); - if(range>trigger.target.hp){ - trigger.directHit=true; - } - else if(range0; - }, - check:function(event,player){ - return get.attitude(player,event.target)<0; - }, - content:function(){ - 'step 0' - player.judge(function(card){ - return get.color(card)=='black'?1:-1; - }); - 'step 1' - if(result.bool){ - player.gainPlayerCard('he',trigger.target); - } - } - }, - jimin:{ - enable:['chooseToRespond','chooseToUse'], - filterCard:true, - viewAs:{name:'shan'}, - viewAsFilter:function(player){ - if(!player.countCards('h')) return false; - if(player.countCards('e')) return false; - }, - prompt:'将一张手牌当闪使用或打出', - check:function(){return 1}, - ai:{ - respondShan:true, - skillTagFilter:function(player){ - if(!player.countCards('h')) return false; - if(player.countCards('e')) return false; - }, - effect:{ - target:function(card,player,target,current){ - if(get.tag(card,'respondShan')&¤t<0&&!target.countCards('e')) return 0.6 - } - } - } - }, - xiadao:{ - trigger:{source:'damageEnd'}, - direct:true, - filter:function(event,player){ - if(event._notrigger.contains(event.player)) return false; - if(event.player.isDead()) return false; - var nh=event.player.countCards('h'); - if(nh==0) return false; - var players=game.filterPlayer(); - for(var i=0;i0) return 0; - return get.attitude(player,target); - } - 'step 1' - if(result.bool){ - player.logSkill('xiadao'); - player.line2([trigger.player,result.targets[0]],'green'); - event.target=result.targets[0]; - game.delay(); - } - else{ - event.finish(); - } - 'step 0' - if(event.target){ - var card=trigger.player.getCards('h').randomGet(); - event.target.gain(card,trigger.player); - trigger.player.$giveAuto(card,event.target); - } - }, - ai:{ - expose:0.2, - threaten:1.4 - } - }, - lzhangyi:{ - trigger:{player:'discardAfter'}, - filter:function(event,player){ - for(var i=0;i2){ - du=0; - } - } - player.chooseTarget(get.prompt('lzhangyi'),function(card,player,target){ - return player!=target - }).set('du',du).ai=function(target){ - var att=get.attitude(_status.event.player,target); - return att*_status.event.du; - }; - "step 2" - if(result.bool){ - var target=result.targets[0]; - player.logSkill('lzhangyi',target); - var cards=[]; - for(var i=0;i0) return 0; - return 7-get.value(card); - }, - ai2:function(target){ - if(target.isMin()) return 0; - return 6-target.maxHp; - } - }); - } - else{ - event.finish(); - } - "step 1" - if(result.bool){ - player.unmark(player.storage.yizhuang+'_charactermark'); - player.discard(result.cards); - player.logSkill('yizhuang',result.targets); - var name=result.targets[0].name; - if(name.indexOf('unknown')==0){ - name=result.targets[0].name2; - } - var list=[]; - var skills=lib.character[name][3]; - for(var j=0;j0; - }, - content:function(){ - player.unmark(player.storage.yizhuang+'_charactermark'); - player.removeAdditionalSkill('yizhuang'); - delete player.storage.yizhuang; - player.checkMarks(); - } - }, - kongju:{ - mod:{ - maxHandcard:function(player,num){ - if(player.hptarget.maxHp){ - if(card.name=='lebu') return false; - } - } - }, - }, - tuqiang:{ - trigger:{player:['respond','useCard']}, - filter:function(event,player){ - return event.card&&event.card.name=='shan'; - }, - frequent:true, - content:function(){ - player.draw(); - }, - ai:{ - mingzhi:false, - effect:{ - target:function(card,player,target){ - if(get.tag(card,'respondShan')){ - return 0.8; - } - } - }, - } - }, - xumou:{ - inherit:'jushou' - }, - zhensha:{ - trigger:{global:'dying'}, - priority:9, - filter:function(event,player){ - return event.player.hp<=0&&(player.countCards('h','jiu')>0||player.countCards('h',{color:'black'})>=2)&&player!=event.player; - }, - direct:true, - content:function(){ - 'step 0' - var goon=(get.attitude(player,trigger.player)<0); - var next=player.chooseToDiscard('鸠杀:是否弃置一张酒或两张黑色手牌令'+get.translation(trigger.player)+'立即死亡?'); - next.ai=function(card){ - if(ui.selected.cards.length){ - if(ui.selected.cards[0].name=='jiu') return 0; - } - if(goon){ - if(card.name=='jiu') return 2; - return 1; - } - return 0; - }; - next.filterCard=function(card){ - if(ui.selected.cards.length){ - return get.color(card)=='black'; - } - return get.color(card)=='black'||card.name=='jiu'; - }; - next.complexCard=true, - next.logSkill=['zhensha',trigger.player]; - next.selectCard=function(){ - if(ui.selected.cards.length){ - if(get.color(ui.selected.cards[0])!='black') return [1,1]; - } - return [1,2]; - } - 'step 1' - if(result.bool){ - trigger.player.die(); - } - else{ - event.finish(); - } - 'step 2' - if(!trigger.player.isAlive()){ - trigger.cancel(true); - } - }, - ai:{ - threaten:1.5 - } - }, - ducai:{ - enable:'phaseUse', - usable:1, - unique:true, - forceunique:true, - check:function(card){ - if(_status.event.player.countCards('h')>=3){ - return 5-get.value(card); - } - return 0; - }, - position:'he', - filterCard:true, - content:function(){ - player.storage.ducai2=cards[0]; - player.addTempSkill('ducai2',{player:'phaseBegin'}); - }, - ai:{ - order:8, - result:{ - player:1 - } - }, - global:'ducai3' - }, - ducai2:{ - mark:'card', - intro:{ - content:'card' - } - }, - ducai3:{ - mod:{ - cardEnabled:function(card,player){ - if(player.hasSkill('ducai2')) return; - var suit,players=game.filterPlayer(); - for(var i=0;i=3; - }, - filterTarget:function(card,player,target){ - return player.canUse('sha',target); - }, - selectTarget:[1,3], - multitarget:true, - multiline:true, - content:function(){ - player.storage.tongling-=3; - player.unmarkSkill('tongling'); - player.syncStorage('tongling'); - player.useCard({name:'sha'},targets,false); - }, - ai:{ - combo:'tongling', - order:2 - } - }, - fanpu_old:{ - enable:'phaseUse', - usable:1, - filter:function(event,player){ - return player.storage.tongling>=3; - }, - promptfunc:function(){ - return '令自己在本轮内不能成为出杀的目标(选择自己),或对攻击范围内的至多两名角色使用一张杀' - }, - filterTarget:function(card,player,target){ - return player==target||get.distance(player,target,'attack')<=1; - }, - content:function(){ - if(target==player){ - target.addTempSkill('fanpu_disable',{player:'phaseBegin'}); - } - else{ - target.damage(); - } - player.storage.tongling-=3; - player.unmarkSkill('tongling'); - player.syncStorage('tongling'); - }, - subSkill:{ - disable:{ - mark:true, - intro:{ - content:'不能成为杀的目标' - }, - mod:{ - targetEnabled:function(card,player,target,now){ - if(card.name=='sha') return false; - } - } - } - }, - ai:{ - combo:'tongling', - order:2, - result:{ - target:function(player,target){ - if(player==target){ - if(player.hp<=2&&!player.countCards('h','shan')){ - return 2; - } - return 0; - } - else{ - return get.damageEffect(target,player,target); - } - } - } - } - }, - fenghuo:{ - enable:'chooseToUse', - filter:function(event,player){ - return player.countCards('e')>0; - }, - filterCard:true, - position:'e', - viewAs:{name:'nanman'}, - prompt:'将一张装备区内的牌当南蛮入侵使用', - check:function(card){ - var player=_status.currentPhase; - if(player.countCards('he',{subtype:get.subtype(card)})>1){ - return 11-get.equipValue(card); - } - if(player.countCards('h')=2; - }, - content:function(){ - trigger.cancel(); - player.addSkill('nichang2'); - } - }, - nichang2:{ - trigger:{player:'phaseEnd'}, - forced:true, - content:function(){ - "step 0" - if(player.countCards('h')){ - player.showHandcards(); - } - player.removeSkill('nichang2'); - "step 1" - var suits=['spade','heart','diamond','club']; - var cards=player.getCards('h'); - for(var i=0;i1){ - if(game.phaseNumber=3){ - break; - } - } - event.cards=cards; - event.suit=suit; - player.showCards(cards); - } - else{ - event.finish(); - } - "step 2" - if(event.cards&&event.cards.length){ - if(get.suit(event.cards[event.cards.length-1])==event.suit){ - event.cards.pop().discard(); - } - if(event.cards.length){ - player.gain(event.cards,'draw2'); - } - } - }, - ai:{ - maixie:true, - maixie_hp:true, - effect:{ - target:function(card,player,target){ - if(get.tag(card,'damage')){ - if(player.hasSkillTag('jueqing',false,target)) return [1,-2]; - if(!target.hasFriend()) return; - if(target.hp>=4) return [1,2]; - if(target.hp==3) return [1,1.5]; - if(target.hp==2) return [1,0.5]; - } - } - } - } - }, - bolehuiyan:{ - trigger:{global:'shaBegin'}, - direct:true, - priority:11, - filter:function(event,player){ - if(player.hasSkill('bolehuiyan4')) return false; - if(event.target.isUnderControl()) return false; - return event.player!=player&&event.target!=player&&event.target.countCards('h')>0; - }, - group:['bolehuiyan2','bolehuiyan3'], - content:function(){ - "step 0" - if(event.isMine()){ - event.dialog=ui.create.dialog('慧眼:预言'+get.translation(trigger.player)+'对'+get.translation(trigger.target)+'的杀能否命中'); - } - player.chooseControl('能命中','不能命中','cancel').ai=function(event){ - if(trigger.player.hasSkill('wushuang')) return 0; - if(trigger.player.hasSkill('liegong')) return 0; - if(trigger.player.hasSkill('tieji')) return 0; - if(trigger.player.hasSkill('juji')) return 0; - if(trigger.player.hasSkill('retieji')) return 0; - if(trigger.player.hasSkill('roulin')&&trigger.target.sex=='female') return 0; - if(trigger.player.hasSkill('nvquan')&&trigger.target.sex=='male') return 0; - if(trigger.target.hasSkill('yijue2')) return 0; - if(trigger.target.hasSkill('shejie2')) return 0; - if(trigger.target.hasSkill('shanguang2')) return 0; - - var equip=trigger.target.getEquip(2); - if(equip&&equip.name=='bagua') return 1; - return trigger.target.countCards('h')<2?0:1; - }; - "step 1" - if(event.dialog){ - event.dialog.close(); - } - if(result.control!='cancel'){ - player.addTempSkill('bolehuiyan4'); - player.logSkill(['bolehuiyan',result.control],trigger.target); - game.log(player,'预言'+result.control); - player.storage.bolehuiyan=result.control; - game.delay(); - } - }, - ai:{ - threaten:1.3 - } - }, - bolehuiyan2:{ - trigger:{global:'shaEnd'}, - forced:true, - popup:false, - filter:function(event,player){ - return player.storage.bolehuiyan?true:false; - }, - content:function(){ - if(player.storage.bolehuiyan=='不能命中'){ - player.popup('预言成功'); - player.draw(); - } - else{ - player.popup('预言失败'); - player.chooseToDiscard('预言失败,请弃置一张牌','he',true); - } - delete player.storage.bolehuiyan; - } - }, - bolehuiyan3:{ - trigger:{global:'shaDamage'}, - forced:true, - popup:false, - filter:function(event,player){ - return player.storage.bolehuiyan?true:false; - }, - content:function(){ - if(player.storage.bolehuiyan=='能命中'){ - player.popup('预言成功'); - player.draw(); - } - else{ - player.popup('预言失败'); - player.chooseToDiscard('预言失败,请弃置一张牌','he',true); - } - delete player.storage.bolehuiyan; - } - }, - bolehuiyan4:{}, - oldbolehuiyan:{ - trigger:{global:'judgeBegin'}, - direct:true, - priority:11, - filter:function(event,player){ - return event.player!=player; - }, - content:function(){ - "step 0" - if(event.isMine()){ - event.dialog=ui.create.dialog('慧眼:预言'+get.translation(trigger.player)+'的'+trigger.judgestr+'判定'); - } - player.chooseControl('heart2','diamond2','club2','spade2','cancel').ai=function(event){ - switch(Math.floor(Math.random()*4)){ - case 0:return 'heart2'; - case 1:return 'diamond2'; - case 2:return 'club2'; - case 3:return 'spade2'; - } - }; - "step 1" - if(event.dialog){ - event.dialog.close(); - } - if(result.control!='cancel'){ - game.log(player,'预言判定结果为'+get.translation(result.control)); - player.storage.bolehuiyan=result.control.slice(0,result.control.length-1); - player.popup(result.control); - game.delay(); - } - }, - group:'bolehuiyan2' - }, - oldbolehuiyan2:{ - trigger:{global:'judgeEnd'}, - forced:true, - popup:false, - content:function(){ - if(player.storage.bolehuiyan==trigger.result.suit){ - game.log(player,'预言成功'); - player.popup('洗具'); - player.draw(2); - } - else if(get.color({suit:player.storage.bolehuiyan})==trigger.result.color){ - player.popup('洗具'); - player.draw(); - } - delete player.storage.bolehuiyan; - } - }, - xiangma:{ - inherit:'yicong' - }, - weiyi:{ - trigger:{player:'damageEnd'}, - filter:function(event,player){ - return (event.source&&event.source.countCards('he')); - }, - check:function(event,player){ - return get.attitude(player,event.source)<0; - }, - content:function(){ - trigger.source.chooseToDiscard(2,'he',true); - }, - logTarget:'source', - ai:{ - maixie_defend:true, - expose:0.3, - result:{ - target:function(card,player,target){ - if(player.countCards('he')>1&&get.tag(card,'damage')){ - if(player.hasSkillTag('jueqing',false,target)) return [1,-1]; - if(get.attitude(target,player)<0) return [1,0,0,-1.5]; - } - } - } - } - }, - qiandu:{ - enable:'phaseUse', - usable:1, - changeSeat:true, - filterTarget:function(card,player,target){ - return player!=target&&player.next!=target; - }, - filterCard:{color:'black'}, - check:function(card){ - return 4-get.value(card); - }, - content:function(){ - game.swapSeat(player,target); - }, - ai:{ - order:5, - result:{ - player:function(player,target){ - var att=get.attitude(player,target); - if(target==player.previous&&att>0) return att; - if(target==player.next&&att<0) return -att; - var att2=get.attitude(player,player.next); - if(target==player.next.next&&att<0&&att2<0) return -att-att2; - return 0; - } - } - } - }, - nvquan:{ - locked:true, - group:['nvquan1','nvquan2','nvquan3'], - }, - nvquan1:{ - trigger:{player:'shaBegin'}, - forced:true, - filter:function(event){ - return event.target.sex=='male'; - }, - priority:-1, - content:function(){ - if(typeof trigger.shanRequired=='number'){ - trigger.shanRequired++; - } - else{ - trigger.shanRequired=2; - } - } - }, - nvquan2:{ - trigger:{player:'juedou',target:'juedou'}, - forced:true, - filter:function(event,player){ - return event.turn!=player&&event.turn.sex=='male'; - }, - priority:-1, - content:function(){ - "step 0" - var next=trigger.turn.chooseToRespond({name:'sha'},'请打出一张杀响应决斗'); - next.set('prompt2','(共需打出2张杀)'); - next.autochoose=lib.filter.autoRespondSha; - next.ai=function(card){ - if(get.attitude(trigger.turn,player)<0&&trigger.turn.countCards('h','sha')>1){ - return get.unuseful2(card); - } - return -1; - }; - "step 1" - if(result.bool==false){ - trigger.directHit=true; - } - }, - ai:{ - result:{ - target:function(card,player,target){ - if(card.name=='juedou'&&target.countCards('h')>0) return [1,0,0,-1]; - } - } - } - }, - nvquan3:{ - mod:{ - targetEnabled:function(card,player,target){ - if(card.name=='juedou'&&player.sex=='male'){ - return false; - } - } - } - }, - feigong:{ - trigger:{global:'useCard'}, - priority:15, - filter:function(event,player){ - return event.card.name=='sha'&&event.player!=player&& - player.countCards('h','sha')>0&&event.targets.contains(player)==false; - }, - direct:true, - content:function(){ - "step 0" - var effect=0; - for(var i=0;i2){ - eff+=0.5; - } - } - if(get.attitude(player,players[i])>0){ - num+=eff; - } - else if(get.attitude(player,players[i])<0){ - num-=eff; - } - } - return num>0; - }, - content:function(){ - "step 0" - event.targets=game.filterPlayer(); - event.targets.remove(player); - "step 1" - if(event.targets.length){ - event.targets.shift().recover(); - event.redo(); - } - }, - ai:{ - expose:0.1 - } - }, - jieyong:{ - trigger:{player:'useCardAfter'}, - direct:true, - filter:function(event,player){ - if(get.position(event.card)!='d') return false; - if(player.hasSkill('jieyong2')) return false; - return player.countCards('he',{color:'black'})>0; - }, - content:function(){ - "step 0" - var next=player.chooseToDiscard('he','是否弃置一张黑色牌并收回'+get.translation(trigger.card)+'?',{color:'black'}); - next.ai=function(card){ - return get.value(trigger.card)-get.value(card); - } - next.logSkill='jieyong'; - "step 1" - if(result.bool){ - player.gain(trigger.card,'gain2'); - player.addTempSkill('jieyong2',['phaseAfter','phaseBegin']); - } - }, - ai:{ - threaten:1.3 - } - }, - jieyong2:{ - filterCard:{suit:'heart'}, - popname:true, - }, - jieyong3:{ - trigger:{player:'useCardBefore'}, - forced:true, - popup:false, - filter:function(event,player){ - return event.skill=='jieyong2'; - }, - content:function(){ - player.popup(trigger.card.name); - player.getStat('skill').jieyong++; - } - }, - jieyong4:{}, - jieyong5:{}, - jieyong6:{}, - zhulu:{ - trigger:{global:'useCardAfter'}, - direct:true, - filter:function(event,player){ - return _status.currentPhase!=player&&event.player!=player&&get.type(event.card)=='trick'&& - get.position(event.card)=='d'&&!player.hasSkill('zhulu2')&& - get.itemtype(event.card)=='card'&&player.countCards('he',{suit:get.suit(event.card)})>0; - }, - content:function(){ - "step 0" - var val=get.value(trigger.card); - var suit=get.suit(trigger.card); - var next=player.chooseToDiscard('he','逐鹿:是否弃置一张'+get.translation(suit)+ - '牌并获得'+get.translation(trigger.card)+'?',{suit:suit}); - next.ai=function(card){ - return val-get.value(card); - }; - next.logSkill='zhulu'; - "step 1" - if(result.bool){ - player.gain(trigger.card,'gain2'); - player.addTempSkill('zhulu2'); - } - }, - ai:{ - threaten:1.2 - } - }, - zhulu2:{}, - xieling:{ - enable:'phaseUse', - usable:1, - filterCard:true, - selectCard:2, - check:function(card){ - return 7-get.value(card); - }, - multitarget:true, - targetprompt:['被移走','移动目标'], - filterTarget:function(card,player,target){ - if(ui.selected.targets.length){ - var from=ui.selected.targets[0]; - var judges=from.getCards('j'); - for(var i=0;i0; - } - }, - selectTarget:2, - content:function(){ - "step 0" - if(targets.length==2){ - player.choosePlayerCard('ej',function(button){ - if(get.attitude(player,targets[0])>get.attitude(player,targets[1])){ - return get.position(button.link)=='j'?10:0; - } - else{ - if(get.position(button.link)=='j') return -10; - return get.equipValue(button.link); - } - },targets[0]); - } - else{ - event.finish(); - } - "step 1" - if(result.bool){ - if(get.position(result.buttons[0].link)=='e'){ - event.targets[1].equip(result.buttons[0].link); - } - else if(result.buttons[0].link.viewAs){ - event.targets[1].addJudge({name:result.buttons[0].link.viewAs},[result.buttons[0].link]); - } - else{ - event.targets[1].addJudge(result.buttons[0].link); - } - event.targets[0].$give(result.buttons[0].link,event.targets[1]) - game.delay(); - } - }, - ai:{ - order:10, - result:{ - target:function(player,target){ - if(ui.selected.targets.length==0){ - if(target.countCards('j')&&get.attitude(player,target)>0) return 1; - if(get.attitude(player,target)<0){ - var players=game.filterPlayer(); - for(var i=0;i0){ - if((target.getEquip(1)&&!players[i].getEquip(1))|| - (target.getEquip(2)&&!players[i].getEquip(2))|| - (target.getEquip(3)&&!players[i].getEquip(3))|| - (target.getEquip(4)&&!players[i].getEquip(4))|| - (target.getEquip(5)&&!players[i].getEquip(5))) - return -1; - } - } - } - return 0; - } - else{ - return get.attitude(player,ui.selected.targets[0])>0?-1:1; - } - }, - }, - expose:0.2, - threaten:1.5 - } - }, - qiangyun:{ - trigger:{player:'loseEnd'}, - frequent:true, - filter:function(event,player){ - if(player.countCards('h')) return false; - for(var i=0;i0; - }, - content:function(){ - 'step 0' - player.discardPlayerCard(target,true); - 'step 1' - if(result.bool){ - var type=get.type(result.cards[0]); - if(type!='basic'&&type!='trick'){ - player.chooseToDiscard('he',true); - event.finish(); - } - else{ - event.card=result.cards[0]; - } - } - else{ - event.finish(); - } - 'step 2' - var card=event.card; - card={name:card.name,nature:card.nature,suit:card.suit,number:card.number}; - if(lib.filter.cardEnabled(card)){ - if(game.hasPlayer(function(current){ - return player.canUse(card,current); - })){ - lib.skill.miaobix.viewAs=card; - var next=player.chooseToUse(); - next.logSkill='miaobi'; - next.set('openskilldialog','妙笔:将一张手牌当'+get.translation(card)+'使用'); - next.set('norestore',true); - next.set('_backupevent','miaobix'); - next.backup('miaobix'); - } - } - }, - ai:{ - order:9, - result:{ - target:-1 - } - } - }, - zhexian:{ - trigger:{player:'loseEnd'}, - usable:1, - filter:function(event,player){ - return _status.currentPhase!=player; - }, - frequent:true, - content:function(){ - player.draw(); - } - }, - guifu:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return player!=target&&target.countCards('e')>0; - }, - content:function(){ - 'step 0' - player.discardPlayerCard(target,'e',true); - 'step 1' - game.asyncDraw([player,target]); - }, - ai:{ - order:8, - threaten:1.5, - result:{ - target:-1, - player:0.5 - } - } - }, - lshengong:{ - enable:'phaseUse', - usable:1, - filterTarget:function(card,player,target){ - return target.hasCard(function(card){ - return !get.info(card).unique; - },'e'); - }, - check:function(card){ - return 6-get.value(card); - }, - filterCard:function(card){ - var info=lib.card[card.name]; - if(!info) return false; - return !info.image&&!info.fullimage; - }, - discard:false, - lose:false, - content:function(){ - 'step 0' - var next=player.choosePlayerCard(target,'e',true); - next.ai=get.buttonValue; - next.filterButton=function(button){ - return !get.info(button.link).unique; - } - 'step 1' - if(result.links[0]){ - cards[0].init([result.links[0].suit,result.links[0].number,result.links[0].name,result.links[0].nature]); - event.card=cards[0]; - player.chooseTarget('选择一个角色装备'+get.translation(result.links),function(card,player,target){ - return !target.isMin(); - }).ai=function(target){ - if(!target.countCards('e',{subtype:get.subtype(event.card)})){ - return get.attitude(player,target); - } - return 0; - } - } - else{ - event.finish(); - } - 'step 2' - if(result.targets&&result.targets[0]&&event.card){ - player.$give(event.card,result.targets[0]); - game.delay(); - event.toequip=result.targets[0]; - } - else{ - event.finish(); - } - 'step 3' - if(event.toequip){ - event.toequip.equip(event.card); - } - }, - ai:{ - order:9, - threaten:1.5, - result:{ - player:function(player){ - if(player.countCards('e')<3) return 1; - return 0; - } - } - } - } - }, - translate:{ - yxs_guanyu:'关羽', - yxs_wuzetian:'武则天', - yxs_caocao:'曹操', - yxs_mozi:'墨子', - yxs_bole:'伯乐', - yxs_aijiyanhou:'埃及艳后', - yxs_diaochan:'貂蝉', - yxs_yangyuhuan:'杨玉环', - yxs_baosi:'褒姒', - yxs_napolun:'拿破仑', - yxs_kaisa:'凯撒', - yxs_zhuyuanzhang:'朱元璋', - yxs_jinke:'荆轲', - yxs_libai:'李白', - yxs_luban:'鲁班', - yxs_lvzhi:'吕雉', - yxs_goujian:'勾践', - yxs_lishimin:'李世民', - yxs_huamulan:'花木兰', - yxs_luobinhan:'罗宾汉', - yxs_chengjisihan:'成吉思汗', - yxs_mingchenghuanghou:'明成皇后', - yxs_wangzhaojun:'王昭君', - yxs_luocheng:'罗成', - yxs_direnjie:'狄仁杰', - yxs_sunwu:'孙武', - yxs_chengyaojin:'程咬金', - yxs_yujix:'虞姬', - yxs_xiangyu:'项羽', - yxs_yingzheng:'嬴政', - yxs_yuefei:'岳飞', - yxs_fuermosi:'福尔摩斯', - yxs_guiguzi:'鬼谷子', - yxs_xiaoqiao:'小乔', - yxs_luzhishen:'鲁智深', - yxs_handingdun:'汉丁顿伯爵', - yxs_zhaoyong:'赵雍', - yxs_yangguang:'杨广', - yxs_tangbohu:'唐伯虎', - yxs_zhangsanfeng:'张三丰', - yxs_nandinggeer:'南丁格尔', - yxs_weizhongxian:'魏忠贤', - yxs_lanlinwang:'兰陵王', - yxs_meixi:'妹喜', - yxs_qinqiong:"秦琼", - - yxs_fanji:"反击", - yxs_fanji2:"反击", - yxs_fanji_info:"当你受到【杀】或【决斗】造成的伤害后,你可以对伤害来源使用一张【杀】。若此【杀】为红色,其不可闪避", - yxs_menshen:"门神", - yxs_menshen2:"门神", - yxs_menshen3:"门神", - yxs_menshen_info:"回合结束阶段,你可选择一名其他角色,若如此做,直到你的下回合开始,所有角色对该角色使用的【杀】或【决斗】均视为对你使用", - zhuxin:'诛心', - zhuxin_info:'出牌阶段限一次,你可以与一名其他角色拼点,若你赢,你对其造成一点伤害', - wlianhuan:'连环', - wlianhuan_info:'你使用杀造成伤害时,可以弃置一张装备区内的牌并令伤害+1', - liebo:'裂帛', - liebo_info:'出牌阶段限一次,你可以将你的手牌与一名其他角色交换(手牌数之差不能多于1)', - yaoji:'妖姬', - yaoji_info:'每当你受到一次伤害,你可以将一张乐不思蜀置入伤害来源的判定区', - guimian:'鬼面', - guimian_info:'锁定技,每当你在出牌阶段使用杀造成伤害,本阶段内出杀次数上限+1', - lyuxue:'浴血', - lyuxue2:'浴血', - lyuxue_info:'锁定技,每当你造成一次伤害,若目标没有浴血标记,你令其获得一个浴血标记;当一名角色失去浴血标记时,其流失一点体力;准备阶段,若场上浴血标记的数量不少于存活角色数的一半(向下取整),你清空浴血标记;当你即将死亡时,你清空浴血标记', - huli:'护理', - huli_info:'出牌阶段,你可以将一张红桃手牌当作桃对距离1以内的角色使用', - yixin:'医心', - yixin_info:'限定技,你可以弃置两张牌,然后令一名已受伤角色回复X点体力并摸4-X张牌(X为该角色已损失的体力值且不超过4)', - xianqu:'先驱', - xianqu_info:'锁定技,你不能成为点数小于8的杀的目标', - zbudao:'布道', - zbudao_info:'摸牌阶段,你可以额外摸一张牌,然后将摸到的牌中的一张交给一名其他角色', - taiji:'太极', - taiji_info:'每当你使用或打出一张闪,你可以使用一张杀', - luobi:'落笔', - luobi_info:'结束阶段,可以摸数量等同于已损失体力值的牌,并以任意方式分配给任意角色', - fengliu:'风流', - fengliu_info:'锁定技,摸牌阶段,你额外摸X张牌,X为存活女性角色数且不超过2', - shiqin:'弑亲', - shiqin_info:'锁定技,其他群势力角色濒死时,你令其立即死亡', - yjujian:'拒谏', - yjujian_info:'出牌阶段限一次,你可以交给一名其他角色一张牌,该角色的锦囊牌不能指定你为目标直到你的下一回合开始', - yaoyi:'徭役', - yaoyi_info:'每当你受到一次伤害,你可以令至多2名非群势力角色交给你一张手牌', - zyhufu:'胡服', - zyhufu_info:'锁定技,当你的装备区内没有防具牌时,你摸牌阶段额外摸一张牌;当你装备区内有防具牌时,你的手牌上限+5', - hanbei:'捍北', - hanbei_info:'锁定技,你的装备区有马时,你的杀不可闪避', - kuangchan:'狂禅', - kuangchan_info:'锁定技,你做主公时,不增加体力上限', - dili:'底力', - // dili_info:'锁定技,摸牌阶段,你额外摸X张牌,X为你已损失的体力值', - dili_info:'锁定技,摸牌阶段,你额外摸X张牌,X为你已损失的体力值的一半,向上取整且最多为2', - chujia:'初嫁', - chujia_info:'出牌阶段限一次,你可以弃置两张相同颜色的手牌,指定任意一名角色摸X张牌。(X为该角色已损失的体力值) ', - zhijie:'知节', - zhijie_info:'出牌阶段限一次,你的红桃手牌可以当做无中生有使用', - baihe:'捭阖', - baihe_info:'出牌阶段限一次,你可以弃置一张牌,选择以下1项执行:(1)横置1名未横置角色,该角色摸一张牌;(2)重置一名已横置角色,该角色弃置一张手牌', - yinyang:'阴阳', - yinyang_info:'出牌阶段限一次,你可以弃置两张手牌并选择3名角色,分别横置或重置这些角色', - xiushen:'修身', - // xiushen_info:'锁定技,结束阶段,若场上有横置角色,你摸两张牌', - xiushen_info:'锁定技,出牌阶段结束时,若场上有横置角色,你摸两张牌', - yanyi:'演绎', - yanyi_info:'出牌阶段限一次,你可以弃置一张黑色牌,指定1名角色和1种花色,若被指定角色的手牌中含有此花色,则受到1点伤害', - jiean:'结案', - jiean_info:'每当【演绎】造成伤害时,你可以摸X张牌,并以任意数量分配给任意角色(X为被【演绎】造成伤害角色的已损失体力值)。', - wumu:'武穆', - wumu_info:'锁定技,你的黑杀无视距离,红色不计入回合内的出杀限制', - ysheshen:'舍身', - ysheshen_info:'每当你受到一点伤害,可以观看牌堆顶的两张牌,并将其交给任意1~2名角色', - sanbanfu:'三板斧', - sanbanfu_info:'当你对其他角色使用杀时,你可以使此杀有如下效果:若对方没有出闪,其受到2点伤害;若对方打出了一张闪,你与其各受到1点伤害;若对方打出了两张闪,你受到一点伤害', - bingsheng:'兵圣', - bingsheng_info:'出牌阶段限一次,你可以弃置两张花色不同的手牌,指定一名其他角色使其体力值与你相同(体力最多变化2点)', - taolue:'韬略', - taolue_info:'锁定技,你的手牌上限+1', - shentan:'神探', - shentan_info:'出牌阶段限一次,你可以弃置一张牌,获得距离2以内的一名角色的手牌,并可以将其交给任意一名角色', - hanqiang:'寒枪', - hanqiang_info:'锁定技,当你没装备武器时,攻击范围+1', - biaoqi:'骠骑', - biaoqi_info:'锁定技,当你出杀指定目标后,若你的攻击范围大于目标体力值,则此杀不可闪避;若你的攻击范围小于目标体力值,你摸一张牌', - wluoyan:'落雁', - wluoyan_info:'锁定技,你防止即将受到的伤害,改为流失一点体力', - heqin:'和亲', - heqin2:'和亲', - heqin3:'和亲', - heqin_info:'限定技,你可以与场上一名男性角色形成【和亲】状态,你与该男性角色于摸牌阶段摸牌数+1。你或者男性角色阵亡时,【和亲】状态消失', - chajue:'察觉', - chajue2:'察觉', - chajue_info:'锁定技,你的回合外,你每受到一次伤害,任何【杀】或普通锦囊牌均对你无效,直到你的回合开始', - tiewan:'铁腕', - tiewan_info:'每当其他角色使用延时类锦囊牌时,你可以立即将一张红色牌当作乐不思蜀使用', - qianglue:'强掠', - qianglue_info:'每当你的杀被闪避时,你可以进行一次判定,若结果为黑色,你可以获得对方的一张牌', - xiadao:'侠盗', - xiadao_info:'每当你造成一次伤害,你可以令一名手牌数不少于受伤害角色的另一名角色获得其一张手牌', - jimin:'机敏', - jimin_info:'当你的装备区内没有牌时,你可以将一张手牌当作闪使用或打出', - sheshu:'射术', - sheshu_info:'锁定技,你的杀无视距离;体力值不小于3的角色不能闪避你的杀', - tongyu:'统御', - tongyu_info:'出牌阶段,你可以弃置一张牌,并转变为罗宾汉(每回合只能转变一次)', - lguiyin:'归隐', - lguiyin_info:'出牌阶段,若你本回合内未造成伤害,你可以摸一张牌,并转变为汉丁顿伯爵(每回合只能转变一次)', - lzhangyi:'仗义', - lzhangyi_info:'你可以将你弃置的卡牌交给一名其他角色', - yizhuang:'易装', - yizhuang2:'易装', - yizhuang_info:'准备阶段,你可以弃置一张牌并选择一名男性角色,获得其所有技能,直到你首次受到伤害', - kongju:'控局', - kongju_info:'锁定技,你的手牌上限为你的体力上限;当你的手牌数小于体力上限时,你不能成为过河拆桥或顺手牵羊的目标;当你的手牌数大于体力上限时,你不能成为乐不思蜀的目标', - tuqiang:'图强', - tuqiang_info:'每当你使用或打出一张闪,你可以摸一张牌', - zhensha:'鸩杀', - zhensha_info:'当场上有角色进入濒死状态时,你可以弃置一张酒或两张黑色手牌,则该角色立即死亡。', - xumou:'蓄谋', - xumou_info:'结束阶段,你可以将武将牌翻面并摸3张牌', - guifu:'鬼斧', - guifu_info:'出牌阶段限一次,你可以指定一名角色装备区内的一张牌,将其弃掉,自己和对方同时摸取一张牌', - lshengong:'神工', - lshengong_info:'出牌阶段限一次,你可以选定场上任意一名角色的装备区的非特殊牌,出自己的一张手牌复制该装备,然后可以选择装备上自己或者别的角色的装备区', - zhexian:'谪仙', - zhexian_info:'当你于一名其他角色的回合内首次失去牌时,你可以摸一张牌', - miaobi:'妙笔', - miaobi_info:'出牌阶段限一次,你可以弃置一名其他角色的一张牌,若此牌是基本牌或普通锦囊,你可以将一张手牌当此牌使用;否则你须弃置一张牌', - cike:'刺客', - cike_info:'你对别的角色出【杀】时可以选择做一次判定:若判定牌为红色花色,则此【杀】不可回避,直接命中;若判定牌为黑色花色,你可以选择弃掉对方一张牌。', - qiangyun:'强运', - qiangyun_info:'每当你失去最后一张手牌,可摸两张牌', - ducai:'独裁', - ducai2:'独裁', - ducai3:'独裁', - ducai_info:'出牌阶段限一次,你可以弃置一张牌,则本轮内除你外的角色不能使用或打出与该手牌花色相同的手牌', - tongling:'统领', - tongling_info:'锁定技,每当一名友方角色造成一次伤害,你获得1个统领标记(标记上限为3)', - fanpu:'反扑', - fanpu_info:'出牌阶段限一次,你可以移去3枚统领标记并视为对攻击范围内的至多3名角色使用一张杀', - fenghuo:'烽火', - fenghuo_info:'你可以将一张装备区内的牌当作南蛮入侵使用', - weiyi:'威仪', - weiyi_info:'每当你受到一次伤害,可以令伤害来源弃置两张牌', - xieling:'挟令', - xieling_info:'出牌阶段,弃掉两张手牌,将任意一名角色装备区或判定区的牌移动到另一名角色对应的区域', - baye:'霸业', - baye_info:'出牌阶段,你可以将一张牌当做本回合内前一张使用的牌来使用。每回合限用一次。', - nvquan:'女权', - nvquan1:'女权', - nvquan2:'女权', - nvquan_info:'你对男性角色使用【杀】或【决斗】时,对方需连续打出两张【闪】或【杀】响应;你不能成为男性角色的决斗目标', - qiandu:'迁都', - qiandu_info:'出牌阶段,你可以弃一张黑色手牌,和一名存活的玩家与其交换位置。每回合限一次。', - budao:'补刀', - budao_info:'你的回合外,你的攻击范围的一名角色受到【杀】的伤害时,你可以对其使用一张【杀】,只要你的【杀】对目标角色造成了伤害,你就可以继续对其使用【杀】。', - feigong:'非攻', - feigong_info:'其他角色使用杀时,若你不是杀的目标,可以弃置一张杀取消之', - jianai:'兼爱', - jianai_info:'每当你回复一点体力,可以令所有其他角色回复一点体力', - bolehuiyan:'慧眼', - bolehuiyan_info:'当一名有手牌的其他角色成为来源不为你的杀的目标时,你可以预言此杀能否命中,若预言正确,你摸一张牌,否则你须弃置一张牌。每回合限发动一次', - xiangma:'相马', - xiangma_info:'锁定技,只要你的体力值大于2点,你的进攻距离+1;只要你的体力值为2点或更低,你的防御距离+1', - seyou:'色诱', - seyou_info:'限定技,出牌阶段,你可以指定任意1名角色,其他所有男性角色需选择1项执行:(1)对你指定的角色出【杀】;(2)令你获得其一张牌。', - sheshi:'蛇噬', - sheshi_info:'每受到1次伤害,可以指定1种花色,依次展示牌堆顶的牌,直到出现指定花色的牌为止,你获得与指定花色不同花色的所有牌(最多展示4张牌)。', - - - fengyi:'凤仪', - fengyi_info:'出牌阶段,你可以弃一张手牌,指定任意目标摸两张牌。(每回合限用一次)', - wange:'婉歌', - wange_info:'摸牌时,你可以少摸一张牌,则结束阶段你可以抽取一名其他角色的手牌,至少1张,至多X张(X为你当前的掉血量)。', - nichang:'霓裳', - nichang2:'霓裳', - nichang_info:'摸牌时,你可以选择不摸牌,并在结束阶段展示手牌,每少一种花色摸一张牌', - fengyan:'丰艳', - fengyan_info:'你可以获得其他男性角色的红色判定牌。', - zhulu:'逐鹿', - zhulu_info:'回合外,当有普通锦囊牌结算完毕后,你可以立即弃掉一张相同花色手牌或装备区的牌,获得这张锦囊牌。', - jieyong:'节用', - jieyong2:'节用', - jieyong_info:'你使用的卡牌进入弃牌堆后,你可以弃置一张黑色牌并重新获得之(每回合限一次)', - shangtong:'尚同', - shangtong_info:'每当你令其他角色恢复1点血量或掉1点血量时,你可以摸1张牌(摸牌上限为4)', - feiming:'非命', - feiming_info:'其他角色对你造成伤害时,你可以令该角色须选择1项执行:1,将1张红桃花色手牌交给你;2,流失1点血量', - yxsrenwang:'人望', - yxsrenwang_info:'出牌阶段,你可以弃掉2张牌并指定一名手牌数大于你的角色,你摸牌至与该角色手牌数相等,每阶段限一次。', - shiwei:'施威', - shiwei_info:'当其他角色失去最后一张手牌时,你可以将牌堆顶的一张牌背面朝上置于该角色面前,该角色回合,跳过出牌阶段并弃掉这张牌。', - yxswushuang:'无双', - yxswushuang_info:'出牌阶段,你使用【杀】时可同时打出两张【杀】,则该【杀】具有以下效果之一:1,伤害+1;2,额外指定两个目标', - xiaoyong:'骁勇', - xiaoyong_info:'你可以将黑色手牌当作【杀】来使用', - qinzheng:'亲征', - qinzheng_info:'出牌阶段,你对其他角色造成伤害时,可以令场上任意角色摸一张牌。', - juma:'拒马', - juma_info:'你与其他角色的距离始终视为1。', - }, - }; -}); +'use strict'; +game.import('character',function(lib,game,ui,get,ai,_status){ + return { + name:'yxs', + character:{ + yxs_qinqiong:["male","wei",4,["yxs_fanji","yxs_menshen"],[]], + yxs_wuzetian:['female','wu',4,['nvquan','qiandu','weiyi']], + yxs_caocao:['male','wei',4,['zhulu','xieling']], + yxs_mozi:['male','qun',3,['jieyong','feigong','jianai']], + yxs_bole:['male','wu',3,['bolehuiyan','xiangma']], + yxs_aijiyanhou:['female','qun',3,['seyou','sheshi']], + yxs_diaochan:['female','qun',3,['fengyi','wange']], + yxs_yangyuhuan:['female','wu',3,['fengyan','nichang']], + yxs_baosi:['female','wu',3,['jieyin','fenghuo']], + yxs_napolun:['male','wei',4,['tongling','fanpu']], + yxs_kaisa:['male','shu',4,['ducai']], + yxs_zhuyuanzhang:['male','wu',4,['qiangyun']], + // yxs_jinke:['male','qun',3,['cike','qiangxi']], + yxs_libai:['male','qun',3,['miaobi','zhexian']], + yxs_luban:['male','wu',3,['guifu','lshengong']], + yxs_lvzhi:['female','shu',4,['zhensha','xumou']], + yxs_goujian:['male','wu',3,['keji','tuqiang']], + yxs_lishimin:['male','qun',4,['kongju']], + yxs_huamulan:['female','shu',3,['xiaoji','yizhuang']], + yxs_luobinhan:['male','wu',4,['xiadao','sheshu','lguiyin']], + yxs_chengjisihan:['male','qun',4,['mashu','qianglue']], + yxs_mingchenghuanghou:['female','shu',3,['tiewan','chajue']], + yxs_wangzhaojun:['female','wei',3,['heqin','wluoyan']], + yxs_luocheng:['male','wu',4,['hanqiang','biaoqi']], + yxs_direnjie:['male','wei',3,['shentan','kanpo']], + yxs_sunwu:['male','wu',3,['bingsheng','taolue']], + yxs_chengyaojin:['male','shu',4,['sanbanfu']], + yxs_yujix:['female','shu',3,['ysheshen','changnian']], + yxs_xiangyu:['male','shu',4,['wushuang','ciqiu']], + yxs_yingzheng:['male','qun',4,['jianxiong','batu']], + yxs_yuefei:['male','qun',4,['longdan','wumu']], + yxs_fuermosi:['male','wei',3,['yanyi','jiean']], + yxs_guiguzi:['male','qun',3,['baihe','yinyang','xiushen']], + yxs_xiaoqiao:['female','wu',3,['chujia','zhijie']], + yxs_luzhishen:['male','wei',4,['dili','kuangchan']], + yxs_zhaoyong:['male','shu',3,['zyhufu','hanbei']], + yxs_yangguang:['male','qun',3,['shiqin','yaoyi']], + yxs_tangbohu:['male','qun',3,['luobi','fengliu']], + yxs_zhangsanfeng:['male','wei',4,['zbudao','taiji']], + yxs_nandinggeer:['female','shu',3,['huli','xianqu','yixin']], + yxs_weizhongxian:['male','qun',3,['zhuxin','wlianhuan']], + yxs_meixi:['female','shu',3,['liebo','yaoji']], + yxs_lanlinwang:['male','shu',4,['guimian','lyuxue']], + }, + characterIntro:{ + yxs_qinqiong:'秦琼(?—638年),字叔宝,齐州历城(今山东济南市)人,隋末唐初名将。初为隋将,先后在来护儿、张须陀、裴仁基帐下任职,因勇武过人而远近闻名。后随裴仁基投奔瓦岗军领袖李密,瓦岗败亡后转投王世充,因见王世充为人奸诈,与程咬金等人一起投奔李唐。投唐后随李世民南征北战,是一个能在万马军中取敌将首级的勇将,但也因此浑身是伤。唐统一后,秦琼久病缠身,于贞观十二年(638)病逝。生前官至左武卫大将军、翼国公,死后追赠为徐州都督、胡国公,谥曰“壮”。贞观十七年被列入凌烟阁二十四功臣。', + yxs_wuzetian:'中国历史上唯一一个正统的女皇帝,也是继位年龄最大的皇帝(67岁即位),又是寿命最长的皇帝之一(终年82岁)。唐高宗时为皇后(655—683)、唐中宗和唐睿宗时为皇太后(683—690),后自立为武周皇帝(690—705),改国号“唐”为“周”,定都洛阳,并号其为“神都”。史称“武周”或“南周”,705年退位。武则天也是一位女诗人和政治家。', + yxs_caocao:' 曹操(155年7月18日-220年3月15日),字孟德,一名吉利,小字阿瞒,汉族,沛国谯(今安徽省亳州市)人。曹操生于宦官之家,适逢乱世,但是胸怀大志,参与剿灭董卓战争,之后在官渡大败袁绍,占据北方,挟天子以令诸侯。最后兵败赤壁,与吴,蜀三分天下。', + yxs_mozi:' 宋国大夫,名翟,鲁人(今山东滕州人)。墨子是我国战国时期著名的思想家、教育家、科学家、军事家、社会活动家,墨家学派的创始人。墨子曾阻止鲁阳文君攻郑,说服公输般而止楚攻宋。楚惠王打算以书社封墨子,越王也打算以吴之地方五百里以封墨子,但墨子都没有接受。其创立墨家学说,并有《墨子》一书传世。', + yxs_bole:'伯乐,名孙阳,字子良,一作王良。春秋齐(今山东省威武)人。善于相马,为赵简子御。相传天上御者名伯乐,因其善相,遂号之,传至今。初,见老骥 拖车,喘息不定,伯乐哀之,马亦哀啼,方知乃良驹。后世长以伯乐比喻慧眼识人者。', + yxs_aijiyanhou:'埃及艳后即克丽奥佩托拉七世,是古埃及托勒密王朝的最后一任法老。她通过政治联姻为古埃及赢取了22年的和平。埃及艳后的一生富有戏剧性,特别是卷入罗马共和末期的政治漩涡,同恺撒、安东尼关系密切,并伴以种种传闻逸事,使她成为文学和艺术作品中的著名人物。', + yxs_diaochan:'中国古代四大美女之一,今山西忻州人,有野史说其姓霍,无名,又有一说称其任姓,小字红昌。貂蝉是东汉末年司徒王允的义女,国色天香,有倾国倾城之貌,相传貂婵在后花园拜月时,忽然轻风吹来,一块浮云将那皎洁的明月遮住。这时正好王允瞧见,便说我的女儿和月亮比美,月亮比不过,赶紧躲在云彩后面。此后,世人常用“闭月”来形容貂婵的美貌。', + yxs_yangyuhuan:'唐朝贵妃,名玉环,字太真,蒲州永乐人(今山西永济)。杨玉环自小习音律,善歌舞,姿色超群。27岁时,得唐玄宗宠幸,召入宫中,封为贵妃。杨贵妃天生丽质,回眸一笑百媚生,六宫粉黛无颜色,堪称大唐第一美女,此后千余年无出其右者。其与西施、昭君、貂蝉并称中国古代四大美女。', + yxs_baosi:'褒姒,周幽王姬宫涅的王后,褒姒原是一名弃婴,被一对做小买卖的夫妻收养,在褒国(今陕西省汉中西北)长大,公元前七七九年(周幽王三年),周幽王征伐有褒国,褒人献出美女褒姒乞降,幽王爱如掌上明珠,立为妃,宠冠周王宫,翌年,褒姒生子伯服(一作伯般),幽王对她更加宠爱,竟废去王后申氏和太子宜臼,册立褒姒为王后,立伯服为太子,周太史伯阳叹气道:“周王室已面临大祸,这是不可避免的了。”', + yxs_napolun:'法兰西第一共和国执政、法兰西第一帝国皇帝,出生在法国科西嘉岛,是一位卓越的军事天才。他多次击败保王党的反扑和反法同盟的入侵,捍卫了法国大革命的成果。他颁布的《民法典》更是成为了后世资本主义国家的立法蓝本。他执政期间多次对外扩张,形成了庞大的帝国体系,创造了一系列军事奇迹。', + kaisa:'凯撒是罗马共和国末期杰出的军事统帅、政治家。他公元前60年与庞培、克拉苏秘密结成前三巨头同盟,随后出任高卢总督,在大约8年的时间内征服了高卢全境(今法国一带),还袭击了日耳曼和不列颠。前49年,他率军占领罗马,打败庞培,集大权于一身,实行独裁统治并制定了《儒略历》。', + yxs_zhuyuanzhang:' 朱元璋,明王朝的开国皇帝。原名重八,后取名兴宗。汉族,濠州(今安徽凤阳县东)钟离太平乡人。朱元璋自幼贫寒,父母兄长均死于瘟疫,孤苦无依,入皇觉寺为小沙弥,入寺不到二个月,因荒年寺租难收,寺主封仓遣散众僧,只得离乡为游方僧,后参加了起义军,并改名“朱元璋”意为诛(朱)灭元朝的璋(璋,古代的一种玉器)。25岁时参加郭子兴领导的红巾军反抗蒙元暴政,在郭子兴手下,率兵出征,有攻必克;因此郭便把养女马氏嫁与了他。元至正二十八年(1368),在基本击破各路农民起义军和扫平元的残余势力后,于南京称帝,国号大明,年号洪武,建立了全国统一的封建政权。朱元璋统治时期被称为“洪武之治”。葬于明孝陵。', + yxs_jinke:'荆轲,喜好读书击剑,为人慷慨侠义。后游历到燕国,被称为“荆卿”(或荆叔),随之由燕国智勇深沉的“节侠”田光推荐给太子丹,拜为上卿。秦国灭赵后,兵锋直指燕国南界,太子丹震惧,与田光密谋,决定派荆轲入秦行刺秦王。荆轲献计太子丹,拟以秦国叛将樊于期之头及燕督亢(今河北涿县、易县、固安一带,是一块肥沃的土地)地图进献秦王,相机行刺。太子丹不忍杀樊于期,荆轲只好私见樊于期,告以实情,樊于期为成全荆轲而自刎。', + yxs_libai:'字太白,号青莲居士,又号“谪仙人”,祖籍陇西郡成纪县(今甘肃省平凉市静宁县南)。李白是唐朝著名的浪漫主义诗人,有“诗仙”之称。李白生平作诗无数,存世诗文达千余篇之多,《蜀道难》、《行路难》、《梦游天姥吟留别》、《将进酒》等诗篇脍炙人口,妇孺皆知,另有《李太白集》传世。', + yxs_luban:' 鲁班,姓公输,名般。战国时期鲁国公族之后,故又称公输子、班输等。出身于工匠世家,是我国古代最著名的发明家、建筑家。鲁班一生发明无数,而最具贡献意义的则要数木工使用的工具,诸如墨斗、锯、和鲁班尺等。为后世的建筑学提供了最基础的工具。除此之外,相传石磨、云梯等工具也是鲁班发明。', + yxs_lvzhi:' 吕雉,西汉开国皇帝高祖刘邦的原配夫人,中国历史上第一位掌权的女性统治者,是历史上有记载以来的第一位皇后、皇太后。于高祖刘邦死后掌握政权,实行高祖的“黄老政治”,百姓安乐民富国强,为“文景之治”奠定了坚实的基础。', + yxs_goujian:'勾践,又写作句践,在出土文物“越王勾践剑”里写为鸠浅,司马贞《史记索隐》引《纪年》作菼执。是中国春秋时代后期的越国君主。有关他的先世,有说“其先禹之苗裔”,亦有说“先世无所考”,也有说他是“祝融之后”并且是楚国的芈姓,众说纷纭。父亲则是越侯允常。', + yxs_lishimin:' 李世民,唐朝第二位皇帝。他的前半生是立下赫赫武功的军事家。平窦建德、王世充之后,始大量接触文学与书法,有《温泉铭》、《晋祠铭》等墨宝传世。后在玄武门之变杀死自己的兄弟李建成、李元吉两人,成为太子,唐高祖不久被迫让位。世民即位为帝后,积极听取群臣的意见、努力学习文治天下,成功转型为中国史上最出名的政治家与明君之一。唐太宗开创了历史上的“贞观之治”,经过主动消灭群雄割据势力,虚心纳谏、在国内厉行节约、使百姓休养生息,终于使得社会出现了国泰民安的局面。此举为后来的开元盛世奠定了重要的基础,将中国传统农业社会推向一个高峰。', + yxs_huamulan:' 花木兰是中国文学作品中的一位代父从军的巾帼英雄,其真实性不详。花木兰最早出现于南北朝一首叙事诗《木兰辞》中,该诗约作于北魏,最初录于南朝陈的《古今乐录》,僧人智匠在《古今乐录》称:“木兰不知名。”', + yxs_luobinhan:'罗宾汉是英国民间传说中的侠盗式的一个英雄人物,人称汉丁顿伯爵。他武艺出众、机智勇敢、聪明,仇视官吏和教士,是一位劫富济贫、行侠仗义的绿林英雄。传说他住在诺丁汉雪伍德森林。从14世纪中叶起,关于罗宾汉的民谣和传说就开始在民间流传。罗宾汉最突出的就是射箭术高超。现代射箭比赛里就有“罗宾汉”这一术语,指射中另一支已中靶心的箭。', + yxs_chengjisihan:'成吉思汗,名铁木真,孛儿只斤氏,奇渥温姓,乞颜(起延)部人。从小遭受结拜兄弟札木合迫害,形成刚毅坚韧的性格。1206年,被推举为蒙古帝国的大汗,统一蒙古各部,为之后的进攻中原提供了坚实的基础。', + yxs_mingchenghuanghou:'明成皇后,朝鲜近代史上的女政治家,本名闵兹映,通称闵妃,是朝鲜京畿道骊州郡人。她是朝鲜王朝高宗李熙的王妃,骊兴闵氏外戚集团的核心人物,19世纪末朝鲜的实际统治者。由于闵妃早期主张开放、后期力抗日本并身死殉难,故深受后世韩国人民的尊崇。 1897年,高宗李熙改国号称“大韩帝国”,追谥闵妃为“孝慈元圣正化合天明成皇后”,故现今韩国史学家多称她为“明成皇后”。', + yxs_wangzhaojun:'王昭君,名嫱,字昭君,晋朝时为避司马昭讳,又称“明妃”,汉元帝时期宫女,汉族,西汉南郡秭归(今湖北省兴山县)人。匈奴呼韩邪单于阏氏。 “昭君出塞”是汉匈交往上的大事,稳定了汉朝和匈奴的外交关系,《汉书.匈奴传》和《后汉书.南匈奴传》都记载了这件事。相传和亲途中,南飞的大雁听到昭君奏起悲壮的离别之曲,看到骑在马上的这位美丽女子,忘记摆动翅膀,跌落地下,因此得“落雁”之名。昭君出塞的故事也被后世传为佳话。', + yxs_luocheng:'《隋唐》说书和《说唐传》中的虚构人物,隋唐十八杰中列第七,十六杰列第八。在清初禇人获的讲史小说《隋唐演义》中,也虚构了罗成,是燕山罗艺的儿子,秦琼的表弟,精通枪法。', + yxs_direnjie:'唐武周时期杰出的著名政治家,时任豫州刺史、魏州刺史等要职,官至凤阁鸾台平章事、内史,卒后追封梁国公。狄仁杰生于贞观、卒于武周时期,经历了大唐鼎盛和动乱的年代。其一生秉承了以民为本、不畏权贵、为民请命的宗旨。狄仁杰通晓了吏治、兵刑等法律制度,在任大理丞任期内解决了诸多案件,被誉为“神探”。狄仁杰为官清廉,素有政绩,有辅国安邦之能,史称“唐室砥柱”。', + yxs_sunwu:'著名军事家,字长卿,中国春秋时期齐国乐安人。曾率领吴国军队大破数倍于己的楚国军队,占领了楚国都城郢城,几乎亡楚。其著有巨作《孙子兵法》十三篇,为后世兵法家所推崇,被誉为“兵学圣典”,置于《武经七书》之首,被译为英文、法文、德文、日文,成为国际间最著名的兵学典范之书。后人尊称其为孙子、孙武子、兵圣、百世兵家之师、东方兵学的鼻祖。', + yxs_chengyaojin:'程咬金,原名咬金,后更名知节,字义贞,中国济州东阿斑鸠店人(现山东省东平县斑鸠店)。“凌烟阁二十四功臣”之一,唐朝开国名将。隋朝末年,隋炀帝杨广统治残暴,骄奢荒淫,民不聊生,最终爆发了大规模的农民起义。程咬金先入瓦岗军,投王世充,后降唐,成为秦王李世民的骨干成员。据史书记载,程咬金“少骁勇,善用马槊。”而在以《说唐》为代表的系列话本及历史演义小说中,程咬金则使得一柄八卦宣花斧,以“三板斧”武艺著称,是一名性格直爽、粗中有细的福将。', + yxs_yujix:'虞姬,又称虞美人,西楚霸王项羽爱姬,相传为江苏沭阳县颜集乡人,一说苏州常熟人。公元前209年,项羽与叔父项梁起义反秦。项羽军中战将虞子期的妹妹虞姬,貌美好武,倾慕年轻勇猛的项羽,嫁其为妾,常伴左右随军出征,至终形影不离。 公元前202年,项羽在垓下之战中被刘邦、韩信、彭越三方大军合围困于垓下(今安徽灵璧县城南沱河北岸城后村),身陷十面埋伏,兵孤粮缺,夜闻四面楚歌,楚军士气尽失。项羽认为大势已去,帐中酌酒,对着虞姬唱起悲壮的“垓下歌”。虞姬拔剑起舞,含泪唱和:“汉兵已略地,四面楚歌声。大王义气尽,贱妾何聊生。”为免后顾之忧影响项羽突围,唱毕于其面前自刎。', + yxs_xiangyu:'项籍(前232—前202)字羽,通常被称作项羽,中国古代著名将领及政治人物,汉族,秦下相(今江苏省宿迁市宿城区)人。秦末时被楚怀王熊心封为鲁公,在前207年的决定性战役巨鹿之战中统率楚军大破秦军。秦亡后自封“西楚霸王”,统治黄河及长江下游的梁楚九郡。后在楚汉战争中为汉高祖刘邦所败,在乌江(今安徽和县)自刎而死。', + yxs_yingzheng:'秦始皇,赢姓,赵氏,名政,秦庄襄王之子。秦始皇22岁时,在雍城举行国君成人加冕仪式,开始“亲理朝政”。后除掉吕不韦,嫪毐等人,重用李斯,尉缭。自公元前230年至前221年,采取由近及远,集中力量,各个击破的策略,先后灭六国,完成统一中国的大业。同时建立起历史上第一个书同文,度同制,车同轨,行同伦的中央集权国家——秦朝。', + yxs_yuefei:' 岳飞(1103年-1142年),字鹏举,相州汤阴(今属河南)人。南宋军事家,中国历史上著名的抗金名将。绍兴十一年(1142)十二月二十九日,秦桧以“莫须有”的罪名将岳飞毒死于临安风波亭。1162年,宋孝宗时诏复官,谥武穆,宁宗时追封为鄂王,改谥忠武,有《岳武穆集》传世。', + yxs_fuermosi:'福尔摩斯,是一个虚构的侦探人物,是由19世纪末20世纪初的英国侦探小说家阿瑟?柯南·道尔所塑造的一个才华横溢的侦探形象。福尔摩斯不但头脑冷静、观察力敏锐、推理能力极强;而且,他的剑术、拳术和小提琴演奏水平也相当高超,已经成为侦探小说中的典型代表人物之一。', + yxs_guiguzi:'本名王诩,春秋时纵横家,卫国(今河南鹤壁一带)人。曾隐于清溪鬼谷,常入云梦采药修道,弟子无数。有张仪、苏秦、孙膑、庞涓四弟子。精于兵法、奇门遁甲、五行八卦之学。后人称之为王禅老祖。今传《鬼谷子》十四篇。', + yxs_xiaoqiao:'小乔, 庐江皖县(今安徽潜山)人。 史书中称小桥,是中国汉末三国时期的女性, 乔公的次女,东吴名将周瑜的妻子。传说与其姐大乔均为绝世美女。合称“二乔”。', + yxs_luzhishen:'鲁智深,梁山泊第十三位好汉,十员步军头领第一名。鲁智深原名鲁达,绰号花和尚。是经略的提辖,因为见郑屠欺侮金翠莲父女,三拳打死了镇关西。被官府追捕,逃到五台山削发为僧,改名鲁智深。', + yxs_zhaoyong:'赵武灵王,战国中后期赵国君主,嬴姓,赵氏,名雍。赵武灵王在位时,推行的“胡服骑射”政策,赵国因而得以强盛,灭中山国,败林胡、楼烦二族,辟云中、雁门、代三郡,并修筑了“赵长城”。', + yxs_yangguang:'隋炀帝杨广,是隋朝第二代皇帝,华阴(今陕西华阴)人,生于隋京师长安。杨广在位期间修建大运河,营建东都迁都洛阳城,开创科举制度,亲征吐谷浑,三征高句丽。但因为杨广滥用民力,导致了隋朝的灭亡,618年在江都被部下缢杀。', + yxs_tangbohu:'唐伯虎,名寅,字伯虎,自号六如居士,明代诗人、画家,吴县(今江苏苏州)人。出身富商家庭,后家道衰落,因祝枝山之劝而潜心读书。公试时为状元,会试时候因科场舞弊案牵连而被斥为吏。后绝意仕途,以卖画为生。唐伯虎为人玩世不恭而又才气横溢,诗文擅名,与祝枝山、文征明、徐祯卿并称“江南四大才子”,画名更著,与沈周、文征明、仇英并称“吴门四家”。民间盛传其点秋香的故事。', + yxs_zhangsanfeng:'明朝最著名的武术家、道士。原名张通,字君宝,在武当山开山立派,成为武当派开山祖师。明英宗赐号“通微显化真人”;明宪宗特封号为“韬光尚志真仙”;明世宗赠封他为“清虚元妙真君”。传说其丰姿魁伟,大耳圆目,须髯如戟。无论寒暑,只一衲一蓑,一餐能食升斗,或数日一食,或数月不食,事能前知。其在武术上的造诣和超乎寻常的长寿都为后人称道。 曾传洪武年间,两度受朱元璋诏请入京,皆避而不见。其与明初巨贾沈万三亦有交际。其所创太极拳一直延续至今,成为后人养身妙术。', + yxs_nandinggeer:'出生于意大利,英国护士和统计学家。她谙熟数学,精通英、法、德、意四门语言,除古典文学外,还精于自然科学、历史和哲学,擅长音乐与绘画。在德国学习护理后,曾往伦敦的医院工作。南丁格尔于1854年和38位护士到克里米亚野战医院工作,成为该院的护士长,被称为“克里米亚的天使”又称“提灯女神”。1860年6月15日,南丁格尔在伦敦成立世界第一所护士学校。为了纪念她的成就,1912年,国际护士会倡仪各国医院和护士学校定每年5月12日南丁格尔诞辰日举行纪念活动,并将5月12日定为“国际护士节”,以缅怀和纪念这位伟大的女性。', + yxs_weizhongxian:'魏忠贤(1568年-1627年12月11日),字完吾,北直隶肃宁(今河北沧州肃宁县)人,汉族,原名李进忠。由才人王氏复姓,出任秉笔太监后,改名魏忠贤。明朝末期宦官。明熹宗时期,出任司礼秉笔太监,极受宠信,被称为“九千九百岁”,排除异己,专断国政,以致人们“只知有忠贤,而不知有皇上”。朱由检继位后,打击惩治阉党,治魏忠贤十大罪,命逮捕法办,自缢而亡,其余党亦被肃清。', + yxs_meixi:'妺(mò)喜,姓嬉(喜),生卒年不详,亦作妺嬉、末喜、末嬉,有施氏之女,夏朝最后一位君主夏桀的王后。根据先秦时代记述女子名时所用的全称和简称方式,妺喜应姓喜,即嬉(也作僖)。由于其名字的“妺”字与“妹妹”的“妹”字字形相似,且在《庄子》等作中也有以妺为妹的用法,因此常误作"妹喜"。', + yxs_lanlinwang:'高长恭(541年―573年),又名高孝瓘、高肃,祖籍渤海调蓨(今河北省景县),神武帝高欢之孙,文襄帝高澄第四子,生母不详,南北朝时期北齐宗室、将领,封爵兰陵郡王。高长恭貌柔心壮,音容兼美。为将躬勤细事,每得甘美,虽一瓜数果,必与将士分享。累次升任至并州刺史。突厥攻入晋阳,高长恭奋力将其击退。邙山之战,高长恭为中军,率领五百骑兵再入周军包围圈,直至金墉城下,因高长恭戴着头盔,城中的人不确定是敌军或是我军,直到高长恭把头盔脱下来城上的人才知道是高长恭,派弓箭手开始放箭保护他,之后高长恭成功替金墉解围,高长恭在此次战中威名大振,士兵们为此战而讴歌他,即后来知名的《兰陵王入阵曲》。', + }, + characterTitle:{ + "yxs_qinqiong":"Sukincen", + }, + skill:{ + yxs_fanji:{ + audio:2, + trigger:{ + player:"damageEnd", + }, + direct:true, + priority:12, + filter:function (event,player){ + if(!player.countCards('h',{name:'sha'})) return false; + return event.card.name=='sha'||event.card.name=='juedou'; + }, + content:function (){ + player.addTempSkill('yxs_fanji2','shaAfter'); + player.chooseToUse({name:'sha'},trigger.source,'反击:是否对'+get.translation(trigger.source)+'使用一张杀?').logSkill='yxs_fanji'; + }, + }, + yxs_fanji2:{ + audio:2, + trigger:{ + player:"shaBegin", + }, + direct:true, + filter:function (event,player){ + return event.card&&event.card.name=='sha'&&get.color(event.card)=='red'; + }, + content:function (){ + trigger.directHit=true; + }, + }, + + yxs_menshen3:{ + trigger:{ + player:['phaseBegin','dieBegin'], + }, + silent:true, + filter:function(event,player){ + return game.hasPlayer(function(current){ + return current.hasSkill('yxs_menshen2'); + }); + }, + content:function(){ + for(var i=0;i1; + }, + content:function (){ + "step 0" + player.chooseTarget('选择【门神】的目标',lib.translate.yxs_menshen_info,true,function(card,player,target){ + return target!=player; + }).set('ai',function(target){ + return get.attitude(player,target); + }); + "step 1" + if(result.bool){ + var target=result.targets[0]; + player.line(target,'green'); + game.log(target,'成为了','【门神】','的目标'); + target.storage.yxs_menshen2=player; + target.addSkill('yxs_menshen2'); + } + else { + event.finish(); + } + }, + ai:{ + expose:0.5, + }, + }, + + yxs_menshen2:{ + audio:2, + mark:'character', + intro:{ + content:'当你成为【杀】或【决斗】的目标后,改为$成为目标' + }, + nopop:true, + priority:15, + trigger:{ + target:["shaBegin","juedouBegin"], + }, + forced:true, + popup:false, + filter:function(event,player){ + return player.isAlive(); + }, + content:function (){ + var target=player.storage.yxs_menshen2; + trigger.player.line(target,'green'); + trigger.targets.remove(player); + trigger.targets.push(target); + trigger.target = target; + }, + }, + + guimian:{ + trigger:{source:'damageEnd'}, + forced:true, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&_status.currentPhase==player; + }, + content:function(){ + player.getStat().card.sha--; + } + }, + lyuxue:{ + trigger:{source:'damageEnd'}, + forced:true, + logTarget:'player', + filter:function(event,player){ + if(event._notrigger.contains(event.player)) return false; + return event.player.isIn()&&!event.player.hasSkill('lyuxue2'); + }, + content:function(){ + trigger.player.addSkill('lyuxue2'); + }, + subSkill:{ + clear:{ + trigger:{player:['phaseBegin','dieBegin']}, + forced:true, + filter:function(event,player){ + var num=game.countPlayer(function(current){ + return current.hasSkill('lyuxue2'); + }); + if(!num) return false; + if(event.name=='die') return true; + return num>=Math.floor(game.countPlayer()/2); + }, + content:function(){ + 'step 0' + var list=game.filterPlayer(function(current){ + return current.hasSkill('lyuxue2'); + }); + list.sortBySeat(); + event.list=list; + player.line(list,'green'); + 'step 1' + if(event.list.length){ + event.list.shift().removeSkill('lyuxue2'); + event.redo(); + } + } + } + }, + group:'lyuxue_clear', + }, + lyuxue2:{ + mark:true, + intro:{ + content:'已获得浴血标记' + }, + onremove:function(player){ + player.loseHp(); + } + }, + yaoji:{ + trigger:{player:'damageEnd'}, + filter:function(event,player){ + return event.source&&event.source.isIn()&&event.source!=player&&!event.source.hasJudge('lebu'); + }, + check:function(event,player){ + return get.attitude(player,event.source)<=0; + }, + logTarget:'source', + content:function(){ + var card=game.createCard('lebu'); + trigger.source.addJudge(card); + trigger.source.$draw(card); + game.delay(); + }, + ai:{ + maixie_defend:true, + } + }, + liebo:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return Math.abs(target.countCards('h')-player.countCards('h'))<=1; + }, + content:function(){ + player.swapHandcards(target); + }, + ai:{ + order:function(){ + var player=_status.event.player; + if(player.hasCard(function(card){ + return get.value(card)>=8; + })){ + return 0; + } + var nh=player.countCards('h'); + if(game.hasPlayer(function(current){ + return get.attitude(player,current)<=0&¤t.countCards('h')==nh+1; + })){ + return 9; + } + return 1; + }, + result:{ + player:function(player,target){ + var att=get.attitude(player,target); + if(att>0) return 0; + if(player.hasCard(function(card){ + return get.value(card)>=8; + })){ + return 0; + } + var n1=target.countCards('h'),n2=player.countCards('h'); + var num=0; + if(n1-n2==1){ + num=1; + } + if(player.countCards('h','du')){ + if(n1==n2) num=0.5; + else num=0.1; + } + if(att==0){ + num/=2; + } + return num; + } + } + } + }, + zhuxin:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return target!=player&&target.countCards('h'); + }, + filter:function(event,player){ + return player.countCards('h'); + }, + content:function(){ + 'step 0' + player.chooseToCompare(target); + 'step 1' + if(result.bool){ + target.damage(); + } + }, + ai:{ + order:8, + result:{ + target:function(player,target){ + return get.damageEffect(target,player,target); + } + } + } + }, + wlianhuan:{ + trigger:{source:'damageBegin'}, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&player.countCards('e'); + }, + direct:true, + content:function(){ + 'step 0' + var next=player.chooseToDiscard('e',get.prompt('wlianhuan',trigger.player),'弃置一张装备区内的牌使伤害+1'); + next.ai=function(card){ + if(get.attitude(player,trigger.player)<0){ + return 7-get.value(card); + } + return 0; + } + next.logSkill=['wlianhuan',trigger.player]; + 'step 1' + if(result.bool){ + trigger.num++; + } + } + }, + huli:{ + enable:'phaseUse', + filterCard:{suit:'heart'}, + filterTarget:function(card,player,target){ + return get.distance(player,target)<=1&&lib.filter.cardEnabled({name:'tao'},target,target); + }, + check:function(card){ + return 8-get.value(card); + }, + discard:false, + filter:function(event,player){ + if(player.countCards('h',{suit:'heart'})){ + return true; + } + return false; + }, + prepare:'throw', + content:function(){ + player.useCard({name:'tao'},cards,targets[0]).animate=false; + }, + ai:{ + order:9.5, + result:{ + target:function(player,target){ + return get.recoverEffect(target,player,target); + } + }, + threaten:1.6 + } + }, + yixin:{ + skillAnimation:true, + unique:true, + mark:true, + init:function(player){ + player.storage.yixin=false; + }, + enable:'phaseUse', + filter:function(event,player){ + return !player.storage.yixin&&player.countCards('he')>2; + }, + intro:{ + content:'limited' + }, + filterTarget:function(card,player,target){ + return target.isDamaged(); + }, + filterCard:true, + position:'he', + selectCard:2, + check:function(card){ + return 7-get.value(card); + }, + content:function(){ + player.awakenSkill('yixin'); + player.storage.yixin=true; + var num=Math.min(4,target.maxHp-target.hp); + target.recover(num); + if(num<4){ + target.draw(4-num); + } + }, + ai:{ + order:9.6, + result:{ + target:function(player,target){ + if(target.hp==1&&target.maxHp>=3){ + return get.recoverEffect(target,player,target); + } + return 0; + } + } + } + }, + xianqu:{ + mod:{ + targetEnabled:function(card){ + if(card.name=='sha'&&card.number<8) return false; + } + }, + }, + zbudao:{ + trigger:{player:'phaseDrawBegin'}, + //check:function(event,player){ + // if(player.hasFriend()) return true; + // return false; + //}, + content:function(){ + trigger.num++; + player.addTempSkill('zbudao2','phaseDrawAfter'); + }, + ai:{ + threaten:1.3 + }, + }, + zbudao2:{ + trigger:{player:'phaseDrawEnd'}, + forced:true, + popup:false, + filter:function(event){ + return event.cards&&event.cards.length; + }, + content:function(){ + 'step 0' + event.cards=trigger.cards.slice(0); + player.chooseCardTarget({ + filterCard:function(card){ + return _status.event.getParent().cards.contains(card); + }, + selectCard:1, + filterTarget:function(card,player,target){ + return player!=target; + }, + ai1:function(card){ + if(ui.selected.cards.length>0) return -1; + if(card.name=='du') return 20; + return (_status.event.player.countCards('h')-_status.event.player.hp); + }, + ai2:function(target){ + var att=get.attitude(_status.event.player,target); + if(ui.selected.cards.length&&ui.selected.cards[0].name=='du'){ + return 1-att; + } + return att-4; + }, + //forced:true, + prompt:'将获得的一张牌交给一名其他角色,或点取消' + }); + "step 1" + if(result.bool){ + player.line(result.targets,'green'); + result.targets[0].gain(result.cards,player); + player.$give(result.cards.length,result.targets[0]); + game.delay(0.7); + } + } + }, + taiji:{ + trigger:{player:['useCard','respond']}, + filter:function(event,player){ + return event.card.name=='shan'&&player.hasSha(); + }, + direct:true, + content:function(){ + player.chooseToUse({name:'sha'},'太极:是否使用一张杀?').logSkill='taiji'; + }, + }, + fengliu:{ + trigger:{player:'phaseDrawBegin'}, + filter:function(event,player){ + return game.hasPlayer(function(current){ + return current.sex=='female'; + }); + }, + forced:true, + content:function(){ + var num=game.countPlayer(function(current){ + return current.sex=='female'; + }); + if(num>2) num=2; + trigger.num+=num; + }, + ai:{ + threaten:function(){ + var num=game.countPlayer(function(current){ + return current.sex=='female'; + }); + switch(num){ + case 0:return 1; + case 1:return 1.3; + default:return 2; + } + } + }, + }, + luobi:{ + trigger:{player:'phaseEnd'}, + filter:function(event,player){ + return player.isDamaged(); + }, + content:function(){ + "step 0" + player.draw(player.maxHp-player.hp); + "step 1" + event.cards=result; + "step 2" + player.chooseCardTarget({ + filterCard:function(card){ + return _status.event.getParent().cards.contains(card); + }, + selectCard:[1,event.cards.length], + filterTarget:function(card,player,target){ + return player!=target; + }, + ai1:function(card){ + if(ui.selected.cards.length>0) return -1; + if(card.name=='du') return 20; + return (_status.event.player.countCards('h')-_status.event.player.hp); + }, + ai2:function(target){ + var att=get.attitude(_status.event.player,target); + if(ui.selected.cards.length&&ui.selected.cards[0].name=='du'){ + return 1-att; + } + return att-4; + }, + prompt:'请选择要送人的卡牌' + }); + "step 3" + if(result.bool){ + player.line(result.targets,'green'); + result.targets[0].gain(result.cards,player); + player.$give(result.cards.length,result.targets[0]); + for(var i=0;i0){ + return game.hasPlayer(function(current){ + return current.group!='qun'&¤t!=player; + }); + } + return false; + }, + content:function(){ + 'step 0' + player.chooseTarget(get.prompt('yaoyi'),[1,2],function(card,player,target){ + return target.countCards('h')&&target.group!='qun'&&target!=player; + }).set('ai',function(target){ + return 0.5-get.attitude(_status.event.player,target); + }); + 'step 1' + if(result.bool){ + player.logSkill('yaoyi',result.targets); + event.targets=result.targets; + } + else{ + event.finish(); + } + 'step 2' + if(event.targets&&event.targets.length){ + event.target=event.targets.shift(); + event.target.chooseCard('交给'+get.translation(player)+'一张手牌',true).ai=function(card){ + return -get.value(card); + } + } + else{ + event.finish(); + } + 'step 3' + if(result.bool&&result.cards&&result.cards.length){ + event.target.$give(1,player); + player.gain(result.cards,event.target); + } + event.goto(2); + }, + ai:{ + maixie:true, + maixie_hp:true, + expose:0.2, + effect:{ + target:function(card,player,target){ + if(get.tag(card,'damage')){ + if(player.hasSkillTag('jueqing',false,target)) return [1,-2]; + if(!target.hasFriend()) return; + var players=game.filterPlayer(); + for(var i=0;i=4) return [1,get.tag(card,'damage')*2]; + if(target.hp==3) return [1,get.tag(card,'damage')*1.5]; + if(target.hp==2) return [1,get.tag(card,'damage')*0.5]; + } + } + } + } + } + } + }, + shiqin:{ + trigger:{global:'dying'}, + priority:9, + filter:function(event,player){ + return event.player!=player&&event.player.hp<=0&&event.player.group=='qun'; + }, + check:function(event,player){ + return get.attitude(player,event.player)<0; + }, + forced:true, + logTarget:'player', + content:function(){ + 'step 0' + game.delayx(); + trigger.player.die(); + 'step 1' + if(!trigger.player.isAlive()){ + trigger.cancel(true); + } + } + }, + zyhufu:{ + trigger:{player:'phaseDrawBegin'}, + filter:function(event,player){ + return !player.getEquip(2); + }, + forced:true, + content:function(){ + trigger.num++; + }, + ai:{ + threaten:1.3 + }, + mod:{ + maxHandcard:function(player,num){ + if(player.getEquip(2)) return num+5; + } + } + }, + hanbei:{ + trigger:{player:'shaBegin'}, + forced:true, + filter:function(event,player){ + if(player.getEquip(3)||player.getEquip(4)) return true; + return false; + }, + content:function(){ + trigger.directHit=true; + } + }, + sheshu:{ + trigger:{player:'shaBegin'}, + forced:true, + filter:function(event,player){ + return event.target.hp>=3; + }, + content:function(){ + trigger.directHit=true; + }, + mod:{ + targetInRange:function(card){ + if(card.name=='sha') return true; + }, + }, + }, + lguiyin:{ + unique:true, + forceunique:true, + enable:'phaseUse', + filter:function(event,player){ + return !player.hasSkill('tongyu_guiyin')&&!player.getStat('damage'); + }, + derivation:['lzhangyi','jimin','tongyu'], + content:function(){ + player.draw(); + player.setAvatar('yxs_luobinhan','yxs_handingdun'); + player.removeSkill('lguiyin'); + player.removeSkill('sheshu'); + player.removeSkill('xiadao'); + player.addSkill('jimin'); + player.addSkill('lzhangyi'); + player.addSkill('tongyu'); + player.addTempSkill('tongyu_guiyin'); + }, + ai:{ + order:function(){ + if(_status.event.player.hp==1) return 9; + return 0.5; + }, + result:{ + player:function(player){ + if(player.hp0&&!player.hasSkill('tongyu_guiyin'); + }, + filterCard:true, + position:'he', + check:function(card){ + return 7-get.value(card); + }, + content:function(){ + player.setAvatar('yxs_luobinhan','yxs_luobinhan'); + player.addSkill('lguiyin'); + player.addSkill('sheshu'); + player.addSkill('xiadao'); + player.removeSkill('jimin'); + player.removeSkill('lzhangyi'); + player.removeSkill('tongyu'); + player.addTempSkill('tongyu_guiyin'); + }, + ai:{ + order:9, + result:{ + player:function(player){ + if(player.hasFriend()) return 1; + return 0; + } + } + } + }, + tongyu_guiyin:{}, + zhijie:{ + enable:'phaseUse', + usable:1, + viewAsFilter:function(player){ + return player.countCards('h',{suit:'heart'})>0; + }, + viewAs:{name:'wuzhong'}, + filterCard:{suit:'heart'}, + check:function(card){ + return 8-get.value(card); + } + }, + dili:{ + trigger:{player:'phaseDrawBegin'}, + forced:true, + filter:function(event,player){ + return player.hp=player.maxHp-1) return [0,0]; + } + } + } + }, + kuangchan:{ + ai:{ + neg:true + }, + init:function(player){ + if(player.isZhu){ + player.maxHp--; + player.update(); + } + } + }, + chujia:{ + enable:'phaseUse', + filterCard:function(card){ + if(ui.selected.cards.length){ + return get.color(card)==get.color(ui.selected.cards[0]); + } + return true; + }, + complexCard:true, + usable:1, + selectCard:2, + check:function(card){ + return 6-get.value(card); + }, + filterTarget:function(card,player,target){ + return target.hptarget.hp){ + target.draw(target.maxHp-target.hp); + } + }, + ai:{ + order:2, + result:{ + target:function(player,target){ + var num=target.maxHp-target.hp; + if(num>2) return num; + return 0; + } + } + } + }, + baihe:{ + enable:'phaseUse', + usable:1, + filterCard:true, + position:'he', + filterTarget:true, + content:function(){ + 'step 0' + if(target.isLinked()){ + target.link(); + } + else{ + target.link(); + target.draw(); + event.finish(); + } + 'step 1' + if(target.countCards('h')){ + target.chooseToDiscard('h',true); + } + }, + check:function(card){ + return 8-get.value(card); + }, + ai:{ + order:1, + result:{ + player:function(player,target){ + if(!player.hasSkill('xiushen')) return 0; + if(target.isLinked()) return 0; + if(game.hasPlayer(function(current){ + return current.isLinked(); + })){ + return 0; + } + return 1; + } + } + } + }, + yinyang:{ + enable:'phaseUse', + usable:1, + filterCard:true, + selectCard:2, + filterTarget:true, + selectTarget:3, + content:function(){ + target.link(); + }, + check:function(card){ + return 6-get.value(card); + }, + ai:{ + order:2, + result:{ + target:function(player,target){ + if(target.isLinked()) return 1; + return -1; + } + } + } + }, + xiushen:{ + trigger:{player:'phaseUseEnd'}, + forced:true, + filter:function(event,player){ + return game.hasPlayer(function(current){ + return current.isLinked(); + }); + }, + content:function(){ + player.draw(2); + }, + ai:{ + threaten:1.6 + } + }, + jiean:{ + trigger:{source:'damageEnd'}, + frequent:true, + filter:function(event){ + if(event._notrigger.contains(event.player)) return false; + return event.player.isAlive()&&event.parent.name=='yanyi'&&event.player.hp0) return -1; + return (_status.event.player.countCards('h')-_status.event.player.hp); + }, + ai2:function(target){ + return get.attitude(_status.event.player,target)-4; + }, + prompt:'请选择要送人的卡牌' + }); + "step 3" + if(result.bool){ + result.targets[0].gain(result.cards,player); + player.$give(result.cards.length,result.targets[0]); + for(var i=0;i0; + }, + content:function(){ + "step 0" + player.chooseControl('heart2','diamond2','club2','spade2').ai=function(event){ + switch(Math.floor(Math.random()*5)){ + case 0:return 'heart2'; + case 1:case 4:return 'diamond2'; + case 2:return 'club2'; + case 3:return 'spade2'; + } + }; + "step 1" + game.log(player,'选择了'+get.translation(result.control)); + event.choice=result.control.slice(0,result.control.length-1); + target.popup(result.control); + target.showHandcards(); + "step 2" + if(target.countCards('h',{suit:event.choice})){ + target.damage(); + } + }, + ai:{ + result:{ + target:function(player,target){ + return get.damageEffect(target,player,target); + } + } + } + }, + wumu:{ + mod:{ + targetInRange:function(card,player){ + if(card.name=='sha'&&get.color(card)=='black') return true; + }, + cardUsable:function(card){ + if(card.name=='sha'&&get.color(card)=='red') return Infinity; + } + }, + trigger:{player:'useCard'}, + filter:function(event,player){ + return event.card.name=='sha'&&get.color(event.card)=='red'; + }, + forced:true, + content:function(){ + if(player.stat[player.stat.length-1].card.sha>0){ + player.stat[player.stat.length-1].card.sha--; + } + }, + }, + ysheshen:{ + inherit:'yiji' + }, + sanbanfu:{ + trigger:{player:'shaBegin'}, + filter:function(event,player){ + if(player.storage.sanbanfu||player.storage.sanbanfu2) return false; + return !event.directHit; + }, + check:function(event,player){ + if(get.attitude(player,event.target)>=0) return false; + if(event.target.getEquip('bagua')) return false; + if(event.target.hasSkillTag('respondShan')&&event.target.countCards('h')>=3) return false; + return true; + }, + logTarget:'target', + content:function(){ + "step 0" + var next=trigger.target.chooseToRespond({name:'shan'}); + next.autochoose=lib.filter.autoRespondShan; + next.ai=function(card){ + return get.unuseful2(card); + }; + player.storage.sanbanfu=false; + player.storage.sanbanfu2=false; + "step 1" + if(result.bool==false){ + trigger.untrigger(); + trigger.directHit=true; + player.storage.sanbanfu2=true; + } + else{ + player.storage.sanbanfu=true; + } + }, + group:['sanbanfu2','sanbanfu3'] + }, + sanbanfu2:{ + trigger:{player:'shaAfter'}, + silent:true, + content:function(){ + if(player.storage.sanbanfu) player.damage(trigger.target); + delete player.storage.sanbanfu; + delete player.storage.sanbanfu2; + } + }, + sanbanfu3:{ + trigger:{source:'damageBegin'}, + silent:true, + filter:function(event,player){ + return event.card&&event.card.name=='sha'&&player.storage.sanbanfu2; + }, + content:function(){ + trigger.num++; + } + }, + bingsheng:{ + enable:'phaseUse', + usable:1, + filterCard:function(card){ + if(ui.selected.cards.length){ + return get.suit(card)!=get.suit(ui.selected.cards[0]); + } + return true; + }, + complexCard:true, + selectCard:2, + check:function(card){ + return 8-get.value(card); + }, + filterTarget:function(card,player,target){ + if(target.hp==Infinity) return false; + if(target.hp>player.hp) return true; + if(target.hp2){ + num=2; + } + if(num<-2){ + num=-2; + } + if(num>0){ + target.damage(num); + } + else if(num<0&&target.hptarget.maxHp){ + num=player.hp-target.maxHp; + } + else{ + num=player.hp-target.hp; + } + if(target.hp==1&&num){ + return num+1; + } + return num; + } + } + } + }, + taolue:{ + mod:{ + maxHandcard:function(player,num){ + return num+1; + } + }, + }, + shentan:{ + enable:'phaseUse', + usable:1, + filterCard:true, + filterTarget:function(card,player,target){ + return target.countCards('h')>0&&get.distance(player,target)<=2; + }, + check:function(card){ + return 7-get.value(card); + }, + position:'he', + content:function(){ + "step 0" + var hs=target.getCards('h'); + if(hs.length){ + event.card=hs.randomGet(); + player.gain(event.card,target); + target.$giveAuto(event.card,player); + } + else{ + event.finish(); + } + "step 1" + var source=target; + player.chooseTarget('选择一个目标送出'+get.translation(event.card),function(card,player,target){ + return target!=player; + }).ai=function(target){ + var att=get.attitude(player,target); + if(att>3&&player.countCards('h')>target.countCards('h')){ + return att; + } + return 0; + } + "step 2" + if(result.bool){ + result.targets[0].gain(card,player); + player.$give(1,result.targets[0]); + player.line(result.targets,'green'); + game.delay(); + } + }, + ai:{ + order:9, + result:{ + target:-1, + player:function(player,target){ + if(get.attitude(player,target)>0){ + return 0; + } + return 1; + } + }, + }, + }, + hanqiang:{ + mod:{ + attackFrom:function(from,to,distance){ + if(!from.getEquip(1)) return distance-1 + } + } + }, + biaoqi:{ + trigger:{player:'shaBegin'}, + forced:true, + content:function(){ + var range=player.getAttackRange(); + if(range>trigger.target.hp){ + trigger.directHit=true; + } + else if(range0; + }, + check:function(event,player){ + return get.attitude(player,event.target)<0; + }, + content:function(){ + 'step 0' + player.judge(function(card){ + return get.color(card)=='black'?1:-1; + }); + 'step 1' + if(result.bool){ + player.gainPlayerCard('he',trigger.target); + } + } + }, + jimin:{ + enable:['chooseToRespond','chooseToUse'], + filterCard:true, + viewAs:{name:'shan'}, + viewAsFilter:function(player){ + if(!player.countCards('h')) return false; + if(player.countCards('e')) return false; + }, + prompt:'将一张手牌当闪使用或打出', + check:function(){return 1}, + ai:{ + respondShan:true, + skillTagFilter:function(player){ + if(!player.countCards('h')) return false; + if(player.countCards('e')) return false; + }, + effect:{ + target:function(card,player,target,current){ + if(get.tag(card,'respondShan')&¤t<0&&!target.countCards('e')) return 0.6 + } + } + } + }, + xiadao:{ + trigger:{source:'damageEnd'}, + direct:true, + filter:function(event,player){ + if(event._notrigger.contains(event.player)) return false; + if(event.player.isDead()) return false; + var nh=event.player.countCards('h'); + if(nh==0) return false; + var players=game.filterPlayer(); + for(var i=0;i0) return 0; + return get.attitude(player,target); + } + 'step 1' + if(result.bool){ + player.logSkill('xiadao'); + player.line2([trigger.player,result.targets[0]],'green'); + event.target=result.targets[0]; + game.delay(); + } + else{ + event.finish(); + } + 'step 0' + if(event.target){ + var card=trigger.player.getCards('h').randomGet(); + event.target.gain(card,trigger.player); + trigger.player.$giveAuto(card,event.target); + } + }, + ai:{ + expose:0.2, + threaten:1.4 + } + }, + lzhangyi:{ + trigger:{player:'discardAfter'}, + filter:function(event,player){ + for(var i=0;i2){ + du=0; + } + } + player.chooseTarget(get.prompt('lzhangyi'),function(card,player,target){ + return player!=target + }).set('du',du).ai=function(target){ + var att=get.attitude(_status.event.player,target); + return att*_status.event.du; + }; + "step 2" + if(result.bool){ + var target=result.targets[0]; + player.logSkill('lzhangyi',target); + var cards=[]; + for(var i=0;i0) return 0; + return 7-get.value(card); + }, + ai2:function(target){ + if(target.isMin()) return 0; + return 6-target.maxHp; + } + }); + } + else{ + event.finish(); + } + "step 1" + if(result.bool){ + player.unmark(player.storage.yizhuang+'_charactermark'); + player.discard(result.cards); + player.logSkill('yizhuang',result.targets); + var name=result.targets[0].name; + if(name.indexOf('unknown')==0){ + name=result.targets[0].name2; + } + var list=[]; + var skills=lib.character[name][3]; + for(var j=0;j0; + }, + content:function(){ + player.unmark(player.storage.yizhuang+'_charactermark'); + player.removeAdditionalSkill('yizhuang'); + delete player.storage.yizhuang; + player.checkMarks(); + } + }, + kongju:{ + mod:{ + maxHandcard:function(player,num){ + if(player.hptarget.maxHp){ + if(card.name=='lebu') return false; + } + } + }, + }, + tuqiang:{ + trigger:{player:['respond','useCard']}, + filter:function(event,player){ + return event.card&&event.card.name=='shan'; + }, + frequent:true, + content:function(){ + player.draw(); + }, + ai:{ + mingzhi:false, + effect:{ + target:function(card,player,target){ + if(get.tag(card,'respondShan')){ + return 0.8; + } + } + }, + } + }, + xumou:{ + inherit:'jushou' + }, + zhensha:{ + trigger:{global:'dying'}, + priority:9, + filter:function(event,player){ + return event.player.hp<=0&&(player.countCards('h','jiu')>0||player.countCards('h',{color:'black'})>=2)&&player!=event.player; + }, + direct:true, + content:function(){ + 'step 0' + var goon=(get.attitude(player,trigger.player)<0); + var next=player.chooseToDiscard('鸠杀:是否弃置一张酒或两张黑色手牌令'+get.translation(trigger.player)+'立即死亡?'); + next.ai=function(card){ + if(ui.selected.cards.length){ + if(ui.selected.cards[0].name=='jiu') return 0; + } + if(goon){ + if(card.name=='jiu') return 2; + return 1; + } + return 0; + }; + next.filterCard=function(card){ + if(ui.selected.cards.length){ + return get.color(card)=='black'; + } + return get.color(card)=='black'||card.name=='jiu'; + }; + next.complexCard=true, + next.logSkill=['zhensha',trigger.player]; + next.selectCard=function(){ + if(ui.selected.cards.length){ + if(get.color(ui.selected.cards[0])!='black') return [1,1]; + } + return [1,2]; + } + 'step 1' + if(result.bool){ + trigger.player.die(); + } + else{ + event.finish(); + } + 'step 2' + if(!trigger.player.isAlive()){ + trigger.cancel(true); + } + }, + ai:{ + threaten:1.5 + } + }, + ducai:{ + enable:'phaseUse', + usable:1, + unique:true, + forceunique:true, + check:function(card){ + if(_status.event.player.countCards('h')>=3){ + return 5-get.value(card); + } + return 0; + }, + position:'he', + filterCard:true, + content:function(){ + player.storage.ducai2=cards[0]; + player.addTempSkill('ducai2',{player:'phaseBegin'}); + }, + ai:{ + order:8, + result:{ + player:1 + } + }, + global:'ducai3' + }, + ducai2:{ + mark:'card', + intro:{ + content:'card' + } + }, + ducai3:{ + mod:{ + cardEnabled:function(card,player){ + if(player.hasSkill('ducai2')) return; + var suit,players=game.filterPlayer(); + for(var i=0;i=3; + }, + filterTarget:function(card,player,target){ + return player.canUse('sha',target); + }, + selectTarget:[1,3], + multitarget:true, + multiline:true, + content:function(){ + player.storage.tongling-=3; + player.unmarkSkill('tongling'); + player.syncStorage('tongling'); + player.useCard({name:'sha'},targets,false); + }, + ai:{ + combo:'tongling', + order:2 + } + }, + fanpu_old:{ + enable:'phaseUse', + usable:1, + filter:function(event,player){ + return player.storage.tongling>=3; + }, + promptfunc:function(){ + return '令自己在本轮内不能成为出杀的目标(选择自己),或对攻击范围内的至多两名角色使用一张杀' + }, + filterTarget:function(card,player,target){ + return player==target||get.distance(player,target,'attack')<=1; + }, + content:function(){ + if(target==player){ + target.addTempSkill('fanpu_disable',{player:'phaseBegin'}); + } + else{ + target.damage(); + } + player.storage.tongling-=3; + player.unmarkSkill('tongling'); + player.syncStorage('tongling'); + }, + subSkill:{ + disable:{ + mark:true, + intro:{ + content:'不能成为杀的目标' + }, + mod:{ + targetEnabled:function(card,player,target,now){ + if(card.name=='sha') return false; + } + } + } + }, + ai:{ + combo:'tongling', + order:2, + result:{ + target:function(player,target){ + if(player==target){ + if(player.hp<=2&&!player.countCards('h','shan')){ + return 2; + } + return 0; + } + else{ + return get.damageEffect(target,player,target); + } + } + } + } + }, + fenghuo:{ + enable:'chooseToUse', + filter:function(event,player){ + return player.countCards('e')>0; + }, + filterCard:true, + position:'e', + viewAs:{name:'nanman'}, + prompt:'将一张装备区内的牌当南蛮入侵使用', + check:function(card){ + var player=_status.currentPhase; + if(player.countCards('he',{subtype:get.subtype(card)})>1){ + return 11-get.equipValue(card); + } + if(player.countCards('h')=2; + }, + content:function(){ + trigger.cancel(); + player.addSkill('nichang2'); + } + }, + nichang2:{ + trigger:{player:'phaseEnd'}, + forced:true, + content:function(){ + "step 0" + if(player.countCards('h')){ + player.showHandcards(); + } + player.removeSkill('nichang2'); + "step 1" + var suits=['spade','heart','diamond','club']; + var cards=player.getCards('h'); + for(var i=0;i1){ + if(game.phaseNumber=3){ + break; + } + } + event.cards=cards; + event.suit=suit; + player.showCards(cards); + } + else{ + event.finish(); + } + "step 2" + if(event.cards&&event.cards.length){ + if(get.suit(event.cards[event.cards.length-1])==event.suit){ + event.cards.pop().discard(); + } + if(event.cards.length){ + player.gain(event.cards,'draw2'); + } + } + }, + ai:{ + maixie:true, + maixie_hp:true, + effect:{ + target:function(card,player,target){ + if(get.tag(card,'damage')){ + if(player.hasSkillTag('jueqing',false,target)) return [1,-2]; + if(!target.hasFriend()) return; + if(target.hp>=4) return [1,2]; + if(target.hp==3) return [1,1.5]; + if(target.hp==2) return [1,0.5]; + } + } + } + } + }, + bolehuiyan:{ + trigger:{global:'shaBegin'}, + direct:true, + priority:11, + filter:function(event,player){ + if(player.hasSkill('bolehuiyan4')) return false; + if(event.target.isUnderControl()) return false; + return event.player!=player&&event.target!=player&&event.target.countCards('h')>0; + }, + group:['bolehuiyan2','bolehuiyan3'], + content:function(){ + "step 0" + if(event.isMine()){ + event.dialog=ui.create.dialog('慧眼:预言'+get.translation(trigger.player)+'对'+get.translation(trigger.target)+'的杀能否命中'); + } + player.chooseControl('能命中','不能命中','cancel').ai=function(event){ + if(trigger.player.hasSkill('wushuang')) return 0; + if(trigger.player.hasSkill('liegong')) return 0; + if(trigger.player.hasSkill('tieji')) return 0; + if(trigger.player.hasSkill('juji')) return 0; + if(trigger.player.hasSkill('retieji')) return 0; + if(trigger.player.hasSkill('roulin')&&trigger.target.sex=='female') return 0; + if(trigger.player.hasSkill('nvquan')&&trigger.target.sex=='male') return 0; + if(trigger.target.hasSkill('yijue2')) return 0; + if(trigger.target.hasSkill('shejie2')) return 0; + if(trigger.target.hasSkill('shanguang2')) return 0; + + var equip=trigger.target.getEquip(2); + if(equip&&equip.name=='bagua') return 1; + return trigger.target.countCards('h')<2?0:1; + }; + "step 1" + if(event.dialog){ + event.dialog.close(); + } + if(result.control!='cancel'){ + player.addTempSkill('bolehuiyan4'); + player.logSkill(['bolehuiyan',result.control],trigger.target); + game.log(player,'预言'+result.control); + player.storage.bolehuiyan=result.control; + game.delay(); + } + }, + ai:{ + threaten:1.3 + } + }, + bolehuiyan2:{ + trigger:{global:'shaEnd'}, + forced:true, + popup:false, + filter:function(event,player){ + return player.storage.bolehuiyan?true:false; + }, + content:function(){ + if(player.storage.bolehuiyan=='不能命中'){ + player.popup('预言成功'); + player.draw(); + } + else{ + player.popup('预言失败'); + player.chooseToDiscard('预言失败,请弃置一张牌','he',true); + } + delete player.storage.bolehuiyan; + } + }, + bolehuiyan3:{ + trigger:{global:'shaDamage'}, + forced:true, + popup:false, + filter:function(event,player){ + return player.storage.bolehuiyan?true:false; + }, + content:function(){ + if(player.storage.bolehuiyan=='能命中'){ + player.popup('预言成功'); + player.draw(); + } + else{ + player.popup('预言失败'); + player.chooseToDiscard('预言失败,请弃置一张牌','he',true); + } + delete player.storage.bolehuiyan; + } + }, + bolehuiyan4:{}, + oldbolehuiyan:{ + trigger:{global:'judgeBegin'}, + direct:true, + priority:11, + filter:function(event,player){ + return event.player!=player; + }, + content:function(){ + "step 0" + if(event.isMine()){ + event.dialog=ui.create.dialog('慧眼:预言'+get.translation(trigger.player)+'的'+trigger.judgestr+'判定'); + } + player.chooseControl('heart2','diamond2','club2','spade2','cancel').ai=function(event){ + switch(Math.floor(Math.random()*4)){ + case 0:return 'heart2'; + case 1:return 'diamond2'; + case 2:return 'club2'; + case 3:return 'spade2'; + } + }; + "step 1" + if(event.dialog){ + event.dialog.close(); + } + if(result.control!='cancel'){ + game.log(player,'预言判定结果为'+get.translation(result.control)); + player.storage.bolehuiyan=result.control.slice(0,result.control.length-1); + player.popup(result.control); + game.delay(); + } + }, + group:'bolehuiyan2' + }, + oldbolehuiyan2:{ + trigger:{global:'judgeEnd'}, + forced:true, + popup:false, + content:function(){ + if(player.storage.bolehuiyan==trigger.result.suit){ + game.log(player,'预言成功'); + player.popup('洗具'); + player.draw(2); + } + else if(get.color({suit:player.storage.bolehuiyan})==trigger.result.color){ + player.popup('洗具'); + player.draw(); + } + delete player.storage.bolehuiyan; + } + }, + xiangma:{ + inherit:'yicong' + }, + weiyi:{ + trigger:{player:'damageEnd'}, + filter:function(event,player){ + return (event.source&&event.source.countCards('he')); + }, + check:function(event,player){ + return get.attitude(player,event.source)<0; + }, + content:function(){ + trigger.source.chooseToDiscard(2,'he',true); + }, + logTarget:'source', + ai:{ + maixie_defend:true, + expose:0.3, + result:{ + target:function(card,player,target){ + if(player.countCards('he')>1&&get.tag(card,'damage')){ + if(player.hasSkillTag('jueqing',false,target)) return [1,-1]; + if(get.attitude(target,player)<0) return [1,0,0,-1.5]; + } + } + } + } + }, + qiandu:{ + enable:'phaseUse', + usable:1, + changeSeat:true, + filterTarget:function(card,player,target){ + return player!=target&&player.next!=target; + }, + filterCard:{color:'black'}, + check:function(card){ + return 4-get.value(card); + }, + content:function(){ + game.swapSeat(player,target); + }, + ai:{ + order:5, + result:{ + player:function(player,target){ + var att=get.attitude(player,target); + if(target==player.previous&&att>0) return att; + if(target==player.next&&att<0) return -att; + var att2=get.attitude(player,player.next); + if(target==player.next.next&&att<0&&att2<0) return -att-att2; + return 0; + } + } + } + }, + nvquan:{ + locked:true, + group:['nvquan1','nvquan2','nvquan3'], + }, + nvquan1:{ + trigger:{player:'shaBegin'}, + forced:true, + filter:function(event){ + return event.target.sex=='male'; + }, + priority:-1, + content:function(){ + if(typeof trigger.shanRequired=='number'){ + trigger.shanRequired++; + } + else{ + trigger.shanRequired=2; + } + } + }, + nvquan2:{ + trigger:{player:'juedou',target:'juedou'}, + forced:true, + filter:function(event,player){ + return event.turn!=player&&event.turn.sex=='male'; + }, + priority:-1, + content:function(){ + "step 0" + var next=trigger.turn.chooseToRespond({name:'sha'},'请打出一张杀响应决斗'); + next.set('prompt2','(共需打出2张杀)'); + next.autochoose=lib.filter.autoRespondSha; + next.ai=function(card){ + if(get.attitude(trigger.turn,player)<0&&trigger.turn.countCards('h','sha')>1){ + return get.unuseful2(card); + } + return -1; + }; + "step 1" + if(result.bool==false){ + trigger.directHit=true; + } + }, + ai:{ + result:{ + target:function(card,player,target){ + if(card.name=='juedou'&&target.countCards('h')>0) return [1,0,0,-1]; + } + } + } + }, + nvquan3:{ + mod:{ + targetEnabled:function(card,player,target){ + if(card.name=='juedou'&&player.sex=='male'){ + return false; + } + } + } + }, + feigong:{ + trigger:{global:'useCard'}, + priority:15, + filter:function(event,player){ + return event.card.name=='sha'&&event.player!=player&& + player.countCards('h','sha')>0&&event.targets.contains(player)==false; + }, + direct:true, + content:function(){ + "step 0" + var effect=0; + for(var i=0;i2){ + eff+=0.5; + } + } + if(get.attitude(player,players[i])>0){ + num+=eff; + } + else if(get.attitude(player,players[i])<0){ + num-=eff; + } + } + return num>0; + }, + content:function(){ + "step 0" + event.targets=game.filterPlayer(); + event.targets.remove(player); + "step 1" + if(event.targets.length){ + event.targets.shift().recover(); + event.redo(); + } + }, + ai:{ + expose:0.1 + } + }, + jieyong:{ + trigger:{player:'useCardAfter'}, + direct:true, + filter:function(event,player){ + if(get.position(event.card)!='d') return false; + if(player.hasSkill('jieyong2')) return false; + return player.countCards('he',{color:'black'})>0; + }, + content:function(){ + "step 0" + var next=player.chooseToDiscard('he','是否弃置一张黑色牌并收回'+get.translation(trigger.card)+'?',{color:'black'}); + next.ai=function(card){ + return get.value(trigger.card)-get.value(card); + } + next.logSkill='jieyong'; + "step 1" + if(result.bool){ + player.gain(trigger.card,'gain2'); + player.addTempSkill('jieyong2',['phaseAfter','phaseBegin']); + } + }, + ai:{ + threaten:1.3 + } + }, + jieyong2:{ + filterCard:{suit:'heart'}, + popname:true, + }, + jieyong3:{ + trigger:{player:'useCardBefore'}, + forced:true, + popup:false, + filter:function(event,player){ + return event.skill=='jieyong2'; + }, + content:function(){ + player.popup(trigger.card.name); + player.getStat('skill').jieyong++; + } + }, + jieyong4:{}, + jieyong5:{}, + jieyong6:{}, + zhulu:{ + trigger:{global:'useCardAfter'}, + direct:true, + filter:function(event,player){ + return _status.currentPhase!=player&&event.player!=player&&get.type(event.card)=='trick'&& + get.position(event.card)=='d'&&!player.hasSkill('zhulu2')&& + get.itemtype(event.card)=='card'&&player.countCards('he',{suit:get.suit(event.card)})>0; + }, + content:function(){ + "step 0" + var val=get.value(trigger.card); + var suit=get.suit(trigger.card); + var next=player.chooseToDiscard('he','逐鹿:是否弃置一张'+get.translation(suit)+ + '牌并获得'+get.translation(trigger.card)+'?',{suit:suit}); + next.ai=function(card){ + return val-get.value(card); + }; + next.logSkill='zhulu'; + "step 1" + if(result.bool){ + player.gain(trigger.card,'gain2'); + player.addTempSkill('zhulu2'); + } + }, + ai:{ + threaten:1.2 + } + }, + zhulu2:{}, + xieling:{ + enable:'phaseUse', + usable:1, + filterCard:true, + selectCard:2, + check:function(card){ + return 7-get.value(card); + }, + multitarget:true, + targetprompt:['被移走','移动目标'], + filterTarget:function(card,player,target){ + if(ui.selected.targets.length){ + var from=ui.selected.targets[0]; + var judges=from.getCards('j'); + for(var i=0;i0; + } + }, + selectTarget:2, + content:function(){ + "step 0" + if(targets.length==2){ + player.choosePlayerCard('ej',function(button){ + if(get.attitude(player,targets[0])>get.attitude(player,targets[1])){ + return get.position(button.link)=='j'?10:0; + } + else{ + if(get.position(button.link)=='j') return -10; + return get.equipValue(button.link); + } + },targets[0]); + } + else{ + event.finish(); + } + "step 1" + if(result.bool){ + if(get.position(result.buttons[0].link)=='e'){ + event.targets[1].equip(result.buttons[0].link); + } + else if(result.buttons[0].link.viewAs){ + event.targets[1].addJudge({name:result.buttons[0].link.viewAs},[result.buttons[0].link]); + } + else{ + event.targets[1].addJudge(result.buttons[0].link); + } + event.targets[0].$give(result.buttons[0].link,event.targets[1]) + game.delay(); + } + }, + ai:{ + order:10, + result:{ + target:function(player,target){ + if(ui.selected.targets.length==0){ + if(target.countCards('j')&&get.attitude(player,target)>0) return 1; + if(get.attitude(player,target)<0){ + var players=game.filterPlayer(); + for(var i=0;i0){ + if((target.getEquip(1)&&!players[i].getEquip(1))|| + (target.getEquip(2)&&!players[i].getEquip(2))|| + (target.getEquip(3)&&!players[i].getEquip(3))|| + (target.getEquip(4)&&!players[i].getEquip(4))|| + (target.getEquip(5)&&!players[i].getEquip(5))) + return -1; + } + } + } + return 0; + } + else{ + return get.attitude(player,ui.selected.targets[0])>0?-1:1; + } + }, + }, + expose:0.2, + threaten:1.5 + } + }, + qiangyun:{ + trigger:{player:'loseEnd'}, + frequent:true, + filter:function(event,player){ + if(player.countCards('h')) return false; + for(var i=0;i0; + }, + content:function(){ + 'step 0' + player.discardPlayerCard(target,true); + 'step 1' + if(result.bool){ + var type=get.type(result.cards[0]); + if(type!='basic'&&type!='trick'){ + player.chooseToDiscard('he',true); + event.finish(); + } + else{ + event.card=result.cards[0]; + } + } + else{ + event.finish(); + } + 'step 2' + var card=event.card; + card={name:card.name,nature:card.nature,suit:card.suit,number:card.number}; + if(lib.filter.cardEnabled(card)){ + if(game.hasPlayer(function(current){ + return player.canUse(card,current); + })){ + lib.skill.miaobix.viewAs=card; + var next=player.chooseToUse(); + next.logSkill='miaobi'; + next.set('openskilldialog','妙笔:将一张手牌当'+get.translation(card)+'使用'); + next.set('norestore',true); + next.set('_backupevent','miaobix'); + next.backup('miaobix'); + } + } + }, + ai:{ + order:9, + result:{ + target:-1 + } + } + }, + zhexian:{ + trigger:{player:'loseEnd'}, + usable:1, + filter:function(event,player){ + return _status.currentPhase!=player; + }, + frequent:true, + content:function(){ + player.draw(); + } + }, + guifu:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return player!=target&&target.countCards('e')>0; + }, + content:function(){ + 'step 0' + player.discardPlayerCard(target,'e',true); + 'step 1' + game.asyncDraw([player,target]); + }, + ai:{ + order:8, + threaten:1.5, + result:{ + target:-1, + player:0.5 + } + } + }, + lshengong:{ + enable:'phaseUse', + usable:1, + filterTarget:function(card,player,target){ + return target.hasCard(function(card){ + return !get.info(card).unique; + },'e'); + }, + check:function(card){ + return 6-get.value(card); + }, + filterCard:function(card){ + var info=lib.card[card.name]; + if(!info) return false; + return !info.image&&!info.fullimage; + }, + discard:false, + lose:false, + content:function(){ + 'step 0' + var next=player.choosePlayerCard(target,'e',true); + next.ai=get.buttonValue; + next.filterButton=function(button){ + return !get.info(button.link).unique; + } + 'step 1' + if(result.links[0]){ + cards[0].init([result.links[0].suit,result.links[0].number,result.links[0].name,result.links[0].nature]); + event.card=cards[0]; + player.chooseTarget('选择一个角色装备'+get.translation(result.links),function(card,player,target){ + return !target.isMin(); + }).ai=function(target){ + if(!target.countCards('e',{subtype:get.subtype(event.card)})){ + return get.attitude(player,target); + } + return 0; + } + } + else{ + event.finish(); + } + 'step 2' + if(result.targets&&result.targets[0]&&event.card){ + player.$give(event.card,result.targets[0]); + game.delay(); + event.toequip=result.targets[0]; + } + else{ + event.finish(); + } + 'step 3' + if(event.toequip){ + event.toequip.equip(event.card); + } + }, + ai:{ + order:9, + threaten:1.5, + result:{ + player:function(player){ + if(player.countCards('e')<3) return 1; + return 0; + } + } + } + } + }, + translate:{ + yxs_guanyu:'关羽', + yxs_wuzetian:'武则天', + yxs_caocao:'曹操', + yxs_mozi:'墨子', + yxs_bole:'伯乐', + yxs_aijiyanhou:'埃及艳后', + yxs_diaochan:'貂蝉', + yxs_yangyuhuan:'杨玉环', + yxs_baosi:'褒姒', + yxs_napolun:'拿破仑', + yxs_kaisa:'凯撒', + yxs_zhuyuanzhang:'朱元璋', + yxs_jinke:'荆轲', + yxs_libai:'李白', + yxs_luban:'鲁班', + yxs_lvzhi:'吕雉', + yxs_goujian:'勾践', + yxs_lishimin:'李世民', + yxs_huamulan:'花木兰', + yxs_luobinhan:'罗宾汉', + yxs_chengjisihan:'成吉思汗', + yxs_mingchenghuanghou:'明成皇后', + yxs_wangzhaojun:'王昭君', + yxs_luocheng:'罗成', + yxs_direnjie:'狄仁杰', + yxs_sunwu:'孙武', + yxs_chengyaojin:'程咬金', + yxs_yujix:'虞姬', + yxs_xiangyu:'项羽', + yxs_yingzheng:'嬴政', + yxs_yuefei:'岳飞', + yxs_fuermosi:'福尔摩斯', + yxs_guiguzi:'鬼谷子', + yxs_xiaoqiao:'小乔', + yxs_luzhishen:'鲁智深', + yxs_handingdun:'汉丁顿伯爵', + yxs_zhaoyong:'赵雍', + yxs_yangguang:'杨广', + yxs_tangbohu:'唐伯虎', + yxs_zhangsanfeng:'张三丰', + yxs_nandinggeer:'南丁格尔', + yxs_weizhongxian:'魏忠贤', + yxs_lanlinwang:'兰陵王', + yxs_meixi:'妹喜', + yxs_qinqiong:"秦琼", + + yxs_fanji:"反击", + yxs_fanji2:"反击", + yxs_fanji_info:"当你受到【杀】或【决斗】造成的伤害后,你可以对伤害来源使用一张【杀】。若此【杀】为红色,其不可闪避", + yxs_menshen:"门神", + yxs_menshen2:"门神", + yxs_menshen3:"门神", + yxs_menshen_info:"回合结束阶段,你可选择一名其他角色,若如此做,直到你的下回合开始,所有角色对该角色使用的【杀】或【决斗】均视为对你使用", + zhuxin:'诛心', + zhuxin_info:'出牌阶段限一次,你可以与一名其他角色拼点,若你赢,你对其造成一点伤害', + wlianhuan:'连环', + wlianhuan_info:'你使用杀造成伤害时,可以弃置一张装备区内的牌并令伤害+1', + liebo:'裂帛', + liebo_info:'出牌阶段限一次,你可以将你的手牌与一名其他角色交换(手牌数之差不能多于1)', + yaoji:'妖姬', + yaoji_info:'每当你受到一次伤害,你可以将一张乐不思蜀置入伤害来源的判定区', + guimian:'鬼面', + guimian_info:'锁定技,每当你在出牌阶段使用杀造成伤害,本阶段内出杀次数上限+1', + lyuxue:'浴血', + lyuxue2:'浴血', + lyuxue_info:'锁定技,每当你造成一次伤害,若目标没有浴血标记,你令其获得一个浴血标记;当一名角色失去浴血标记时,其流失一点体力;准备阶段,若场上浴血标记的数量不少于存活角色数的一半(向下取整),你清空浴血标记;当你即将死亡时,你清空浴血标记', + huli:'护理', + huli_info:'出牌阶段,你可以将一张红桃手牌当作桃对距离1以内的角色使用', + yixin:'医心', + yixin_info:'限定技,你可以弃置两张牌,然后令一名已受伤角色回复X点体力并摸4-X张牌(X为该角色已损失的体力值且不超过4)', + xianqu:'先驱', + xianqu_info:'锁定技,你不能成为点数小于8的杀的目标', + zbudao:'布道', + zbudao_info:'摸牌阶段,你可以额外摸一张牌,然后将摸到的牌中的一张交给一名其他角色', + taiji:'太极', + taiji_info:'每当你使用或打出一张闪,你可以使用一张杀', + luobi:'落笔', + luobi_info:'结束阶段,可以摸数量等同于已损失体力值的牌,并以任意方式分配给任意角色', + fengliu:'风流', + fengliu_info:'锁定技,摸牌阶段,你额外摸X张牌,X为存活女性角色数且不超过2', + shiqin:'弑亲', + shiqin_info:'锁定技,其他群势力角色濒死时,你令其立即死亡', + yjujian:'拒谏', + yjujian_info:'出牌阶段限一次,你可以交给一名其他角色一张牌,该角色的锦囊牌不能指定你为目标直到你的下一回合开始', + yaoyi:'徭役', + yaoyi_info:'每当你受到一次伤害,你可以令至多2名非群势力角色交给你一张手牌', + zyhufu:'胡服', + zyhufu_info:'锁定技,当你的装备区内没有防具牌时,你摸牌阶段额外摸一张牌;当你装备区内有防具牌时,你的手牌上限+5', + hanbei:'捍北', + hanbei_info:'锁定技,你的装备区有马时,你的杀不可闪避', + kuangchan:'狂禅', + kuangchan_info:'锁定技,你做主公时,不增加体力上限', + dili:'底力', + // dili_info:'锁定技,摸牌阶段,你额外摸X张牌,X为你已损失的体力值', + dili_info:'锁定技,摸牌阶段,你额外摸X张牌,X为你已损失的体力值的一半,向上取整且最多为2', + chujia:'初嫁', + chujia_info:'出牌阶段限一次,你可以弃置两张相同颜色的手牌,指定任意一名角色摸X张牌。(X为该角色已损失的体力值) ', + zhijie:'知节', + zhijie_info:'出牌阶段限一次,你的红桃手牌可以当做无中生有使用', + baihe:'捭阖', + baihe_info:'出牌阶段限一次,你可以弃置一张牌,选择以下1项执行:(1)横置1名未横置角色,该角色摸一张牌;(2)重置一名已横置角色,该角色弃置一张手牌', + yinyang:'阴阳', + yinyang_info:'出牌阶段限一次,你可以弃置两张手牌并选择3名角色,分别横置或重置这些角色', + xiushen:'修身', + // xiushen_info:'锁定技,结束阶段,若场上有横置角色,你摸两张牌', + xiushen_info:'锁定技,出牌阶段结束时,若场上有横置角色,你摸两张牌', + yanyi:'演绎', + yanyi_info:'出牌阶段限一次,你可以弃置一张黑色牌,指定1名角色和1种花色,若被指定角色的手牌中含有此花色,则受到1点伤害', + jiean:'结案', + jiean_info:'每当【演绎】造成伤害时,你可以摸X张牌,并以任意数量分配给任意角色(X为被【演绎】造成伤害角色的已损失体力值)。', + wumu:'武穆', + wumu_info:'锁定技,你的黑杀无视距离,红色不计入回合内的出杀限制', + ysheshen:'舍身', + ysheshen_info:'每当你受到一点伤害,可以观看牌堆顶的两张牌,并将其交给任意1~2名角色', + sanbanfu:'三板斧', + sanbanfu_info:'当你对其他角色使用杀时,你可以使此杀有如下效果:若对方没有出闪,其受到2点伤害;若对方打出了一张闪,你与其各受到1点伤害;若对方打出了两张闪,你受到一点伤害', + bingsheng:'兵圣', + bingsheng_info:'出牌阶段限一次,你可以弃置两张花色不同的手牌,指定一名其他角色使其体力值与你相同(体力最多变化2点)', + taolue:'韬略', + taolue_info:'锁定技,你的手牌上限+1', + shentan:'神探', + shentan_info:'出牌阶段限一次,你可以弃置一张牌,获得距离2以内的一名角色的手牌,并可以将其交给任意一名角色', + hanqiang:'寒枪', + hanqiang_info:'锁定技,当你没装备武器时,攻击范围+1', + biaoqi:'骠骑', + biaoqi_info:'锁定技,当你出杀指定目标后,若你的攻击范围大于目标体力值,则此杀不可闪避;若你的攻击范围小于目标体力值,你摸一张牌', + wluoyan:'落雁', + wluoyan_info:'锁定技,你防止即将受到的伤害,改为流失一点体力', + heqin:'和亲', + heqin2:'和亲', + heqin3:'和亲', + heqin_info:'限定技,你可以与场上一名男性角色形成【和亲】状态,你与该男性角色于摸牌阶段摸牌数+1。你或者男性角色阵亡时,【和亲】状态消失', + chajue:'察觉', + chajue2:'察觉', + chajue_info:'锁定技,你的回合外,你每受到一次伤害,任何【杀】或普通锦囊牌均对你无效,直到你的回合开始', + tiewan:'铁腕', + tiewan_info:'每当其他角色使用延时类锦囊牌时,你可以立即将一张红色牌当作乐不思蜀使用', + qianglue:'强掠', + qianglue_info:'每当你的杀被闪避时,你可以进行一次判定,若结果为黑色,你可以获得对方的一张牌', + xiadao:'侠盗', + xiadao_info:'每当你造成一次伤害,你可以令一名手牌数不少于受伤害角色的另一名角色获得其一张手牌', + jimin:'机敏', + jimin_info:'当你的装备区内没有牌时,你可以将一张手牌当作闪使用或打出', + sheshu:'射术', + sheshu_info:'锁定技,你的杀无视距离;体力值不小于3的角色不能闪避你的杀', + tongyu:'统御', + tongyu_info:'出牌阶段,你可以弃置一张牌,并转变为罗宾汉(每回合只能转变一次)', + lguiyin:'归隐', + lguiyin_info:'出牌阶段,若你本回合内未造成伤害,你可以摸一张牌,并转变为汉丁顿伯爵(每回合只能转变一次)', + lzhangyi:'仗义', + lzhangyi_info:'你可以将你弃置的卡牌交给一名其他角色', + yizhuang:'易装', + yizhuang2:'易装', + yizhuang_info:'准备阶段,你可以弃置一张牌并选择一名男性角色,获得其所有技能,直到你首次受到伤害', + kongju:'控局', + kongju_info:'锁定技,你的手牌上限为你的体力上限;当你的手牌数小于体力上限时,你不能成为过河拆桥或顺手牵羊的目标;当你的手牌数大于体力上限时,你不能成为乐不思蜀的目标', + tuqiang:'图强', + tuqiang_info:'每当你使用或打出一张闪,你可以摸一张牌', + zhensha:'鸩杀', + zhensha_info:'当场上有角色进入濒死状态时,你可以弃置一张酒或两张黑色手牌,则该角色立即死亡。', + xumou:'蓄谋', + xumou_info:'结束阶段,你可以将武将牌翻面并摸3张牌', + guifu:'鬼斧', + guifu_info:'出牌阶段限一次,你可以指定一名角色装备区内的一张牌,将其弃掉,自己和对方同时摸取一张牌', + lshengong:'神工', + lshengong_info:'出牌阶段限一次,你可以选定场上任意一名角色的装备区的非特殊牌,出自己的一张手牌复制该装备,然后可以选择装备上自己或者别的角色的装备区', + zhexian:'谪仙', + zhexian_info:'当你于一名其他角色的回合内首次失去牌时,你可以摸一张牌', + miaobi:'妙笔', + miaobi_info:'出牌阶段限一次,你可以弃置一名其他角色的一张牌,若此牌是基本牌或普通锦囊,你可以将一张手牌当此牌使用;否则你须弃置一张牌', + cike:'刺客', + cike_info:'你对别的角色出【杀】时可以选择做一次判定:若判定牌为红色花色,则此【杀】不可回避,直接命中;若判定牌为黑色花色,你可以选择弃掉对方一张牌。', + qiangyun:'强运', + qiangyun_info:'每当你失去最后一张手牌,可摸两张牌', + ducai:'独裁', + ducai2:'独裁', + ducai3:'独裁', + ducai_info:'出牌阶段限一次,你可以弃置一张牌,则本轮内除你外的角色不能使用或打出与该手牌花色相同的手牌', + tongling:'统领', + tongling_info:'锁定技,每当一名友方角色造成一次伤害,你获得1个统领标记(标记上限为3)', + fanpu:'反扑', + fanpu_info:'出牌阶段限一次,你可以移去3枚统领标记并视为对攻击范围内的至多3名角色使用一张杀', + fenghuo:'烽火', + fenghuo_info:'你可以将一张装备区内的牌当作南蛮入侵使用', + weiyi:'威仪', + weiyi_info:'每当你受到一次伤害,可以令伤害来源弃置两张牌', + xieling:'挟令', + xieling_info:'出牌阶段,弃掉两张手牌,将任意一名角色装备区或判定区的牌移动到另一名角色对应的区域', + baye:'霸业', + baye_info:'出牌阶段,你可以将一张牌当做本回合内前一张使用的牌来使用。每回合限用一次。', + nvquan:'女权', + nvquan1:'女权', + nvquan2:'女权', + nvquan_info:'你对男性角色使用【杀】或【决斗】时,对方需连续打出两张【闪】或【杀】响应;你不能成为男性角色的决斗目标', + qiandu:'迁都', + qiandu_info:'出牌阶段,你可以弃一张黑色手牌,和一名存活的玩家与其交换位置。每回合限一次。', + budao:'补刀', + budao_info:'你的回合外,你的攻击范围的一名角色受到【杀】的伤害时,你可以对其使用一张【杀】,只要你的【杀】对目标角色造成了伤害,你就可以继续对其使用【杀】。', + feigong:'非攻', + feigong_info:'其他角色使用杀时,若你不是杀的目标,可以弃置一张杀取消之', + jianai:'兼爱', + jianai_info:'每当你回复一点体力,可以令所有其他角色回复一点体力', + bolehuiyan:'慧眼', + bolehuiyan_info:'当一名有手牌的其他角色成为来源不为你的杀的目标时,你可以预言此杀能否命中,若预言正确,你摸一张牌,否则你须弃置一张牌。每回合限发动一次', + xiangma:'相马', + xiangma_info:'锁定技,只要你的体力值大于2点,你的进攻距离+1;只要你的体力值为2点或更低,你的防御距离+1', + seyou:'色诱', + seyou_info:'限定技,出牌阶段,你可以指定任意1名角色,其他所有男性角色需选择1项执行:(1)对你指定的角色出【杀】;(2)令你获得其一张牌。', + sheshi:'蛇噬', + sheshi_info:'每受到1次伤害,可以指定1种花色,依次展示牌堆顶的牌,直到出现指定花色的牌为止,你获得与指定花色不同花色的所有牌(最多展示4张牌)。', + + + fengyi:'凤仪', + fengyi_info:'出牌阶段,你可以弃一张手牌,指定任意目标摸两张牌。(每回合限用一次)', + wange:'婉歌', + wange_info:'摸牌时,你可以少摸一张牌,则结束阶段你可以抽取一名其他角色的手牌,至少1张,至多X张(X为你当前的掉血量)。', + nichang:'霓裳', + nichang2:'霓裳', + nichang_info:'摸牌时,你可以选择不摸牌,并在结束阶段展示手牌,每少一种花色摸一张牌', + fengyan:'丰艳', + fengyan_info:'你可以获得其他男性角色的红色判定牌。', + zhulu:'逐鹿', + zhulu_info:'回合外,当有普通锦囊牌结算完毕后,你可以立即弃掉一张相同花色手牌或装备区的牌,获得这张锦囊牌。', + jieyong:'节用', + jieyong2:'节用', + jieyong_info:'你使用的卡牌进入弃牌堆后,你可以弃置一张黑色牌并重新获得之(每回合限一次)', + shangtong:'尚同', + shangtong_info:'每当你令其他角色恢复1点血量或掉1点血量时,你可以摸1张牌(摸牌上限为4)', + feiming:'非命', + feiming_info:'其他角色对你造成伤害时,你可以令该角色须选择1项执行:1,将1张红桃花色手牌交给你;2,流失1点血量', + yxsrenwang:'人望', + yxsrenwang_info:'出牌阶段,你可以弃掉2张牌并指定一名手牌数大于你的角色,你摸牌至与该角色手牌数相等,每阶段限一次。', + shiwei:'施威', + shiwei_info:'当其他角色失去最后一张手牌时,你可以将牌堆顶的一张牌背面朝上置于该角色面前,该角色回合,跳过出牌阶段并弃掉这张牌。', + yxswushuang:'无双', + yxswushuang_info:'出牌阶段,你使用【杀】时可同时打出两张【杀】,则该【杀】具有以下效果之一:1,伤害+1;2,额外指定两个目标', + xiaoyong:'骁勇', + xiaoyong_info:'你可以将黑色手牌当作【杀】来使用', + qinzheng:'亲征', + qinzheng_info:'出牌阶段,你对其他角色造成伤害时,可以令场上任意角色摸一张牌。', + juma:'拒马', + juma_info:'你与其他角色的距离始终视为1。', + }, + }; +}); diff --git a/character/zhuogui.js b/character/zhuogui.js index 5b02774b3..622aa5724 100644 --- a/character/zhuogui.js +++ b/character/zhuogui.js @@ -1,342 +1,342 @@ -'use strict'; -game.import('character',function(lib,game,ui,get,ai,_status){ - return { - name:'zhuogui', - character:{ - nianshou:['male','shu',4,['nianrui','qixiang']], - mamian:['male','qun',4,['lianyu','guiji']], - niutou:['male','shu',4,['manjia','xiaoshou']], - baiwuchang:['male','qun',3,['qiangzheng','moukui']], - heiwuchang:['male','qun',3,['suoling','xixing']], - }, - skill:{ - qixiang:{ - group:['qixiang1','qixiang2'], - ai:{ - effect:{ - target:function(card,player,target,current){ - if(card.name=='lebu'&&card.name=='bingliang') return 0.8; - } - } - } - }, - qixiang1:{ - trigger:{player:'judge'}, - forced:true, - filter:function(event,player){ - if(event.card){ - if(event.card.viewAs){ - return event.card.viewAs=='lebu'; - } - else{ - return event.card.name=='lebu'; - } - } - }, - content:function(){ - player.addTempSkill('qixiang3','phaseJudgeAfter'); - } - }, - qixiang2:{ - trigger:{player:'judge'}, - forced:true, - filter:function(event,player){ - if(event.card){ - if(event.card.viewAs){ - return event.card.viewAs=='bingliang'; - } - else{ - return event.card.name=='bingliang'; - } - } - }, - content:function(){ - player.addTempSkill('qixiang4','phaseJudgeAfter'); - } - }, - qixiang3:{ - mod:{ - suit:function(card,suit){ - if(suit=='diamond') return 'heart'; - } - } - }, - qixiang4:{ - mod:{ - suit:function(card,suit){ - if(suit=='spade') return 'club'; - } - } - }, - nianrui:{ - trigger:{player:['phaseBegin','phaseEnd']}, - content:function(){ - "step 0" - player.judge(function(card){ - return get.color(card)=='red'?1:0; - }); - "step 1" - if(result.bool){ - player.draw(); - } - } - }, - lianyu:{ - enable:'phaseUse', - usable:1, - filterCard:{color:'red'}, - check:function(card){return 6-get.value(card)}, - filterTarget:true, - selectTarget:-1, - line:'fire', - content:function(){ - target.damage('fire'); - }, - ai:{ - result:{ - player:function(card,player,target){ - var eff=0; - for(var i=0;iplayers[1].hp&&players[0]!=player; - }, - check:function(event,player){ - var players=game.players.slice(0); - players.sort(function(a,b){ - return b.hp-a.hp; - }); - return get.damageEffect(players[0],player,player,'fire')>0; - }, - prompt:function(){ - var players=game.players.slice(0); - players.sort(function(a,b){ - return b.hp-a.hp; - }); - return '枭首:是否对'+get.translation(players[0])+'造成一点火焰伤害?'; - }, - content:function(){ - var players=game.players.slice(0); - players.sort(function(a,b){ - return b.hp-a.hp; - }); - if(players[0].hp>players[1].hp&&players[0]!=player){ - players[0].damage('fire'); - player.line(players[0],'fire'); - } - }, - ai:{ - expose:0.2 - } - }, - guiji:{ - trigger:{player:'phaseJudgeBegin'}, - forced:true, - content:function(){ - player.discard(player.getCards('j').randomGet()); - }, - filter:function(event ,player){ - return player.countCards('j')>0; - }, - ai:{ - effect:{ - target:function(card,player,target,current){ - if(get.type(card)=='delay'&&target.countCards('j')==0) return 0.1; - } - } - } - }, - qiangzheng:{ - audio:2, - trigger:{player:'phaseEnd'}, - direct:true, - forced:true, - filter:function(event,player){ - for(var i=0;i0; - }).ai=function(target){ - return -get.attitude(player,target); - }; - "step 1" - if(result.targets&&result.targets.length){ - player.logSkill('qiangzheng',result.targets); - player.gain(result.targets[0].getCards('h').randomGet(),result.targets[0]); - result.targets[0].$give(1,player); - game.delay(); - } - }, - ai:{ - threaten:1.7 - } - }, - suoling:{ - trigger:{player:'phaseEnd'}, - forced:true, - filter:function(event,player){ - if(player.isLinked()) return true; - for(var i=0;iplayers[1].hp&&players[0]!=player; + }, + check:function(event,player){ + var players=game.players.slice(0); + players.sort(function(a,b){ + return b.hp-a.hp; + }); + return get.damageEffect(players[0],player,player,'fire')>0; + }, + prompt:function(){ + var players=game.players.slice(0); + players.sort(function(a,b){ + return b.hp-a.hp; + }); + return '枭首:是否对'+get.translation(players[0])+'造成一点火焰伤害?'; + }, + content:function(){ + var players=game.players.slice(0); + players.sort(function(a,b){ + return b.hp-a.hp; + }); + if(players[0].hp>players[1].hp&&players[0]!=player){ + players[0].damage('fire'); + player.line(players[0],'fire'); + } + }, + ai:{ + expose:0.2 + } + }, + guiji:{ + trigger:{player:'phaseJudgeBegin'}, + forced:true, + content:function(){ + player.discard(player.getCards('j').randomGet()); + }, + filter:function(event ,player){ + return player.countCards('j')>0; + }, + ai:{ + effect:{ + target:function(card,player,target,current){ + if(get.type(card)=='delay'&&target.countCards('j')==0) return 0.1; + } + } + } + }, + qiangzheng:{ + audio:2, + trigger:{player:'phaseEnd'}, + direct:true, + forced:true, + filter:function(event,player){ + for(var i=0;i0; + }).ai=function(target){ + return -get.attitude(player,target); + }; + "step 1" + if(result.targets&&result.targets.length){ + player.logSkill('qiangzheng',result.targets); + player.gain(result.targets[0].getCards('h').randomGet(),result.targets[0]); + result.targets[0].$give(1,player); + game.delay(); + } + }, + ai:{ + threaten:1.7 + } + }, + suoling:{ + trigger:{player:'phaseEnd'}, + forced:true, + filter:function(event,player){ + if(player.isLinked()) return true; + for(var i=0;i 0.5) { - _this.noSleepVideo.currentTime = Math.random(); - } - }); - } - }); - } - } - - _createClass(NoSleep, [{ - key: '_addSourceToVideo', - value: function _addSourceToVideo(element, type, dataURI) { - var source = document.createElement('source'); - source.src = dataURI; - source.type = 'video/' + type; - element.appendChild(source); - } - }, { - key: 'enable', - value: function enable() { - if (oldIOS) { - this.disable(); - console.warn('\n NoSleep enabled for older iOS devices. This can interrupt\n active or long-running network requests from completing successfully.\n See https://github.com/richtr/NoSleep.js/issues/15 for more details.\n '); - this.noSleepTimer = window.setInterval(function () { - if (!document.hidden) { - window.location.href = window.location.href.split('#')[0]; - window.setTimeout(window.stop, 0); - } - }, 15000); - } else { - this.noSleepVideo.play(); - } - } - }, { - key: 'disable', - value: function disable() { - if (oldIOS) { - if (this.noSleepTimer) { - console.warn('\n NoSleep now disabled for older iOS devices.\n '); - window.clearInterval(this.noSleepTimer); - this.noSleepTimer = null; - } - } else { - this.noSleepVideo.pause(); - } - } - }]); - - return NoSleep; -}(); - -; - -module.exports = NoSleep; - -/***/ }), -/* 1 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = { - webm: 'data:video/webm;base64,GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA=', - mp4: 'data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQADQGgcIQADQGgcAAAACQGeYUQn/wALKCEAA0BoHAAAAAkBnmNEJ/8ACykhAANAaBwhAANAaBwAAAANQZpoNExDP/6plgAMuSEAA0BoHAAAAAtBnoZFESwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBnqVEJ/8ACykhAANAaBwAAAAJAZ6nRCf/AAsoIQADQGgcIQADQGgcAAAADUGarDRMQz/+qZYADLghAANAaBwAAAALQZ7KRRUsK/8ACPkhAANAaBwAAAAJAZ7pRCf/AAsoIQADQGgcIQADQGgcAAAACQGe60Qn/wALKCEAA0BoHAAAAA1BmvA0TEM//qmWAAy5IQADQGgcIQADQGgcAAAAC0GfDkUVLCv/AAj5IQADQGgcAAAACQGfLUQn/wALKSEAA0BoHCEAA0BoHAAAAAkBny9EJ/8ACyghAANAaBwAAAANQZs0NExDP/6plgAMuCEAA0BoHAAAAAtBn1JFFSwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBn3FEJ/8ACyghAANAaBwAAAAJAZ9zRCf/AAsoIQADQGgcIQADQGgcAAAADUGbeDRMQz/+qZYADLkhAANAaBwAAAALQZ+WRRUsK/8ACPghAANAaBwhAANAaBwAAAAJAZ+1RCf/AAspIQADQGgcAAAACQGft0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bm7w0TEM//qmWAAy4IQADQGgcAAAAC0Gf2kUVLCv/AAj5IQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHAAAAAkBn/tEJ/8ACykhAANAaBwAAAANQZvgNExDP/6plgAMuSEAA0BoHCEAA0BoHAAAAAtBnh5FFSwr/wAI+CEAA0BoHAAAAAkBnj1EJ/8ACyghAANAaBwhAANAaBwAAAAJAZ4/RCf/AAspIQADQGgcAAAADUGaJDRMQz/+qZYADLghAANAaBwAAAALQZ5CRRUsK/8ACPkhAANAaBwhAANAaBwAAAAJAZ5hRCf/AAsoIQADQGgcAAAACQGeY0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bmmg0TEM//qmWAAy5IQADQGgcAAAAC0GehkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGepUQn/wALKSEAA0BoHAAAAAkBnqdEJ/8ACyghAANAaBwAAAANQZqsNExDP/6plgAMuCEAA0BoHCEAA0BoHAAAAAtBnspFFSwr/wAI+SEAA0BoHAAAAAkBnulEJ/8ACyghAANAaBwhAANAaBwAAAAJAZ7rRCf/AAsoIQADQGgcAAAADUGa8DRMQz/+qZYADLkhAANAaBwhAANAaBwAAAALQZ8ORRUsK/8ACPkhAANAaBwAAAAJAZ8tRCf/AAspIQADQGgcIQADQGgcAAAACQGfL0Qn/wALKCEAA0BoHAAAAA1BmzQ0TEM//qmWAAy4IQADQGgcAAAAC0GfUkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGfcUQn/wALKCEAA0BoHAAAAAkBn3NEJ/8ACyghAANAaBwhAANAaBwAAAANQZt4NExC//6plgAMuSEAA0BoHAAAAAtBn5ZFFSwr/wAI+CEAA0BoHCEAA0BoHAAAAAkBn7VEJ/8ACykhAANAaBwAAAAJAZ+3RCf/AAspIQADQGgcAAAADUGbuzRMQn/+nhAAYsAhAANAaBwhAANAaBwAAAAJQZ/aQhP/AAspIQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHAAACiFtb292AAAAbG12aGQAAAAA1YCCX9WAgl8AAAPoAAAH/AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAGGlvZHMAAAAAEICAgAcAT////v7/AAAF+XRyYWsAAABcdGtoZAAAAAPVgIJf1YCCXwAAAAEAAAAAAAAH0AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAygAAAMoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAB9AAABdwAAEAAAAABXFtZGlhAAAAIG1kaGQAAAAA1YCCX9WAgl8AAV+QAAK/IFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAUcbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAE3HN0YmwAAACYc3RzZAAAAAAAAAABAAAAiGF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAygDKAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAyYXZjQwFNQCj/4QAbZ01AKOyho3ySTUBAQFAAAAMAEAAr8gDxgxlgAQAEaO+G8gAAABhzdHRzAAAAAAAAAAEAAAA8AAALuAAAABRzdHNzAAAAAAAAAAEAAAABAAAB8GN0dHMAAAAAAAAAPAAAAAEAABdwAAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAAC7gAAAAAQAAF3AAAAABAAAAAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAEEc3RzegAAAAAAAAAAAAAAPAAAAzQAAAAQAAAADQAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAANAAAADQAAAQBzdGNvAAAAAAAAADwAAAAwAAADZAAAA3QAAAONAAADoAAAA7kAAAPQAAAD6wAAA/4AAAQXAAAELgAABEMAAARcAAAEbwAABIwAAAShAAAEugAABM0AAATkAAAE/wAABRIAAAUrAAAFQgAABV0AAAVwAAAFiQAABaAAAAW1AAAFzgAABeEAAAX+AAAGEwAABiwAAAY/AAAGVgAABnEAAAaEAAAGnQAABrQAAAbPAAAG4gAABvUAAAcSAAAHJwAAB0AAAAdTAAAHcAAAB4UAAAeeAAAHsQAAB8gAAAfjAAAH9gAACA8AAAgmAAAIQQAACFQAAAhnAAAIhAAACJcAAAMsdHJhawAAAFx0a2hkAAAAA9WAgl/VgIJfAAAAAgAAAAAAAAf8AAAAAAAAAAAAAAABAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAACsm1kaWEAAAAgbWRoZAAAAADVgIJf1YCCXwAArEQAAWAAVcQAAAAAACdoZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU3RlcmVvAAAAAmNtaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAidzdGJsAAAAZ3N0c2QAAAAAAAAAAQAAAFdtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAArEQAAAAAADNlc2RzAAAAAAOAgIAiAAIABICAgBRAFQAAAAADDUAAAAAABYCAgAISEAaAgIABAgAAABhzdHRzAAAAAAAAAAEAAABYAAAEAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAAGAAAAWAAAAXBzdGNvAAAAAAAAAFgAAAOBAAADhwAAA5oAAAOtAAADswAAA8oAAAPfAAAD5QAAA/gAAAQLAAAEEQAABCgAAAQ9AAAEUAAABFYAAARpAAAEgAAABIYAAASbAAAErgAABLQAAATHAAAE3gAABPMAAAT5AAAFDAAABR8AAAUlAAAFPAAABVEAAAVXAAAFagAABX0AAAWDAAAFmgAABa8AAAXCAAAFyAAABdsAAAXyAAAF+AAABg0AAAYgAAAGJgAABjkAAAZQAAAGZQAABmsAAAZ+AAAGkQAABpcAAAauAAAGwwAABskAAAbcAAAG7wAABwYAAAcMAAAHIQAABzQAAAc6AAAHTQAAB2QAAAdqAAAHfwAAB5IAAAeYAAAHqwAAB8IAAAfXAAAH3QAAB/AAAAgDAAAICQAACCAAAAg1AAAIOwAACE4AAAhhAAAIeAAACH4AAAiRAAAIpAAACKoAAAiwAAAItgAACLwAAAjCAAAAFnVkdGEAAAAObmFtZVN0ZXJlbwAAAHB1ZHRhAAAAaG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAO2lsc3QAAAAzqXRvbwAAACtkYXRhAAAAAQAAAABIYW5kQnJha2UgMC4xMC4yIDIwMTUwNjExMDA=' -}; - -/***/ }) -/******/ ]); +/*! NoSleep.js v0.9.0 - git.io/vfn01 - Rich Tibbett - MIT license */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["NoSleep"] = factory(); + else + root["NoSleep"] = factory(); +})(typeof self !== 'undefined' ? self : this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var _require = __webpack_require__(1), + webm = _require.webm, + mp4 = _require.mp4; + +// Detect iOS browsers < version 10 + + +var oldIOS = typeof navigator !== 'undefined' && parseFloat(('' + (/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0, ''])[1]).replace('undefined', '3_2').replace('_', '.').replace('_', '')) < 10 && !window.MSStream; + +var NoSleep = function () { + function NoSleep() { + var _this = this; + + _classCallCheck(this, NoSleep); + + if (oldIOS) { + this.noSleepTimer = null; + } else { + // Set up no sleep video element + this.noSleepVideo = document.createElement('video'); + + this.noSleepVideo.setAttribute('muted', ''); + this.noSleepVideo.setAttribute('title', 'No Sleep'); + this.noSleepVideo.setAttribute('playsinline', ''); + + this._addSourceToVideo(this.noSleepVideo, 'webm', webm); + this._addSourceToVideo(this.noSleepVideo, 'mp4', mp4); + + this.noSleepVideo.addEventListener('loadedmetadata', function () { + if (_this.noSleepVideo.duration <= 1) { + // webm source + _this.noSleepVideo.setAttribute('loop', ''); + } else { + // mp4 source + _this.noSleepVideo.addEventListener('timeupdate', function () { + if (_this.noSleepVideo.currentTime > 0.5) { + _this.noSleepVideo.currentTime = Math.random(); + } + }); + } + }); + } + } + + _createClass(NoSleep, [{ + key: '_addSourceToVideo', + value: function _addSourceToVideo(element, type, dataURI) { + var source = document.createElement('source'); + source.src = dataURI; + source.type = 'video/' + type; + element.appendChild(source); + } + }, { + key: 'enable', + value: function enable() { + if (oldIOS) { + this.disable(); + console.warn('\n NoSleep enabled for older iOS devices. This can interrupt\n active or long-running network requests from completing successfully.\n See https://github.com/richtr/NoSleep.js/issues/15 for more details.\n '); + this.noSleepTimer = window.setInterval(function () { + if (!document.hidden) { + window.location.href = window.location.href.split('#')[0]; + window.setTimeout(window.stop, 0); + } + }, 15000); + } else { + this.noSleepVideo.play(); + } + } + }, { + key: 'disable', + value: function disable() { + if (oldIOS) { + if (this.noSleepTimer) { + console.warn('\n NoSleep now disabled for older iOS devices.\n '); + window.clearInterval(this.noSleepTimer); + this.noSleepTimer = null; + } + } else { + this.noSleepVideo.pause(); + } + } + }]); + + return NoSleep; +}(); + +; + +module.exports = NoSleep; + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = { + webm: 'data:video/webm;base64,GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA=', + mp4: 'data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQADQGgcIQADQGgcAAAACQGeYUQn/wALKCEAA0BoHAAAAAkBnmNEJ/8ACykhAANAaBwhAANAaBwAAAANQZpoNExDP/6plgAMuSEAA0BoHAAAAAtBnoZFESwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBnqVEJ/8ACykhAANAaBwAAAAJAZ6nRCf/AAsoIQADQGgcIQADQGgcAAAADUGarDRMQz/+qZYADLghAANAaBwAAAALQZ7KRRUsK/8ACPkhAANAaBwAAAAJAZ7pRCf/AAsoIQADQGgcIQADQGgcAAAACQGe60Qn/wALKCEAA0BoHAAAAA1BmvA0TEM//qmWAAy5IQADQGgcIQADQGgcAAAAC0GfDkUVLCv/AAj5IQADQGgcAAAACQGfLUQn/wALKSEAA0BoHCEAA0BoHAAAAAkBny9EJ/8ACyghAANAaBwAAAANQZs0NExDP/6plgAMuCEAA0BoHAAAAAtBn1JFFSwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBn3FEJ/8ACyghAANAaBwAAAAJAZ9zRCf/AAsoIQADQGgcIQADQGgcAAAADUGbeDRMQz/+qZYADLkhAANAaBwAAAALQZ+WRRUsK/8ACPghAANAaBwhAANAaBwAAAAJAZ+1RCf/AAspIQADQGgcAAAACQGft0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bm7w0TEM//qmWAAy4IQADQGgcAAAAC0Gf2kUVLCv/AAj5IQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHAAAAAkBn/tEJ/8ACykhAANAaBwAAAANQZvgNExDP/6plgAMuSEAA0BoHCEAA0BoHAAAAAtBnh5FFSwr/wAI+CEAA0BoHAAAAAkBnj1EJ/8ACyghAANAaBwhAANAaBwAAAAJAZ4/RCf/AAspIQADQGgcAAAADUGaJDRMQz/+qZYADLghAANAaBwAAAALQZ5CRRUsK/8ACPkhAANAaBwhAANAaBwAAAAJAZ5hRCf/AAsoIQADQGgcAAAACQGeY0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bmmg0TEM//qmWAAy5IQADQGgcAAAAC0GehkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGepUQn/wALKSEAA0BoHAAAAAkBnqdEJ/8ACyghAANAaBwAAAANQZqsNExDP/6plgAMuCEAA0BoHCEAA0BoHAAAAAtBnspFFSwr/wAI+SEAA0BoHAAAAAkBnulEJ/8ACyghAANAaBwhAANAaBwAAAAJAZ7rRCf/AAsoIQADQGgcAAAADUGa8DRMQz/+qZYADLkhAANAaBwhAANAaBwAAAALQZ8ORRUsK/8ACPkhAANAaBwAAAAJAZ8tRCf/AAspIQADQGgcIQADQGgcAAAACQGfL0Qn/wALKCEAA0BoHAAAAA1BmzQ0TEM//qmWAAy4IQADQGgcAAAAC0GfUkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGfcUQn/wALKCEAA0BoHAAAAAkBn3NEJ/8ACyghAANAaBwhAANAaBwAAAANQZt4NExC//6plgAMuSEAA0BoHAAAAAtBn5ZFFSwr/wAI+CEAA0BoHCEAA0BoHAAAAAkBn7VEJ/8ACykhAANAaBwAAAAJAZ+3RCf/AAspIQADQGgcAAAADUGbuzRMQn/+nhAAYsAhAANAaBwhAANAaBwAAAAJQZ/aQhP/AAspIQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHAAACiFtb292AAAAbG12aGQAAAAA1YCCX9WAgl8AAAPoAAAH/AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAGGlvZHMAAAAAEICAgAcAT////v7/AAAF+XRyYWsAAABcdGtoZAAAAAPVgIJf1YCCXwAAAAEAAAAAAAAH0AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAygAAAMoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAB9AAABdwAAEAAAAABXFtZGlhAAAAIG1kaGQAAAAA1YCCX9WAgl8AAV+QAAK/IFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAUcbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAE3HN0YmwAAACYc3RzZAAAAAAAAAABAAAAiGF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAygDKAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAyYXZjQwFNQCj/4QAbZ01AKOyho3ySTUBAQFAAAAMAEAAr8gDxgxlgAQAEaO+G8gAAABhzdHRzAAAAAAAAAAEAAAA8AAALuAAAABRzdHNzAAAAAAAAAAEAAAABAAAB8GN0dHMAAAAAAAAAPAAAAAEAABdwAAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAAC7gAAAAAQAAF3AAAAABAAAAAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAEEc3RzegAAAAAAAAAAAAAAPAAAAzQAAAAQAAAADQAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAANAAAADQAAAQBzdGNvAAAAAAAAADwAAAAwAAADZAAAA3QAAAONAAADoAAAA7kAAAPQAAAD6wAAA/4AAAQXAAAELgAABEMAAARcAAAEbwAABIwAAAShAAAEugAABM0AAATkAAAE/wAABRIAAAUrAAAFQgAABV0AAAVwAAAFiQAABaAAAAW1AAAFzgAABeEAAAX+AAAGEwAABiwAAAY/AAAGVgAABnEAAAaEAAAGnQAABrQAAAbPAAAG4gAABvUAAAcSAAAHJwAAB0AAAAdTAAAHcAAAB4UAAAeeAAAHsQAAB8gAAAfjAAAH9gAACA8AAAgmAAAIQQAACFQAAAhnAAAIhAAACJcAAAMsdHJhawAAAFx0a2hkAAAAA9WAgl/VgIJfAAAAAgAAAAAAAAf8AAAAAAAAAAAAAAABAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAACsm1kaWEAAAAgbWRoZAAAAADVgIJf1YCCXwAArEQAAWAAVcQAAAAAACdoZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU3RlcmVvAAAAAmNtaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAidzdGJsAAAAZ3N0c2QAAAAAAAAAAQAAAFdtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAArEQAAAAAADNlc2RzAAAAAAOAgIAiAAIABICAgBRAFQAAAAADDUAAAAAABYCAgAISEAaAgIABAgAAABhzdHRzAAAAAAAAAAEAAABYAAAEAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAAGAAAAWAAAAXBzdGNvAAAAAAAAAFgAAAOBAAADhwAAA5oAAAOtAAADswAAA8oAAAPfAAAD5QAAA/gAAAQLAAAEEQAABCgAAAQ9AAAEUAAABFYAAARpAAAEgAAABIYAAASbAAAErgAABLQAAATHAAAE3gAABPMAAAT5AAAFDAAABR8AAAUlAAAFPAAABVEAAAVXAAAFagAABX0AAAWDAAAFmgAABa8AAAXCAAAFyAAABdsAAAXyAAAF+AAABg0AAAYgAAAGJgAABjkAAAZQAAAGZQAABmsAAAZ+AAAGkQAABpcAAAauAAAGwwAABskAAAbcAAAG7wAABwYAAAcMAAAHIQAABzQAAAc6AAAHTQAAB2QAAAdqAAAHfwAAB5IAAAeYAAAHqwAAB8IAAAfXAAAH3QAAB/AAAAgDAAAICQAACCAAAAg1AAAIOwAACE4AAAhhAAAIeAAACH4AAAiRAAAIpAAACKoAAAiwAAAItgAACLwAAAjCAAAAFnVkdGEAAAAObmFtZVN0ZXJlbwAAAHB1ZHRhAAAAaG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAO2lsc3QAAAAzqXRvbwAAACtkYXRhAAAAAQAAAABIYW5kQnJha2UgMC4xMC4yIDIwMTUwNjExMDA=' +}; + +/***/ }) +/******/ ]); }); \ No newline at end of file diff --git a/game/codemirror.js b/game/codemirror.js index 833dfc82e..67050635e 100644 --- a/game/codemirror.js +++ b/game/codemirror.js @@ -1,9877 +1,9877 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -// This is CodeMirror (http://codemirror.net), a code editor -// implemented in JavaScript on top of the browser's DOM. -// -// You can find some technical background for some of the code below -// at http://marijnhaverbeke.nl/blog/#cm-internals . - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - module.exports = mod(); - else if (typeof define == "function" && define.amd) // AMD - return define([], mod); - else // Plain browser env - (this || window).CodeMirror = mod(); -})(function() { - "use strict"; - - // BROWSER SNIFFING - - // Kludges for bugs and behavior differences that can't be feature - // detected are enabled based on userAgent etc sniffing. - var userAgent = navigator.userAgent; - var platform = navigator.platform; - - var gecko = /gecko\/\d/i.test(userAgent); - var ie_upto10 = /MSIE \d/.test(userAgent); - var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); - var ie = ie_upto10 || ie_11up; - var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); - var webkit = /WebKit\//.test(userAgent); - var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); - var chrome = /Chrome\//.test(userAgent); - var presto = /Opera\//.test(userAgent); - var safari = /Apple Computer/.test(navigator.vendor); - var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); - var phantom = /PhantomJS/.test(userAgent); - - var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); - // This is woefully incomplete. Suggestions for alternative methods welcome. - var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); - var mac = ios || /Mac/.test(platform); - var chromeOS = /\bCrOS\b/.test(userAgent); - var windows = /win/i.test(platform); - - var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); - if (presto_version) presto_version = Number(presto_version[1]); - if (presto_version && presto_version >= 15) { presto = false; webkit = true; } - // Some browsers use the wrong event properties to signal cmd/ctrl on OS X - var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); - var captureRightClick = gecko || (ie && ie_version >= 9); - - // Optimize some code when these features are not used. - var sawReadOnlySpans = false, sawCollapsedSpans = false; - - // EDITOR CONSTRUCTOR - - // A CodeMirror instance represents an editor. This is the object - // that user code is usually dealing with. - - function CodeMirror(place, options) { - if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); - - this.options = options = options ? copyObj(options) : {}; - // Determine effective options based on given values and defaults. - copyObj(defaults, options, false); - setGuttersForLineNumbers(options); - - var doc = options.value; - if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator); - this.doc = doc; - - var input = new CodeMirror.inputStyles[options.inputStyle](this); - var display = this.display = new Display(place, doc, input); - display.wrapper.CodeMirror = this; - updateGutters(this); - themeChanged(this); - if (options.lineWrapping) - this.display.wrapper.className += " CodeMirror-wrap"; - if (options.autofocus && !mobile) display.input.focus(); - initScrollbars(this); - - this.state = { - keyMaps: [], // stores maps added by addKeyMap - overlays: [], // highlighting overlays, as added by addOverlay - modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info - overwrite: false, - delayingBlurEvent: false, - focused: false, - suppressEdits: false, // used to disable editing during key handlers when in readOnly mode - pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll - selectingText: false, - draggingText: false, - highlight: new Delayed(), // stores highlight worker timeout - keySeq: null, // Unfinished key sequence - specialChars: null - }; - - var cm = this; - - // Override magic textarea content restore that IE sometimes does - // on our hidden textarea on reload - if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20); - - registerEventHandlers(this); - ensureGlobalHandlers(); - - startOperation(this); - this.curOp.forceUpdate = true; - attachDoc(this, doc); - - if ((options.autofocus && !mobile) || cm.hasFocus()) - setTimeout(bind(onFocus, this), 20); - else - onBlur(this); - - for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) - optionHandlers[opt](this, options[opt], Init); - maybeUpdateLineNumberWidth(this); - if (options.finishInit) options.finishInit(this); - for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); - endOperation(this); - // Suppress optimizelegibility in Webkit, since it breaks text - // measuring on line wrapping boundaries. - if (webkit && options.lineWrapping && - getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") - display.lineDiv.style.textRendering = "auto"; - } - - // DISPLAY CONSTRUCTOR - - // The display handles the DOM integration, both for input reading - // and content drawing. It holds references to DOM nodes and - // display-related state. - - function Display(place, doc, input) { - var d = this; - this.input = input; - - // Covers bottom-right square when both scrollbars are present. - d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); - d.scrollbarFiller.setAttribute("cm-not-content", "true"); - // Covers bottom of gutter when coverGutterNextToScrollbar is on - // and h scrollbar is present. - d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); - d.gutterFiller.setAttribute("cm-not-content", "true"); - // Will contain the actual code, positioned to cover the viewport. - d.lineDiv = elt("div", null, "CodeMirror-code"); - // Elements are added to these to represent selection and cursors. - d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); - d.cursorDiv = elt("div", null, "CodeMirror-cursors"); - // A visibility: hidden element used to find the size of things. - d.measure = elt("div", null, "CodeMirror-measure"); - // When lines outside of the viewport are measured, they are drawn in this. - d.lineMeasure = elt("div", null, "CodeMirror-measure"); - // Wraps everything that needs to exist inside the vertically-padded coordinate system - d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], - null, "position: relative; outline: none"); - // Moved around its parent to cover visible view. - d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); - // Set to the height of the document, allowing scrolling. - d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); - d.sizerWidth = null; - // Behavior of elts with overflow: auto and padding is - // inconsistent across browsers. This is used to ensure the - // scrollable area is big enough. - d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); - // Will contain the gutters, if any. - d.gutters = elt("div", null, "CodeMirror-gutters"); - d.lineGutter = null; - // Actual scrollable element. - d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); - d.scroller.setAttribute("tabIndex", "-1"); - // The element in which the editor lives. - d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); - - // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) - if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } - if (!webkit && !(gecko && mobile)) d.scroller.draggable = true; - - if (place) { - if (place.appendChild) place.appendChild(d.wrapper); - else place(d.wrapper); - } - - // Current rendered range (may be bigger than the view window). - d.viewFrom = d.viewTo = doc.first; - d.reportedViewFrom = d.reportedViewTo = doc.first; - // Information about the rendered lines. - d.view = []; - d.renderedView = null; - // Holds info about a single rendered line when it was rendered - // for measurement, while not in view. - d.externalMeasured = null; - // Empty space (in pixels) above the view - d.viewOffset = 0; - d.lastWrapHeight = d.lastWrapWidth = 0; - d.updateLineNumbers = null; - - d.nativeBarWidth = d.barHeight = d.barWidth = 0; - d.scrollbarsClipped = false; - - // Used to only resize the line number gutter when necessary (when - // the amount of lines crosses a boundary that makes its width change) - d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; - // Set to true when a non-horizontal-scrolling line widget is - // added. As an optimization, line widget aligning is skipped when - // this is false. - d.alignWidgets = false; - - d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; - - // Tracks the maximum line length so that the horizontal scrollbar - // can be kept static when scrolling. - d.maxLine = null; - d.maxLineLength = 0; - d.maxLineChanged = false; - - // Used for measuring wheel scrolling granularity - d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; - - // True when shift is held down. - d.shift = false; - - // Used to track whether anything happened since the context menu - // was opened. - d.selForContextMenu = null; - - d.activeTouch = null; - - input.init(d); - } - - // STATE UPDATES - - // Used to get the editor into a consistent state again when options change. - - function loadMode(cm) { - cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); - resetModeState(cm); - } - - function resetModeState(cm) { - cm.doc.iter(function(line) { - if (line.stateAfter) line.stateAfter = null; - if (line.styles) line.styles = null; - }); - cm.doc.frontier = cm.doc.first; - startWorker(cm, 100); - cm.state.modeGen++; - if (cm.curOp) regChange(cm); - } - - function wrappingChanged(cm) { - if (cm.options.lineWrapping) { - addClass(cm.display.wrapper, "CodeMirror-wrap"); - cm.display.sizer.style.minWidth = ""; - cm.display.sizerWidth = null; - } else { - rmClass(cm.display.wrapper, "CodeMirror-wrap"); - findMaxLine(cm); - } - estimateLineHeights(cm); - regChange(cm); - clearCaches(cm); - setTimeout(function(){updateScrollbars(cm);}, 100); - } - - // Returns a function that estimates the height of a line, to use as - // first approximation until the line becomes visible (and is thus - // properly measurable). - function estimateHeight(cm) { - var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; - var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); - return function(line) { - if (lineIsHidden(cm.doc, line)) return 0; - - var widgetsHeight = 0; - if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { - if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; - } - - if (wrapping) - return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; - else - return widgetsHeight + th; - }; - } - - function estimateLineHeights(cm) { - var doc = cm.doc, est = estimateHeight(cm); - doc.iter(function(line) { - var estHeight = est(line); - if (estHeight != line.height) updateLineHeight(line, estHeight); - }); - } - - function themeChanged(cm) { - cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + - cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); - clearCaches(cm); - } - - function guttersChanged(cm) { - updateGutters(cm); - regChange(cm); - setTimeout(function(){alignHorizontally(cm);}, 20); - } - - // Rebuild the gutter elements, ensure the margin to the left of the - // code matches their width. - function updateGutters(cm) { - var gutters = cm.display.gutters, specs = cm.options.gutters; - removeChildren(gutters); - for (var i = 0; i < specs.length; ++i) { - var gutterClass = specs[i]; - var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); - if (gutterClass == "CodeMirror-linenumbers") { - cm.display.lineGutter = gElt; - gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; - } - } - gutters.style.display = i ? "" : "none"; - updateGutterSpace(cm); - } - - function updateGutterSpace(cm) { - var width = cm.display.gutters.offsetWidth; - cm.display.sizer.style.marginLeft = width + "px"; - } - - // Compute the character length of a line, taking into account - // collapsed ranges (see markText) that might hide parts, and join - // other lines onto it. - function lineLength(line) { - if (line.height == 0) return 0; - var len = line.text.length, merged, cur = line; - while (merged = collapsedSpanAtStart(cur)) { - var found = merged.find(0, true); - cur = found.from.line; - len += found.from.ch - found.to.ch; - } - cur = line; - while (merged = collapsedSpanAtEnd(cur)) { - var found = merged.find(0, true); - len -= cur.text.length - found.from.ch; - cur = found.to.line; - len += cur.text.length - found.to.ch; - } - return len; - } - - // Find the longest line in the document. - function findMaxLine(cm) { - var d = cm.display, doc = cm.doc; - d.maxLine = getLine(doc, doc.first); - d.maxLineLength = lineLength(d.maxLine); - d.maxLineChanged = true; - doc.iter(function(line) { - var len = lineLength(line); - if (len > d.maxLineLength) { - d.maxLineLength = len; - d.maxLine = line; - } - }); - } - - // Make sure the gutters options contains the element - // "CodeMirror-linenumbers" when the lineNumbers option is true. - function setGuttersForLineNumbers(options) { - var found = indexOf(options.gutters, "CodeMirror-linenumbers"); - if (found == -1 && options.lineNumbers) { - options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); - } else if (found > -1 && !options.lineNumbers) { - options.gutters = options.gutters.slice(0); - options.gutters.splice(found, 1); - } - } - - // SCROLLBARS - - // Prepare DOM reads needed to update the scrollbars. Done in one - // shot to minimize update/measure roundtrips. - function measureForScrollbars(cm) { - var d = cm.display, gutterW = d.gutters.offsetWidth; - var docH = Math.round(cm.doc.height + paddingVert(cm.display)); - return { - clientHeight: d.scroller.clientHeight, - viewHeight: d.wrapper.clientHeight, - scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, - viewWidth: d.wrapper.clientWidth, - barLeft: cm.options.fixedGutter ? gutterW : 0, - docHeight: docH, - scrollHeight: docH + scrollGap(cm) + d.barHeight, - nativeBarWidth: d.nativeBarWidth, - gutterWidth: gutterW - }; - } - - function NativeScrollbars(place, scroll, cm) { - this.cm = cm; - var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); - var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); - place(vert); place(horiz); - - on(vert, "scroll", function() { - if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); - }); - on(horiz, "scroll", function() { - if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); - }); - - this.checkedZeroWidth = false; - // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). - if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; - } - - NativeScrollbars.prototype = copyObj({ - update: function(measure) { - var needsH = measure.scrollWidth > measure.clientWidth + 1; - var needsV = measure.scrollHeight > measure.clientHeight + 1; - var sWidth = measure.nativeBarWidth; - - if (needsV) { - this.vert.style.display = "block"; - this.vert.style.bottom = needsH ? sWidth + "px" : "0"; - var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); - // A bug in IE8 can cause this value to be negative, so guard it. - this.vert.firstChild.style.height = - Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; - } else { - this.vert.style.display = ""; - this.vert.firstChild.style.height = "0"; - } - - if (needsH) { - this.horiz.style.display = "block"; - this.horiz.style.right = needsV ? sWidth + "px" : "0"; - this.horiz.style.left = measure.barLeft + "px"; - var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); - this.horiz.firstChild.style.width = - (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; - } else { - this.horiz.style.display = ""; - this.horiz.firstChild.style.width = "0"; - } - - if (!this.checkedZeroWidth && measure.clientHeight > 0) { - if (sWidth == 0) this.zeroWidthHack(); - this.checkedZeroWidth = true; - } - - return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; - }, - setScrollLeft: function(pos) { - if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; - if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz); - }, - setScrollTop: function(pos) { - if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; - if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert); - }, - zeroWidthHack: function() { - var w = mac && !mac_geMountainLion ? "12px" : "18px"; - this.horiz.style.height = this.vert.style.width = w; - this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; - this.disableHoriz = new Delayed; - this.disableVert = new Delayed; - }, - enableZeroWidthBar: function(bar, delay) { - bar.style.pointerEvents = "auto"; - function maybeDisable() { - // To find out whether the scrollbar is still visible, we - // check whether the element under the pixel in the bottom - // left corner of the scrollbar box is the scrollbar box - // itself (when the bar is still visible) or its filler child - // (when the bar is hidden). If it is still visible, we keep - // it enabled, if it's hidden, we disable pointer events. - var box = bar.getBoundingClientRect(); - var elt = document.elementFromPoint(box.left + 1, box.bottom - 1); - if (elt != bar) bar.style.pointerEvents = "none"; - else delay.set(1000, maybeDisable); - } - delay.set(1000, maybeDisable); - }, - clear: function() { - var parent = this.horiz.parentNode; - parent.removeChild(this.horiz); - parent.removeChild(this.vert); - } - }, NativeScrollbars.prototype); - - function NullScrollbars() {} - - NullScrollbars.prototype = copyObj({ - update: function() { return {bottom: 0, right: 0}; }, - setScrollLeft: function() {}, - setScrollTop: function() {}, - clear: function() {} - }, NullScrollbars.prototype); - - CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; - - function initScrollbars(cm) { - if (cm.display.scrollbars) { - cm.display.scrollbars.clear(); - if (cm.display.scrollbars.addClass) - rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); - } - - cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) { - cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); - // Prevent clicks in the scrollbars from killing focus - on(node, "mousedown", function() { - if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0); - }); - node.setAttribute("cm-not-content", "true"); - }, function(pos, axis) { - if (axis == "horizontal") setScrollLeft(cm, pos); - else setScrollTop(cm, pos); - }, cm); - if (cm.display.scrollbars.addClass) - addClass(cm.display.wrapper, cm.display.scrollbars.addClass); - } - - function updateScrollbars(cm, measure) { - if (!measure) measure = measureForScrollbars(cm); - var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; - updateScrollbarsInner(cm, measure); - for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { - if (startWidth != cm.display.barWidth && cm.options.lineWrapping) - updateHeightsInViewport(cm); - updateScrollbarsInner(cm, measureForScrollbars(cm)); - startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; - } - } - - // Re-synchronize the fake scrollbars with the actual size of the - // content. - function updateScrollbarsInner(cm, measure) { - var d = cm.display; - var sizes = d.scrollbars.update(measure); - - d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; - d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; - d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" - - if (sizes.right && sizes.bottom) { - d.scrollbarFiller.style.display = "block"; - d.scrollbarFiller.style.height = sizes.bottom + "px"; - d.scrollbarFiller.style.width = sizes.right + "px"; - } else d.scrollbarFiller.style.display = ""; - if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { - d.gutterFiller.style.display = "block"; - d.gutterFiller.style.height = sizes.bottom + "px"; - d.gutterFiller.style.width = measure.gutterWidth + "px"; - } else d.gutterFiller.style.display = ""; - } - - // Compute the lines that are visible in a given viewport (defaults - // the the current scroll position). viewport may contain top, - // height, and ensure (see op.scrollToPos) properties. - function visibleLines(display, doc, viewport) { - var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; - top = Math.floor(top - paddingTop(display)); - var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; - - var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); - // Ensure is a {from: {line, ch}, to: {line, ch}} object, and - // forces those lines into the viewport (if possible). - if (viewport && viewport.ensure) { - var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; - if (ensureFrom < from) { - from = ensureFrom; - to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); - } else if (Math.min(ensureTo, doc.lastLine()) >= to) { - from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); - to = ensureTo; - } - } - return {from: from, to: Math.max(to, from + 1)}; - } - - // LINE NUMBERS - - // Re-align line numbers and gutter marks to compensate for - // horizontal scrolling. - function alignHorizontally(cm) { - var display = cm.display, view = display.view; - if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; - var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; - var gutterW = display.gutters.offsetWidth, left = comp + "px"; - for (var i = 0; i < view.length; i++) if (!view[i].hidden) { - if (cm.options.fixedGutter && view[i].gutter) - view[i].gutter.style.left = left; - var align = view[i].alignable; - if (align) for (var j = 0; j < align.length; j++) - align[j].style.left = left; - } - if (cm.options.fixedGutter) - display.gutters.style.left = (comp + gutterW) + "px"; - } - - // Used to ensure that the line number gutter is still the right - // size for the current document size. Returns true when an update - // is needed. - function maybeUpdateLineNumberWidth(cm) { - if (!cm.options.lineNumbers) return false; - var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; - if (last.length != display.lineNumChars) { - var test = display.measure.appendChild(elt("div", [elt("div", last)], - "CodeMirror-linenumber CodeMirror-gutter-elt")); - var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; - display.lineGutter.style.width = ""; - display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; - display.lineNumWidth = display.lineNumInnerWidth + padding; - display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; - display.lineGutter.style.width = display.lineNumWidth + "px"; - updateGutterSpace(cm); - return true; - } - return false; - } - - function lineNumberFor(options, i) { - return String(options.lineNumberFormatter(i + options.firstLineNumber)); - } - - // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, - // but using getBoundingClientRect to get a sub-pixel-accurate - // result. - function compensateForHScroll(display) { - return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; - } - - // DISPLAY DRAWING - - function DisplayUpdate(cm, viewport, force) { - var display = cm.display; - - this.viewport = viewport; - // Store some values that we'll need later (but don't want to force a relayout for) - this.visible = visibleLines(display, cm.doc, viewport); - this.editorIsHidden = !display.wrapper.offsetWidth; - this.wrapperHeight = display.wrapper.clientHeight; - this.wrapperWidth = display.wrapper.clientWidth; - this.oldDisplayWidth = displayWidth(cm); - this.force = force; - this.dims = getDimensions(cm); - this.events = []; - } - - DisplayUpdate.prototype.signal = function(emitter, type) { - if (hasHandler(emitter, type)) - this.events.push(arguments); - }; - DisplayUpdate.prototype.finish = function() { - for (var i = 0; i < this.events.length; i++) - signal.apply(null, this.events[i]); - }; - - function maybeClipScrollbars(cm) { - var display = cm.display; - if (!display.scrollbarsClipped && display.scroller.offsetWidth) { - display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; - display.heightForcer.style.height = scrollGap(cm) + "px"; - display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; - display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; - display.scrollbarsClipped = true; - } - } - - // Does the actual updating of the line display. Bails out - // (returning false) when there is nothing to be done and forced is - // false. - function updateDisplayIfNeeded(cm, update) { - var display = cm.display, doc = cm.doc; - - if (update.editorIsHidden) { - resetView(cm); - return false; - } - - // Bail out if the visible area is already rendered and nothing changed. - if (!update.force && - update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && - (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && - display.renderedView == display.view && countDirtyView(cm) == 0) - return false; - - if (maybeUpdateLineNumberWidth(cm)) { - resetView(cm); - update.dims = getDimensions(cm); - } - - // Compute a suitable new viewport (from & to) - var end = doc.first + doc.size; - var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); - var to = Math.min(end, update.visible.to + cm.options.viewportMargin); - if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); - if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); - if (sawCollapsedSpans) { - from = visualLineNo(cm.doc, from); - to = visualLineEndNo(cm.doc, to); - } - - var different = from != display.viewFrom || to != display.viewTo || - display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; - adjustView(cm, from, to); - - display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); - // Position the mover div to align with the current scroll position - cm.display.mover.style.top = display.viewOffset + "px"; - - var toUpdate = countDirtyView(cm); - if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && - (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) - return false; - - // For big changes, we hide the enclosing element during the - // update, since that speeds up the operations on most browsers. - var focused = activeElt(); - if (toUpdate > 4) display.lineDiv.style.display = "none"; - patchDisplay(cm, display.updateLineNumbers, update.dims); - if (toUpdate > 4) display.lineDiv.style.display = ""; - display.renderedView = display.view; - // There might have been a widget with a focused element that got - // hidden or updated, if so re-focus it. - if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); - - // Prevent selection and cursors from interfering with the scroll - // width and height. - removeChildren(display.cursorDiv); - removeChildren(display.selectionDiv); - display.gutters.style.height = display.sizer.style.minHeight = 0; - - if (different) { - display.lastWrapHeight = update.wrapperHeight; - display.lastWrapWidth = update.wrapperWidth; - startWorker(cm, 400); - } - - display.updateLineNumbers = null; - - return true; - } - - function postUpdateDisplay(cm, update) { - var viewport = update.viewport; - - for (var first = true;; first = false) { - if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { - // Clip forced viewport to actual scrollable area. - if (viewport && viewport.top != null) - viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; - // Updated line heights might result in the drawn area not - // actually covering the viewport. Keep looping until it does. - update.visible = visibleLines(cm.display, cm.doc, viewport); - if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) - break; - } - if (!updateDisplayIfNeeded(cm, update)) break; - updateHeightsInViewport(cm); - var barMeasure = measureForScrollbars(cm); - updateSelection(cm); - updateScrollbars(cm, barMeasure); - setDocumentHeight(cm, barMeasure); - } - - update.signal(cm, "update", cm); - if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { - update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); - cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; - } - } - - function updateDisplaySimple(cm, viewport) { - var update = new DisplayUpdate(cm, viewport); - if (updateDisplayIfNeeded(cm, update)) { - updateHeightsInViewport(cm); - postUpdateDisplay(cm, update); - var barMeasure = measureForScrollbars(cm); - updateSelection(cm); - updateScrollbars(cm, barMeasure); - setDocumentHeight(cm, barMeasure); - update.finish(); - } - } - - function setDocumentHeight(cm, measure) { - cm.display.sizer.style.minHeight = measure.docHeight + "px"; - cm.display.heightForcer.style.top = measure.docHeight + "px"; - cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"; - } - - // Read the actual heights of the rendered lines, and update their - // stored heights to match. - function updateHeightsInViewport(cm) { - var display = cm.display; - var prevBottom = display.lineDiv.offsetTop; - for (var i = 0; i < display.view.length; i++) { - var cur = display.view[i], height; - if (cur.hidden) continue; - if (ie && ie_version < 8) { - var bot = cur.node.offsetTop + cur.node.offsetHeight; - height = bot - prevBottom; - prevBottom = bot; - } else { - var box = cur.node.getBoundingClientRect(); - height = box.bottom - box.top; - } - var diff = cur.line.height - height; - if (height < 2) height = textHeight(display); - if (diff > .001 || diff < -.001) { - updateLineHeight(cur.line, height); - updateWidgetHeight(cur.line); - if (cur.rest) for (var j = 0; j < cur.rest.length; j++) - updateWidgetHeight(cur.rest[j]); - } - } - } - - // Read and store the height of line widgets associated with the - // given line. - function updateWidgetHeight(line) { - if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) - line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight; - } - - // Do a bulk-read of the DOM positions and sizes needed to draw the - // view, so that we don't interleave reading and writing to the DOM. - function getDimensions(cm) { - var d = cm.display, left = {}, width = {}; - var gutterLeft = d.gutters.clientLeft; - for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { - left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; - width[cm.options.gutters[i]] = n.clientWidth; - } - return {fixedPos: compensateForHScroll(d), - gutterTotalWidth: d.gutters.offsetWidth, - gutterLeft: left, - gutterWidth: width, - wrapperWidth: d.wrapper.clientWidth}; - } - - // Sync the actual display DOM structure with display.view, removing - // nodes for lines that are no longer in view, and creating the ones - // that are not there yet, and updating the ones that are out of - // date. - function patchDisplay(cm, updateNumbersFrom, dims) { - var display = cm.display, lineNumbers = cm.options.lineNumbers; - var container = display.lineDiv, cur = container.firstChild; - - function rm(node) { - var next = node.nextSibling; - // Works around a throw-scroll bug in OS X Webkit - if (webkit && mac && cm.display.currentWheelTarget == node) - node.style.display = "none"; - else - node.parentNode.removeChild(node); - return next; - } - - var view = display.view, lineN = display.viewFrom; - // Loop over the elements in the view, syncing cur (the DOM nodes - // in display.lineDiv) with the view as we go. - for (var i = 0; i < view.length; i++) { - var lineView = view[i]; - if (lineView.hidden) { - } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet - var node = buildLineElement(cm, lineView, lineN, dims); - container.insertBefore(node, cur); - } else { // Already drawn - while (cur != lineView.node) cur = rm(cur); - var updateNumber = lineNumbers && updateNumbersFrom != null && - updateNumbersFrom <= lineN && lineView.lineNumber; - if (lineView.changes) { - if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; - updateLineForChanges(cm, lineView, lineN, dims); - } - if (updateNumber) { - removeChildren(lineView.lineNumber); - lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); - } - cur = lineView.node.nextSibling; - } - lineN += lineView.size; - } - while (cur) cur = rm(cur); - } - - // When an aspect of a line changes, a string is added to - // lineView.changes. This updates the relevant part of the line's - // DOM structure. - function updateLineForChanges(cm, lineView, lineN, dims) { - for (var j = 0; j < lineView.changes.length; j++) { - var type = lineView.changes[j]; - if (type == "text") updateLineText(cm, lineView); - else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); - else if (type == "class") updateLineClasses(lineView); - else if (type == "widget") updateLineWidgets(cm, lineView, dims); - } - lineView.changes = null; - } - - // Lines with gutter elements, widgets or a background class need to - // be wrapped, and have the extra elements added to the wrapper div - function ensureLineWrapped(lineView) { - if (lineView.node == lineView.text) { - lineView.node = elt("div", null, null, "position: relative"); - if (lineView.text.parentNode) - lineView.text.parentNode.replaceChild(lineView.node, lineView.text); - lineView.node.appendChild(lineView.text); - if (ie && ie_version < 8) lineView.node.style.zIndex = 2; - } - return lineView.node; - } - - function updateLineBackground(lineView) { - var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; - if (cls) cls += " CodeMirror-linebackground"; - if (lineView.background) { - if (cls) lineView.background.className = cls; - else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } - } else if (cls) { - var wrap = ensureLineWrapped(lineView); - lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); - } - } - - // Wrapper around buildLineContent which will reuse the structure - // in display.externalMeasured when possible. - function getLineContent(cm, lineView) { - var ext = cm.display.externalMeasured; - if (ext && ext.line == lineView.line) { - cm.display.externalMeasured = null; - lineView.measure = ext.measure; - return ext.built; - } - return buildLineContent(cm, lineView); - } - - // Redraw the line's text. Interacts with the background and text - // classes because the mode may output tokens that influence these - // classes. - function updateLineText(cm, lineView) { - var cls = lineView.text.className; - var built = getLineContent(cm, lineView); - if (lineView.text == lineView.node) lineView.node = built.pre; - lineView.text.parentNode.replaceChild(built.pre, lineView.text); - lineView.text = built.pre; - if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { - lineView.bgClass = built.bgClass; - lineView.textClass = built.textClass; - updateLineClasses(lineView); - } else if (cls) { - lineView.text.className = cls; - } - } - - function updateLineClasses(lineView) { - updateLineBackground(lineView); - if (lineView.line.wrapClass) - ensureLineWrapped(lineView).className = lineView.line.wrapClass; - else if (lineView.node != lineView.text) - lineView.node.className = ""; - var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; - lineView.text.className = textClass || ""; - } - - function updateLineGutter(cm, lineView, lineN, dims) { - if (lineView.gutter) { - lineView.node.removeChild(lineView.gutter); - lineView.gutter = null; - } - if (lineView.gutterBackground) { - lineView.node.removeChild(lineView.gutterBackground); - lineView.gutterBackground = null; - } - if (lineView.line.gutterClass) { - var wrap = ensureLineWrapped(lineView); - lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, - "left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + - "px; width: " + dims.gutterTotalWidth + "px"); - wrap.insertBefore(lineView.gutterBackground, lineView.text); - } - var markers = lineView.line.gutterMarkers; - if (cm.options.lineNumbers || markers) { - var wrap = ensureLineWrapped(lineView); - var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " + - (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"); - cm.display.input.setUneditable(gutterWrap); - wrap.insertBefore(gutterWrap, lineView.text); - if (lineView.line.gutterClass) - gutterWrap.className += " " + lineView.line.gutterClass; - if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) - lineView.lineNumber = gutterWrap.appendChild( - elt("div", lineNumberFor(cm.options, lineN), - "CodeMirror-linenumber CodeMirror-gutter-elt", - "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " - + cm.display.lineNumInnerWidth + "px")); - if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { - var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; - if (found) - gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + - dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); - } - } - } - - function updateLineWidgets(cm, lineView, dims) { - if (lineView.alignable) lineView.alignable = null; - for (var node = lineView.node.firstChild, next; node; node = next) { - var next = node.nextSibling; - if (node.className == "CodeMirror-linewidget") - lineView.node.removeChild(node); - } - insertLineWidgets(cm, lineView, dims); - } - - // Build a line's DOM representation from scratch - function buildLineElement(cm, lineView, lineN, dims) { - var built = getLineContent(cm, lineView); - lineView.text = lineView.node = built.pre; - if (built.bgClass) lineView.bgClass = built.bgClass; - if (built.textClass) lineView.textClass = built.textClass; - - updateLineClasses(lineView); - updateLineGutter(cm, lineView, lineN, dims); - insertLineWidgets(cm, lineView, dims); - return lineView.node; - } - - // A lineView may contain multiple logical lines (when merged by - // collapsed spans). The widgets for all of them need to be drawn. - function insertLineWidgets(cm, lineView, dims) { - insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); - if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) - insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); - } - - function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { - if (!line.widgets) return; - var wrap = ensureLineWrapped(lineView); - for (var i = 0, ws = line.widgets; i < ws.length; ++i) { - var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); - if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true"); - positionLineWidget(widget, node, lineView, dims); - cm.display.input.setUneditable(node); - if (allowAbove && widget.above) - wrap.insertBefore(node, lineView.gutter || lineView.text); - else - wrap.appendChild(node); - signalLater(widget, "redraw"); - } - } - - function positionLineWidget(widget, node, lineView, dims) { - if (widget.noHScroll) { - (lineView.alignable || (lineView.alignable = [])).push(node); - var width = dims.wrapperWidth; - node.style.left = dims.fixedPos + "px"; - if (!widget.coverGutter) { - width -= dims.gutterTotalWidth; - node.style.paddingLeft = dims.gutterTotalWidth + "px"; - } - node.style.width = width + "px"; - } - if (widget.coverGutter) { - node.style.zIndex = 5; - node.style.position = "relative"; - if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; - } - } - - // POSITION OBJECT - - // A Pos instance represents a position within the text. - var Pos = CodeMirror.Pos = function(line, ch) { - if (!(this instanceof Pos)) return new Pos(line, ch); - this.line = line; this.ch = ch; - }; - - // Compare two positions, return 0 if they are the same, a negative - // number when a is less, and a positive number otherwise. - var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; }; - - function copyPos(x) {return Pos(x.line, x.ch);} - function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } - function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } - - // INPUT HANDLING - - function ensureFocus(cm) { - if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } - } - - // This will be set to a {lineWise: bool, text: [string]} object, so - // that, when pasting, we know what kind of selections the copied - // text was made out of. - var lastCopied = null; - - function applyTextInput(cm, inserted, deleted, sel, origin) { - var doc = cm.doc; - cm.display.shift = false; - if (!sel) sel = doc.sel; - - var paste = cm.state.pasteIncoming || origin == "paste"; - var textLines = doc.splitLines(inserted), multiPaste = null - // When pasing N lines into N selections, insert one line per selection - if (paste && sel.ranges.length > 1) { - if (lastCopied && lastCopied.text.join("\n") == inserted) { - if (sel.ranges.length % lastCopied.text.length == 0) { - multiPaste = []; - for (var i = 0; i < lastCopied.text.length; i++) - multiPaste.push(doc.splitLines(lastCopied.text[i])); - } - } else if (textLines.length == sel.ranges.length) { - multiPaste = map(textLines, function(l) { return [l]; }); - } - } - - // Normal behavior is to insert the new text into every selection - for (var i = sel.ranges.length - 1; i >= 0; i--) { - var range = sel.ranges[i]; - var from = range.from(), to = range.to(); - if (range.empty()) { - if (deleted && deleted > 0) // Handle deletion - from = Pos(from.line, from.ch - deleted); - else if (cm.state.overwrite && !paste) // Handle overwrite - to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); - else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) - from = to = Pos(from.line, 0) - } - var updateInput = cm.curOp.updateInput; - var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, - origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")}; - makeChange(cm.doc, changeEvent); - signalLater(cm, "inputRead", cm, changeEvent); - } - if (inserted && !paste) - triggerElectric(cm, inserted); - - ensureCursorVisible(cm); - cm.curOp.updateInput = updateInput; - cm.curOp.typing = true; - cm.state.pasteIncoming = cm.state.cutIncoming = false; - } - - function handlePaste(e, cm) { - var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); - if (pasted) { - e.preventDefault(); - if (!cm.isReadOnly() && !cm.options.disableInput) - runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); }); - return true; - } - } - - function triggerElectric(cm, inserted) { - // When an 'electric' character is inserted, immediately trigger a reindent - if (!cm.options.electricChars || !cm.options.smartIndent) return; - var sel = cm.doc.sel; - - for (var i = sel.ranges.length - 1; i >= 0; i--) { - var range = sel.ranges[i]; - if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue; - var mode = cm.getModeAt(range.head); - var indented = false; - if (mode.electricChars) { - for (var j = 0; j < mode.electricChars.length; j++) - if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { - indented = indentLine(cm, range.head.line, "smart"); - break; - } - } else if (mode.electricInput) { - if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) - indented = indentLine(cm, range.head.line, "smart"); - } - if (indented) signalLater(cm, "electricInput", cm, range.head.line); - } - } - - function copyableRanges(cm) { - var text = [], ranges = []; - for (var i = 0; i < cm.doc.sel.ranges.length; i++) { - var line = cm.doc.sel.ranges[i].head.line; - var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; - ranges.push(lineRange); - text.push(cm.getRange(lineRange.anchor, lineRange.head)); - } - return {text: text, ranges: ranges}; - } - - function disableBrowserMagic(field) { - field.setAttribute("autocorrect", "off"); - field.setAttribute("autocapitalize", "off"); - field.setAttribute("spellcheck", "false"); - } - - // TEXTAREA INPUT STYLE - - function TextareaInput(cm) { - this.cm = cm; - // See input.poll and input.reset - this.prevInput = ""; - - // Flag that indicates whether we expect input to appear real soon - // now (after some event like 'keypress' or 'input') and are - // polling intensively. - this.pollingFast = false; - // Self-resetting timeout for the poller - this.polling = new Delayed(); - // Tracks when input.reset has punted to just putting a short - // string into the textarea instead of the full selection. - this.inaccurateSelection = false; - // Used to work around IE issue with selection being forgotten when focus moves away from textarea - this.hasSelection = false; - this.composing = null; - }; - - function hiddenTextarea() { - var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none"); - var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); - // The textarea is kept positioned near the cursor to prevent the - // fact that it'll be scrolled into view on input from scrolling - // our fake cursor out of view. On webkit, when wrap=off, paste is - // very slow. So make the area wide instead. - if (webkit) te.style.width = "1000px"; - else te.setAttribute("wrap", "off"); - // If border: 0; -- iOS fails to open keyboard (issue #1287) - if (ios) te.style.border = "1px solid black"; - disableBrowserMagic(te); - return div; - } - - TextareaInput.prototype = copyObj({ - init: function(display) { - var input = this, cm = this.cm; - - // Wraps and hides input textarea - var div = this.wrapper = hiddenTextarea(); - // The semihidden textarea that is focused when the editor is - // focused, and receives input. - var te = this.textarea = div.firstChild; - display.wrapper.insertBefore(div, display.wrapper.firstChild); - - // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) - if (ios) te.style.width = "0px"; - - on(te, "input", function() { - if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null; - input.poll(); - }); - - on(te, "paste", function(e) { - if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return - - cm.state.pasteIncoming = true; - input.fastPoll(); - }); - - function prepareCopyCut(e) { - if (signalDOMEvent(cm, e)) return - if (cm.somethingSelected()) { - lastCopied = {lineWise: false, text: cm.getSelections()}; - if (input.inaccurateSelection) { - input.prevInput = ""; - input.inaccurateSelection = false; - te.value = lastCopied.text.join("\n"); - selectInput(te); - } - } else if (!cm.options.lineWiseCopyCut) { - return; - } else { - var ranges = copyableRanges(cm); - lastCopied = {lineWise: true, text: ranges.text}; - if (e.type == "cut") { - cm.setSelections(ranges.ranges, null, sel_dontScroll); - } else { - input.prevInput = ""; - te.value = ranges.text.join("\n"); - selectInput(te); - } - } - if (e.type == "cut") cm.state.cutIncoming = true; - } - on(te, "cut", prepareCopyCut); - on(te, "copy", prepareCopyCut); - - on(display.scroller, "paste", function(e) { - if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return; - cm.state.pasteIncoming = true; - input.focus(); - }); - - // Prevent normal selection in the editor (we handle our own) - on(display.lineSpace, "selectstart", function(e) { - if (!eventInWidget(display, e)) e_preventDefault(e); - }); - - on(te, "compositionstart", function() { - var start = cm.getCursor("from"); - if (input.composing) input.composing.range.clear() - input.composing = { - start: start, - range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) - }; - }); - on(te, "compositionend", function() { - if (input.composing) { - input.poll(); - input.composing.range.clear(); - input.composing = null; - } - }); - }, - - prepareSelection: function() { - // Redraw the selection and/or cursor - var cm = this.cm, display = cm.display, doc = cm.doc; - var result = prepareSelection(cm); - - // Move the hidden textarea near the cursor to prevent scrolling artifacts - if (cm.options.moveInputWithCursor) { - var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); - var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); - result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, - headPos.top + lineOff.top - wrapOff.top)); - result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, - headPos.left + lineOff.left - wrapOff.left)); - } - - return result; - }, - - showSelection: function(drawn) { - var cm = this.cm, display = cm.display; - removeChildrenAndAdd(display.cursorDiv, drawn.cursors); - removeChildrenAndAdd(display.selectionDiv, drawn.selection); - if (drawn.teTop != null) { - this.wrapper.style.top = drawn.teTop + "px"; - this.wrapper.style.left = drawn.teLeft + "px"; - } - }, - - // Reset the input to correspond to the selection (or to be empty, - // when not typing and nothing is selected) - reset: function(typing) { - if (this.contextMenuPending) return; - var minimal, selected, cm = this.cm, doc = cm.doc; - if (cm.somethingSelected()) { - this.prevInput = ""; - var range = doc.sel.primary(); - minimal = hasCopyEvent && - (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); - var content = minimal ? "-" : selected || cm.getSelection(); - this.textarea.value = content; - if (cm.state.focused) selectInput(this.textarea); - if (ie && ie_version >= 9) this.hasSelection = content; - } else if (!typing) { - this.prevInput = this.textarea.value = ""; - if (ie && ie_version >= 9) this.hasSelection = null; - } - this.inaccurateSelection = minimal; - }, - - getField: function() { return this.textarea; }, - - supportsTouch: function() { return false; }, - - focus: function() { - if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { - try { this.textarea.focus(); } - catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM - } - }, - - blur: function() { this.textarea.blur(); }, - - resetPosition: function() { - this.wrapper.style.top = this.wrapper.style.left = 0; - }, - - receivedFocus: function() { this.slowPoll(); }, - - // Poll for input changes, using the normal rate of polling. This - // runs as long as the editor is focused. - slowPoll: function() { - var input = this; - if (input.pollingFast) return; - input.polling.set(this.cm.options.pollInterval, function() { - input.poll(); - if (input.cm.state.focused) input.slowPoll(); - }); - }, - - // When an event has just come in that is likely to add or change - // something in the input textarea, we poll faster, to ensure that - // the change appears on the screen quickly. - fastPoll: function() { - var missed = false, input = this; - input.pollingFast = true; - function p() { - var changed = input.poll(); - if (!changed && !missed) {missed = true; input.polling.set(60, p);} - else {input.pollingFast = false; input.slowPoll();} - } - input.polling.set(20, p); - }, - - // Read input from the textarea, and update the document to match. - // When something is selected, it is present in the textarea, and - // selected (unless it is huge, in which case a placeholder is - // used). When nothing is selected, the cursor sits after previously - // seen text (can be empty), which is stored in prevInput (we must - // not reset the textarea when typing, because that breaks IME). - poll: function() { - var cm = this.cm, input = this.textarea, prevInput = this.prevInput; - // Since this is called a *lot*, try to bail out as cheaply as - // possible when it is clear that nothing happened. hasSelection - // will be the case when there is a lot of text in the textarea, - // in which case reading its value would be expensive. - if (this.contextMenuPending || !cm.state.focused || - (hasSelection(input) && !prevInput && !this.composing) || - cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) - return false; - - var text = input.value; - // If nothing changed, bail. - if (text == prevInput && !cm.somethingSelected()) return false; - // Work around nonsensical selection resetting in IE9/10, and - // inexplicable appearance of private area unicode characters on - // some key combos in Mac (#2689). - if (ie && ie_version >= 9 && this.hasSelection === text || - mac && /[\uf700-\uf7ff]/.test(text)) { - cm.display.input.reset(); - return false; - } - - if (cm.doc.sel == cm.display.selForContextMenu) { - var first = text.charCodeAt(0); - if (first == 0x200b && !prevInput) prevInput = "\u200b"; - if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); } - } - // Find the part of the input that is actually new - var same = 0, l = Math.min(prevInput.length, text.length); - while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; - - var self = this; - runInOp(cm, function() { - applyTextInput(cm, text.slice(same), prevInput.length - same, - null, self.composing ? "*compose" : null); - - // Don't leave long text in the textarea, since it makes further polling slow - if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = ""; - else self.prevInput = text; - - if (self.composing) { - self.composing.range.clear(); - self.composing.range = cm.markText(self.composing.start, cm.getCursor("to"), - {className: "CodeMirror-composing"}); - } - }); - return true; - }, - - ensurePolled: function() { - if (this.pollingFast && this.poll()) this.pollingFast = false; - }, - - onKeyPress: function() { - if (ie && ie_version >= 9) this.hasSelection = null; - this.fastPoll(); - }, - - onContextMenu: function(e) { - var input = this, cm = input.cm, display = cm.display, te = input.textarea; - var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; - if (!pos || presto) return; // Opera is difficult. - - // Reset the current text selection only if the click is done outside of the selection - // and 'resetSelectionOnContextMenu' option is true. - var reset = cm.options.resetSelectionOnContextMenu; - if (reset && cm.doc.sel.contains(pos) == -1) - operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); - - var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText; - input.wrapper.style.cssText = "position: absolute" - var wrapperBox = input.wrapper.getBoundingClientRect() - te.style.cssText = "position: absolute; width: 30px; height: 30px; top: " + (e.clientY - wrapperBox.top - 5) + - "px; left: " + (e.clientX - wrapperBox.left - 5) + "px; z-index: 1000; background: " + - (ie ? "rgba(255, 255, 255, .05)" : "transparent") + - "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; - if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) - display.input.focus(); - if (webkit) window.scrollTo(null, oldScrollY); - display.input.reset(); - // Adds "Select all" to context menu in FF - if (!cm.somethingSelected()) te.value = input.prevInput = " "; - input.contextMenuPending = true; - display.selForContextMenu = cm.doc.sel; - clearTimeout(display.detectingSelectAll); - - // Select-all will be greyed out if there's nothing to select, so - // this adds a zero-width space so that we can later check whether - // it got selected. - function prepareSelectAllHack() { - if (te.selectionStart != null) { - var selected = cm.somethingSelected(); - var extval = "\u200b" + (selected ? te.value : ""); - te.value = "\u21da"; // Used to catch context-menu undo - te.value = extval; - input.prevInput = selected ? "" : "\u200b"; - te.selectionStart = 1; te.selectionEnd = extval.length; - // Re-set this, in case some other handler touched the - // selection in the meantime. - display.selForContextMenu = cm.doc.sel; - } - } - function rehide() { - input.contextMenuPending = false; - input.wrapper.style.cssText = oldWrapperCSS - te.style.cssText = oldCSS; - if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); - - // Try to detect the user choosing select-all - if (te.selectionStart != null) { - if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); - var i = 0, poll = function() { - if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && - te.selectionEnd > 0 && input.prevInput == "\u200b") - operation(cm, commands.selectAll)(cm); - else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); - else display.input.reset(); - }; - display.detectingSelectAll = setTimeout(poll, 200); - } - } - - if (ie && ie_version >= 9) prepareSelectAllHack(); - if (captureRightClick) { - e_stop(e); - var mouseup = function() { - off(window, "mouseup", mouseup); - setTimeout(rehide, 20); - }; - on(window, "mouseup", mouseup); - } else { - setTimeout(rehide, 50); - } - }, - - readOnlyChanged: function(val) { - if (!val) this.reset(); - }, - - setUneditable: nothing, - - needsContentAttribute: false - }, TextareaInput.prototype); - - // CONTENTEDITABLE INPUT STYLE - - function ContentEditableInput(cm) { - this.cm = cm; - this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; - this.polling = new Delayed(); - this.gracePeriod = false; - } - - ContentEditableInput.prototype = copyObj({ - init: function(display) { - var input = this, cm = input.cm; - var div = input.div = display.lineDiv; - disableBrowserMagic(div); - - on(div, "paste", function(e) { - if (!signalDOMEvent(cm, e)) handlePaste(e, cm); - }) - - on(div, "compositionstart", function(e) { - var data = e.data; - input.composing = {sel: cm.doc.sel, data: data, startData: data}; - if (!data) return; - var prim = cm.doc.sel.primary(); - var line = cm.getLine(prim.head.line); - var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length)); - if (found > -1 && found <= prim.head.ch) - input.composing.sel = simpleSelection(Pos(prim.head.line, found), - Pos(prim.head.line, found + data.length)); - }); - on(div, "compositionupdate", function(e) { - input.composing.data = e.data; - }); - on(div, "compositionend", function(e) { - var ours = input.composing; - if (!ours) return; - if (e.data != ours.startData && !/\u200b/.test(e.data)) - ours.data = e.data; - // Need a small delay to prevent other code (input event, - // selection polling) from doing damage when fired right after - // compositionend. - setTimeout(function() { - if (!ours.handled) - input.applyComposition(ours); - if (input.composing == ours) - input.composing = null; - }, 50); - }); - - on(div, "touchstart", function() { - input.forceCompositionEnd(); - }); - - on(div, "input", function() { - if (input.composing) return; - if (cm.isReadOnly() || !input.pollContent()) - runInOp(input.cm, function() {regChange(cm);}); - }); - - function onCopyCut(e) { - if (signalDOMEvent(cm, e)) return - if (cm.somethingSelected()) { - lastCopied = {lineWise: false, text: cm.getSelections()}; - if (e.type == "cut") cm.replaceSelection("", null, "cut"); - } else if (!cm.options.lineWiseCopyCut) { - return; - } else { - var ranges = copyableRanges(cm); - lastCopied = {lineWise: true, text: ranges.text}; - if (e.type == "cut") { - cm.operation(function() { - cm.setSelections(ranges.ranges, 0, sel_dontScroll); - cm.replaceSelection("", null, "cut"); - }); - } - } - // iOS exposes the clipboard API, but seems to discard content inserted into it - if (e.clipboardData && !ios) { - e.preventDefault(); - e.clipboardData.clearData(); - e.clipboardData.setData("text/plain", lastCopied.text.join("\n")); - } else { - // Old-fashioned briefly-focus-a-textarea hack - var kludge = hiddenTextarea(), te = kludge.firstChild; - cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); - te.value = lastCopied.text.join("\n"); - var hadFocus = document.activeElement; - selectInput(te); - setTimeout(function() { - cm.display.lineSpace.removeChild(kludge); - hadFocus.focus(); - }, 50); - } - } - on(div, "copy", onCopyCut); - on(div, "cut", onCopyCut); - }, - - prepareSelection: function() { - var result = prepareSelection(this.cm, false); - result.focus = this.cm.state.focused; - return result; - }, - - showSelection: function(info, takeFocus) { - if (!info || !this.cm.display.view.length) return; - if (info.focus || takeFocus) this.showPrimarySelection(); - this.showMultipleSelections(info); - }, - - showPrimarySelection: function() { - var sel = window.getSelection(), prim = this.cm.doc.sel.primary(); - var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset); - var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset); - if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && - cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && - cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) - return; - - var start = posToDOM(this.cm, prim.from()); - var end = posToDOM(this.cm, prim.to()); - if (!start && !end) return; - - var view = this.cm.display.view; - var old = sel.rangeCount && sel.getRangeAt(0); - if (!start) { - start = {node: view[0].measure.map[2], offset: 0}; - } else if (!end) { // FIXME dangerously hacky - var measure = view[view.length - 1].measure; - var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; - end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}; - } - - try { var rng = range(start.node, start.offset, end.offset, end.node); } - catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible - if (rng) { - if (!gecko && this.cm.state.focused) { - sel.collapse(start.node, start.offset); - if (!rng.collapsed) sel.addRange(rng); - } else { - sel.removeAllRanges(); - sel.addRange(rng); - } - if (old && sel.anchorNode == null) sel.addRange(old); - else if (gecko) this.startGracePeriod(); - } - this.rememberSelection(); - }, - - startGracePeriod: function() { - var input = this; - clearTimeout(this.gracePeriod); - this.gracePeriod = setTimeout(function() { - input.gracePeriod = false; - if (input.selectionChanged()) - input.cm.operation(function() { input.cm.curOp.selectionChanged = true; }); - }, 20); - }, - - showMultipleSelections: function(info) { - removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); - removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); - }, - - rememberSelection: function() { - var sel = window.getSelection(); - this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; - this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; - }, - - selectionInEditor: function() { - var sel = window.getSelection(); - if (!sel.rangeCount) return false; - var node = sel.getRangeAt(0).commonAncestorContainer; - return contains(this.div, node); - }, - - focus: function() { - if (this.cm.options.readOnly != "nocursor") this.div.focus(); - }, - blur: function() { this.div.blur(); }, - getField: function() { return this.div; }, - - supportsTouch: function() { return true; }, - - receivedFocus: function() { - var input = this; - if (this.selectionInEditor()) - this.pollSelection(); - else - runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; }); - - function poll() { - if (input.cm.state.focused) { - input.pollSelection(); - input.polling.set(input.cm.options.pollInterval, poll); - } - } - this.polling.set(this.cm.options.pollInterval, poll); - }, - - selectionChanged: function() { - var sel = window.getSelection(); - return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || - sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset; - }, - - pollSelection: function() { - if (!this.composing && !this.gracePeriod && this.selectionChanged()) { - var sel = window.getSelection(), cm = this.cm; - this.rememberSelection(); - var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); - var head = domToPos(cm, sel.focusNode, sel.focusOffset); - if (anchor && head) runInOp(cm, function() { - setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); - if (anchor.bad || head.bad) cm.curOp.selectionChanged = true; - }); - } - }, - - pollContent: function() { - var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); - var from = sel.from(), to = sel.to(); - if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false; - - var fromIndex; - if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { - var fromLine = lineNo(display.view[0].line); - var fromNode = display.view[0].node; - } else { - var fromLine = lineNo(display.view[fromIndex].line); - var fromNode = display.view[fromIndex - 1].node.nextSibling; - } - var toIndex = findViewIndex(cm, to.line); - if (toIndex == display.view.length - 1) { - var toLine = display.viewTo - 1; - var toNode = display.lineDiv.lastChild; - } else { - var toLine = lineNo(display.view[toIndex + 1].line) - 1; - var toNode = display.view[toIndex + 1].node.previousSibling; - } - - var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); - var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); - while (newText.length > 1 && oldText.length > 1) { - if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } - else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } - else break; - } - - var cutFront = 0, cutEnd = 0; - var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); - while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) - ++cutFront; - var newBot = lst(newText), oldBot = lst(oldText); - var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), - oldBot.length - (oldText.length == 1 ? cutFront : 0)); - while (cutEnd < maxCutEnd && - newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) - ++cutEnd; - - newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd); - newText[0] = newText[0].slice(cutFront); - - var chFrom = Pos(fromLine, cutFront); - var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); - if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { - replaceRange(cm.doc, newText, chFrom, chTo, "+input"); - return true; - } - }, - - ensurePolled: function() { - this.forceCompositionEnd(); - }, - reset: function() { - this.forceCompositionEnd(); - }, - forceCompositionEnd: function() { - if (!this.composing || this.composing.handled) return; - this.applyComposition(this.composing); - this.composing.handled = true; - this.div.blur(); - this.div.focus(); - }, - applyComposition: function(composing) { - if (this.cm.isReadOnly()) - operation(this.cm, regChange)(this.cm) - else if (composing.data && composing.data != composing.startData) - operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel); - }, - - setUneditable: function(node) { - node.contentEditable = "false" - }, - - onKeyPress: function(e) { - e.preventDefault(); - if (!this.cm.isReadOnly()) - operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); - }, - - readOnlyChanged: function(val) { - this.div.contentEditable = String(val != "nocursor") - }, - - onContextMenu: nothing, - resetPosition: nothing, - - needsContentAttribute: true - }, ContentEditableInput.prototype); - - function posToDOM(cm, pos) { - var view = findViewForLine(cm, pos.line); - if (!view || view.hidden) return null; - var line = getLine(cm.doc, pos.line); - var info = mapFromLineView(view, line, pos.line); - - var order = getOrder(line), side = "left"; - if (order) { - var partPos = getBidiPartAt(order, pos.ch); - side = partPos % 2 ? "right" : "left"; - } - var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); - result.offset = result.collapse == "right" ? result.end : result.start; - return result; - } - - function badPos(pos, bad) { if (bad) pos.bad = true; return pos; } - - function domToPos(cm, node, offset) { - var lineNode; - if (node == cm.display.lineDiv) { - lineNode = cm.display.lineDiv.childNodes[offset]; - if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true); - node = null; offset = 0; - } else { - for (lineNode = node;; lineNode = lineNode.parentNode) { - if (!lineNode || lineNode == cm.display.lineDiv) return null; - if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break; - } - } - for (var i = 0; i < cm.display.view.length; i++) { - var lineView = cm.display.view[i]; - if (lineView.node == lineNode) - return locateNodeInLineView(lineView, node, offset); - } - } - - function locateNodeInLineView(lineView, node, offset) { - var wrapper = lineView.text.firstChild, bad = false; - if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true); - if (node == wrapper) { - bad = true; - node = wrapper.childNodes[offset]; - offset = 0; - if (!node) { - var line = lineView.rest ? lst(lineView.rest) : lineView.line; - return badPos(Pos(lineNo(line), line.text.length), bad); - } - } - - var textNode = node.nodeType == 3 ? node : null, topNode = node; - if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { - textNode = node.firstChild; - if (offset) offset = textNode.nodeValue.length; - } - while (topNode.parentNode != wrapper) topNode = topNode.parentNode; - var measure = lineView.measure, maps = measure.maps; - - function find(textNode, topNode, offset) { - for (var i = -1; i < (maps ? maps.length : 0); i++) { - var map = i < 0 ? measure.map : maps[i]; - for (var j = 0; j < map.length; j += 3) { - var curNode = map[j + 2]; - if (curNode == textNode || curNode == topNode) { - var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); - var ch = map[j] + offset; - if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)]; - return Pos(line, ch); - } - } - } - } - var found = find(textNode, topNode, offset); - if (found) return badPos(found, bad); - - // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems - for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { - found = find(after, after.firstChild, 0); - if (found) - return badPos(Pos(found.line, found.ch - dist), bad); - else - dist += after.textContent.length; - } - for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { - found = find(before, before.firstChild, -1); - if (found) - return badPos(Pos(found.line, found.ch + dist), bad); - else - dist += after.textContent.length; - } - } - - function domTextBetween(cm, from, to, fromLine, toLine) { - var text = "", closing = false, lineSep = cm.doc.lineSeparator(); - function recognizeMarker(id) { return function(marker) { return marker.id == id; }; } - function walk(node) { - if (node.nodeType == 1) { - var cmText = node.getAttribute("cm-text"); - if (cmText != null) { - if (cmText == "") cmText = node.textContent.replace(/\u200b/g, ""); - text += cmText; - return; - } - var markerID = node.getAttribute("cm-marker"), range; - if (markerID) { - var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); - if (found.length && (range = found[0].find())) - text += getBetween(cm.doc, range.from, range.to).join(lineSep); - return; - } - if (node.getAttribute("contenteditable") == "false") return; - for (var i = 0; i < node.childNodes.length; i++) - walk(node.childNodes[i]); - if (/^(pre|div|p)$/i.test(node.nodeName)) - closing = true; - } else if (node.nodeType == 3) { - var val = node.nodeValue; - if (!val) return; - if (closing) { - text += lineSep; - closing = false; - } - text += val; - } - } - for (;;) { - walk(from); - if (from == to) break; - from = from.nextSibling; - } - return text; - } - - CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; - - // SELECTION / CURSOR - - // Selection objects are immutable. A new one is created every time - // the selection changes. A selection is one or more non-overlapping - // (and non-touching) ranges, sorted, and an integer that indicates - // which one is the primary selection (the one that's scrolled into - // view, that getCursor returns, etc). - function Selection(ranges, primIndex) { - this.ranges = ranges; - this.primIndex = primIndex; - } - - Selection.prototype = { - primary: function() { return this.ranges[this.primIndex]; }, - equals: function(other) { - if (other == this) return true; - if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false; - for (var i = 0; i < this.ranges.length; i++) { - var here = this.ranges[i], there = other.ranges[i]; - if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false; - } - return true; - }, - deepCopy: function() { - for (var out = [], i = 0; i < this.ranges.length; i++) - out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); - return new Selection(out, this.primIndex); - }, - somethingSelected: function() { - for (var i = 0; i < this.ranges.length; i++) - if (!this.ranges[i].empty()) return true; - return false; - }, - contains: function(pos, end) { - if (!end) end = pos; - for (var i = 0; i < this.ranges.length; i++) { - var range = this.ranges[i]; - if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) - return i; - } - return -1; - } - }; - - function Range(anchor, head) { - this.anchor = anchor; this.head = head; - } - - Range.prototype = { - from: function() { return minPos(this.anchor, this.head); }, - to: function() { return maxPos(this.anchor, this.head); }, - empty: function() { - return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; - } - }; - - // Take an unsorted, potentially overlapping set of ranges, and - // build a selection out of it. 'Consumes' ranges array (modifying - // it). - function normalizeSelection(ranges, primIndex) { - var prim = ranges[primIndex]; - ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); - primIndex = indexOf(ranges, prim); - for (var i = 1; i < ranges.length; i++) { - var cur = ranges[i], prev = ranges[i - 1]; - if (cmp(prev.to(), cur.from()) >= 0) { - var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); - var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; - if (i <= primIndex) --primIndex; - ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); - } - } - return new Selection(ranges, primIndex); - } - - function simpleSelection(anchor, head) { - return new Selection([new Range(anchor, head || anchor)], 0); - } - - // Most of the external API clips given positions to make sure they - // actually exist within the document. - function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} - function clipPos(doc, pos) { - if (pos.line < doc.first) return Pos(doc.first, 0); - var last = doc.first + doc.size - 1; - if (pos.line > last) return Pos(last, getLine(doc, last).text.length); - return clipToLen(pos, getLine(doc, pos.line).text.length); - } - function clipToLen(pos, linelen) { - var ch = pos.ch; - if (ch == null || ch > linelen) return Pos(pos.line, linelen); - else if (ch < 0) return Pos(pos.line, 0); - else return pos; - } - function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} - function clipPosArray(doc, array) { - for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]); - return out; - } - - // SELECTION UPDATES - - // The 'scroll' parameter given to many of these indicated whether - // the new cursor position should be scrolled into view after - // modifying the selection. - - // If shift is held or the extend flag is set, extends a range to - // include a given position (and optionally a second position). - // Otherwise, simply returns the range between the given positions. - // Used for cursor motion and such. - function extendRange(doc, range, head, other) { - if (doc.cm && doc.cm.display.shift || doc.extend) { - var anchor = range.anchor; - if (other) { - var posBefore = cmp(head, anchor) < 0; - if (posBefore != (cmp(other, anchor) < 0)) { - anchor = head; - head = other; - } else if (posBefore != (cmp(head, other) < 0)) { - head = other; - } - } - return new Range(anchor, head); - } else { - return new Range(other || head, head); - } - } - - // Extend the primary selection range, discard the rest. - function extendSelection(doc, head, other, options) { - setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options); - } - - // Extend all selections (pos is an array of selections with length - // equal the number of selections) - function extendSelections(doc, heads, options) { - for (var out = [], i = 0; i < doc.sel.ranges.length; i++) - out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); - var newSel = normalizeSelection(out, doc.sel.primIndex); - setSelection(doc, newSel, options); - } - - // Updates a single range in the selection. - function replaceOneSelection(doc, i, range, options) { - var ranges = doc.sel.ranges.slice(0); - ranges[i] = range; - setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); - } - - // Reset the selection to a single range. - function setSimpleSelection(doc, anchor, head, options) { - setSelection(doc, simpleSelection(anchor, head), options); - } - - // Give beforeSelectionChange handlers a change to influence a - // selection update. - function filterSelectionChange(doc, sel, options) { - var obj = { - ranges: sel.ranges, - update: function(ranges) { - this.ranges = []; - for (var i = 0; i < ranges.length; i++) - this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), - clipPos(doc, ranges[i].head)); - }, - origin: options && options.origin - }; - signal(doc, "beforeSelectionChange", doc, obj); - if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); - if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1); - else return sel; - } - - function setSelectionReplaceHistory(doc, sel, options) { - var done = doc.history.done, last = lst(done); - if (last && last.ranges) { - done[done.length - 1] = sel; - setSelectionNoUndo(doc, sel, options); - } else { - setSelection(doc, sel, options); - } - } - - // Set a new selection. - function setSelection(doc, sel, options) { - setSelectionNoUndo(doc, sel, options); - addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); - } - - function setSelectionNoUndo(doc, sel, options) { - if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) - sel = filterSelectionChange(doc, sel, options); - - var bias = options && options.bias || - (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); - setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); - - if (!(options && options.scroll === false) && doc.cm) - ensureCursorVisible(doc.cm); - } - - function setSelectionInner(doc, sel) { - if (sel.equals(doc.sel)) return; - - doc.sel = sel; - - if (doc.cm) { - doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; - signalCursorActivity(doc.cm); - } - signalLater(doc, "cursorActivity", doc); - } - - // Verify that the selection does not partially select any atomic - // marked ranges. - function reCheckSelection(doc) { - setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll); - } - - // Return a selection that does not partially select any atomic - // ranges. - function skipAtomicInSelection(doc, sel, bias, mayClear) { - var out; - for (var i = 0; i < sel.ranges.length; i++) { - var range = sel.ranges[i]; - var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; - var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); - var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear); - if (out || newAnchor != range.anchor || newHead != range.head) { - if (!out) out = sel.ranges.slice(0, i); - out[i] = new Range(newAnchor, newHead); - } - } - return out ? normalizeSelection(out, sel.primIndex) : sel; - } - - function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { - var line = getLine(doc, pos.line); - if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { - var sp = line.markedSpans[i], m = sp.marker; - if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && - (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { - if (mayClear) { - signal(m, "beforeCursorEnter"); - if (m.explicitlyCleared) { - if (!line.markedSpans) break; - else {--i; continue;} - } - } - if (!m.atomic) continue; - - if (oldPos) { - var near = m.find(dir < 0 ? 1 : -1), diff; - if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) - near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); - if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) - return skipAtomicInner(doc, near, pos, dir, mayClear); - } - - var far = m.find(dir < 0 ? -1 : 1); - if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) - far = movePos(doc, far, dir, far.line == pos.line ? line : null); - return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null; - } - } - return pos; - } - - // Ensure a given position is not inside an atomic range. - function skipAtomic(doc, pos, oldPos, bias, mayClear) { - var dir = bias || 1; - var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || - (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || - skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || - (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); - if (!found) { - doc.cantEdit = true; - return Pos(doc.first, 0); - } - return found; - } - - function movePos(doc, pos, dir, line) { - if (dir < 0 && pos.ch == 0) { - if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)); - else return null; - } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { - if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0); - else return null; - } else { - return new Pos(pos.line, pos.ch + dir); - } - } - - // SELECTION DRAWING - - function updateSelection(cm) { - cm.display.input.showSelection(cm.display.input.prepareSelection()); - } - - function prepareSelection(cm, primary) { - var doc = cm.doc, result = {}; - var curFragment = result.cursors = document.createDocumentFragment(); - var selFragment = result.selection = document.createDocumentFragment(); - - for (var i = 0; i < doc.sel.ranges.length; i++) { - if (primary === false && i == doc.sel.primIndex) continue; - var range = doc.sel.ranges[i]; - if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue; - var collapsed = range.empty(); - if (collapsed || cm.options.showCursorWhenSelecting) - drawSelectionCursor(cm, range.head, curFragment); - if (!collapsed) - drawSelectionRange(cm, range, selFragment); - } - return result; - } - - // Draws a cursor for the given range - function drawSelectionCursor(cm, head, output) { - var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine); - - var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); - cursor.style.left = pos.left + "px"; - cursor.style.top = pos.top + "px"; - cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; - - if (pos.other) { - // Secondary cursor, shown when on a 'jump' in bi-directional text - var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); - otherCursor.style.display = ""; - otherCursor.style.left = pos.other.left + "px"; - otherCursor.style.top = pos.other.top + "px"; - otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; - } - } - - // Draws the given range as a highlighted selection - function drawSelectionRange(cm, range, output) { - var display = cm.display, doc = cm.doc; - var fragment = document.createDocumentFragment(); - var padding = paddingH(cm.display), leftSide = padding.left; - var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; - - function add(left, top, width, bottom) { - if (top < 0) top = 0; - top = Math.round(top); - bottom = Math.round(bottom); - fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + - "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + - "px; height: " + (bottom - top) + "px")); - } - - function drawForLine(line, fromArg, toArg) { - var lineObj = getLine(doc, line); - var lineLen = lineObj.text.length; - var start, end; - function coords(ch, bias) { - return charCoords(cm, Pos(line, ch), "div", lineObj, bias); - } - - iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { - var leftPos = coords(from, "left"), rightPos, left, right; - if (from == to) { - rightPos = leftPos; - left = right = leftPos.left; - } else { - rightPos = coords(to - 1, "right"); - if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } - left = leftPos.left; - right = rightPos.right; - } - if (fromArg == null && from == 0) left = leftSide; - if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part - add(left, leftPos.top, null, leftPos.bottom); - left = leftSide; - if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); - } - if (toArg == null && to == lineLen) right = rightSide; - if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) - start = leftPos; - if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) - end = rightPos; - if (left < leftSide + 1) left = leftSide; - add(left, rightPos.top, right - left, rightPos.bottom); - }); - return {start: start, end: end}; - } - - var sFrom = range.from(), sTo = range.to(); - if (sFrom.line == sTo.line) { - drawForLine(sFrom.line, sFrom.ch, sTo.ch); - } else { - var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); - var singleVLine = visualLine(fromLine) == visualLine(toLine); - var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; - var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; - if (singleVLine) { - if (leftEnd.top < rightStart.top - 2) { - add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); - add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); - } else { - add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); - } - } - if (leftEnd.bottom < rightStart.top) - add(leftSide, leftEnd.bottom, null, rightStart.top); - } - - output.appendChild(fragment); - } - - // Cursor-blinking - function restartBlink(cm) { - if (!cm.state.focused) return; - var display = cm.display; - clearInterval(display.blinker); - var on = true; - display.cursorDiv.style.visibility = ""; - if (cm.options.cursorBlinkRate > 0) - display.blinker = setInterval(function() { - display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; - }, cm.options.cursorBlinkRate); - else if (cm.options.cursorBlinkRate < 0) - display.cursorDiv.style.visibility = "hidden"; - } - - // HIGHLIGHT WORKER - - function startWorker(cm, time) { - if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) - cm.state.highlight.set(time, bind(highlightWorker, cm)); - } - - function highlightWorker(cm) { - var doc = cm.doc; - if (doc.frontier < doc.first) doc.frontier = doc.first; - if (doc.frontier >= cm.display.viewTo) return; - var end = +new Date + cm.options.workTime; - var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); - var changedLines = []; - - doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { - if (doc.frontier >= cm.display.viewFrom) { // Visible - var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHighlightLength; - var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true); - line.styles = highlighted.styles; - var oldCls = line.styleClasses, newCls = highlighted.classes; - if (newCls) line.styleClasses = newCls; - else if (oldCls) line.styleClasses = null; - var ischange = !oldStyles || oldStyles.length != line.styles.length || - oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); - for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; - if (ischange) changedLines.push(doc.frontier); - line.stateAfter = tooLong ? state : copyState(doc.mode, state); - } else { - if (line.text.length <= cm.options.maxHighlightLength) - processLine(cm, line.text, state); - line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; - } - ++doc.frontier; - if (+new Date > end) { - startWorker(cm, cm.options.workDelay); - return true; - } - }); - if (changedLines.length) runInOp(cm, function() { - for (var i = 0; i < changedLines.length; i++) - regLineChange(cm, changedLines[i], "text"); - }); - } - - // Finds the line to start with when starting a parse. Tries to - // find a line with a stateAfter, so that it can start with a - // valid state. If that fails, it returns the line with the - // smallest indentation, which tends to need the least context to - // parse correctly. - function findStartLine(cm, n, precise) { - var minindent, minline, doc = cm.doc; - var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); - for (var search = n; search > lim; --search) { - if (search <= doc.first) return doc.first; - var line = getLine(doc, search - 1); - if (line.stateAfter && (!precise || search <= doc.frontier)) return search; - var indented = countColumn(line.text, null, cm.options.tabSize); - if (minline == null || minindent > indented) { - minline = search - 1; - minindent = indented; - } - } - return minline; - } - - function getStateBefore(cm, n, precise) { - var doc = cm.doc, display = cm.display; - if (!doc.mode.startState) return true; - var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; - if (!state) state = startState(doc.mode); - else state = copyState(doc.mode, state); - doc.iter(pos, n, function(line) { - processLine(cm, line.text, state); - var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo; - line.stateAfter = save ? copyState(doc.mode, state) : null; - ++pos; - }); - if (precise) doc.frontier = pos; - return state; - } - - // POSITION MEASUREMENT - - function paddingTop(display) {return display.lineSpace.offsetTop;} - function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} - function paddingH(display) { - if (display.cachedPaddingH) return display.cachedPaddingH; - var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); - var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; - var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; - if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; - return data; - } - - function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } - function displayWidth(cm) { - return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; - } - function displayHeight(cm) { - return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; - } - - // Ensure the lineView.wrapping.heights array is populated. This is - // an array of bottom offsets for the lines that make up a drawn - // line. When lineWrapping is on, there might be more than one - // height. - function ensureLineHeights(cm, lineView, rect) { - var wrapping = cm.options.lineWrapping; - var curWidth = wrapping && displayWidth(cm); - if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { - var heights = lineView.measure.heights = []; - if (wrapping) { - lineView.measure.width = curWidth; - var rects = lineView.text.firstChild.getClientRects(); - for (var i = 0; i < rects.length - 1; i++) { - var cur = rects[i], next = rects[i + 1]; - if (Math.abs(cur.bottom - next.bottom) > 2) - heights.push((cur.bottom + next.top) / 2 - rect.top); - } - } - heights.push(rect.bottom - rect.top); - } - } - - // Find a line map (mapping character offsets to text nodes) and a - // measurement cache for the given line number. (A line view might - // contain multiple lines when collapsed ranges are present.) - function mapFromLineView(lineView, line, lineN) { - if (lineView.line == line) - return {map: lineView.measure.map, cache: lineView.measure.cache}; - for (var i = 0; i < lineView.rest.length; i++) - if (lineView.rest[i] == line) - return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}; - for (var i = 0; i < lineView.rest.length; i++) - if (lineNo(lineView.rest[i]) > lineN) - return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}; - } - - // Render a line into the hidden node display.externalMeasured. Used - // when measurement is needed for a line that's not in the viewport. - function updateExternalMeasurement(cm, line) { - line = visualLine(line); - var lineN = lineNo(line); - var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); - view.lineN = lineN; - var built = view.built = buildLineContent(cm, view); - view.text = built.pre; - removeChildrenAndAdd(cm.display.lineMeasure, built.pre); - return view; - } - - // Get a {top, bottom, left, right} box (in line-local coordinates) - // for a given character. - function measureChar(cm, line, ch, bias) { - return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); - } - - // Find a line view that corresponds to the given line number. - function findViewForLine(cm, lineN) { - if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) - return cm.display.view[findViewIndex(cm, lineN)]; - var ext = cm.display.externalMeasured; - if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) - return ext; - } - - // Measurement can be split in two steps, the set-up work that - // applies to the whole line, and the measurement of the actual - // character. Functions like coordsChar, that need to do a lot of - // measurements in a row, can thus ensure that the set-up work is - // only done once. - function prepareMeasureForLine(cm, line) { - var lineN = lineNo(line); - var view = findViewForLine(cm, lineN); - if (view && !view.text) { - view = null; - } else if (view && view.changes) { - updateLineForChanges(cm, view, lineN, getDimensions(cm)); - cm.curOp.forceUpdate = true; - } - if (!view) - view = updateExternalMeasurement(cm, line); - - var info = mapFromLineView(view, line, lineN); - return { - line: line, view: view, rect: null, - map: info.map, cache: info.cache, before: info.before, - hasHeights: false - }; - } - - // Given a prepared measurement object, measures the position of an - // actual character (or fetches it from the cache). - function measureCharPrepared(cm, prepared, ch, bias, varHeight) { - if (prepared.before) ch = -1; - var key = ch + (bias || ""), found; - if (prepared.cache.hasOwnProperty(key)) { - found = prepared.cache[key]; - } else { - if (!prepared.rect) - prepared.rect = prepared.view.text.getBoundingClientRect(); - if (!prepared.hasHeights) { - ensureLineHeights(cm, prepared.view, prepared.rect); - prepared.hasHeights = true; - } - found = measureCharInner(cm, prepared, ch, bias); - if (!found.bogus) prepared.cache[key] = found; - } - return {left: found.left, right: found.right, - top: varHeight ? found.rtop : found.top, - bottom: varHeight ? found.rbottom : found.bottom}; - } - - var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; - - function nodeAndOffsetInLineMap(map, ch, bias) { - var node, start, end, collapse; - // First, search the line map for the text node corresponding to, - // or closest to, the target character. - for (var i = 0; i < map.length; i += 3) { - var mStart = map[i], mEnd = map[i + 1]; - if (ch < mStart) { - start = 0; end = 1; - collapse = "left"; - } else if (ch < mEnd) { - start = ch - mStart; - end = start + 1; - } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { - end = mEnd - mStart; - start = end - 1; - if (ch >= mEnd) collapse = "right"; - } - if (start != null) { - node = map[i + 2]; - if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) - collapse = bias; - if (bias == "left" && start == 0) - while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { - node = map[(i -= 3) + 2]; - collapse = "left"; - } - if (bias == "right" && start == mEnd - mStart) - while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { - node = map[(i += 3) + 2]; - collapse = "right"; - } - break; - } - } - return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}; - } - - function getUsefulRect(rects, bias) { - var rect = nullRect - if (bias == "left") for (var i = 0; i < rects.length; i++) { - if ((rect = rects[i]).left != rect.right) break - } else for (var i = rects.length - 1; i >= 0; i--) { - if ((rect = rects[i]).left != rect.right) break - } - return rect - } - - function measureCharInner(cm, prepared, ch, bias) { - var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); - var node = place.node, start = place.start, end = place.end, collapse = place.collapse; - - var rect; - if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. - for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned - while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start; - while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end; - if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) - rect = node.parentNode.getBoundingClientRect(); - else - rect = getUsefulRect(range(node, start, end).getClientRects(), bias) - if (rect.left || rect.right || start == 0) break; - end = start; - start = start - 1; - collapse = "right"; - } - if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); - } else { // If it is a widget, simply get the box for the whole widget. - if (start > 0) collapse = bias = "right"; - var rects; - if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) - rect = rects[bias == "right" ? rects.length - 1 : 0]; - else - rect = node.getBoundingClientRect(); - } - if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { - var rSpan = node.parentNode.getClientRects()[0]; - if (rSpan) - rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; - else - rect = nullRect; - } - - var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; - var mid = (rtop + rbot) / 2; - var heights = prepared.view.measure.heights; - for (var i = 0; i < heights.length - 1; i++) - if (mid < heights[i]) break; - var top = i ? heights[i - 1] : 0, bot = heights[i]; - var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, - right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, - top: top, bottom: bot}; - if (!rect.left && !rect.right) result.bogus = true; - if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } - - return result; - } - - // Work around problem with bounding client rects on ranges being - // returned incorrectly when zoomed on IE10 and below. - function maybeUpdateRectForZooming(measure, rect) { - if (!window.screen || screen.logicalXDPI == null || - screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) - return rect; - var scaleX = screen.logicalXDPI / screen.deviceXDPI; - var scaleY = screen.logicalYDPI / screen.deviceYDPI; - return {left: rect.left * scaleX, right: rect.right * scaleX, - top: rect.top * scaleY, bottom: rect.bottom * scaleY}; - } - - function clearLineMeasurementCacheFor(lineView) { - if (lineView.measure) { - lineView.measure.cache = {}; - lineView.measure.heights = null; - if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) - lineView.measure.caches[i] = {}; - } - } - - function clearLineMeasurementCache(cm) { - cm.display.externalMeasure = null; - removeChildren(cm.display.lineMeasure); - for (var i = 0; i < cm.display.view.length; i++) - clearLineMeasurementCacheFor(cm.display.view[i]); - } - - function clearCaches(cm) { - clearLineMeasurementCache(cm); - cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; - if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; - cm.display.lineNumChars = null; - } - - function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } - function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } - - // Converts a {top, bottom, left, right} box from line-local - // coordinates into another coordinate system. Context may be one of - // "line", "div" (display.lineDiv), "local"/null (editor), "window", - // or "page". - function intoCoordSystem(cm, lineObj, rect, context) { - if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { - var size = widgetHeight(lineObj.widgets[i]); - rect.top += size; rect.bottom += size; - } - if (context == "line") return rect; - if (!context) context = "local"; - var yOff = heightAtLine(lineObj); - if (context == "local") yOff += paddingTop(cm.display); - else yOff -= cm.display.viewOffset; - if (context == "page" || context == "window") { - var lOff = cm.display.lineSpace.getBoundingClientRect(); - yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); - var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); - rect.left += xOff; rect.right += xOff; - } - rect.top += yOff; rect.bottom += yOff; - return rect; - } - - // Coverts a box from "div" coords to another coordinate system. - // Context may be "window", "page", "div", or "local"/null. - function fromCoordSystem(cm, coords, context) { - if (context == "div") return coords; - var left = coords.left, top = coords.top; - // First move into "page" coordinate system - if (context == "page") { - left -= pageScrollX(); - top -= pageScrollY(); - } else if (context == "local" || !context) { - var localBox = cm.display.sizer.getBoundingClientRect(); - left += localBox.left; - top += localBox.top; - } - - var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); - return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; - } - - function charCoords(cm, pos, context, lineObj, bias) { - if (!lineObj) lineObj = getLine(cm.doc, pos.line); - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); - } - - // Returns a box for a given cursor position, which may have an - // 'other' property containing the position of the secondary cursor - // on a bidi boundary. - function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { - lineObj = lineObj || getLine(cm.doc, pos.line); - if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); - function get(ch, right) { - var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); - if (right) m.left = m.right; else m.right = m.left; - return intoCoordSystem(cm, lineObj, m, context); - } - function getBidi(ch, partPos) { - var part = order[partPos], right = part.level % 2; - if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { - part = order[--partPos]; - ch = bidiRight(part) - (part.level % 2 ? 0 : 1); - right = true; - } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { - part = order[++partPos]; - ch = bidiLeft(part) - part.level % 2; - right = false; - } - if (right && ch == part.to && ch > part.from) return get(ch - 1); - return get(ch, right); - } - var order = getOrder(lineObj), ch = pos.ch; - if (!order) return get(ch); - var partPos = getBidiPartAt(order, ch); - var val = getBidi(ch, partPos); - if (bidiOther != null) val.other = getBidi(ch, bidiOther); - return val; - } - - // Used to cheaply estimate the coordinates for a position. Used for - // intermediate scroll updates. - function estimateCoords(cm, pos) { - var left = 0, pos = clipPos(cm.doc, pos); - if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; - var lineObj = getLine(cm.doc, pos.line); - var top = heightAtLine(lineObj) + paddingTop(cm.display); - return {left: left, right: left, top: top, bottom: top + lineObj.height}; - } - - // Positions returned by coordsChar contain some extra information. - // xRel is the relative x position of the input coordinates compared - // to the found position (so xRel > 0 means the coordinates are to - // the right of the character position, for example). When outside - // is true, that means the coordinates lie outside the line's - // vertical range. - function PosWithInfo(line, ch, outside, xRel) { - var pos = Pos(line, ch); - pos.xRel = xRel; - if (outside) pos.outside = true; - return pos; - } - - // Compute the character position closest to the given coordinates. - // Input must be lineSpace-local ("div" coordinate system). - function coordsChar(cm, x, y) { - var doc = cm.doc; - y += cm.display.viewOffset; - if (y < 0) return PosWithInfo(doc.first, 0, true, -1); - var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; - if (lineN > last) - return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); - if (x < 0) x = 0; - - var lineObj = getLine(doc, lineN); - for (;;) { - var found = coordsCharInner(cm, lineObj, lineN, x, y); - var merged = collapsedSpanAtEnd(lineObj); - var mergedPos = merged && merged.find(0, true); - if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) - lineN = lineNo(lineObj = mergedPos.to.line); - else - return found; - } - } - - function coordsCharInner(cm, lineObj, lineNo, x, y) { - var innerOff = y - heightAtLine(lineObj); - var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; - var preparedMeasure = prepareMeasureForLine(cm, lineObj); - - function getX(ch) { - var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure); - wrongLine = true; - if (innerOff > sp.bottom) return sp.left - adjust; - else if (innerOff < sp.top) return sp.left + adjust; - else wrongLine = false; - return sp.left; - } - - var bidi = getOrder(lineObj), dist = lineObj.text.length; - var from = lineLeft(lineObj), to = lineRight(lineObj); - var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; - - if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); - // Do a binary search between these bounds. - for (;;) { - if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { - var ch = x < fromX || x - fromX <= toX - x ? from : to; - var outside = ch == from ? fromOutside : toOutside - var xDiff = x - (ch == from ? fromX : toX); - // This is a kludge to handle the case where the coordinates - // are after a line-wrapped line. We should replace it with a - // more general handling of cursor positions around line - // breaks. (Issue #4078) - if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff > 0 && - ch < lineObj.text.length && preparedMeasure.view.measure.heights.length > 1) { - var charSize = measureCharPrepared(cm, preparedMeasure, ch, "right"); - if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.abs(x - charSize.right) < xDiff) { - outside = false - ch++ - xDiff = x - charSize.right - } - } - while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; - var pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); - return pos; - } - var step = Math.ceil(dist / 2), middle = from + step; - if (bidi) { - middle = from; - for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); - } - var middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} - else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} - } - } - - var measureText; - // Compute the default text height. - function textHeight(display) { - if (display.cachedTextHeight != null) return display.cachedTextHeight; - if (measureText == null) { - measureText = elt("pre"); - // Measure a bunch of lines, for browsers that compute - // fractional heights. - for (var i = 0; i < 49; ++i) { - measureText.appendChild(document.createTextNode("x")); - measureText.appendChild(elt("br")); - } - measureText.appendChild(document.createTextNode("x")); - } - removeChildrenAndAdd(display.measure, measureText); - var height = measureText.offsetHeight / 50; - if (height > 3) display.cachedTextHeight = height; - removeChildren(display.measure); - return height || 1; - } - - // Compute the default character width. - function charWidth(display) { - if (display.cachedCharWidth != null) return display.cachedCharWidth; - var anchor = elt("span", "xxxxxxxxxx"); - var pre = elt("pre", [anchor]); - removeChildrenAndAdd(display.measure, pre); - var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; - if (width > 2) display.cachedCharWidth = width; - return width || 10; - } - - // OPERATIONS - - // Operations are used to wrap a series of changes to the editor - // state in such a way that each change won't have to update the - // cursor and display (which would be awkward, slow, and - // error-prone). Instead, display updates are batched and then all - // combined and executed at once. - - var operationGroup = null; - - var nextOpId = 0; - // Start a new operation. - function startOperation(cm) { - cm.curOp = { - cm: cm, - viewChanged: false, // Flag that indicates that lines might need to be redrawn - startHeight: cm.doc.height, // Used to detect need to update scrollbar - forceUpdate: false, // Used to force a redraw - updateInput: null, // Whether to reset the input textarea - typing: false, // Whether this reset should be careful to leave existing text (for compositing) - changeObjs: null, // Accumulated changes, for firing change events - cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on - cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already - selectionChanged: false, // Whether the selection needs to be redrawn - updateMaxLine: false, // Set when the widest line needs to be determined anew - scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet - scrollToPos: null, // Used to scroll to a specific position - focus: false, - id: ++nextOpId // Unique ID - }; - if (operationGroup) { - operationGroup.ops.push(cm.curOp); - } else { - cm.curOp.ownsGroup = operationGroup = { - ops: [cm.curOp], - delayedCallbacks: [] - }; - } - } - - function fireCallbacksForOps(group) { - // Calls delayed callbacks and cursorActivity handlers until no - // new ones appear - var callbacks = group.delayedCallbacks, i = 0; - do { - for (; i < callbacks.length; i++) - callbacks[i].call(null); - for (var j = 0; j < group.ops.length; j++) { - var op = group.ops[j]; - if (op.cursorActivityHandlers) - while (op.cursorActivityCalled < op.cursorActivityHandlers.length) - op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); - } - } while (i < callbacks.length); - } - - // Finish an operation, updating the display and signalling delayed events - function endOperation(cm) { - var op = cm.curOp, group = op.ownsGroup; - if (!group) return; - - try { fireCallbacksForOps(group); } - finally { - operationGroup = null; - for (var i = 0; i < group.ops.length; i++) - group.ops[i].cm.curOp = null; - endOperations(group); - } - } - - // The DOM updates done when an operation finishes are batched so - // that the minimum number of relayouts are required. - function endOperations(group) { - var ops = group.ops; - for (var i = 0; i < ops.length; i++) // Read DOM - endOperation_R1(ops[i]); - for (var i = 0; i < ops.length; i++) // Write DOM (maybe) - endOperation_W1(ops[i]); - for (var i = 0; i < ops.length; i++) // Read DOM - endOperation_R2(ops[i]); - for (var i = 0; i < ops.length; i++) // Write DOM (maybe) - endOperation_W2(ops[i]); - for (var i = 0; i < ops.length; i++) // Read DOM - endOperation_finish(ops[i]); - } - - function endOperation_R1(op) { - var cm = op.cm, display = cm.display; - maybeClipScrollbars(cm); - if (op.updateMaxLine) findMaxLine(cm); - - op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || - op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || - op.scrollToPos.to.line >= display.viewTo) || - display.maxLineChanged && cm.options.lineWrapping; - op.update = op.mustUpdate && - new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); - } - - function endOperation_W1(op) { - op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); - } - - function endOperation_R2(op) { - var cm = op.cm, display = cm.display; - if (op.updatedDisplay) updateHeightsInViewport(cm); - - op.barMeasure = measureForScrollbars(cm); - - // If the max line changed since it was last measured, measure it, - // and ensure the document's width matches it. - // updateDisplay_W2 will use these properties to do the actual resizing - if (display.maxLineChanged && !cm.options.lineWrapping) { - op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; - cm.display.sizerWidth = op.adjustWidthTo; - op.barMeasure.scrollWidth = - Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); - op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); - } - - if (op.updatedDisplay || op.selectionChanged) - op.preparedSelection = display.input.prepareSelection(op.focus); - } - - function endOperation_W2(op) { - var cm = op.cm; - - if (op.adjustWidthTo != null) { - cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; - if (op.maxScrollLeft < cm.doc.scrollLeft) - setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); - cm.display.maxLineChanged = false; - } - - var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()) - if (op.preparedSelection) - cm.display.input.showSelection(op.preparedSelection, takeFocus); - if (op.updatedDisplay || op.startHeight != cm.doc.height) - updateScrollbars(cm, op.barMeasure); - if (op.updatedDisplay) - setDocumentHeight(cm, op.barMeasure); - - if (op.selectionChanged) restartBlink(cm); - - if (cm.state.focused && op.updateInput) - cm.display.input.reset(op.typing); - if (takeFocus) ensureFocus(op.cm); - } - - function endOperation_finish(op) { - var cm = op.cm, display = cm.display, doc = cm.doc; - - if (op.updatedDisplay) postUpdateDisplay(cm, op.update); - - // Abort mouse wheel delta measurement, when scrolling explicitly - if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) - display.wheelStartX = display.wheelStartY = null; - - // Propagate the scroll position to the actual DOM scroller - if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { - doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); - display.scrollbars.setScrollTop(doc.scrollTop); - display.scroller.scrollTop = doc.scrollTop; - } - if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { - doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)); - display.scrollbars.setScrollLeft(doc.scrollLeft); - display.scroller.scrollLeft = doc.scrollLeft; - alignHorizontally(cm); - } - // If we need to scroll a specific position into view, do so. - if (op.scrollToPos) { - var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), - clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); - if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); - } - - // Fire events for markers that are hidden/unidden by editing or - // undoing - var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; - if (hidden) for (var i = 0; i < hidden.length; ++i) - if (!hidden[i].lines.length) signal(hidden[i], "hide"); - if (unhidden) for (var i = 0; i < unhidden.length; ++i) - if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); - - if (display.wrapper.offsetHeight) - doc.scrollTop = cm.display.scroller.scrollTop; - - // Fire change events, and delayed event handlers - if (op.changeObjs) - signal(cm, "changes", cm, op.changeObjs); - if (op.update) - op.update.finish(); - } - - // Run the given function in an operation - function runInOp(cm, f) { - if (cm.curOp) return f(); - startOperation(cm); - try { return f(); } - finally { endOperation(cm); } - } - // Wraps a function in an operation. Returns the wrapped function. - function operation(cm, f) { - return function() { - if (cm.curOp) return f.apply(cm, arguments); - startOperation(cm); - try { return f.apply(cm, arguments); } - finally { endOperation(cm); } - }; - } - // Used to add methods to editor and doc instances, wrapping them in - // operations. - function methodOp(f) { - return function() { - if (this.curOp) return f.apply(this, arguments); - startOperation(this); - try { return f.apply(this, arguments); } - finally { endOperation(this); } - }; - } - function docMethodOp(f) { - return function() { - var cm = this.cm; - if (!cm || cm.curOp) return f.apply(this, arguments); - startOperation(cm); - try { return f.apply(this, arguments); } - finally { endOperation(cm); } - }; - } - - // VIEW TRACKING - - // These objects are used to represent the visible (currently drawn) - // part of the document. A LineView may correspond to multiple - // logical lines, if those are connected by collapsed ranges. - function LineView(doc, line, lineN) { - // The starting line - this.line = line; - // Continuing lines, if any - this.rest = visualLineContinued(line); - // Number of logical lines in this visual line - this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; - this.node = this.text = null; - this.hidden = lineIsHidden(doc, line); - } - - // Create a range of LineView objects for the given lines. - function buildViewArray(cm, from, to) { - var array = [], nextPos; - for (var pos = from; pos < to; pos = nextPos) { - var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); - nextPos = pos + view.size; - array.push(view); - } - return array; - } - - // Updates the display.view data structure for a given change to the - // document. From and to are in pre-change coordinates. Lendiff is - // the amount of lines added or subtracted by the change. This is - // used for changes that span multiple lines, or change the way - // lines are divided into visual lines. regLineChange (below) - // registers single-line changes. - function regChange(cm, from, to, lendiff) { - if (from == null) from = cm.doc.first; - if (to == null) to = cm.doc.first + cm.doc.size; - if (!lendiff) lendiff = 0; - - var display = cm.display; - if (lendiff && to < display.viewTo && - (display.updateLineNumbers == null || display.updateLineNumbers > from)) - display.updateLineNumbers = from; - - cm.curOp.viewChanged = true; - - if (from >= display.viewTo) { // Change after - if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) - resetView(cm); - } else if (to <= display.viewFrom) { // Change before - if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { - resetView(cm); - } else { - display.viewFrom += lendiff; - display.viewTo += lendiff; - } - } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap - resetView(cm); - } else if (from <= display.viewFrom) { // Top overlap - var cut = viewCuttingPoint(cm, to, to + lendiff, 1); - if (cut) { - display.view = display.view.slice(cut.index); - display.viewFrom = cut.lineN; - display.viewTo += lendiff; - } else { - resetView(cm); - } - } else if (to >= display.viewTo) { // Bottom overlap - var cut = viewCuttingPoint(cm, from, from, -1); - if (cut) { - display.view = display.view.slice(0, cut.index); - display.viewTo = cut.lineN; - } else { - resetView(cm); - } - } else { // Gap in the middle - var cutTop = viewCuttingPoint(cm, from, from, -1); - var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); - if (cutTop && cutBot) { - display.view = display.view.slice(0, cutTop.index) - .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) - .concat(display.view.slice(cutBot.index)); - display.viewTo += lendiff; - } else { - resetView(cm); - } - } - - var ext = display.externalMeasured; - if (ext) { - if (to < ext.lineN) - ext.lineN += lendiff; - else if (from < ext.lineN + ext.size) - display.externalMeasured = null; - } - } - - // Register a change to a single line. Type must be one of "text", - // "gutter", "class", "widget" - function regLineChange(cm, line, type) { - cm.curOp.viewChanged = true; - var display = cm.display, ext = cm.display.externalMeasured; - if (ext && line >= ext.lineN && line < ext.lineN + ext.size) - display.externalMeasured = null; - - if (line < display.viewFrom || line >= display.viewTo) return; - var lineView = display.view[findViewIndex(cm, line)]; - if (lineView.node == null) return; - var arr = lineView.changes || (lineView.changes = []); - if (indexOf(arr, type) == -1) arr.push(type); - } - - // Clear the view. - function resetView(cm) { - cm.display.viewFrom = cm.display.viewTo = cm.doc.first; - cm.display.view = []; - cm.display.viewOffset = 0; - } - - // Find the view element corresponding to a given line. Return null - // when the line isn't visible. - function findViewIndex(cm, n) { - if (n >= cm.display.viewTo) return null; - n -= cm.display.viewFrom; - if (n < 0) return null; - var view = cm.display.view; - for (var i = 0; i < view.length; i++) { - n -= view[i].size; - if (n < 0) return i; - } - } - - function viewCuttingPoint(cm, oldN, newN, dir) { - var index = findViewIndex(cm, oldN), diff, view = cm.display.view; - if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) - return {index: index, lineN: newN}; - for (var i = 0, n = cm.display.viewFrom; i < index; i++) - n += view[i].size; - if (n != oldN) { - if (dir > 0) { - if (index == view.length - 1) return null; - diff = (n + view[index].size) - oldN; - index++; - } else { - diff = n - oldN; - } - oldN += diff; newN += diff; - } - while (visualLineNo(cm.doc, newN) != newN) { - if (index == (dir < 0 ? 0 : view.length - 1)) return null; - newN += dir * view[index - (dir < 0 ? 1 : 0)].size; - index += dir; - } - return {index: index, lineN: newN}; - } - - // Force the view to cover a given range, adding empty view element - // or clipping off existing ones as needed. - function adjustView(cm, from, to) { - var display = cm.display, view = display.view; - if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { - display.view = buildViewArray(cm, from, to); - display.viewFrom = from; - } else { - if (display.viewFrom > from) - display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); - else if (display.viewFrom < from) - display.view = display.view.slice(findViewIndex(cm, from)); - display.viewFrom = from; - if (display.viewTo < to) - display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); - else if (display.viewTo > to) - display.view = display.view.slice(0, findViewIndex(cm, to)); - } - display.viewTo = to; - } - - // Count the number of lines in the view whose DOM representation is - // out of date (or nonexistent). - function countDirtyView(cm) { - var view = cm.display.view, dirty = 0; - for (var i = 0; i < view.length; i++) { - var lineView = view[i]; - if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; - } - return dirty; - } - - // EVENT HANDLERS - - // Attach the necessary event handlers when initializing the editor - function registerEventHandlers(cm) { - var d = cm.display; - on(d.scroller, "mousedown", operation(cm, onMouseDown)); - // Older IE's will not fire a second mousedown for a double click - if (ie && ie_version < 11) - on(d.scroller, "dblclick", operation(cm, function(e) { - if (signalDOMEvent(cm, e)) return; - var pos = posFromMouse(cm, e); - if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; - e_preventDefault(e); - var word = cm.findWordAt(pos); - extendSelection(cm.doc, word.anchor, word.head); - })); - else - on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); - // Some browsers fire contextmenu *after* opening the menu, at - // which point we can't mess with it anymore. Context menu is - // handled in onMouseDown for these browsers. - if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); - - // Used to suppress mouse event handling when a touch happens - var touchFinished, prevTouch = {end: 0}; - function finishTouch() { - if (d.activeTouch) { - touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000); - prevTouch = d.activeTouch; - prevTouch.end = +new Date; - } - }; - function isMouseLikeTouchEvent(e) { - if (e.touches.length != 1) return false; - var touch = e.touches[0]; - return touch.radiusX <= 1 && touch.radiusY <= 1; - } - function farAway(touch, other) { - if (other.left == null) return true; - var dx = other.left - touch.left, dy = other.top - touch.top; - return dx * dx + dy * dy > 20 * 20; - } - on(d.scroller, "touchstart", function(e) { - if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { - clearTimeout(touchFinished); - var now = +new Date; - d.activeTouch = {start: now, moved: false, - prev: now - prevTouch.end <= 300 ? prevTouch : null}; - if (e.touches.length == 1) { - d.activeTouch.left = e.touches[0].pageX; - d.activeTouch.top = e.touches[0].pageY; - } - } - }); - on(d.scroller, "touchmove", function() { - if (d.activeTouch) d.activeTouch.moved = true; - }); - on(d.scroller, "touchend", function(e) { - var touch = d.activeTouch; - if (touch && !eventInWidget(d, e) && touch.left != null && - !touch.moved && new Date - touch.start < 300) { - var pos = cm.coordsChar(d.activeTouch, "page"), range; - if (!touch.prev || farAway(touch, touch.prev)) // Single tap - range = new Range(pos, pos); - else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap - range = cm.findWordAt(pos); - else // Triple tap - range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); - cm.setSelection(range.anchor, range.head); - cm.focus(); - e_preventDefault(e); - } - finishTouch(); - }); - on(d.scroller, "touchcancel", finishTouch); - - // Sync scrolling between fake scrollbars and real scrollable - // area, ensure viewport is updated when scrolling. - on(d.scroller, "scroll", function() { - if (d.scroller.clientHeight) { - setScrollTop(cm, d.scroller.scrollTop); - setScrollLeft(cm, d.scroller.scrollLeft, true); - signal(cm, "scroll", cm); - } - }); - - // Listen to wheel events in order to try and update the viewport on time. - on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); - on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); - - // Prevent wrapper from ever scrolling - on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); - - d.dragFunctions = { - enter: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, - over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }}, - start: function(e){onDragStart(cm, e);}, - drop: operation(cm, onDrop), - leave: function(e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} - }; - - var inp = d.input.getField(); - on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); - on(inp, "keydown", operation(cm, onKeyDown)); - on(inp, "keypress", operation(cm, onKeyPress)); - on(inp, "focus", bind(onFocus, cm)); - on(inp, "blur", bind(onBlur, cm)); - } - - function dragDropChanged(cm, value, old) { - var wasOn = old && old != CodeMirror.Init; - if (!value != !wasOn) { - var funcs = cm.display.dragFunctions; - var toggle = value ? on : off; - toggle(cm.display.scroller, "dragstart", funcs.start); - toggle(cm.display.scroller, "dragenter", funcs.enter); - toggle(cm.display.scroller, "dragover", funcs.over); - toggle(cm.display.scroller, "dragleave", funcs.leave); - toggle(cm.display.scroller, "drop", funcs.drop); - } - } - - // Called when the window resizes - function onResize(cm) { - var d = cm.display; - if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) - return; - // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; - d.scrollbarsClipped = false; - cm.setSize(); - } - - // MOUSE EVENTS - - // Return true when the given mouse event happened in a widget - function eventInWidget(display, e) { - for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { - if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || - (n.parentNode == display.sizer && n != display.mover)) - return true; - } - } - - // Given a mouse event, find the corresponding position. If liberal - // is false, it checks whether a gutter or scrollbar was clicked, - // and returns null if it was. forRect is used by rectangular - // selections, and tries to estimate a character position even for - // coordinates beyond the right of the text. - function posFromMouse(cm, e, liberal, forRect) { - var display = cm.display; - if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null; - - var x, y, space = display.lineSpace.getBoundingClientRect(); - // Fails unpredictably on IE[67] when mouse is dragged around quickly. - try { x = e.clientX - space.left; y = e.clientY - space.top; } - catch (e) { return null; } - var coords = coordsChar(cm, x, y), line; - if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { - var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; - coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); - } - return coords; - } - - // A mouse down can be a single click, double click, triple click, - // start of selection drag, start of text drag, new cursor - // (ctrl-click), rectangle drag (alt-drag), or xwin - // middle-click-paste. Or it might be a click on something we should - // not interfere with, such as a scrollbar or widget. - function onMouseDown(e) { - var cm = this, display = cm.display; - if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return; - display.shift = e.shiftKey; - - if (eventInWidget(display, e)) { - if (!webkit) { - // Briefly turn off draggability, to allow widgets to do - // normal dragging things. - display.scroller.draggable = false; - setTimeout(function(){display.scroller.draggable = true;}, 100); - } - return; - } - if (clickInGutter(cm, e)) return; - var start = posFromMouse(cm, e); - window.focus(); - - switch (e_button(e)) { - case 1: - // #3261: make sure, that we're not starting a second selection - if (cm.state.selectingText) - cm.state.selectingText(e); - else if (start) - leftButtonDown(cm, e, start); - else if (e_target(e) == display.scroller) - e_preventDefault(e); - break; - case 2: - if (webkit) cm.state.lastMiddleDown = +new Date; - if (start) extendSelection(cm.doc, start); - setTimeout(function() {display.input.focus();}, 20); - e_preventDefault(e); - break; - case 3: - if (captureRightClick) onContextMenu(cm, e); - else delayBlurEvent(cm); - break; - } - } - - var lastClick, lastDoubleClick; - function leftButtonDown(cm, e, start) { - if (ie) setTimeout(bind(ensureFocus, cm), 0); - else cm.curOp.focus = activeElt(); - - var now = +new Date, type; - if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { - type = "triple"; - } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { - type = "double"; - lastDoubleClick = {time: now, pos: start}; - } else { - type = "single"; - lastClick = {time: now, pos: start}; - } - - var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; - if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && - type == "single" && (contained = sel.contains(start)) > -1 && - (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) && - (cmp(contained.to(), start) > 0 || start.xRel < 0)) - leftButtonStartDrag(cm, e, start, modifier); - else - leftButtonSelect(cm, e, start, type, modifier); - } - - // Start a text drag. When it ends, see if any dragging actually - // happen, and treat as a click if it didn't. - function leftButtonStartDrag(cm, e, start, modifier) { - var display = cm.display, startTime = +new Date; - var dragEnd = operation(cm, function(e2) { - if (webkit) display.scroller.draggable = false; - cm.state.draggingText = false; - off(document, "mouseup", dragEnd); - off(display.scroller, "drop", dragEnd); - if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { - e_preventDefault(e2); - if (!modifier && +new Date - 200 < startTime) - extendSelection(cm.doc, start); - // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) - if (webkit || ie && ie_version == 9) - setTimeout(function() {document.body.focus(); display.input.focus();}, 20); - else - display.input.focus(); - } - }); - // Let the drag handler handle this. - if (webkit) display.scroller.draggable = true; - cm.state.draggingText = dragEnd; - dragEnd.copy = mac ? e.altKey : e.ctrlKey - // IE's approach to draggable - if (display.scroller.dragDrop) display.scroller.dragDrop(); - on(document, "mouseup", dragEnd); - on(display.scroller, "drop", dragEnd); - } - - // Normal selection, as opposed to text dragging. - function leftButtonSelect(cm, e, start, type, addNew) { - var display = cm.display, doc = cm.doc; - e_preventDefault(e); - - var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; - if (addNew && !e.shiftKey) { - ourIndex = doc.sel.contains(start); - if (ourIndex > -1) - ourRange = ranges[ourIndex]; - else - ourRange = new Range(start, start); - } else { - ourRange = doc.sel.primary(); - ourIndex = doc.sel.primIndex; - } - - if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) { - type = "rect"; - if (!addNew) ourRange = new Range(start, start); - start = posFromMouse(cm, e, true, true); - ourIndex = -1; - } else if (type == "double") { - var word = cm.findWordAt(start); - if (cm.display.shift || doc.extend) - ourRange = extendRange(doc, ourRange, word.anchor, word.head); - else - ourRange = word; - } else if (type == "triple") { - var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))); - if (cm.display.shift || doc.extend) - ourRange = extendRange(doc, ourRange, line.anchor, line.head); - else - ourRange = line; - } else { - ourRange = extendRange(doc, ourRange, start); - } - - if (!addNew) { - ourIndex = 0; - setSelection(doc, new Selection([ourRange], 0), sel_mouse); - startSel = doc.sel; - } else if (ourIndex == -1) { - ourIndex = ranges.length; - setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), - {scroll: false, origin: "*mouse"}); - } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { - setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), - {scroll: false, origin: "*mouse"}); - startSel = doc.sel; - } else { - replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); - } - - var lastPos = start; - function extendTo(pos) { - if (cmp(lastPos, pos) == 0) return; - lastPos = pos; - - if (type == "rect") { - var ranges = [], tabSize = cm.options.tabSize; - var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); - var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); - var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); - for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); - line <= end; line++) { - var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); - if (left == right) - ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); - else if (text.length > leftPos) - ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); - } - if (!ranges.length) ranges.push(new Range(start, start)); - setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), - {origin: "*mouse", scroll: false}); - cm.scrollIntoView(pos); - } else { - var oldRange = ourRange; - var anchor = oldRange.anchor, head = pos; - if (type != "single") { - if (type == "double") - var range = cm.findWordAt(pos); - else - var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); - if (cmp(range.anchor, anchor) > 0) { - head = range.head; - anchor = minPos(oldRange.from(), range.anchor); - } else { - head = range.anchor; - anchor = maxPos(oldRange.to(), range.head); - } - } - var ranges = startSel.ranges.slice(0); - ranges[ourIndex] = new Range(clipPos(doc, anchor), head); - setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); - } - } - - var editorSize = display.wrapper.getBoundingClientRect(); - // Used to ensure timeout re-tries don't fire when another extend - // happened in the meantime (clearTimeout isn't reliable -- at - // least on Chrome, the timeouts still happen even when cleared, - // if the clear happens after their scheduled firing time). - var counter = 0; - - function extend(e) { - var curCount = ++counter; - var cur = posFromMouse(cm, e, true, type == "rect"); - if (!cur) return; - if (cmp(cur, lastPos) != 0) { - cm.curOp.focus = activeElt(); - extendTo(cur); - var visible = visibleLines(display, doc); - if (cur.line >= visible.to || cur.line < visible.from) - setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); - } else { - var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; - if (outside) setTimeout(operation(cm, function() { - if (counter != curCount) return; - display.scroller.scrollTop += outside; - extend(e); - }), 50); - } - } - - function done(e) { - cm.state.selectingText = false; - counter = Infinity; - e_preventDefault(e); - display.input.focus(); - off(document, "mousemove", move); - off(document, "mouseup", up); - doc.history.lastSelOrigin = null; - } - - var move = operation(cm, function(e) { - if (!e_button(e)) done(e); - else extend(e); - }); - var up = operation(cm, done); - cm.state.selectingText = up; - on(document, "mousemove", move); - on(document, "mouseup", up); - } - - // Determines whether an event happened in the gutter, and fires the - // handlers for the corresponding event. - function gutterEvent(cm, e, type, prevent) { - try { var mX = e.clientX, mY = e.clientY; } - catch(e) { return false; } - if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; - if (prevent) e_preventDefault(e); - - var display = cm.display; - var lineBox = display.lineDiv.getBoundingClientRect(); - - if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); - mY -= lineBox.top - display.viewOffset; - - for (var i = 0; i < cm.options.gutters.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && g.getBoundingClientRect().right >= mX) { - var line = lineAtHeight(cm.doc, mY); - var gutter = cm.options.gutters[i]; - signal(cm, type, cm, line, gutter, e); - return e_defaultPrevented(e); - } - } - } - - function clickInGutter(cm, e) { - return gutterEvent(cm, e, "gutterClick", true); - } - - // Kludge to work around strange IE behavior where it'll sometimes - // re-fire a series of drag-related events right after the drop (#1551) - var lastDrop = 0; - - function onDrop(e) { - var cm = this; - clearDragCursor(cm); - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) - return; - e_preventDefault(e); - if (ie) lastDrop = +new Date; - var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; - if (!pos || cm.isReadOnly()) return; - // Might be a file drop, in which case we simply extract the text - // and insert it. - if (files && files.length && window.FileReader && window.File) { - var n = files.length, text = Array(n), read = 0; - var loadFile = function(file, i) { - if (cm.options.allowDropFileTypes && - indexOf(cm.options.allowDropFileTypes, file.type) == -1) - return; - - var reader = new FileReader; - reader.onload = operation(cm, function() { - var content = reader.result; - if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = ""; - text[i] = content; - if (++read == n) { - pos = clipPos(cm.doc, pos); - var change = {from: pos, to: pos, - text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), - origin: "paste"}; - makeChange(cm.doc, change); - setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); - } - }); - reader.readAsText(file); - }; - for (var i = 0; i < n; ++i) loadFile(files[i], i); - } else { // Normal drop - // Don't do a replace if the drop happened inside of the selected text. - if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { - cm.state.draggingText(e); - // Ensure the editor is re-focused - setTimeout(function() {cm.display.input.focus();}, 20); - return; - } - try { - var text = e.dataTransfer.getData("Text"); - if (text) { - if (cm.state.draggingText && !cm.state.draggingText.copy) - var selected = cm.listSelections(); - setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); - if (selected) for (var i = 0; i < selected.length; ++i) - replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); - cm.replaceSelection(text, "around", "paste"); - cm.display.input.focus(); - } - } - catch(e){} - } - } - - function onDragStart(cm, e) { - if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; - - e.dataTransfer.setData("Text", cm.getSelection()); - e.dataTransfer.effectAllowed = "copyMove" - - // Use dummy image instead of default browsers image. - // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. - if (e.dataTransfer.setDragImage && !safari) { - var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); - img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; - if (presto) { - img.width = img.height = 1; - cm.display.wrapper.appendChild(img); - // Force a relayout, or Opera won't use our image for some obscure reason - img._top = img.offsetTop; - } - e.dataTransfer.setDragImage(img, 0, 0); - if (presto) img.parentNode.removeChild(img); - } - } - - function onDragOver(cm, e) { - var pos = posFromMouse(cm, e); - if (!pos) return; - var frag = document.createDocumentFragment(); - drawSelectionCursor(cm, pos, frag); - if (!cm.display.dragCursor) { - cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors"); - cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv); - } - removeChildrenAndAdd(cm.display.dragCursor, frag); - } - - function clearDragCursor(cm) { - if (cm.display.dragCursor) { - cm.display.lineSpace.removeChild(cm.display.dragCursor); - cm.display.dragCursor = null; - } - } - - // SCROLL EVENTS - - // Sync the scrollable area and scrollbars, ensure the viewport - // covers the visible area. - function setScrollTop(cm, val) { - if (Math.abs(cm.doc.scrollTop - val) < 2) return; - cm.doc.scrollTop = val; - if (!gecko) updateDisplaySimple(cm, {top: val}); - if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; - cm.display.scrollbars.setScrollTop(val); - if (gecko) updateDisplaySimple(cm); - startWorker(cm, 100); - } - // Sync scroller and scrollbar, ensure the gutter elements are - // aligned. - function setScrollLeft(cm, val, isScroller) { - if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; - val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); - cm.doc.scrollLeft = val; - alignHorizontally(cm); - if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; - cm.display.scrollbars.setScrollLeft(val); - } - - // Since the delta values reported on mouse wheel events are - // unstandardized between browsers and even browser versions, and - // generally horribly unpredictable, this code starts by measuring - // the scroll effect that the first few mouse wheel events have, - // and, from that, detects the way it can convert deltas to pixel - // offsets afterwards. - // - // The reason we want to know the amount a wheel event will scroll - // is that it gives us a chance to update the display before the - // actual scrolling happens, reducing flickering. - - var wheelSamples = 0, wheelPixelsPerUnit = null; - // Fill in a browser-detected starting value on browsers where we - // know one. These don't have to be accurate -- the result of them - // being wrong would just be a slight flicker on the first wheel - // scroll (if it is large enough). - if (ie) wheelPixelsPerUnit = -.53; - else if (gecko) wheelPixelsPerUnit = 15; - else if (chrome) wheelPixelsPerUnit = -.7; - else if (safari) wheelPixelsPerUnit = -1/3; - - var wheelEventDelta = function(e) { - var dx = e.wheelDeltaX, dy = e.wheelDeltaY; - if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; - if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; - else if (dy == null) dy = e.wheelDelta; - return {x: dx, y: dy}; - }; - CodeMirror.wheelEventPixels = function(e) { - var delta = wheelEventDelta(e); - delta.x *= wheelPixelsPerUnit; - delta.y *= wheelPixelsPerUnit; - return delta; - }; - - function onScrollWheel(cm, e) { - var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; - - var display = cm.display, scroll = display.scroller; - // Quit if there's nothing to scroll here - var canScrollX = scroll.scrollWidth > scroll.clientWidth; - var canScrollY = scroll.scrollHeight > scroll.clientHeight; - if (!(dx && canScrollX || dy && canScrollY)) return; - - // Webkit browsers on OS X abort momentum scrolls when the target - // of the scroll event is removed from the scrollable element. - // This hack (see related code in patchDisplay) makes sure the - // element is kept around. - if (dy && mac && webkit) { - outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { - for (var i = 0; i < view.length; i++) { - if (view[i].node == cur) { - cm.display.currentWheelTarget = cur; - break outer; - } - } - } - } - - // On some browsers, horizontal scrolling will cause redraws to - // happen before the gutter has been realigned, causing it to - // wriggle around in a most unseemly way. When we have an - // estimated pixels/delta value, we just handle horizontal - // scrolling entirely here. It'll be slightly off from native, but - // better than glitching out. - if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { - if (dy && canScrollY) - setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); - setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); - // Only prevent default scrolling if vertical scrolling is - // actually possible. Otherwise, it causes vertical scroll - // jitter on OSX trackpads when deltaX is small and deltaY - // is large (issue #3579) - if (!dy || (dy && canScrollY)) - e_preventDefault(e); - display.wheelStartX = null; // Abort measurement, if in progress - return; - } - - // 'Project' the visible viewport to cover the area that is being - // scrolled into view (if we know enough to estimate it). - if (dy && wheelPixelsPerUnit != null) { - var pixels = dy * wheelPixelsPerUnit; - var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; - if (pixels < 0) top = Math.max(0, top + pixels - 50); - else bot = Math.min(cm.doc.height, bot + pixels + 50); - updateDisplaySimple(cm, {top: top, bottom: bot}); - } - - if (wheelSamples < 20) { - if (display.wheelStartX == null) { - display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; - display.wheelDX = dx; display.wheelDY = dy; - setTimeout(function() { - if (display.wheelStartX == null) return; - var movedX = scroll.scrollLeft - display.wheelStartX; - var movedY = scroll.scrollTop - display.wheelStartY; - var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || - (movedX && display.wheelDX && movedX / display.wheelDX); - display.wheelStartX = display.wheelStartY = null; - if (!sample) return; - wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); - ++wheelSamples; - }, 200); - } else { - display.wheelDX += dx; display.wheelDY += dy; - } - } - } - - // KEY EVENTS - - // Run a handler that was bound to a key. - function doHandleBinding(cm, bound, dropShift) { - if (typeof bound == "string") { - bound = commands[bound]; - if (!bound) return false; - } - // Ensure previous input has been read, so that the handler sees a - // consistent view of the document - cm.display.input.ensurePolled(); - var prevShift = cm.display.shift, done = false; - try { - if (cm.isReadOnly()) cm.state.suppressEdits = true; - if (dropShift) cm.display.shift = false; - done = bound(cm) != Pass; - } finally { - cm.display.shift = prevShift; - cm.state.suppressEdits = false; - } - return done; - } - - function lookupKeyForEditor(cm, name, handle) { - for (var i = 0; i < cm.state.keyMaps.length; i++) { - var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); - if (result) return result; - } - return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) - || lookupKey(name, cm.options.keyMap, handle, cm); - } - - var stopSeq = new Delayed; - function dispatchKey(cm, name, e, handle) { - var seq = cm.state.keySeq; - if (seq) { - if (isModifierKey(name)) return "handled"; - stopSeq.set(50, function() { - if (cm.state.keySeq == seq) { - cm.state.keySeq = null; - cm.display.input.reset(); - } - }); - name = seq + " " + name; - } - var result = lookupKeyForEditor(cm, name, handle); - - if (result == "multi") - cm.state.keySeq = name; - if (result == "handled") - signalLater(cm, "keyHandled", cm, name, e); - - if (result == "handled" || result == "multi") { - e_preventDefault(e); - restartBlink(cm); - } - - if (seq && !result && /\'$/.test(name)) { - e_preventDefault(e); - return true; - } - return !!result; - } - - // Handle a key from the keydown event. - function handleKeyBinding(cm, e) { - var name = keyName(e, true); - if (!name) return false; - - if (e.shiftKey && !cm.state.keySeq) { - // First try to resolve full name (including 'Shift-'). Failing - // that, see if there is a cursor-motion command (starting with - // 'go') bound to the keyname without 'Shift-'. - return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);}) - || dispatchKey(cm, name, e, function(b) { - if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) - return doHandleBinding(cm, b); - }); - } else { - return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); }); - } - } - - // Handle a key from the keypress event - function handleCharBinding(cm, e, ch) { - return dispatchKey(cm, "'" + ch + "'", e, - function(b) { return doHandleBinding(cm, b, true); }); - } - - var lastStoppedKey = null; - function onKeyDown(e) { - var cm = this; - cm.curOp.focus = activeElt(); - if (signalDOMEvent(cm, e)) return; - // IE does strange things with escape. - if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; - var code = e.keyCode; - cm.display.shift = code == 16 || e.shiftKey; - var handled = handleKeyBinding(cm, e); - if (presto) { - lastStoppedKey = handled ? code : null; - // Opera has no cut event... we try to at least catch the key combo - if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) - cm.replaceSelection("", null, "cut"); - } - - // Turn mouse into crosshair when Alt is held on Mac. - if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) - showCrossHair(cm); - } - - function showCrossHair(cm) { - var lineDiv = cm.display.lineDiv; - addClass(lineDiv, "CodeMirror-crosshair"); - - function up(e) { - if (e.keyCode == 18 || !e.altKey) { - rmClass(lineDiv, "CodeMirror-crosshair"); - off(document, "keyup", up); - off(document, "mouseover", up); - } - } - on(document, "keyup", up); - on(document, "mouseover", up); - } - - function onKeyUp(e) { - if (e.keyCode == 16) this.doc.sel.shift = false; - signalDOMEvent(this, e); - } - - function onKeyPress(e) { - var cm = this; - if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; - var keyCode = e.keyCode, charCode = e.charCode; - if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} - if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return; - var ch = String.fromCharCode(charCode == null ? keyCode : charCode); - if (handleCharBinding(cm, e, ch)) return; - cm.display.input.onKeyPress(e); - } - - // FOCUS/BLUR EVENTS - - function delayBlurEvent(cm) { - cm.state.delayingBlurEvent = true; - setTimeout(function() { - if (cm.state.delayingBlurEvent) { - cm.state.delayingBlurEvent = false; - onBlur(cm); - } - }, 100); - } - - function onFocus(cm) { - if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; - - if (cm.options.readOnly == "nocursor") return; - if (!cm.state.focused) { - signal(cm, "focus", cm); - cm.state.focused = true; - addClass(cm.display.wrapper, "CodeMirror-focused"); - // This test prevents this from firing when a context - // menu is closed (since the input reset would kill the - // select-all detection hack) - if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { - cm.display.input.reset(); - if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730 - } - cm.display.input.receivedFocus(); - } - restartBlink(cm); - } - function onBlur(cm) { - if (cm.state.delayingBlurEvent) return; - - if (cm.state.focused) { - signal(cm, "blur", cm); - cm.state.focused = false; - rmClass(cm.display.wrapper, "CodeMirror-focused"); - } - clearInterval(cm.display.blinker); - setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); - } - - // CONTEXT MENU HANDLING - - // To make the context menu work, we need to briefly unhide the - // textarea (making it as unobtrusive as possible) to let the - // right-click take effect on it. - function onContextMenu(cm, e) { - if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; - if (signalDOMEvent(cm, e, "contextmenu")) return; - cm.display.input.onContextMenu(e); - } - - function contextMenuInGutter(cm, e) { - if (!hasHandler(cm, "gutterContextMenu")) return false; - return gutterEvent(cm, e, "gutterContextMenu", false); - } - - // UPDATING - - // Compute the position of the end of a change (its 'to' property - // refers to the pre-change end). - var changeEnd = CodeMirror.changeEnd = function(change) { - if (!change.text) return change.to; - return Pos(change.from.line + change.text.length - 1, - lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); - }; - - // Adjust a position to refer to the post-change position of the - // same text, or the end of the change if the change covers it. - function adjustForChange(pos, change) { - if (cmp(pos, change.from) < 0) return pos; - if (cmp(pos, change.to) <= 0) return changeEnd(change); - - var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; - if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; - return Pos(line, ch); - } - - function computeSelAfterChange(doc, change) { - var out = []; - for (var i = 0; i < doc.sel.ranges.length; i++) { - var range = doc.sel.ranges[i]; - out.push(new Range(adjustForChange(range.anchor, change), - adjustForChange(range.head, change))); - } - return normalizeSelection(out, doc.sel.primIndex); - } - - function offsetPos(pos, old, nw) { - if (pos.line == old.line) - return Pos(nw.line, pos.ch - old.ch + nw.ch); - else - return Pos(nw.line + (pos.line - old.line), pos.ch); - } - - // Used by replaceSelections to allow moving the selection to the - // start or around the replaced test. Hint may be "start" or "around". - function computeReplacedSel(doc, changes, hint) { - var out = []; - var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; - for (var i = 0; i < changes.length; i++) { - var change = changes[i]; - var from = offsetPos(change.from, oldPrev, newPrev); - var to = offsetPos(changeEnd(change), oldPrev, newPrev); - oldPrev = change.to; - newPrev = to; - if (hint == "around") { - var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; - out[i] = new Range(inv ? to : from, inv ? from : to); - } else { - out[i] = new Range(from, from); - } - } - return new Selection(out, doc.sel.primIndex); - } - - // Allow "beforeChange" event handlers to influence a change - function filterChange(doc, change, update) { - var obj = { - canceled: false, - from: change.from, - to: change.to, - text: change.text, - origin: change.origin, - cancel: function() { this.canceled = true; } - }; - if (update) obj.update = function(from, to, text, origin) { - if (from) this.from = clipPos(doc, from); - if (to) this.to = clipPos(doc, to); - if (text) this.text = text; - if (origin !== undefined) this.origin = origin; - }; - signal(doc, "beforeChange", doc, obj); - if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); - - if (obj.canceled) return null; - return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; - } - - // Apply a change to a document, and add it to the document's - // history, and propagating it to all linked documents. - function makeChange(doc, change, ignoreReadOnly) { - if (doc.cm) { - if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); - if (doc.cm.state.suppressEdits) return; - } - - if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { - change = filterChange(doc, change, true); - if (!change) return; - } - - // Possibly split or suppress the update based on the presence - // of read-only spans in its range. - var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); - if (split) { - for (var i = split.length - 1; i >= 0; --i) - makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}); - } else { - makeChangeInner(doc, change); - } - } - - function makeChangeInner(doc, change) { - if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return; - var selAfter = computeSelAfterChange(doc, change); - addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); - - makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); - var rebased = []; - - linkedDocs(doc, function(doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); - }); - } - - // Revert a change stored in a document's history. - function makeChangeFromHistory(doc, type, allowSelectionOnly) { - if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) return; - - var hist = doc.history, event, selAfter = doc.sel; - var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; - - // Verify that there is a useable event (so that ctrl-z won't - // needlessly clear selection events) - for (var i = 0; i < source.length; i++) { - event = source[i]; - if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) - break; - } - if (i == source.length) return; - hist.lastOrigin = hist.lastSelOrigin = null; - - for (;;) { - event = source.pop(); - if (event.ranges) { - pushSelectionToHistory(event, dest); - if (allowSelectionOnly && !event.equals(doc.sel)) { - setSelection(doc, event, {clearRedo: false}); - return; - } - selAfter = event; - } - else break; - } - - // Build up a reverse change object to add to the opposite history - // stack (redo when undoing, and vice versa). - var antiChanges = []; - pushSelectionToHistory(selAfter, dest); - dest.push({changes: antiChanges, generation: hist.generation}); - hist.generation = event.generation || ++hist.maxGeneration; - - var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); - - for (var i = event.changes.length - 1; i >= 0; --i) { - var change = event.changes[i]; - change.origin = type; - if (filter && !filterChange(doc, change, false)) { - source.length = 0; - return; - } - - antiChanges.push(historyChangeFromChange(doc, change)); - - var after = i ? computeSelAfterChange(doc, change) : lst(source); - makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); - if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); - var rebased = []; - - // Propagate to the linked documents - linkedDocs(doc, function(doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); - }); - } - } - - // Sub-views need their line numbers shifted when text is added - // above or below them in the parent document. - function shiftDoc(doc, distance) { - if (distance == 0) return; - doc.first += distance; - doc.sel = new Selection(map(doc.sel.ranges, function(range) { - return new Range(Pos(range.anchor.line + distance, range.anchor.ch), - Pos(range.head.line + distance, range.head.ch)); - }), doc.sel.primIndex); - if (doc.cm) { - regChange(doc.cm, doc.first, doc.first - distance, distance); - for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) - regLineChange(doc.cm, l, "gutter"); - } - } - - // More lower-level change function, handling only a single document - // (not linked ones). - function makeChangeSingleDoc(doc, change, selAfter, spans) { - if (doc.cm && !doc.cm.curOp) - return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); - - if (change.to.line < doc.first) { - shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); - return; - } - if (change.from.line > doc.lastLine()) return; - - // Clip the change to the size of this doc - if (change.from.line < doc.first) { - var shift = change.text.length - 1 - (doc.first - change.from.line); - shiftDoc(doc, shift); - change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), - text: [lst(change.text)], origin: change.origin}; - } - var last = doc.lastLine(); - if (change.to.line > last) { - change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), - text: [change.text[0]], origin: change.origin}; - } - - change.removed = getBetween(doc, change.from, change.to); - - if (!selAfter) selAfter = computeSelAfterChange(doc, change); - if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); - else updateDoc(doc, change, spans); - setSelectionNoUndo(doc, selAfter, sel_dontScroll); - } - - // Handle the interaction of a change to a document with the editor - // that this document is part of. - function makeChangeSingleDocInEditor(cm, change, spans) { - var doc = cm.doc, display = cm.display, from = change.from, to = change.to; - - var recomputeMaxLength = false, checkWidthStart = from.line; - if (!cm.options.lineWrapping) { - checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); - doc.iter(checkWidthStart, to.line + 1, function(line) { - if (line == display.maxLine) { - recomputeMaxLength = true; - return true; - } - }); - } - - if (doc.sel.contains(change.from, change.to) > -1) - signalCursorActivity(cm); - - updateDoc(doc, change, spans, estimateHeight(cm)); - - if (!cm.options.lineWrapping) { - doc.iter(checkWidthStart, from.line + change.text.length, function(line) { - var len = lineLength(line); - if (len > display.maxLineLength) { - display.maxLine = line; - display.maxLineLength = len; - display.maxLineChanged = true; - recomputeMaxLength = false; - } - }); - if (recomputeMaxLength) cm.curOp.updateMaxLine = true; - } - - // Adjust frontier, schedule worker - doc.frontier = Math.min(doc.frontier, from.line); - startWorker(cm, 400); - - var lendiff = change.text.length - (to.line - from.line) - 1; - // Remember that these lines changed, for updating the display - if (change.full) - regChange(cm); - else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) - regLineChange(cm, from.line, "text"); - else - regChange(cm, from.line, to.line + 1, lendiff); - - var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); - if (changeHandler || changesHandler) { - var obj = { - from: from, to: to, - text: change.text, - removed: change.removed, - origin: change.origin - }; - if (changeHandler) signalLater(cm, "change", cm, obj); - if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); - } - cm.display.selForContextMenu = null; - } - - function replaceRange(doc, code, from, to, origin) { - if (!to) to = from; - if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } - if (typeof code == "string") code = doc.splitLines(code); - makeChange(doc, {from: from, to: to, text: code, origin: origin}); - } - - // SCROLLING THINGS INTO VIEW - - // If an editor sits on the top or bottom of the window, partially - // scrolled out of view, this ensures that the cursor is visible. - function maybeScrollWindow(cm, coords) { - if (signalDOMEvent(cm, "scrollCursorIntoView")) return; - - var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; - if (coords.top + box.top < 0) doScroll = true; - else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; - if (doScroll != null && !phantom) { - var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + - (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + - (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " + - coords.left + "px; width: 2px;"); - cm.display.lineSpace.appendChild(scrollNode); - scrollNode.scrollIntoView(doScroll); - cm.display.lineSpace.removeChild(scrollNode); - } - } - - // Scroll a given position into view (immediately), verifying that - // it actually became visible (as line heights are accurately - // measured, the position of something may 'drift' during drawing). - function scrollPosIntoView(cm, pos, end, margin) { - if (margin == null) margin = 0; - for (var limit = 0; limit < 5; limit++) { - var changed = false, coords = cursorCoords(cm, pos); - var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); - var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), - Math.min(coords.top, endCoords.top) - margin, - Math.max(coords.left, endCoords.left), - Math.max(coords.bottom, endCoords.bottom) + margin); - var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; - if (scrollPos.scrollTop != null) { - setScrollTop(cm, scrollPos.scrollTop); - if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; - } - if (scrollPos.scrollLeft != null) { - setScrollLeft(cm, scrollPos.scrollLeft); - if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; - } - if (!changed) break; - } - return coords; - } - - // Scroll a given set of coordinates into view (immediately). - function scrollIntoView(cm, x1, y1, x2, y2) { - var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); - if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); - if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); - } - - // Calculate a new scroll position needed to scroll the given - // rectangle into view. Returns an object with scrollTop and - // scrollLeft properties. When these are undefined, the - // vertical/horizontal position does not need to be adjusted. - function calculateScrollPos(cm, x1, y1, x2, y2) { - var display = cm.display, snapMargin = textHeight(cm.display); - if (y1 < 0) y1 = 0; - var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; - var screen = displayHeight(cm), result = {}; - if (y2 - y1 > screen) y2 = y1 + screen; - var docBottom = cm.doc.height + paddingVert(display); - var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; - if (y1 < screentop) { - result.scrollTop = atTop ? 0 : y1; - } else if (y2 > screentop + screen) { - var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); - if (newTop != screentop) result.scrollTop = newTop; - } - - var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; - var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); - var tooWide = x2 - x1 > screenw; - if (tooWide) x2 = x1 + screenw; - if (x1 < 10) - result.scrollLeft = 0; - else if (x1 < screenleft) - result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); - else if (x2 > screenw + screenleft - 3) - result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; - return result; - } - - // Store a relative adjustment to the scroll position in the current - // operation (to be applied when the operation finishes). - function addToScrollPos(cm, left, top) { - if (left != null || top != null) resolveScrollToPos(cm); - if (left != null) - cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left; - if (top != null) - cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; - } - - // Make sure that at the end of the operation the current cursor is - // shown. - function ensureCursorVisible(cm) { - resolveScrollToPos(cm); - var cur = cm.getCursor(), from = cur, to = cur; - if (!cm.options.lineWrapping) { - from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; - to = Pos(cur.line, cur.ch + 1); - } - cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true}; - } - - // When an operation has its scrollToPos property set, and another - // scroll action is applied before the end of the operation, this - // 'simulates' scrolling that position into view in a cheap way, so - // that the effect of intermediate scroll commands is not ignored. - function resolveScrollToPos(cm) { - var range = cm.curOp.scrollToPos; - if (range) { - cm.curOp.scrollToPos = null; - var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); - var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), - Math.min(from.top, to.top) - range.margin, - Math.max(from.right, to.right), - Math.max(from.bottom, to.bottom) + range.margin); - cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); - } - } - - // API UTILITIES - - // Indent the given line. The how parameter can be "smart", - // "add"/null, "subtract", or "prev". When aggressive is false - // (typically set to true for forced single-line indents), empty - // lines are not indented, and places where the mode returns Pass - // are left alone. - function indentLine(cm, n, how, aggressive) { - var doc = cm.doc, state; - if (how == null) how = "add"; - if (how == "smart") { - // Fall back to "prev" when the mode doesn't have an indentation - // method. - if (!doc.mode.indent) how = "prev"; - else state = getStateBefore(cm, n); - } - - var tabSize = cm.options.tabSize; - var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); - if (line.stateAfter) line.stateAfter = null; - var curSpaceString = line.text.match(/^\s*/)[0], indentation; - if (!aggressive && !/\S/.test(line.text)) { - indentation = 0; - how = "not"; - } else if (how == "smart") { - indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass || indentation > 150) { - if (!aggressive) return; - how = "prev"; - } - } - if (how == "prev") { - if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); - else indentation = 0; - } else if (how == "add") { - indentation = curSpace + cm.options.indentUnit; - } else if (how == "subtract") { - indentation = curSpace - cm.options.indentUnit; - } else if (typeof how == "number") { - indentation = curSpace + how; - } - indentation = Math.max(0, indentation); - - var indentString = "", pos = 0; - if (cm.options.indentWithTabs) - for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} - if (pos < indentation) indentString += spaceStr(indentation - pos); - - if (indentString != curSpaceString) { - replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); - line.stateAfter = null; - return true; - } else { - // Ensure that, if the cursor was in the whitespace at the start - // of the line, it is moved to the end of that space. - for (var i = 0; i < doc.sel.ranges.length; i++) { - var range = doc.sel.ranges[i]; - if (range.head.line == n && range.head.ch < curSpaceString.length) { - var pos = Pos(n, curSpaceString.length); - replaceOneSelection(doc, i, new Range(pos, pos)); - break; - } - } - } - } - - // Utility for applying a change to a line by handle or number, - // returning the number and optionally registering the line as - // changed. - function changeLine(doc, handle, changeType, op) { - var no = handle, line = handle; - if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); - else no = lineNo(handle); - if (no == null) return null; - if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); - return line; - } - - // Helper for deleting text near the selection(s), used to implement - // backspace, delete, and similar functionality. - function deleteNearSelection(cm, compute) { - var ranges = cm.doc.sel.ranges, kill = []; - // Build up a set of ranges to kill first, merging overlapping - // ranges. - for (var i = 0; i < ranges.length; i++) { - var toKill = compute(ranges[i]); - while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { - var replaced = kill.pop(); - if (cmp(replaced.from, toKill.from) < 0) { - toKill.from = replaced.from; - break; - } - } - kill.push(toKill); - } - // Next, remove those actual ranges. - runInOp(cm, function() { - for (var i = kill.length - 1; i >= 0; i--) - replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); - ensureCursorVisible(cm); - }); - } - - // Used for horizontal relative motion. Dir is -1 or 1 (left or - // right), unit can be "char", "column" (like char, but doesn't - // cross line boundaries), "word" (across next word), or "group" (to - // the start of next group of word or non-word-non-whitespace - // chars). The visually param controls whether, in right-to-left - // text, direction 1 means to move towards the next index in the - // string, or towards the character to the right of the current - // position. The resulting position will have a hitSide=true - // property if it reached the end of the document. - function findPosH(doc, pos, dir, unit, visually) { - var line = pos.line, ch = pos.ch, origDir = dir; - var lineObj = getLine(doc, line); - function findNextLine() { - var l = line + dir; - if (l < doc.first || l >= doc.first + doc.size) return false - line = l; - return lineObj = getLine(doc, l); - } - function moveOnce(boundToLine) { - var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); - if (next == null) { - if (!boundToLine && findNextLine()) { - if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); - else ch = dir < 0 ? lineObj.text.length : 0; - } else return false - } else ch = next; - return true; - } - - if (unit == "char") { - moveOnce() - } else if (unit == "column") { - moveOnce(true) - } else if (unit == "word" || unit == "group") { - var sawType = null, group = unit == "group"; - var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); - for (var first = true;; first = false) { - if (dir < 0 && !moveOnce(!first)) break; - var cur = lineObj.text.charAt(ch) || "\n"; - var type = isWordChar(cur, helper) ? "w" - : group && cur == "\n" ? "n" - : !group || /\s/.test(cur) ? null - : "p"; - if (group && !first && !type) type = "s"; - if (sawType && sawType != type) { - if (dir < 0) {dir = 1; moveOnce();} - break; - } - - if (type) sawType = type; - if (dir > 0 && !moveOnce(!first)) break; - } - } - var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true); - if (!cmp(pos, result)) result.hitSide = true; - return result; - } - - // For relative vertical movement. Dir may be -1 or 1. Unit can be - // "page" or "line". The resulting position will have a hitSide=true - // property if it reached the end of the document. - function findPosV(cm, pos, dir, unit) { - var doc = cm.doc, x = pos.left, y; - if (unit == "page") { - var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); - y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); - } else if (unit == "line") { - y = dir > 0 ? pos.bottom + 3 : pos.top - 3; - } - for (;;) { - var target = coordsChar(cm, x, y); - if (!target.outside) break; - if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } - y += dir * 5; - } - return target; - } - - // EDITOR METHODS - - // The publicly visible API. Note that methodOp(f) means - // 'wrap f in an operation, performed on its `this` parameter'. - - // This is not the complete set of editor methods. Most of the - // methods defined on the Doc type are also injected into - // CodeMirror.prototype, for backwards compatibility and - // convenience. - - CodeMirror.prototype = { - constructor: CodeMirror, - focus: function(){window.focus(); this.display.input.focus();}, - - setOption: function(option, value) { - var options = this.options, old = options[option]; - if (options[option] == value && option != "mode") return; - options[option] = value; - if (optionHandlers.hasOwnProperty(option)) - operation(this, optionHandlers[option])(this, value, old); - }, - - getOption: function(option) {return this.options[option];}, - getDoc: function() {return this.doc;}, - - addKeyMap: function(map, bottom) { - this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); - }, - removeKeyMap: function(map) { - var maps = this.state.keyMaps; - for (var i = 0; i < maps.length; ++i) - if (maps[i] == map || maps[i].name == map) { - maps.splice(i, 1); - return true; - } - }, - - addOverlay: methodOp(function(spec, options) { - var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); - if (mode.startState) throw new Error("Overlays may not be stateful."); - this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); - this.state.modeGen++; - regChange(this); - }), - removeOverlay: methodOp(function(spec) { - var overlays = this.state.overlays; - for (var i = 0; i < overlays.length; ++i) { - var cur = overlays[i].modeSpec; - if (cur == spec || typeof spec == "string" && cur.name == spec) { - overlays.splice(i, 1); - this.state.modeGen++; - regChange(this); - return; - } - } - }), - - indentLine: methodOp(function(n, dir, aggressive) { - if (typeof dir != "string" && typeof dir != "number") { - if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; - else dir = dir ? "add" : "subtract"; - } - if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); - }), - indentSelection: methodOp(function(how) { - var ranges = this.doc.sel.ranges, end = -1; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - if (!range.empty()) { - var from = range.from(), to = range.to(); - var start = Math.max(end, from.line); - end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; - for (var j = start; j < end; ++j) - indentLine(this, j, how); - var newRanges = this.doc.sel.ranges; - if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) - replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); - } else if (range.head.line > end) { - indentLine(this, range.head.line, how, true); - end = range.head.line; - if (i == this.doc.sel.primIndex) ensureCursorVisible(this); - } - } - }), - - // Fetch the parser token for a given character. Useful for hacks - // that want to inspect the mode state (say, for completion). - getTokenAt: function(pos, precise) { - return takeToken(this, pos, precise); - }, - - getLineTokens: function(line, precise) { - return takeToken(this, Pos(line), precise, true); - }, - - getTokenTypeAt: function(pos) { - pos = clipPos(this.doc, pos); - var styles = getLineStyles(this, getLine(this.doc, pos.line)); - var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; - var type; - if (ch == 0) type = styles[2]; - else for (;;) { - var mid = (before + after) >> 1; - if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; - else if (styles[mid * 2 + 1] < ch) before = mid + 1; - else { type = styles[mid * 2 + 2]; break; } - } - var cut = type ? type.indexOf("cm-overlay ") : -1; - return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); - }, - - getModeAt: function(pos) { - var mode = this.doc.mode; - if (!mode.innerMode) return mode; - return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; - }, - - getHelper: function(pos, type) { - return this.getHelpers(pos, type)[0]; - }, - - getHelpers: function(pos, type) { - var found = []; - if (!helpers.hasOwnProperty(type)) return found; - var help = helpers[type], mode = this.getModeAt(pos); - if (typeof mode[type] == "string") { - if (help[mode[type]]) found.push(help[mode[type]]); - } else if (mode[type]) { - for (var i = 0; i < mode[type].length; i++) { - var val = help[mode[type][i]]; - if (val) found.push(val); - } - } else if (mode.helperType && help[mode.helperType]) { - found.push(help[mode.helperType]); - } else if (help[mode.name]) { - found.push(help[mode.name]); - } - for (var i = 0; i < help._global.length; i++) { - var cur = help._global[i]; - if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) - found.push(cur.val); - } - return found; - }, - - getStateAfter: function(line, precise) { - var doc = this.doc; - line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); - return getStateBefore(this, line + 1, precise); - }, - - cursorCoords: function(start, mode) { - var pos, range = this.doc.sel.primary(); - if (start == null) pos = range.head; - else if (typeof start == "object") pos = clipPos(this.doc, start); - else pos = start ? range.from() : range.to(); - return cursorCoords(this, pos, mode || "page"); - }, - - charCoords: function(pos, mode) { - return charCoords(this, clipPos(this.doc, pos), mode || "page"); - }, - - coordsChar: function(coords, mode) { - coords = fromCoordSystem(this, coords, mode || "page"); - return coordsChar(this, coords.left, coords.top); - }, - - lineAtHeight: function(height, mode) { - height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; - return lineAtHeight(this.doc, height + this.display.viewOffset); - }, - heightAtLine: function(line, mode) { - var end = false, lineObj; - if (typeof line == "number") { - var last = this.doc.first + this.doc.size - 1; - if (line < this.doc.first) line = this.doc.first; - else if (line > last) { line = last; end = true; } - lineObj = getLine(this.doc, line); - } else { - lineObj = line; - } - return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + - (end ? this.doc.height - heightAtLine(lineObj) : 0); - }, - - defaultTextHeight: function() { return textHeight(this.display); }, - defaultCharWidth: function() { return charWidth(this.display); }, - - setGutterMarker: methodOp(function(line, gutterID, value) { - return changeLine(this.doc, line, "gutter", function(line) { - var markers = line.gutterMarkers || (line.gutterMarkers = {}); - markers[gutterID] = value; - if (!value && isEmpty(markers)) line.gutterMarkers = null; - return true; - }); - }), - - clearGutter: methodOp(function(gutterID) { - var cm = this, doc = cm.doc, i = doc.first; - doc.iter(function(line) { - if (line.gutterMarkers && line.gutterMarkers[gutterID]) { - line.gutterMarkers[gutterID] = null; - regLineChange(cm, i, "gutter"); - if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; - } - ++i; - }); - }), - - lineInfo: function(line) { - if (typeof line == "number") { - if (!isLine(this.doc, line)) return null; - var n = line; - line = getLine(this.doc, line); - if (!line) return null; - } else { - var n = lineNo(line); - if (n == null) return null; - } - return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, - textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, - widgets: line.widgets}; - }, - - getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};}, - - addWidget: function(pos, node, scroll, vert, horiz) { - var display = this.display; - pos = cursorCoords(this, clipPos(this.doc, pos)); - var top = pos.bottom, left = pos.left; - node.style.position = "absolute"; - node.setAttribute("cm-ignore-events", "true"); - this.display.input.setUneditable(node); - display.sizer.appendChild(node); - if (vert == "over") { - top = pos.top; - } else if (vert == "above" || vert == "near") { - var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), - hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); - // Default to positioning above (if specified and possible); otherwise default to positioning below - if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) - top = pos.top - node.offsetHeight; - else if (pos.bottom + node.offsetHeight <= vspace) - top = pos.bottom; - if (left + node.offsetWidth > hspace) - left = hspace - node.offsetWidth; - } - node.style.top = top + "px"; - node.style.left = node.style.right = ""; - if (horiz == "right") { - left = display.sizer.clientWidth - node.offsetWidth; - node.style.right = "0px"; - } else { - if (horiz == "left") left = 0; - else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; - node.style.left = left + "px"; - } - if (scroll) - scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); - }, - - triggerOnKeyDown: methodOp(onKeyDown), - triggerOnKeyPress: methodOp(onKeyPress), - triggerOnKeyUp: onKeyUp, - - execCommand: function(cmd) { - if (commands.hasOwnProperty(cmd)) - return commands[cmd].call(null, this); - }, - - triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), - - findPosH: function(from, amount, unit, visually) { - var dir = 1; - if (amount < 0) { dir = -1; amount = -amount; } - for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { - cur = findPosH(this.doc, cur, dir, unit, visually); - if (cur.hitSide) break; - } - return cur; - }, - - moveH: methodOp(function(dir, unit) { - var cm = this; - cm.extendSelectionsBy(function(range) { - if (cm.display.shift || cm.doc.extend || range.empty()) - return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually); - else - return dir < 0 ? range.from() : range.to(); - }, sel_move); - }), - - deleteH: methodOp(function(dir, unit) { - var sel = this.doc.sel, doc = this.doc; - if (sel.somethingSelected()) - doc.replaceSelection("", null, "+delete"); - else - deleteNearSelection(this, function(range) { - var other = findPosH(doc, range.head, dir, unit, false); - return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}; - }); - }), - - findPosV: function(from, amount, unit, goalColumn) { - var dir = 1, x = goalColumn; - if (amount < 0) { dir = -1; amount = -amount; } - for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { - var coords = cursorCoords(this, cur, "div"); - if (x == null) x = coords.left; - else coords.left = x; - cur = findPosV(this, coords, dir, unit); - if (cur.hitSide) break; - } - return cur; - }, - - moveV: methodOp(function(dir, unit) { - var cm = this, doc = this.doc, goals = []; - var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected(); - doc.extendSelectionsBy(function(range) { - if (collapse) - return dir < 0 ? range.from() : range.to(); - var headPos = cursorCoords(cm, range.head, "div"); - if (range.goalColumn != null) headPos.left = range.goalColumn; - goals.push(headPos.left); - var pos = findPosV(cm, headPos, dir, unit); - if (unit == "page" && range == doc.sel.primary()) - addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top); - return pos; - }, sel_move); - if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) - doc.sel.ranges[i].goalColumn = goals[i]; - }), - - // Find the word at the given position (as returned by coordsChar). - findWordAt: function(pos) { - var doc = this.doc, line = getLine(doc, pos.line).text; - var start = pos.ch, end = pos.ch; - if (line) { - var helper = this.getHelper(pos, "wordChars"); - if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; - var startChar = line.charAt(start); - var check = isWordChar(startChar, helper) - ? function(ch) { return isWordChar(ch, helper); } - : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} - : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; - while (start > 0 && check(line.charAt(start - 1))) --start; - while (end < line.length && check(line.charAt(end))) ++end; - } - return new Range(Pos(pos.line, start), Pos(pos.line, end)); - }, - - toggleOverwrite: function(value) { - if (value != null && value == this.state.overwrite) return; - if (this.state.overwrite = !this.state.overwrite) - addClass(this.display.cursorDiv, "CodeMirror-overwrite"); - else - rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); - - signal(this, "overwriteToggle", this, this.state.overwrite); - }, - hasFocus: function() { return this.display.input.getField() == activeElt(); }, - isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit); }, - - scrollTo: methodOp(function(x, y) { - if (x != null || y != null) resolveScrollToPos(this); - if (x != null) this.curOp.scrollLeft = x; - if (y != null) this.curOp.scrollTop = y; - }), - getScrollInfo: function() { - var scroller = this.display.scroller; - return {left: scroller.scrollLeft, top: scroller.scrollTop, - height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, - width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, - clientHeight: displayHeight(this), clientWidth: displayWidth(this)}; - }, - - scrollIntoView: methodOp(function(range, margin) { - if (range == null) { - range = {from: this.doc.sel.primary().head, to: null}; - if (margin == null) margin = this.options.cursorScrollMargin; - } else if (typeof range == "number") { - range = {from: Pos(range, 0), to: null}; - } else if (range.from == null) { - range = {from: range, to: null}; - } - if (!range.to) range.to = range.from; - range.margin = margin || 0; - - if (range.from.line != null) { - resolveScrollToPos(this); - this.curOp.scrollToPos = range; - } else { - var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), - Math.min(range.from.top, range.to.top) - range.margin, - Math.max(range.from.right, range.to.right), - Math.max(range.from.bottom, range.to.bottom) + range.margin); - this.scrollTo(sPos.scrollLeft, sPos.scrollTop); - } - }), - - setSize: methodOp(function(width, height) { - var cm = this; - function interpret(val) { - return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; - } - if (width != null) cm.display.wrapper.style.width = interpret(width); - if (height != null) cm.display.wrapper.style.height = interpret(height); - if (cm.options.lineWrapping) clearLineMeasurementCache(this); - var lineNo = cm.display.viewFrom; - cm.doc.iter(lineNo, cm.display.viewTo, function(line) { - if (line.widgets) for (var i = 0; i < line.widgets.length; i++) - if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; } - ++lineNo; - }); - cm.curOp.forceUpdate = true; - signal(cm, "refresh", this); - }), - - operation: function(f){return runInOp(this, f);}, - - refresh: methodOp(function() { - var oldHeight = this.display.cachedTextHeight; - regChange(this); - this.curOp.forceUpdate = true; - clearCaches(this); - this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); - updateGutterSpace(this); - if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) - estimateLineHeights(this); - signal(this, "refresh", this); - }), - - swapDoc: methodOp(function(doc) { - var old = this.doc; - old.cm = null; - attachDoc(this, doc); - clearCaches(this); - this.display.input.reset(); - this.scrollTo(doc.scrollLeft, doc.scrollTop); - this.curOp.forceScroll = true; - signalLater(this, "swapDoc", this, old); - return old; - }), - - getInputField: function(){return this.display.input.getField();}, - getWrapperElement: function(){return this.display.wrapper;}, - getScrollerElement: function(){return this.display.scroller;}, - getGutterElement: function(){return this.display.gutters;} - }; - eventMixin(CodeMirror); - - // OPTION DEFAULTS - - // The default configuration options. - var defaults = CodeMirror.defaults = {}; - // Functions to run when options are changed. - var optionHandlers = CodeMirror.optionHandlers = {}; - - function option(name, deflt, handle, notOnInit) { - CodeMirror.defaults[name] = deflt; - if (handle) optionHandlers[name] = - notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; - } - - // Passed to option handlers when there is no old value. - var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; - - // These two are, on init, called from the constructor because they - // have to be initialized before the editor can start at all. - option("value", "", function(cm, val) { - cm.setValue(val); - }, true); - option("mode", null, function(cm, val) { - cm.doc.modeOption = val; - loadMode(cm); - }, true); - - option("indentUnit", 2, loadMode, true); - option("indentWithTabs", false); - option("smartIndent", true); - option("tabSize", 4, function(cm) { - resetModeState(cm); - clearCaches(cm); - regChange(cm); - }, true); - option("lineSeparator", null, function(cm, val) { - cm.doc.lineSep = val; - if (!val) return; - var newBreaks = [], lineNo = cm.doc.first; - cm.doc.iter(function(line) { - for (var pos = 0;;) { - var found = line.text.indexOf(val, pos); - if (found == -1) break; - pos = found + val.length; - newBreaks.push(Pos(lineNo, found)); - } - lineNo++; - }); - for (var i = newBreaks.length - 1; i >= 0; i--) - replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) - }); - option("specialChars", /[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) { - cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); - if (old != CodeMirror.Init) cm.refresh(); - }); - option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); - option("electricChars", true); - option("inputStyle", mobile ? "contenteditable" : "textarea", function() { - throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME - }, true); - option("rtlMoveVisually", !windows); - option("wholeLineUpdateBefore", true); - - option("theme", "default", function(cm) { - themeChanged(cm); - guttersChanged(cm); - }, true); - option("keyMap", "default", function(cm, val, old) { - var next = getKeyMap(val); - var prev = old != CodeMirror.Init && getKeyMap(old); - if (prev && prev.detach) prev.detach(cm, next); - if (next.attach) next.attach(cm, prev || null); - }); - option("extraKeys", null); - - option("lineWrapping", false, wrappingChanged, true); - option("gutters", [], function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("fixedGutter", true, function(cm, val) { - cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; - cm.refresh(); - }, true); - option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm);}, true); - option("scrollbarStyle", "native", function(cm) { - initScrollbars(cm); - updateScrollbars(cm); - cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); - cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); - }, true); - option("lineNumbers", false, function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("firstLineNumber", 1, guttersChanged, true); - option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); - option("showCursorWhenSelecting", false, updateSelection, true); - - option("resetSelectionOnContextMenu", true); - option("lineWiseCopyCut", true); - - option("readOnly", false, function(cm, val) { - if (val == "nocursor") { - onBlur(cm); - cm.display.input.blur(); - cm.display.disabled = true; - } else { - cm.display.disabled = false; - } - cm.display.input.readOnlyChanged(val) - }); - option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true); - option("dragDrop", true, dragDropChanged); - option("allowDropFileTypes", null); - - option("cursorBlinkRate", 530); - option("cursorScrollMargin", 0); - option("cursorHeight", 1, updateSelection, true); - option("singleCursorHeightPerLine", true, updateSelection, true); - option("workTime", 100); - option("workDelay", 100); - option("flattenSpans", true, resetModeState, true); - option("addModeClass", false, resetModeState, true); - option("pollInterval", 100); - option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); - option("historyEventDelay", 1250); - option("viewportMargin", 10, function(cm){cm.refresh();}, true); - option("maxHighlightLength", 10000, resetModeState, true); - option("moveInputWithCursor", true, function(cm, val) { - if (!val) cm.display.input.resetPosition(); - }); - - option("tabindex", null, function(cm, val) { - cm.display.input.getField().tabIndex = val || ""; - }); - option("autofocus", null); - - // MODE DEFINITION AND QUERYING - - // Known modes, by name and by MIME - var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; - - // Extra arguments are stored as the mode's dependencies, which is - // used by (legacy) mechanisms like loadmode.js to automatically - // load a mode. (Preferred mechanism is the require/define calls.) - CodeMirror.defineMode = function(name, mode) { - if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; - if (arguments.length > 2) - mode.dependencies = Array.prototype.slice.call(arguments, 2); - modes[name] = mode; - }; - - CodeMirror.defineMIME = function(mime, spec) { - mimeModes[mime] = spec; - }; - - // Given a MIME type, a {name, ...options} config object, or a name - // string, return a mode config object. - CodeMirror.resolveMode = function(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { - spec = mimeModes[spec]; - } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { - var found = mimeModes[spec.name]; - if (typeof found == "string") found = {name: found}; - spec = createObj(found, spec); - spec.name = found.name; - } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { - return CodeMirror.resolveMode("application/xml"); - } - if (typeof spec == "string") return {name: spec}; - else return spec || {name: "null"}; - }; - - // Given a mode spec (anything that resolveMode accepts), find and - // initialize an actual mode object. - CodeMirror.getMode = function(options, spec) { - var spec = CodeMirror.resolveMode(spec); - var mfactory = modes[spec.name]; - if (!mfactory) return CodeMirror.getMode(options, "text/plain"); - var modeObj = mfactory(options, spec); - if (modeExtensions.hasOwnProperty(spec.name)) { - var exts = modeExtensions[spec.name]; - for (var prop in exts) { - if (!exts.hasOwnProperty(prop)) continue; - if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; - modeObj[prop] = exts[prop]; - } - } - modeObj.name = spec.name; - if (spec.helperType) modeObj.helperType = spec.helperType; - if (spec.modeProps) for (var prop in spec.modeProps) - modeObj[prop] = spec.modeProps[prop]; - - return modeObj; - }; - - // Minimal default mode. - CodeMirror.defineMode("null", function() { - return {token: function(stream) {stream.skipToEnd();}}; - }); - CodeMirror.defineMIME("text/plain", "null"); - - // This can be used to attach properties to mode objects from - // outside the actual mode definition. - var modeExtensions = CodeMirror.modeExtensions = {}; - CodeMirror.extendMode = function(mode, properties) { - var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); - copyObj(properties, exts); - }; - - // EXTENSIONS - - CodeMirror.defineExtension = function(name, func) { - CodeMirror.prototype[name] = func; - }; - CodeMirror.defineDocExtension = function(name, func) { - Doc.prototype[name] = func; - }; - CodeMirror.defineOption = option; - - var initHooks = []; - CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; - - var helpers = CodeMirror.helpers = {}; - CodeMirror.registerHelper = function(type, name, value) { - if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; - helpers[type][name] = value; - }; - CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { - CodeMirror.registerHelper(type, name, value); - helpers[type]._global.push({pred: predicate, val: value}); - }; - - // MODE STATE HANDLING - - // Utility functions for working with state. Exported because nested - // modes need to do this for their inner modes. - - var copyState = CodeMirror.copyState = function(mode, state) { - if (state === true) return state; - if (mode.copyState) return mode.copyState(state); - var nstate = {}; - for (var n in state) { - var val = state[n]; - if (val instanceof Array) val = val.concat([]); - nstate[n] = val; - } - return nstate; - }; - - var startState = CodeMirror.startState = function(mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; - }; - - // Given a mode and a state (for that mode), find the inner mode and - // state at the position that the state refers to. - CodeMirror.innerMode = function(mode, state) { - while (mode.innerMode) { - var info = mode.innerMode(state); - if (!info || info.mode == mode) break; - state = info.state; - mode = info.mode; - } - return info || {mode: mode, state: state}; - }; - - // STANDARD COMMANDS - - // Commands are parameter-less actions that can be performed on an - // editor, mostly used for keybindings. - var commands = CodeMirror.commands = { - selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);}, - singleSelection: function(cm) { - cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); - }, - killLine: function(cm) { - deleteNearSelection(cm, function(range) { - if (range.empty()) { - var len = getLine(cm.doc, range.head.line).text.length; - if (range.head.ch == len && range.head.line < cm.lastLine()) - return {from: range.head, to: Pos(range.head.line + 1, 0)}; - else - return {from: range.head, to: Pos(range.head.line, len)}; - } else { - return {from: range.from(), to: range.to()}; - } - }); - }, - deleteLine: function(cm) { - deleteNearSelection(cm, function(range) { - return {from: Pos(range.from().line, 0), - to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; - }); - }, - delLineLeft: function(cm) { - deleteNearSelection(cm, function(range) { - return {from: Pos(range.from().line, 0), to: range.from()}; - }); - }, - delWrappedLineLeft: function(cm) { - deleteNearSelection(cm, function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - var leftPos = cm.coordsChar({left: 0, top: top}, "div"); - return {from: leftPos, to: range.from()}; - }); - }, - delWrappedLineRight: function(cm) { - deleteNearSelection(cm, function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); - return {from: range.from(), to: rightPos }; - }); - }, - undo: function(cm) {cm.undo();}, - redo: function(cm) {cm.redo();}, - undoSelection: function(cm) {cm.undoSelection();}, - redoSelection: function(cm) {cm.redoSelection();}, - goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, - goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, - goLineStart: function(cm) { - cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, - {origin: "+move", bias: 1}); - }, - goLineStartSmart: function(cm) { - cm.extendSelectionsBy(function(range) { - return lineStartSmart(cm, range.head); - }, {origin: "+move", bias: 1}); - }, - goLineEnd: function(cm) { - cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, - {origin: "+move", bias: -1}); - }, - goLineRight: function(cm) { - cm.extendSelectionsBy(function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); - }, sel_move); - }, - goLineLeft: function(cm) { - cm.extendSelectionsBy(function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - return cm.coordsChar({left: 0, top: top}, "div"); - }, sel_move); - }, - goLineLeftSmart: function(cm) { - cm.extendSelectionsBy(function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - var pos = cm.coordsChar({left: 0, top: top}, "div"); - if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); - return pos; - }, sel_move); - }, - goLineUp: function(cm) {cm.moveV(-1, "line");}, - goLineDown: function(cm) {cm.moveV(1, "line");}, - goPageUp: function(cm) {cm.moveV(-1, "page");}, - goPageDown: function(cm) {cm.moveV(1, "page");}, - goCharLeft: function(cm) {cm.moveH(-1, "char");}, - goCharRight: function(cm) {cm.moveH(1, "char");}, - goColumnLeft: function(cm) {cm.moveH(-1, "column");}, - goColumnRight: function(cm) {cm.moveH(1, "column");}, - goWordLeft: function(cm) {cm.moveH(-1, "word");}, - goGroupRight: function(cm) {cm.moveH(1, "group");}, - goGroupLeft: function(cm) {cm.moveH(-1, "group");}, - goWordRight: function(cm) {cm.moveH(1, "word");}, - delCharBefore: function(cm) {cm.deleteH(-1, "char");}, - delCharAfter: function(cm) {cm.deleteH(1, "char");}, - delWordBefore: function(cm) {cm.deleteH(-1, "word");}, - delWordAfter: function(cm) {cm.deleteH(1, "word");}, - delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, - delGroupAfter: function(cm) {cm.deleteH(1, "group");}, - indentAuto: function(cm) {cm.indentSelection("smart");}, - indentMore: function(cm) {cm.indentSelection("add");}, - indentLess: function(cm) {cm.indentSelection("subtract");}, - insertTab: function(cm) {cm.replaceSelection("\t");}, - insertSoftTab: function(cm) { - var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; - for (var i = 0; i < ranges.length; i++) { - var pos = ranges[i].from(); - var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); - spaces.push(spaceStr(tabSize - col % tabSize)); - } - cm.replaceSelections(spaces); - }, - defaultTab: function(cm) { - if (cm.somethingSelected()) cm.indentSelection("add"); - else cm.execCommand("insertTab"); - }, - transposeChars: function(cm) { - runInOp(cm, function() { - var ranges = cm.listSelections(), newSel = []; - for (var i = 0; i < ranges.length; i++) { - var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; - if (line) { - if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); - if (cur.ch > 0) { - cur = new Pos(cur.line, cur.ch + 1); - cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), - Pos(cur.line, cur.ch - 2), cur, "+transpose"); - } else if (cur.line > cm.doc.first) { - var prev = getLine(cm.doc, cur.line - 1).text; - if (prev) - cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + - prev.charAt(prev.length - 1), - Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose"); - } - } - newSel.push(new Range(cur, cur)); - } - cm.setSelections(newSel); - }); - }, - newlineAndIndent: function(cm) { - runInOp(cm, function() { - var len = cm.listSelections().length; - for (var i = 0; i < len; i++) { - var range = cm.listSelections()[i]; - cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+input"); - cm.indentLine(range.from().line + 1, null, true); - } - ensureCursorVisible(cm); - }); - }, - openLine: function(cm) {cm.replaceSelection("\n", "start")}, - toggleOverwrite: function(cm) {cm.toggleOverwrite();} - }; - - - // STANDARD KEYMAPS - - var keyMap = CodeMirror.keyMap = {}; - - keyMap.basic = { - "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", - "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", - "Tab": "defaultTab", "Shift-Tab": "indentAuto", - "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", - "Esc": "singleSelection" - }; - // Note that the save and find-related commands aren't defined by - // default. User code or addons can define them. Unknown commands - // are simply ignored. - keyMap.pcDefault = { - "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", - "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", - "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", - "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", - "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", - "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", - "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", - fallthrough: "basic" - }; - // Very basic readline/emacs-style bindings, which are standard on Mac. - keyMap.emacsy = { - "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", - "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", - "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", - "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", - "Ctrl-O": "openLine" - }; - keyMap.macDefault = { - "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", - "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", - "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", - "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", - "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", - "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", - fallthrough: ["basic", "emacsy"] - }; - keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; - - // KEYMAP DISPATCH - - function normalizeKeyName(name) { - var parts = name.split(/-(?!$)/), name = parts[parts.length - 1]; - var alt, ctrl, shift, cmd; - for (var i = 0; i < parts.length - 1; i++) { - var mod = parts[i]; - if (/^(cmd|meta|m)$/i.test(mod)) cmd = true; - else if (/^a(lt)?$/i.test(mod)) alt = true; - else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true; - else if (/^s(hift)$/i.test(mod)) shift = true; - else throw new Error("Unrecognized modifier name: " + mod); - } - if (alt) name = "Alt-" + name; - if (ctrl) name = "Ctrl-" + name; - if (cmd) name = "Cmd-" + name; - if (shift) name = "Shift-" + name; - return name; - } - - // This is a kludge to keep keymaps mostly working as raw objects - // (backwards compatibility) while at the same time support features - // like normalization and multi-stroke key bindings. It compiles a - // new normalized keymap, and then updates the old object to reflect - // this. - CodeMirror.normalizeKeyMap = function(keymap) { - var copy = {}; - for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { - var value = keymap[keyname]; - if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; - if (value == "...") { delete keymap[keyname]; continue; } - - var keys = map(keyname.split(" "), normalizeKeyName); - for (var i = 0; i < keys.length; i++) { - var val, name; - if (i == keys.length - 1) { - name = keys.join(" "); - val = value; - } else { - name = keys.slice(0, i + 1).join(" "); - val = "..."; - } - var prev = copy[name]; - if (!prev) copy[name] = val; - else if (prev != val) throw new Error("Inconsistent bindings for " + name); - } - delete keymap[keyname]; - } - for (var prop in copy) keymap[prop] = copy[prop]; - return keymap; - }; - - var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) { - map = getKeyMap(map); - var found = map.call ? map.call(key, context) : map[key]; - if (found === false) return "nothing"; - if (found === "...") return "multi"; - if (found != null && handle(found)) return "handled"; - - if (map.fallthrough) { - if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") - return lookupKey(key, map.fallthrough, handle, context); - for (var i = 0; i < map.fallthrough.length; i++) { - var result = lookupKey(key, map.fallthrough[i], handle, context); - if (result) return result; - } - } - }; - - // Modifier key presses don't count as 'real' key presses for the - // purpose of keymap fallthrough. - var isModifierKey = CodeMirror.isModifierKey = function(value) { - var name = typeof value == "string" ? value : keyNames[value.keyCode]; - return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; - }; - - // Look up the name of a key as indicated by an event object. - var keyName = CodeMirror.keyName = function(event, noShift) { - if (presto && event.keyCode == 34 && event["char"]) return false; - var base = keyNames[event.keyCode], name = base; - if (name == null || event.altGraphKey) return false; - if (event.altKey && base != "Alt") name = "Alt-" + name; - if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name; - if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name; - if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name; - return name; - }; - - function getKeyMap(val) { - return typeof val == "string" ? keyMap[val] : val; - } - - // FROMTEXTAREA - - CodeMirror.fromTextArea = function(textarea, options) { - options = options ? copyObj(options) : {}; - options.value = textarea.value; - if (!options.tabindex && textarea.tabIndex) - options.tabindex = textarea.tabIndex; - if (!options.placeholder && textarea.placeholder) - options.placeholder = textarea.placeholder; - // Set autofocus to true if this textarea is focused, or if it has - // autofocus and no other element is focused. - if (options.autofocus == null) { - var hasFocus = activeElt(); - options.autofocus = hasFocus == textarea || - textarea.getAttribute("autofocus") != null && hasFocus == document.body; - } - - function save() {textarea.value = cm.getValue();} - if (textarea.form) { - on(textarea.form, "submit", save); - // Deplorable hack to make the submit method do the right thing. - if (!options.leaveSubmitMethodAlone) { - var form = textarea.form, realSubmit = form.submit; - try { - var wrappedSubmit = form.submit = function() { - save(); - form.submit = realSubmit; - form.submit(); - form.submit = wrappedSubmit; - }; - } catch(e) {} - } - } - - options.finishInit = function(cm) { - cm.save = save; - cm.getTextArea = function() { return textarea; }; - cm.toTextArea = function() { - cm.toTextArea = isNaN; // Prevent this from being ran twice - save(); - textarea.parentNode.removeChild(cm.getWrapperElement()); - textarea.style.display = ""; - if (textarea.form) { - off(textarea.form, "submit", save); - if (typeof textarea.form.submit == "function") - textarea.form.submit = realSubmit; - } - }; - }; - - textarea.style.display = "none"; - var cm = CodeMirror(function(node) { - textarea.parentNode.insertBefore(node, textarea.nextSibling); - }, options); - return cm; - }; - - // STRING STREAM - - // Fed to the mode parsers, provides helper functions to make - // parsers more succinct. - - var StringStream = CodeMirror.StringStream = function(string, tabSize) { - this.pos = this.start = 0; - this.string = string; - this.tabSize = tabSize || 8; - this.lastColumnPos = this.lastColumnValue = 0; - this.lineStart = 0; - }; - - StringStream.prototype = { - eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == this.lineStart;}, - peek: function() {return this.string.charAt(this.pos) || undefined;}, - next: function() { - if (this.pos < this.string.length) - return this.string.charAt(this.pos++); - }, - eat: function(match) { - var ch = this.string.charAt(this.pos); - if (typeof match == "string") var ok = ch == match; - else var ok = ch && (match.test ? match.test(ch) : match(ch)); - if (ok) {++this.pos; return ch;} - }, - eatWhile: function(match) { - var start = this.pos; - while (this.eat(match)){} - return this.pos > start; - }, - eatSpace: function() { - var start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; - return this.pos > start; - }, - skipToEnd: function() {this.pos = this.string.length;}, - skipTo: function(ch) { - var found = this.string.indexOf(ch, this.pos); - if (found > -1) {this.pos = found; return true;} - }, - backUp: function(n) {this.pos -= n;}, - column: function() { - if (this.lastColumnPos < this.start) { - this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); - this.lastColumnPos = this.start; - } - return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); - }, - indentation: function() { - return countColumn(this.string, null, this.tabSize) - - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); - }, - match: function(pattern, consume, caseInsensitive) { - if (typeof pattern == "string") { - var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - var substr = this.string.substr(this.pos, pattern.length); - if (cased(substr) == cased(pattern)) { - if (consume !== false) this.pos += pattern.length; - return true; - } - } else { - var match = this.string.slice(this.pos).match(pattern); - if (match && match.index > 0) return null; - if (match && consume !== false) this.pos += match[0].length; - return match; - } - }, - current: function(){return this.string.slice(this.start, this.pos);}, - hideFirstChars: function(n, inner) { - this.lineStart += n; - try { return inner(); } - finally { this.lineStart -= n; } - } - }; - - // TEXTMARKERS - - // Created with markText and setBookmark methods. A TextMarker is a - // handle that can be used to clear or find a marked position in the - // document. Line objects hold arrays (markedSpans) containing - // {from, to, marker} object pointing to such marker objects, and - // indicating that such a marker is present on that line. Multiple - // lines may point to the same marker when it spans across lines. - // The spans will have null for their from/to properties when the - // marker continues beyond the start/end of the line. Markers have - // links back to the lines they currently touch. - - var nextMarkerId = 0; - - var TextMarker = CodeMirror.TextMarker = function(doc, type) { - this.lines = []; - this.type = type; - this.doc = doc; - this.id = ++nextMarkerId; - }; - eventMixin(TextMarker); - - // Clear the marker. - TextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return; - var cm = this.doc.cm, withOp = cm && !cm.curOp; - if (withOp) startOperation(cm); - if (hasHandler(this, "clear")) { - var found = this.find(); - if (found) signalLater(this, "clear", found.from, found.to); - } - var min = null, max = null; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); - else if (cm) { - if (span.to != null) max = lineNo(line); - if (span.from != null) min = lineNo(line); - } - line.markedSpans = removeMarkedSpan(line.markedSpans, span); - if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) - updateLineHeight(line, textHeight(cm.display)); - } - if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { - var visual = visualLine(this.lines[i]), len = lineLength(visual); - if (len > cm.display.maxLineLength) { - cm.display.maxLine = visual; - cm.display.maxLineLength = len; - cm.display.maxLineChanged = true; - } - } - - if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); - this.lines.length = 0; - this.explicitlyCleared = true; - if (this.atomic && this.doc.cantEdit) { - this.doc.cantEdit = false; - if (cm) reCheckSelection(cm.doc); - } - if (cm) signalLater(cm, "markerCleared", cm, this); - if (withOp) endOperation(cm); - if (this.parent) this.parent.clear(); - }; - - // Find the position of the marker in the document. Returns a {from, - // to} object by default. Side can be passed to get a specific side - // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the - // Pos objects returned contain a line object, rather than a line - // number (used to prevent looking up the same line twice). - TextMarker.prototype.find = function(side, lineObj) { - if (side == null && this.type == "bookmark") side = 1; - var from, to; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (span.from != null) { - from = Pos(lineObj ? line : lineNo(line), span.from); - if (side == -1) return from; - } - if (span.to != null) { - to = Pos(lineObj ? line : lineNo(line), span.to); - if (side == 1) return to; - } - } - return from && {from: from, to: to}; - }; - - // Signals that the marker's widget changed, and surrounding layout - // should be recomputed. - TextMarker.prototype.changed = function() { - var pos = this.find(-1, true), widget = this, cm = this.doc.cm; - if (!pos || !cm) return; - runInOp(cm, function() { - var line = pos.line, lineN = lineNo(pos.line); - var view = findViewForLine(cm, lineN); - if (view) { - clearLineMeasurementCacheFor(view); - cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; - } - cm.curOp.updateMaxLine = true; - if (!lineIsHidden(widget.doc, line) && widget.height != null) { - var oldHeight = widget.height; - widget.height = null; - var dHeight = widgetHeight(widget) - oldHeight; - if (dHeight) - updateLineHeight(line, line.height + dHeight); - } - }); - }; - - TextMarker.prototype.attachLine = function(line) { - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) - (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); - } - this.lines.push(line); - }; - TextMarker.prototype.detachLine = function(line) { - this.lines.splice(indexOf(this.lines, line), 1); - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); - } - }; - - // Collapsed markers have unique ids, in order to be able to order - // them, which is needed for uniquely determining an outer marker - // when they overlap (they may nest, but not partially overlap). - var nextMarkerId = 0; - - // Create a marker, wire it up to the right lines, and - function markText(doc, from, to, options, type) { - // Shared markers (across linked documents) are handled separately - // (markTextShared will call out to this again, once per - // document). - if (options && options.shared) return markTextShared(doc, from, to, options, type); - // Ensure we are in an operation. - if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); - - var marker = new TextMarker(doc, type), diff = cmp(from, to); - if (options) copyObj(options, marker, false); - // Don't connect empty markers unless clearWhenEmpty is false - if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) - return marker; - if (marker.replacedWith) { - // Showing up as a widget implies collapsed (widget replaces text) - marker.collapsed = true; - marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); - if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true"); - if (options.insertLeft) marker.widgetNode.insertLeft = true; - } - if (marker.collapsed) { - if (conflictingCollapsedRange(doc, from.line, from, to, marker) || - from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) - throw new Error("Inserting collapsed marker partially overlapping an existing one"); - sawCollapsedSpans = true; - } - - if (marker.addToHistory) - addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); - - var curLine = from.line, cm = doc.cm, updateMaxLine; - doc.iter(curLine, to.line + 1, function(line) { - if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) - updateMaxLine = true; - if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); - addMarkedSpan(line, new MarkedSpan(marker, - curLine == from.line ? from.ch : null, - curLine == to.line ? to.ch : null)); - ++curLine; - }); - // lineIsHidden depends on the presence of the spans, so needs a second pass - if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { - if (lineIsHidden(doc, line)) updateLineHeight(line, 0); - }); - - if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); - - if (marker.readOnly) { - sawReadOnlySpans = true; - if (doc.history.done.length || doc.history.undone.length) - doc.clearHistory(); - } - if (marker.collapsed) { - marker.id = ++nextMarkerId; - marker.atomic = true; - } - if (cm) { - // Sync editor state - if (updateMaxLine) cm.curOp.updateMaxLine = true; - if (marker.collapsed) - regChange(cm, from.line, to.line + 1); - else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) - for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); - if (marker.atomic) reCheckSelection(cm.doc); - signalLater(cm, "markerAdded", cm, marker); - } - return marker; - } - - // SHARED TEXTMARKERS - - // A shared marker spans multiple linked documents. It is - // implemented as a meta-marker-object controlling multiple normal - // markers. - var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { - this.markers = markers; - this.primary = primary; - for (var i = 0; i < markers.length; ++i) - markers[i].parent = this; - }; - eventMixin(SharedTextMarker); - - SharedTextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return; - this.explicitlyCleared = true; - for (var i = 0; i < this.markers.length; ++i) - this.markers[i].clear(); - signalLater(this, "clear"); - }; - SharedTextMarker.prototype.find = function(side, lineObj) { - return this.primary.find(side, lineObj); - }; - - function markTextShared(doc, from, to, options, type) { - options = copyObj(options); - options.shared = false; - var markers = [markText(doc, from, to, options, type)], primary = markers[0]; - var widget = options.widgetNode; - linkedDocs(doc, function(doc) { - if (widget) options.widgetNode = widget.cloneNode(true); - markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); - for (var i = 0; i < doc.linked.length; ++i) - if (doc.linked[i].isParent) return; - primary = lst(markers); - }); - return new SharedTextMarker(markers, primary); - } - - function findSharedMarkers(doc) { - return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), - function(m) { return m.parent; }); - } - - function copySharedMarkers(doc, markers) { - for (var i = 0; i < markers.length; i++) { - var marker = markers[i], pos = marker.find(); - var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); - if (cmp(mFrom, mTo)) { - var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); - marker.markers.push(subMark); - subMark.parent = marker; - } - } - } - - function detachSharedMarkers(markers) { - for (var i = 0; i < markers.length; i++) { - var marker = markers[i], linked = [marker.primary.doc];; - linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); - for (var j = 0; j < marker.markers.length; j++) { - var subMarker = marker.markers[j]; - if (indexOf(linked, subMarker.doc) == -1) { - subMarker.parent = null; - marker.markers.splice(j--, 1); - } - } - } - } - - // TEXTMARKER SPANS - - function MarkedSpan(marker, from, to) { - this.marker = marker; - this.from = from; this.to = to; - } - - // Search an array of spans for a span matching the given marker. - function getMarkedSpanFor(spans, marker) { - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.marker == marker) return span; - } - } - // Remove a span from an array, returning undefined if no spans are - // left (we don't store arrays for lines without spans). - function removeMarkedSpan(spans, span) { - for (var r, i = 0; i < spans.length; ++i) - if (spans[i] != span) (r || (r = [])).push(spans[i]); - return r; - } - // Add a span to a line. - function addMarkedSpan(line, span) { - line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; - span.marker.attachLine(line); - } - - // Used for the algorithm that adjusts markers for a change in the - // document. These functions cut an array of spans at a given - // character position, returning an array of remaining chunks (or - // undefined if nothing remains). - function markedSpansBefore(old, startCh, isInsert) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); - (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); - } - } - return nw; - } - function markedSpansAfter(old, endCh, isInsert) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); - if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); - (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, - span.to == null ? null : span.to - endCh)); - } - } - return nw; - } - - // Given a change object, compute the new set of marker spans that - // cover the line in which the change took place. Removes spans - // entirely within the change, reconnects spans belonging to the - // same marker that appear on both sides of the change, and cuts off - // spans partially within the change. Returns an array of span - // arrays with one element for each line in (after) the change. - function stretchSpansOverChange(doc, change) { - if (change.full) return null; - var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; - var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; - if (!oldFirst && !oldLast) return null; - - var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; - // Get the spans that 'stick out' on both sides - var first = markedSpansBefore(oldFirst, startCh, isInsert); - var last = markedSpansAfter(oldLast, endCh, isInsert); - - // Next, merge those two ends - var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); - if (first) { - // Fix up .to properties of first - for (var i = 0; i < first.length; ++i) { - var span = first[i]; - if (span.to == null) { - var found = getMarkedSpanFor(last, span.marker); - if (!found) span.to = startCh; - else if (sameLine) span.to = found.to == null ? null : found.to + offset; - } - } - } - if (last) { - // Fix up .from in last (or move them into first in case of sameLine) - for (var i = 0; i < last.length; ++i) { - var span = last[i]; - if (span.to != null) span.to += offset; - if (span.from == null) { - var found = getMarkedSpanFor(first, span.marker); - if (!found) { - span.from = offset; - if (sameLine) (first || (first = [])).push(span); - } - } else { - span.from += offset; - if (sameLine) (first || (first = [])).push(span); - } - } - } - // Make sure we didn't create any zero-length spans - if (first) first = clearEmptySpans(first); - if (last && last != first) last = clearEmptySpans(last); - - var newMarkers = [first]; - if (!sameLine) { - // Fill gap with whole-line-spans - var gap = change.text.length - 2, gapMarkers; - if (gap > 0 && first) - for (var i = 0; i < first.length; ++i) - if (first[i].to == null) - (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)); - for (var i = 0; i < gap; ++i) - newMarkers.push(gapMarkers); - newMarkers.push(last); - } - return newMarkers; - } - - // Remove spans that are empty and don't have a clearWhenEmpty - // option of false. - function clearEmptySpans(spans) { - for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) - spans.splice(i--, 1); - } - if (!spans.length) return null; - return spans; - } - - // Used for un/re-doing changes from the history. Combines the - // result of computing the existing spans with the set of spans that - // existed in the history (so that deleting around a span and then - // undoing brings back the span). - function mergeOldSpans(doc, change) { - var old = getOldSpans(doc, change); - var stretched = stretchSpansOverChange(doc, change); - if (!old) return stretched; - if (!stretched) return old; - - for (var i = 0; i < old.length; ++i) { - var oldCur = old[i], stretchCur = stretched[i]; - if (oldCur && stretchCur) { - spans: for (var j = 0; j < stretchCur.length; ++j) { - var span = stretchCur[j]; - for (var k = 0; k < oldCur.length; ++k) - if (oldCur[k].marker == span.marker) continue spans; - oldCur.push(span); - } - } else if (stretchCur) { - old[i] = stretchCur; - } - } - return old; - } - - // Used to 'clip' out readOnly ranges when making a change. - function removeReadOnlyRanges(doc, from, to) { - var markers = null; - doc.iter(from.line, to.line + 1, function(line) { - if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { - var mark = line.markedSpans[i].marker; - if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) - (markers || (markers = [])).push(mark); - } - }); - if (!markers) return null; - var parts = [{from: from, to: to}]; - for (var i = 0; i < markers.length; ++i) { - var mk = markers[i], m = mk.find(0); - for (var j = 0; j < parts.length; ++j) { - var p = parts[j]; - if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; - var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); - if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) - newParts.push({from: p.from, to: m.from}); - if (dto > 0 || !mk.inclusiveRight && !dto) - newParts.push({from: m.to, to: p.to}); - parts.splice.apply(parts, newParts); - j += newParts.length - 1; - } - } - return parts; - } - - // Connect or disconnect spans from a line. - function detachMarkedSpans(line) { - var spans = line.markedSpans; - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - spans[i].marker.detachLine(line); - line.markedSpans = null; - } - function attachMarkedSpans(line, spans) { - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - spans[i].marker.attachLine(line); - line.markedSpans = spans; - } - - // Helpers used when computing which overlapping collapsed span - // counts as the larger one. - function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } - function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } - - // Returns a number indicating which of two overlapping collapsed - // spans is larger (and thus includes the other). Falls back to - // comparing ids when the spans cover exactly the same range. - function compareCollapsedMarkers(a, b) { - var lenDiff = a.lines.length - b.lines.length; - if (lenDiff != 0) return lenDiff; - var aPos = a.find(), bPos = b.find(); - var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); - if (fromCmp) return -fromCmp; - var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); - if (toCmp) return toCmp; - return b.id - a.id; - } - - // Find out whether a line ends or starts in a collapsed span. If - // so, return the marker for that span. - function collapsedSpanAtSide(line, start) { - var sps = sawCollapsedSpans && line.markedSpans, found; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && - (!found || compareCollapsedMarkers(found, sp.marker) < 0)) - found = sp.marker; - } - return found; - } - function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } - function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } - - // Test whether there exists a collapsed span that partially - // overlaps (covers the start or end, but not both) of a new span. - // Such overlap is not allowed. - function conflictingCollapsedRange(doc, lineNo, from, to, marker) { - var line = getLine(doc, lineNo); - var sps = sawCollapsedSpans && line.markedSpans; - if (sps) for (var i = 0; i < sps.length; ++i) { - var sp = sps[i]; - if (!sp.marker.collapsed) continue; - var found = sp.marker.find(0); - var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); - var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); - if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; - if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || - fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) - return true; - } - } - - // A visual line is a line as drawn on the screen. Folding, for - // example, can cause multiple logical lines to appear on the same - // visual line. This finds the start of the visual line that the - // given line is part of (usually that is the line itself). - function visualLine(line) { - var merged; - while (merged = collapsedSpanAtStart(line)) - line = merged.find(-1, true).line; - return line; - } - - // Returns an array of logical lines that continue the visual line - // started by the argument, or undefined if there are no such lines. - function visualLineContinued(line) { - var merged, lines; - while (merged = collapsedSpanAtEnd(line)) { - line = merged.find(1, true).line; - (lines || (lines = [])).push(line); - } - return lines; - } - - // Get the line number of the start of the visual line that the - // given line number is part of. - function visualLineNo(doc, lineN) { - var line = getLine(doc, lineN), vis = visualLine(line); - if (line == vis) return lineN; - return lineNo(vis); - } - // Get the line number of the start of the next visual line after - // the given line. - function visualLineEndNo(doc, lineN) { - if (lineN > doc.lastLine()) return lineN; - var line = getLine(doc, lineN), merged; - if (!lineIsHidden(doc, line)) return lineN; - while (merged = collapsedSpanAtEnd(line)) - line = merged.find(1, true).line; - return lineNo(line) + 1; - } - - // Compute whether a line is hidden. Lines count as hidden when they - // are part of a visual line that starts with another line, or when - // they are entirely covered by collapsed, non-widget span. - function lineIsHidden(doc, line) { - var sps = sawCollapsedSpans && line.markedSpans; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (!sp.marker.collapsed) continue; - if (sp.from == null) return true; - if (sp.marker.widgetNode) continue; - if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) - return true; - } - } - function lineIsHiddenInner(doc, line, span) { - if (span.to == null) { - var end = span.marker.find(1, true); - return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); - } - if (span.marker.inclusiveRight && span.to == line.text.length) - return true; - for (var sp, i = 0; i < line.markedSpans.length; ++i) { - sp = line.markedSpans[i]; - if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && - (sp.to == null || sp.to != span.from) && - (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && - lineIsHiddenInner(doc, line, sp)) return true; - } - } - - // LINE WIDGETS - - // Line widgets are block elements displayed above or below a line. - - var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { - if (options) for (var opt in options) if (options.hasOwnProperty(opt)) - this[opt] = options[opt]; - this.doc = doc; - this.node = node; - }; - eventMixin(LineWidget); - - function adjustScrollWhenAboveVisible(cm, line, diff) { - if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) - addToScrollPos(cm, null, diff); - } - - LineWidget.prototype.clear = function() { - var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); - if (no == null || !ws) return; - for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); - if (!ws.length) line.widgets = null; - var height = widgetHeight(this); - updateLineHeight(line, Math.max(0, line.height - height)); - if (cm) runInOp(cm, function() { - adjustScrollWhenAboveVisible(cm, line, -height); - regLineChange(cm, no, "widget"); - }); - }; - LineWidget.prototype.changed = function() { - var oldH = this.height, cm = this.doc.cm, line = this.line; - this.height = null; - var diff = widgetHeight(this) - oldH; - if (!diff) return; - updateLineHeight(line, line.height + diff); - if (cm) runInOp(cm, function() { - cm.curOp.forceUpdate = true; - adjustScrollWhenAboveVisible(cm, line, diff); - }); - }; - - function widgetHeight(widget) { - if (widget.height != null) return widget.height; - var cm = widget.doc.cm; - if (!cm) return 0; - if (!contains(document.body, widget.node)) { - var parentStyle = "position: relative;"; - if (widget.coverGutter) - parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; - if (widget.noHScroll) - parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; - removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); - } - return widget.height = widget.node.parentNode.offsetHeight; - } - - function addLineWidget(doc, handle, node, options) { - var widget = new LineWidget(doc, node, options); - var cm = doc.cm; - if (cm && widget.noHScroll) cm.display.alignWidgets = true; - changeLine(doc, handle, "widget", function(line) { - var widgets = line.widgets || (line.widgets = []); - if (widget.insertAt == null) widgets.push(widget); - else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); - widget.line = line; - if (cm && !lineIsHidden(doc, line)) { - var aboveVisible = heightAtLine(line) < doc.scrollTop; - updateLineHeight(line, line.height + widgetHeight(widget)); - if (aboveVisible) addToScrollPos(cm, null, widget.height); - cm.curOp.forceUpdate = true; - } - return true; - }); - return widget; - } - - // LINE DATA STRUCTURE - - // Line objects. These hold state related to a line, including - // highlighting info (the styles array). - var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { - this.text = text; - attachMarkedSpans(this, markedSpans); - this.height = estimateHeight ? estimateHeight(this) : 1; - }; - eventMixin(Line); - Line.prototype.lineNo = function() { return lineNo(this); }; - - // Change the content (text, markers) of a line. Automatically - // invalidates cached information and tries to re-estimate the - // line's height. - function updateLine(line, text, markedSpans, estimateHeight) { - line.text = text; - if (line.stateAfter) line.stateAfter = null; - if (line.styles) line.styles = null; - if (line.order != null) line.order = null; - detachMarkedSpans(line); - attachMarkedSpans(line, markedSpans); - var estHeight = estimateHeight ? estimateHeight(line) : 1; - if (estHeight != line.height) updateLineHeight(line, estHeight); - } - - // Detach a line from the document tree and its markers. - function cleanUpLine(line) { - line.parent = null; - detachMarkedSpans(line); - } - - function extractLineClasses(type, output) { - if (type) for (;;) { - var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); - if (!lineClass) break; - type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); - var prop = lineClass[1] ? "bgClass" : "textClass"; - if (output[prop] == null) - output[prop] = lineClass[2]; - else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) - output[prop] += " " + lineClass[2]; - } - return type; - } - - function callBlankLine(mode, state) { - if (mode.blankLine) return mode.blankLine(state); - if (!mode.innerMode) return; - var inner = CodeMirror.innerMode(mode, state); - if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); - } - - function readToken(mode, stream, state, inner) { - for (var i = 0; i < 10; i++) { - if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode; - var style = mode.token(stream, state); - if (stream.pos > stream.start) return style; - } - throw new Error("Mode " + mode.name + " failed to advance stream."); - } - - // Utility for getTokenAt and getLineTokens - function takeToken(cm, pos, precise, asArray) { - function getObj(copy) { - return {start: stream.start, end: stream.pos, - string: stream.current(), - type: style || null, - state: copy ? copyState(doc.mode, state) : state}; - } - - var doc = cm.doc, mode = doc.mode, style; - pos = clipPos(doc, pos); - var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise); - var stream = new StringStream(line.text, cm.options.tabSize), tokens; - if (asArray) tokens = []; - while ((asArray || stream.pos < pos.ch) && !stream.eol()) { - stream.start = stream.pos; - style = readToken(mode, stream, state); - if (asArray) tokens.push(getObj(true)); - } - return asArray ? tokens : getObj(); - } - - // Run the given mode's parser over a line, calling f for each token. - function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { - var flattenSpans = mode.flattenSpans; - if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; - var curStart = 0, curStyle = null; - var stream = new StringStream(text, cm.options.tabSize), style; - var inner = cm.options.addModeClass && [null]; - if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); - while (!stream.eol()) { - if (stream.pos > cm.options.maxHighlightLength) { - flattenSpans = false; - if (forceToEnd) processLine(cm, text, state, stream.pos); - stream.pos = text.length; - style = null; - } else { - style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses); - } - if (inner) { - var mName = inner[0].name; - if (mName) style = "m-" + (style ? mName + " " + style : mName); - } - if (!flattenSpans || curStyle != style) { - while (curStart < stream.start) { - curStart = Math.min(stream.start, curStart + 50000); - f(curStart, curStyle); - } - curStyle = style; - } - stream.start = stream.pos; - } - while (curStart < stream.pos) { - // Webkit seems to refuse to render text nodes longer than 57444 characters - var pos = Math.min(stream.pos, curStart + 50000); - f(pos, curStyle); - curStart = pos; - } - } - - // Compute a style array (an array starting with a mode generation - // -- for invalidation -- followed by pairs of end positions and - // style strings), which is used to highlight the tokens on the - // line. - function highlightLine(cm, line, state, forceToEnd) { - // A styles array always starts with a number identifying the - // mode/overlays that it is based on (for easy invalidation). - var st = [cm.state.modeGen], lineClasses = {}; - // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, state, function(end, style) { - st.push(end, style); - }, lineClasses, forceToEnd); - - // Run overlays, adjust style array. - for (var o = 0; o < cm.state.overlays.length; ++o) { - var overlay = cm.state.overlays[o], i = 1, at = 0; - runMode(cm, line.text, overlay.mode, true, function(end, style) { - var start = i; - // Ensure there's a token end at the current position, and that i points at it - while (at < end) { - var i_end = st[i]; - if (i_end > end) - st.splice(i, 1, end, st[i+1], i_end); - i += 2; - at = Math.min(end, i_end); - } - if (!style) return; - if (overlay.opaque) { - st.splice(start, i - start, end, "cm-overlay " + style); - i = start + 2; - } else { - for (; start < i; start += 2) { - var cur = st[start+1]; - st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; - } - } - }, lineClasses); - } - - return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; - } - - function getLineStyles(cm, line, updateFrontier) { - if (!line.styles || line.styles[0] != cm.state.modeGen) { - var state = getStateBefore(cm, lineNo(line)); - var result = highlightLine(cm, line, line.text.length > cm.options.maxHighlightLength ? copyState(cm.doc.mode, state) : state); - line.stateAfter = state; - line.styles = result.styles; - if (result.classes) line.styleClasses = result.classes; - else if (line.styleClasses) line.styleClasses = null; - if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; - } - return line.styles; - } - - // Lightweight form of highlight -- proceed over this line and - // update state, but don't save a style array. Used for lines that - // aren't currently visible. - function processLine(cm, text, state, startAt) { - var mode = cm.doc.mode; - var stream = new StringStream(text, cm.options.tabSize); - stream.start = stream.pos = startAt || 0; - if (text == "") callBlankLine(mode, state); - while (!stream.eol()) { - readToken(mode, stream, state); - stream.start = stream.pos; - } - } - - // Convert a style as returned by a mode (either null, or a string - // containing one or more styles) to a CSS style. This is cached, - // and also looks for line-wide styles. - var styleToClassCache = {}, styleToClassCacheWithMode = {}; - function interpretTokenStyle(style, options) { - if (!style || /^\s*$/.test(style)) return null; - var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; - return cache[style] || - (cache[style] = style.replace(/\S+/g, "cm-$&")); - } - - // Render the DOM representation of the text of a line. Also builds - // up a 'line map', which points at the DOM nodes that represent - // specific stretches of text, and is used by the measuring code. - // The returned object contains the DOM node, this map, and - // information about line-wide styles that were set by the mode. - function buildLineContent(cm, lineView) { - // The padding-right forces the element to have a 'border', which - // is needed on Webkit to be able to get line-level bounding - // rectangles for it (in measureChar). - var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); - var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: content, - col: 0, pos: 0, cm: cm, - trailingSpace: false, - splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; - lineView.measure = {}; - - // Iterate over the logical lines that make up this visual line. - for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { - var line = i ? lineView.rest[i - 1] : lineView.line, order; - builder.pos = 0; - builder.addToken = buildToken; - // Optionally wire in some hacks into the token-rendering - // algorithm, to deal with browser quirks. - if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) - builder.addToken = buildTokenBadBidi(builder.addToken, order); - builder.map = []; - var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); - insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); - if (line.styleClasses) { - if (line.styleClasses.bgClass) - builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); - if (line.styleClasses.textClass) - builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); - } - - // Ensure at least a single node is present, for measuring. - if (builder.map.length == 0) - builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); - - // Store the map and a cache object for the current logical line - if (i == 0) { - lineView.measure.map = builder.map; - lineView.measure.cache = {}; - } else { - (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); - (lineView.measure.caches || (lineView.measure.caches = [])).push({}); - } - } - - // See issue #2901 - if (webkit) { - var last = builder.content.lastChild - if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) - builder.content.className = "cm-tab-wrap-hack"; - } - - signal(cm, "renderLine", cm, lineView.line, builder.pre); - if (builder.pre.className) - builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); - - return builder; - } - - function defaultSpecialCharPlaceholder(ch) { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + ch.charCodeAt(0).toString(16); - token.setAttribute("aria-label", token.title); - return token; - } - - // Build up the DOM representation for a single token, and add it to - // the line map. Takes care to render special characters separately. - function buildToken(builder, text, style, startStyle, endStyle, title, css) { - if (!text) return; - var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text - var special = builder.cm.state.specialChars, mustWrap = false; - if (!special.test(text)) { - builder.col += text.length; - var content = document.createTextNode(displayText); - builder.map.push(builder.pos, builder.pos + text.length, content); - if (ie && ie_version < 9) mustWrap = true; - builder.pos += text.length; - } else { - var content = document.createDocumentFragment(), pos = 0; - while (true) { - special.lastIndex = pos; - var m = special.exec(text); - var skipped = m ? m.index - pos : text.length - pos; - if (skipped) { - var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); - if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); - else content.appendChild(txt); - builder.map.push(builder.pos, builder.pos + skipped, txt); - builder.col += skipped; - builder.pos += skipped; - } - if (!m) break; - pos += skipped + 1; - if (m[0] == "\t") { - var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; - var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); - txt.setAttribute("role", "presentation"); - txt.setAttribute("cm-text", "\t"); - builder.col += tabWidth; - } else if (m[0] == "\r" || m[0] == "\n") { - var txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")); - txt.setAttribute("cm-text", m[0]); - builder.col += 1; - } else { - var txt = builder.cm.options.specialCharPlaceholder(m[0]); - txt.setAttribute("cm-text", m[0]); - if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); - else content.appendChild(txt); - builder.col += 1; - } - builder.map.push(builder.pos, builder.pos + 1, txt); - builder.pos++; - } - } - builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 - if (style || startStyle || endStyle || mustWrap || css) { - var fullStyle = style || ""; - if (startStyle) fullStyle += startStyle; - if (endStyle) fullStyle += endStyle; - var token = elt("span", [content], fullStyle, css); - if (title) token.title = title; - return builder.content.appendChild(token); - } - builder.content.appendChild(content); - } - - function splitSpaces(text, trailingBefore) { - if (text.length > 1 && !/ /.test(text)) return text - var spaceBefore = trailingBefore, result = "" - for (var i = 0; i < text.length; i++) { - var ch = text.charAt(i) - if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) - ch = "\u00a0" - result += ch - spaceBefore = ch == " " - } - return result - } - - // Work around nonsense dimensions being reported for stretches of - // right-to-left text. - function buildTokenBadBidi(inner, order) { - return function(builder, text, style, startStyle, endStyle, title, css) { - style = style ? style + " cm-force-border" : "cm-force-border"; - var start = builder.pos, end = start + text.length; - for (;;) { - // Find the part that overlaps with the start of this text - for (var i = 0; i < order.length; i++) { - var part = order[i]; - if (part.to > start && part.from <= start) break; - } - if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css); - inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); - startStyle = null; - text = text.slice(part.to - start); - start = part.to; - } - }; - } - - function buildCollapsedSpan(builder, size, marker, ignoreWidget) { - var widget = !ignoreWidget && marker.widgetNode; - if (widget) builder.map.push(builder.pos, builder.pos + size, widget); - if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { - if (!widget) - widget = builder.content.appendChild(document.createElement("span")); - widget.setAttribute("cm-marker", marker.id); - } - if (widget) { - builder.cm.display.input.setUneditable(widget); - builder.content.appendChild(widget); - } - builder.pos += size; - builder.trailingSpace = false - } - - // Outputs a number of spans to make up a line, taking highlighting - // and marked text into account. - function insertLineContent(line, builder, styles) { - var spans = line.markedSpans, allText = line.text, at = 0; - if (!spans) { - for (var i = 1; i < styles.length; i+=2) - builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); - return; - } - - var len = allText.length, pos = 0, i = 1, text = "", style, css; - var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; - for (;;) { - if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = title = css = ""; - collapsed = null; nextChange = Infinity; - var foundBookmarks = [], endStyles - for (var j = 0; j < spans.length; ++j) { - var sp = spans[j], m = sp.marker; - if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { - foundBookmarks.push(m); - } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { - if (sp.to != null && sp.to != pos && nextChange > sp.to) { - nextChange = sp.to; - spanEndStyle = ""; - } - if (m.className) spanStyle += " " + m.className; - if (m.css) css = (css ? css + ";" : "") + m.css; - if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; - if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to) - if (m.title && !title) title = m.title; - if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) - collapsed = sp; - } else if (sp.from > pos && nextChange > sp.from) { - nextChange = sp.from; - } - } - if (endStyles) for (var j = 0; j < endStyles.length; j += 2) - if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j] - - if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookmarks.length; ++j) - buildCollapsedSpan(builder, 0, foundBookmarks[j]); - if (collapsed && (collapsed.from || 0) == pos) { - buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, - collapsed.marker, collapsed.from == null); - if (collapsed.to == null) return; - if (collapsed.to == pos) collapsed = false; - } - } - if (pos >= len) break; - - var upto = Math.min(len, nextChange); - while (true) { - if (text) { - var end = pos + text.length; - if (!collapsed) { - var tokenText = end > upto ? text.slice(0, upto - pos) : text; - builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, - spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); - } - if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} - pos = end; - spanStartStyle = ""; - } - text = allText.slice(at, at = styles[i++]); - style = interpretTokenStyle(styles[i++], builder.cm.options); - } - } - } - - // DOCUMENT DATA STRUCTURE - - // By default, updates that start and end at the beginning of a line - // are treated specially, in order to make the association of line - // widgets and marker elements with the text behave more intuitive. - function isWholeLineUpdate(doc, change) { - return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && - (!doc.cm || doc.cm.options.wholeLineUpdateBefore); - } - - // Perform a change on the document data structure. - function updateDoc(doc, change, markedSpans, estimateHeight) { - function spansFor(n) {return markedSpans ? markedSpans[n] : null;} - function update(line, text, spans) { - updateLine(line, text, spans, estimateHeight); - signalLater(line, "change", line, change); - } - function linesFor(start, end) { - for (var i = start, result = []; i < end; ++i) - result.push(new Line(text[i], spansFor(i), estimateHeight)); - return result; - } - - var from = change.from, to = change.to, text = change.text; - var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); - var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; - - // Adjust the line structure - if (change.full) { - doc.insert(0, linesFor(0, text.length)); - doc.remove(text.length, doc.size - text.length); - } else if (isWholeLineUpdate(doc, change)) { - // This is a whole-line replace. Treated specially to make - // sure line objects move the way they are supposed to. - var added = linesFor(0, text.length - 1); - update(lastLine, lastLine.text, lastSpans); - if (nlines) doc.remove(from.line, nlines); - if (added.length) doc.insert(from.line, added); - } else if (firstLine == lastLine) { - if (text.length == 1) { - update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); - } else { - var added = linesFor(1, text.length - 1); - added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); - update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); - doc.insert(from.line + 1, added); - } - } else if (text.length == 1) { - update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); - doc.remove(from.line + 1, nlines); - } else { - update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); - update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); - var added = linesFor(1, text.length - 1); - if (nlines > 1) doc.remove(from.line + 1, nlines - 1); - doc.insert(from.line + 1, added); - } - - signalLater(doc, "change", doc, change); - } - - // The document is represented as a BTree consisting of leaves, with - // chunk of lines in them, and branches, with up to ten leaves or - // other branch nodes below them. The top node is always a branch - // node, and is the document object itself (meaning it has - // additional methods and properties). - // - // All nodes have parent links. The tree is used both to go from - // line numbers to line objects, and to go from objects to numbers. - // It also indexes by height, and is used to convert between height - // and line object, and to find the total height of the document. - // - // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html - - function LeafChunk(lines) { - this.lines = lines; - this.parent = null; - for (var i = 0, height = 0; i < lines.length; ++i) { - lines[i].parent = this; - height += lines[i].height; - } - this.height = height; - } - - LeafChunk.prototype = { - chunkSize: function() { return this.lines.length; }, - // Remove the n lines at offset 'at'. - removeInner: function(at, n) { - for (var i = at, e = at + n; i < e; ++i) { - var line = this.lines[i]; - this.height -= line.height; - cleanUpLine(line); - signalLater(line, "delete"); - } - this.lines.splice(at, n); - }, - // Helper used to collapse a small branch into a single leaf. - collapse: function(lines) { - lines.push.apply(lines, this.lines); - }, - // Insert the given array of lines at offset 'at', count them as - // having the given height. - insertInner: function(at, lines, height) { - this.height += height; - this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); - for (var i = 0; i < lines.length; ++i) lines[i].parent = this; - }, - // Used to iterate over a part of the tree. - iterN: function(at, n, op) { - for (var e = at + n; at < e; ++at) - if (op(this.lines[at])) return true; - } - }; - - function BranchChunk(children) { - this.children = children; - var size = 0, height = 0; - for (var i = 0; i < children.length; ++i) { - var ch = children[i]; - size += ch.chunkSize(); height += ch.height; - ch.parent = this; - } - this.size = size; - this.height = height; - this.parent = null; - } - - BranchChunk.prototype = { - chunkSize: function() { return this.size; }, - removeInner: function(at, n) { - this.size -= n; - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var rm = Math.min(n, sz - at), oldHeight = child.height; - child.removeInner(at, rm); - this.height -= oldHeight - child.height; - if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } - if ((n -= rm) == 0) break; - at = 0; - } else at -= sz; - } - // If the result is smaller than 25 lines, ensure that it is a - // single leaf node. - if (this.size - n < 25 && - (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { - var lines = []; - this.collapse(lines); - this.children = [new LeafChunk(lines)]; - this.children[0].parent = this; - } - }, - collapse: function(lines) { - for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines); - }, - insertInner: function(at, lines, height) { - this.size += lines.length; - this.height += height; - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at <= sz) { - child.insertInner(at, lines, height); - if (child.lines && child.lines.length > 50) { - // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. - // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. - var remaining = child.lines.length % 25 + 25 - for (var pos = remaining; pos < child.lines.length;) { - var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); - child.height -= leaf.height; - this.children.splice(++i, 0, leaf); - leaf.parent = this; - } - child.lines = child.lines.slice(0, remaining); - this.maybeSpill(); - } - break; - } - at -= sz; - } - }, - // When a node has grown, check whether it should be split. - maybeSpill: function() { - if (this.children.length <= 10) return; - var me = this; - do { - var spilled = me.children.splice(me.children.length - 5, 5); - var sibling = new BranchChunk(spilled); - if (!me.parent) { // Become the parent node - var copy = new BranchChunk(me.children); - copy.parent = me; - me.children = [copy, sibling]; - me = copy; - } else { - me.size -= sibling.size; - me.height -= sibling.height; - var myIndex = indexOf(me.parent.children, me); - me.parent.children.splice(myIndex + 1, 0, sibling); - } - sibling.parent = me.parent; - } while (me.children.length > 10); - me.parent.maybeSpill(); - }, - iterN: function(at, n, op) { - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var used = Math.min(n, sz - at); - if (child.iterN(at, used, op)) return true; - if ((n -= used) == 0) break; - at = 0; - } else at -= sz; - } - } - }; - - var nextDocId = 0; - var Doc = CodeMirror.Doc = function(text, mode, firstLine, lineSep) { - if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep); - if (firstLine == null) firstLine = 0; - - BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); - this.first = firstLine; - this.scrollTop = this.scrollLeft = 0; - this.cantEdit = false; - this.cleanGeneration = 1; - this.frontier = firstLine; - var start = Pos(firstLine, 0); - this.sel = simpleSelection(start); - this.history = new History(null); - this.id = ++nextDocId; - this.modeOption = mode; - this.lineSep = lineSep; - this.extend = false; - - if (typeof text == "string") text = this.splitLines(text); - updateDoc(this, {from: start, to: start, text: text}); - setSelection(this, simpleSelection(start), sel_dontScroll); - }; - - Doc.prototype = createObj(BranchChunk.prototype, { - constructor: Doc, - // Iterate over the document. Supports two forms -- with only one - // argument, it calls that for each line in the document. With - // three, it iterates over the range given by the first two (with - // the second being non-inclusive). - iter: function(from, to, op) { - if (op) this.iterN(from - this.first, to - from, op); - else this.iterN(this.first, this.first + this.size, from); - }, - - // Non-public interface for adding and removing lines. - insert: function(at, lines) { - var height = 0; - for (var i = 0; i < lines.length; ++i) height += lines[i].height; - this.insertInner(at - this.first, lines, height); - }, - remove: function(at, n) { this.removeInner(at - this.first, n); }, - - // From here, the methods are part of the public interface. Most - // are also available from CodeMirror (editor) instances. - - getValue: function(lineSep) { - var lines = getLines(this, this.first, this.first + this.size); - if (lineSep === false) return lines; - return lines.join(lineSep || this.lineSeparator()); - }, - setValue: docMethodOp(function(code) { - var top = Pos(this.first, 0), last = this.first + this.size - 1; - makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), - text: this.splitLines(code), origin: "setValue", full: true}, true); - setSelection(this, simpleSelection(top)); - }), - replaceRange: function(code, from, to, origin) { - from = clipPos(this, from); - to = to ? clipPos(this, to) : from; - replaceRange(this, code, from, to, origin); - }, - getRange: function(from, to, lineSep) { - var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); - if (lineSep === false) return lines; - return lines.join(lineSep || this.lineSeparator()); - }, - - getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, - - getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, - getLineNumber: function(line) {return lineNo(line);}, - - getLineHandleVisualStart: function(line) { - if (typeof line == "number") line = getLine(this, line); - return visualLine(line); - }, - - lineCount: function() {return this.size;}, - firstLine: function() {return this.first;}, - lastLine: function() {return this.first + this.size - 1;}, - - clipPos: function(pos) {return clipPos(this, pos);}, - - getCursor: function(start) { - var range = this.sel.primary(), pos; - if (start == null || start == "head") pos = range.head; - else if (start == "anchor") pos = range.anchor; - else if (start == "end" || start == "to" || start === false) pos = range.to(); - else pos = range.from(); - return pos; - }, - listSelections: function() { return this.sel.ranges; }, - somethingSelected: function() {return this.sel.somethingSelected();}, - - setCursor: docMethodOp(function(line, ch, options) { - setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); - }), - setSelection: docMethodOp(function(anchor, head, options) { - setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); - }), - extendSelection: docMethodOp(function(head, other, options) { - extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); - }), - extendSelections: docMethodOp(function(heads, options) { - extendSelections(this, clipPosArray(this, heads), options); - }), - extendSelectionsBy: docMethodOp(function(f, options) { - var heads = map(this.sel.ranges, f); - extendSelections(this, clipPosArray(this, heads), options); - }), - setSelections: docMethodOp(function(ranges, primary, options) { - if (!ranges.length) return; - for (var i = 0, out = []; i < ranges.length; i++) - out[i] = new Range(clipPos(this, ranges[i].anchor), - clipPos(this, ranges[i].head)); - if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex); - setSelection(this, normalizeSelection(out, primary), options); - }), - addSelection: docMethodOp(function(anchor, head, options) { - var ranges = this.sel.ranges.slice(0); - ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); - setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); - }), - - getSelection: function(lineSep) { - var ranges = this.sel.ranges, lines; - for (var i = 0; i < ranges.length; i++) { - var sel = getBetween(this, ranges[i].from(), ranges[i].to()); - lines = lines ? lines.concat(sel) : sel; - } - if (lineSep === false) return lines; - else return lines.join(lineSep || this.lineSeparator()); - }, - getSelections: function(lineSep) { - var parts = [], ranges = this.sel.ranges; - for (var i = 0; i < ranges.length; i++) { - var sel = getBetween(this, ranges[i].from(), ranges[i].to()); - if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator()); - parts[i] = sel; - } - return parts; - }, - replaceSelection: function(code, collapse, origin) { - var dup = []; - for (var i = 0; i < this.sel.ranges.length; i++) - dup[i] = code; - this.replaceSelections(dup, collapse, origin || "+input"); - }, - replaceSelections: docMethodOp(function(code, collapse, origin) { - var changes = [], sel = this.sel; - for (var i = 0; i < sel.ranges.length; i++) { - var range = sel.ranges[i]; - changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin}; - } - var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); - for (var i = changes.length - 1; i >= 0; i--) - makeChange(this, changes[i]); - if (newSel) setSelectionReplaceHistory(this, newSel); - else if (this.cm) ensureCursorVisible(this.cm); - }), - undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), - redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), - undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), - redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), - - setExtending: function(val) {this.extend = val;}, - getExtending: function() {return this.extend;}, - - historySize: function() { - var hist = this.history, done = 0, undone = 0; - for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done; - for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone; - return {undo: done, redo: undone}; - }, - clearHistory: function() {this.history = new History(this.history.maxGeneration);}, - - markClean: function() { - this.cleanGeneration = this.changeGeneration(true); - }, - changeGeneration: function(forceSplit) { - if (forceSplit) - this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; - return this.history.generation; - }, - isClean: function (gen) { - return this.history.generation == (gen || this.cleanGeneration); - }, - - getHistory: function() { - return {done: copyHistoryArray(this.history.done), - undone: copyHistoryArray(this.history.undone)}; - }, - setHistory: function(histData) { - var hist = this.history = new History(this.history.maxGeneration); - hist.done = copyHistoryArray(histData.done.slice(0), null, true); - hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); - }, - - addLineClass: docMethodOp(function(handle, where, cls) { - return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { - var prop = where == "text" ? "textClass" - : where == "background" ? "bgClass" - : where == "gutter" ? "gutterClass" : "wrapClass"; - if (!line[prop]) line[prop] = cls; - else if (classTest(cls).test(line[prop])) return false; - else line[prop] += " " + cls; - return true; - }); - }), - removeLineClass: docMethodOp(function(handle, where, cls) { - return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { - var prop = where == "text" ? "textClass" - : where == "background" ? "bgClass" - : where == "gutter" ? "gutterClass" : "wrapClass"; - var cur = line[prop]; - if (!cur) return false; - else if (cls == null) line[prop] = null; - else { - var found = cur.match(classTest(cls)); - if (!found) return false; - var end = found.index + found[0].length; - line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; - } - return true; - }); - }), - - addLineWidget: docMethodOp(function(handle, node, options) { - return addLineWidget(this, handle, node, options); - }), - removeLineWidget: function(widget) { widget.clear(); }, - - markText: function(from, to, options) { - return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range"); - }, - setBookmark: function(pos, options) { - var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), - insertLeft: options && options.insertLeft, - clearWhenEmpty: false, shared: options && options.shared, - handleMouseEvents: options && options.handleMouseEvents}; - pos = clipPos(this, pos); - return markText(this, pos, pos, realOpts, "bookmark"); - }, - findMarksAt: function(pos) { - pos = clipPos(this, pos); - var markers = [], spans = getLine(this, pos.line).markedSpans; - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if ((span.from == null || span.from <= pos.ch) && - (span.to == null || span.to >= pos.ch)) - markers.push(span.marker.parent || span.marker); - } - return markers; - }, - findMarks: function(from, to, filter) { - from = clipPos(this, from); to = clipPos(this, to); - var found = [], lineNo = from.line; - this.iter(from.line, to.line + 1, function(line) { - var spans = line.markedSpans; - if (spans) for (var i = 0; i < spans.length; i++) { - var span = spans[i]; - if (!(span.to != null && lineNo == from.line && from.ch >= span.to || - span.from == null && lineNo != from.line || - span.from != null && lineNo == to.line && span.from >= to.ch) && - (!filter || filter(span.marker))) - found.push(span.marker.parent || span.marker); - } - ++lineNo; - }); - return found; - }, - getAllMarks: function() { - var markers = []; - this.iter(function(line) { - var sps = line.markedSpans; - if (sps) for (var i = 0; i < sps.length; ++i) - if (sps[i].from != null) markers.push(sps[i].marker); - }); - return markers; - }, - - posFromIndex: function(off) { - var ch, lineNo = this.first, sepSize = this.lineSeparator().length; - this.iter(function(line) { - var sz = line.text.length + sepSize; - if (sz > off) { ch = off; return true; } - off -= sz; - ++lineNo; - }); - return clipPos(this, Pos(lineNo, ch)); - }, - indexFromPos: function (coords) { - coords = clipPos(this, coords); - var index = coords.ch; - if (coords.line < this.first || coords.ch < 0) return 0; - var sepSize = this.lineSeparator().length; - this.iter(this.first, coords.line, function (line) { - index += line.text.length + sepSize; - }); - return index; - }, - - copy: function(copyHistory) { - var doc = new Doc(getLines(this, this.first, this.first + this.size), - this.modeOption, this.first, this.lineSep); - doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; - doc.sel = this.sel; - doc.extend = false; - if (copyHistory) { - doc.history.undoDepth = this.history.undoDepth; - doc.setHistory(this.getHistory()); - } - return doc; - }, - - linkedDoc: function(options) { - if (!options) options = {}; - var from = this.first, to = this.first + this.size; - if (options.from != null && options.from > from) from = options.from; - if (options.to != null && options.to < to) to = options.to; - var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep); - if (options.sharedHist) copy.history = this.history; - (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); - copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; - copySharedMarkers(copy, findSharedMarkers(this)); - return copy; - }, - unlinkDoc: function(other) { - if (other instanceof CodeMirror) other = other.doc; - if (this.linked) for (var i = 0; i < this.linked.length; ++i) { - var link = this.linked[i]; - if (link.doc != other) continue; - this.linked.splice(i, 1); - other.unlinkDoc(this); - detachSharedMarkers(findSharedMarkers(this)); - break; - } - // If the histories were shared, split them again - if (other.history == this.history) { - var splitIds = [other.id]; - linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); - other.history = new History(null); - other.history.done = copyHistoryArray(this.history.done, splitIds); - other.history.undone = copyHistoryArray(this.history.undone, splitIds); - } - }, - iterLinkedDocs: function(f) {linkedDocs(this, f);}, - - getMode: function() {return this.mode;}, - getEditor: function() {return this.cm;}, - - splitLines: function(str) { - if (this.lineSep) return str.split(this.lineSep); - return splitLinesAuto(str); - }, - lineSeparator: function() { return this.lineSep || "\n"; } - }); - - // Public alias. - Doc.prototype.eachLine = Doc.prototype.iter; - - // Set up methods on CodeMirror's prototype to redirect to the editor's document. - var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); - for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) - CodeMirror.prototype[prop] = (function(method) { - return function() {return method.apply(this.doc, arguments);}; - })(Doc.prototype[prop]); - - eventMixin(Doc); - - // Call f for all linked documents. - function linkedDocs(doc, f, sharedHistOnly) { - function propagate(doc, skip, sharedHist) { - if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { - var rel = doc.linked[i]; - if (rel.doc == skip) continue; - var shared = sharedHist && rel.sharedHist; - if (sharedHistOnly && !shared) continue; - f(rel.doc, shared); - propagate(rel.doc, doc, shared); - } - } - propagate(doc, null, true); - } - - // Attach a document to an editor. - function attachDoc(cm, doc) { - if (doc.cm) throw new Error("This document is already in use."); - cm.doc = doc; - doc.cm = cm; - estimateLineHeights(cm); - loadMode(cm); - if (!cm.options.lineWrapping) findMaxLine(cm); - cm.options.mode = doc.modeOption; - regChange(cm); - } - - // LINE UTILITIES - - // Find the line object corresponding to the given line number. - function getLine(doc, n) { - n -= doc.first; - if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document."); - for (var chunk = doc; !chunk.lines;) { - for (var i = 0;; ++i) { - var child = chunk.children[i], sz = child.chunkSize(); - if (n < sz) { chunk = child; break; } - n -= sz; - } - } - return chunk.lines[n]; - } - - // Get the part of a document between two positions, as an array of - // strings. - function getBetween(doc, start, end) { - var out = [], n = start.line; - doc.iter(start.line, end.line + 1, function(line) { - var text = line.text; - if (n == end.line) text = text.slice(0, end.ch); - if (n == start.line) text = text.slice(start.ch); - out.push(text); - ++n; - }); - return out; - } - // Get the lines between from and to, as array of strings. - function getLines(doc, from, to) { - var out = []; - doc.iter(from, to, function(line) { out.push(line.text); }); - return out; - } - - // Update the height of a line, propagating the height change - // upwards to parent nodes. - function updateLineHeight(line, height) { - var diff = height - line.height; - if (diff) for (var n = line; n; n = n.parent) n.height += diff; - } - - // Given a line object, find its line number by walking up through - // its parent links. - function lineNo(line) { - if (line.parent == null) return null; - var cur = line.parent, no = indexOf(cur.lines, line); - for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { - for (var i = 0;; ++i) { - if (chunk.children[i] == cur) break; - no += chunk.children[i].chunkSize(); - } - } - return no + cur.first; - } - - // Find the line at the given vertical position, using the height - // information in the document tree. - function lineAtHeight(chunk, h) { - var n = chunk.first; - outer: do { - for (var i = 0; i < chunk.children.length; ++i) { - var child = chunk.children[i], ch = child.height; - if (h < ch) { chunk = child; continue outer; } - h -= ch; - n += child.chunkSize(); - } - return n; - } while (!chunk.lines); - for (var i = 0; i < chunk.lines.length; ++i) { - var line = chunk.lines[i], lh = line.height; - if (h < lh) break; - h -= lh; - } - return n + i; - } - - - // Find the height above the given line. - function heightAtLine(lineObj) { - lineObj = visualLine(lineObj); - - var h = 0, chunk = lineObj.parent; - for (var i = 0; i < chunk.lines.length; ++i) { - var line = chunk.lines[i]; - if (line == lineObj) break; - else h += line.height; - } - for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { - for (var i = 0; i < p.children.length; ++i) { - var cur = p.children[i]; - if (cur == chunk) break; - else h += cur.height; - } - } - return h; - } - - // Get the bidi ordering for the given line (and cache it). Returns - // false for lines that are fully left-to-right, and an array of - // BidiSpan objects otherwise. - function getOrder(line) { - var order = line.order; - if (order == null) order = line.order = bidiOrdering(line.text); - return order; - } - - // HISTORY - - function History(startGen) { - // Arrays of change events and selections. Doing something adds an - // event to done and clears undo. Undoing moves events from done - // to undone, redoing moves them in the other direction. - this.done = []; this.undone = []; - this.undoDepth = Infinity; - // Used to track when changes can be merged into a single undo - // event - this.lastModTime = this.lastSelTime = 0; - this.lastOp = this.lastSelOp = null; - this.lastOrigin = this.lastSelOrigin = null; - // Used by the isClean() method - this.generation = this.maxGeneration = startGen || 1; - } - - // Create a history change event from an updateDoc-style change - // object. - function historyChangeFromChange(doc, change) { - var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; - attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); - linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); - return histChange; - } - - // Pop all selection events off the end of a history array. Stop at - // a change event. - function clearSelectionEvents(array) { - while (array.length) { - var last = lst(array); - if (last.ranges) array.pop(); - else break; - } - } - - // Find the top change event in the history. Pop off selection - // events that are in the way. - function lastChangeEvent(hist, force) { - if (force) { - clearSelectionEvents(hist.done); - return lst(hist.done); - } else if (hist.done.length && !lst(hist.done).ranges) { - return lst(hist.done); - } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { - hist.done.pop(); - return lst(hist.done); - } - } - - // Register a change in the history. Merges changes that are within - // a single operation, ore are close together with an origin that - // allows merging (starting with "+") into a single event. - function addChangeToHistory(doc, change, selAfter, opId) { - var hist = doc.history; - hist.undone.length = 0; - var time = +new Date, cur; - - if ((hist.lastOp == opId || - hist.lastOrigin == change.origin && change.origin && - ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || - change.origin.charAt(0) == "*")) && - (cur = lastChangeEvent(hist, hist.lastOp == opId))) { - // Merge this change into the last event - var last = lst(cur.changes); - if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { - // Optimized case for simple insertion -- don't want to add - // new changesets for every character typed - last.to = changeEnd(change); - } else { - // Add new sub-event - cur.changes.push(historyChangeFromChange(doc, change)); - } - } else { - // Can not be merged, start a new event. - var before = lst(hist.done); - if (!before || !before.ranges) - pushSelectionToHistory(doc.sel, hist.done); - cur = {changes: [historyChangeFromChange(doc, change)], - generation: hist.generation}; - hist.done.push(cur); - while (hist.done.length > hist.undoDepth) { - hist.done.shift(); - if (!hist.done[0].ranges) hist.done.shift(); - } - } - hist.done.push(selAfter); - hist.generation = ++hist.maxGeneration; - hist.lastModTime = hist.lastSelTime = time; - hist.lastOp = hist.lastSelOp = opId; - hist.lastOrigin = hist.lastSelOrigin = change.origin; - - if (!last) signal(doc, "historyAdded"); - } - - function selectionEventCanBeMerged(doc, origin, prev, sel) { - var ch = origin.charAt(0); - return ch == "*" || - ch == "+" && - prev.ranges.length == sel.ranges.length && - prev.somethingSelected() == sel.somethingSelected() && - new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); - } - - // Called whenever the selection changes, sets the new selection as - // the pending selection in the history, and pushes the old pending - // selection into the 'done' array when it was significantly - // different (in number of selected ranges, emptiness, or time). - function addSelectionToHistory(doc, sel, opId, options) { - var hist = doc.history, origin = options && options.origin; - - // A new event is started when the previous origin does not match - // the current, or the origins don't allow matching. Origins - // starting with * are always merged, those starting with + are - // merged when similar and close together in time. - if (opId == hist.lastSelOp || - (origin && hist.lastSelOrigin == origin && - (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || - selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) - hist.done[hist.done.length - 1] = sel; - else - pushSelectionToHistory(sel, hist.done); - - hist.lastSelTime = +new Date; - hist.lastSelOrigin = origin; - hist.lastSelOp = opId; - if (options && options.clearRedo !== false) - clearSelectionEvents(hist.undone); - } - - function pushSelectionToHistory(sel, dest) { - var top = lst(dest); - if (!(top && top.ranges && top.equals(sel))) - dest.push(sel); - } - - // Used to store marked span information in the history. - function attachLocalSpans(doc, change, from, to) { - var existing = change["spans_" + doc.id], n = 0; - doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { - if (line.markedSpans) - (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; - ++n; - }); - } - - // When un/re-doing restores text containing marked spans, those - // that have been explicitly cleared should not be restored. - function removeClearedSpans(spans) { - if (!spans) return null; - for (var i = 0, out; i < spans.length; ++i) { - if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } - else if (out) out.push(spans[i]); - } - return !out ? spans : out.length ? out : null; - } - - // Retrieve and filter the old marked spans stored in a change event. - function getOldSpans(doc, change) { - var found = change["spans_" + doc.id]; - if (!found) return null; - for (var i = 0, nw = []; i < change.text.length; ++i) - nw.push(removeClearedSpans(found[i])); - return nw; - } - - // Used both to provide a JSON-safe object in .getHistory, and, when - // detaching a document, to split the history in two - function copyHistoryArray(events, newGroup, instantiateSel) { - for (var i = 0, copy = []; i < events.length; ++i) { - var event = events[i]; - if (event.ranges) { - copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); - continue; - } - var changes = event.changes, newChanges = []; - copy.push({changes: newChanges}); - for (var j = 0; j < changes.length; ++j) { - var change = changes[j], m; - newChanges.push({from: change.from, to: change.to, text: change.text}); - if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { - if (indexOf(newGroup, Number(m[1])) > -1) { - lst(newChanges)[prop] = change[prop]; - delete change[prop]; - } - } - } - } - return copy; - } - - // Rebasing/resetting history to deal with externally-sourced changes - - function rebaseHistSelSingle(pos, from, to, diff) { - if (to < pos.line) { - pos.line += diff; - } else if (from < pos.line) { - pos.line = from; - pos.ch = 0; - } - } - - // Tries to rebase an array of history events given a change in the - // document. If the change touches the same lines as the event, the - // event, and everything 'behind' it, is discarded. If the change is - // before the event, the event's positions are updated. Uses a - // copy-on-write scheme for the positions, to avoid having to - // reallocate them all on every rebase, but also avoid problems with - // shared position objects being unsafely updated. - function rebaseHistArray(array, from, to, diff) { - for (var i = 0; i < array.length; ++i) { - var sub = array[i], ok = true; - if (sub.ranges) { - if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } - for (var j = 0; j < sub.ranges.length; j++) { - rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); - rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); - } - continue; - } - for (var j = 0; j < sub.changes.length; ++j) { - var cur = sub.changes[j]; - if (to < cur.from.line) { - cur.from = Pos(cur.from.line + diff, cur.from.ch); - cur.to = Pos(cur.to.line + diff, cur.to.ch); - } else if (from <= cur.to.line) { - ok = false; - break; - } - } - if (!ok) { - array.splice(0, i + 1); - i = 0; - } - } - } - - function rebaseHist(hist, change) { - var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; - rebaseHistArray(hist.done, from, to, diff); - rebaseHistArray(hist.undone, from, to, diff); - } - - // EVENT UTILITIES - - // Due to the fact that we still support jurassic IE versions, some - // compatibility wrappers are needed. - - var e_preventDefault = CodeMirror.e_preventDefault = function(e) { - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - }; - var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { - if (e.stopPropagation) e.stopPropagation(); - else e.cancelBubble = true; - }; - function e_defaultPrevented(e) { - return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; - } - var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);}; - - function e_target(e) {return e.target || e.srcElement;} - function e_button(e) { - var b = e.which; - if (b == null) { - if (e.button & 1) b = 1; - else if (e.button & 2) b = 3; - else if (e.button & 4) b = 2; - } - if (mac && e.ctrlKey && b == 1) b = 3; - return b; - } - - // EVENT HANDLING - - // Lightweight event framework. on/off also work on DOM nodes, - // registering native DOM handlers. - - var on = CodeMirror.on = function(emitter, type, f) { - if (emitter.addEventListener) - emitter.addEventListener(type, f, false); - else if (emitter.attachEvent) - emitter.attachEvent("on" + type, f); - else { - var map = emitter._handlers || (emitter._handlers = {}); - var arr = map[type] || (map[type] = []); - arr.push(f); - } - }; - - var noHandlers = [] - function getHandlers(emitter, type, copy) { - var arr = emitter._handlers && emitter._handlers[type] - if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers - else return arr || noHandlers - } - - var off = CodeMirror.off = function(emitter, type, f) { - if (emitter.removeEventListener) - emitter.removeEventListener(type, f, false); - else if (emitter.detachEvent) - emitter.detachEvent("on" + type, f); - else { - var handlers = getHandlers(emitter, type, false) - for (var i = 0; i < handlers.length; ++i) - if (handlers[i] == f) { handlers.splice(i, 1); break; } - } - }; - - var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { - var handlers = getHandlers(emitter, type, true) - if (!handlers.length) return; - var args = Array.prototype.slice.call(arguments, 2); - for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args); - }; - - var orphanDelayedCallbacks = null; - - // Often, we want to signal events at a point where we are in the - // middle of some work, but don't want the handler to start calling - // other methods on the editor, which might be in an inconsistent - // state or simply not expect any other events to happen. - // signalLater looks whether there are any handlers, and schedules - // them to be executed when the last operation ends, or, if no - // operation is active, when a timeout fires. - function signalLater(emitter, type /*, values...*/) { - var arr = getHandlers(emitter, type, false) - if (!arr.length) return; - var args = Array.prototype.slice.call(arguments, 2), list; - if (operationGroup) { - list = operationGroup.delayedCallbacks; - } else if (orphanDelayedCallbacks) { - list = orphanDelayedCallbacks; - } else { - list = orphanDelayedCallbacks = []; - setTimeout(fireOrphanDelayed, 0); - } - function bnd(f) {return function(){f.apply(null, args);};}; - for (var i = 0; i < arr.length; ++i) - list.push(bnd(arr[i])); - } - - function fireOrphanDelayed() { - var delayed = orphanDelayedCallbacks; - orphanDelayedCallbacks = null; - for (var i = 0; i < delayed.length; ++i) delayed[i](); - } - - // The DOM events that CodeMirror handles can be overridden by - // registering a (non-DOM) handler on the editor for the event name, - // and preventDefault-ing the event in that handler. - function signalDOMEvent(cm, e, override) { - if (typeof e == "string") - e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; - signal(cm, override || e.type, cm, e); - return e_defaultPrevented(e) || e.codemirrorIgnore; - } - - function signalCursorActivity(cm) { - var arr = cm._handlers && cm._handlers.cursorActivity; - if (!arr) return; - var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); - for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) - set.push(arr[i]); - } - - function hasHandler(emitter, type) { - return getHandlers(emitter, type).length > 0 - } - - // Add on and off methods to a constructor's prototype, to make - // registering events on such objects more convenient. - function eventMixin(ctor) { - ctor.prototype.on = function(type, f) {on(this, type, f);}; - ctor.prototype.off = function(type, f) {off(this, type, f);}; - } - - // MISC UTILITIES - - // Number of pixels added to scroller and sizer to hide scrollbar - var scrollerGap = 30; - - // Returned or thrown by various protocols to signal 'I'm not - // handling this'. - var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; - - // Reused option objects for setSelection & friends - var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; - - function Delayed() {this.id = null;} - Delayed.prototype.set = function(ms, f) { - clearTimeout(this.id); - this.id = setTimeout(f, ms); - }; - - // Counts the column offset in a string, taking tabs into account. - // Used mostly to find indentation. - var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) { - if (end == null) { - end = string.search(/[^\s\u00a0]/); - if (end == -1) end = string.length; - } - for (var i = startIndex || 0, n = startValue || 0;;) { - var nextTab = string.indexOf("\t", i); - if (nextTab < 0 || nextTab >= end) - return n + (end - i); - n += nextTab - i; - n += tabSize - (n % tabSize); - i = nextTab + 1; - } - }; - - // The inverse of countColumn -- find the offset that corresponds to - // a particular column. - var findColumn = CodeMirror.findColumn = function(string, goal, tabSize) { - for (var pos = 0, col = 0;;) { - var nextTab = string.indexOf("\t", pos); - if (nextTab == -1) nextTab = string.length; - var skipped = nextTab - pos; - if (nextTab == string.length || col + skipped >= goal) - return pos + Math.min(skipped, goal - col); - col += nextTab - pos; - col += tabSize - (col % tabSize); - pos = nextTab + 1; - if (col >= goal) return pos; - } - } - - var spaceStrs = [""]; - function spaceStr(n) { - while (spaceStrs.length <= n) - spaceStrs.push(lst(spaceStrs) + " "); - return spaceStrs[n]; - } - - function lst(arr) { return arr[arr.length-1]; } - - var selectInput = function(node) { node.select(); }; - if (ios) // Mobile Safari apparently has a bug where select() is broken. - selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; - else if (ie) // Suppress mysterious IE10 errors - selectInput = function(node) { try { node.select(); } catch(_e) {} }; - - function indexOf(array, elt) { - for (var i = 0; i < array.length; ++i) - if (array[i] == elt) return i; - return -1; - } - function map(array, f) { - var out = []; - for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); - return out; - } - - function nothing() {} - - function createObj(base, props) { - var inst; - if (Object.create) { - inst = Object.create(base); - } else { - nothing.prototype = base; - inst = new nothing(); - } - if (props) copyObj(props, inst); - return inst; - }; - - function copyObj(obj, target, overwrite) { - if (!target) target = {}; - for (var prop in obj) - if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) - target[prop] = obj[prop]; - return target; - } - - function bind(f) { - var args = Array.prototype.slice.call(arguments, 1); - return function(){return f.apply(null, args);}; - } - - var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; - var isWordCharBasic = CodeMirror.isWordChar = function(ch) { - return /\w/.test(ch) || ch > "\x80" && - (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); - }; - function isWordChar(ch, helper) { - if (!helper) return isWordCharBasic(ch); - if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; - return helper.test(ch); - } - - function isEmpty(obj) { - for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; - return true; - } - - // Extending unicode characters. A series of a non-extending char + - // any number of extending chars is treated as a single unit as far - // as editing and measuring is concerned. This is not fully correct, - // since some scripts/fonts/browsers also treat other configurations - // of code points as a group. - var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; - function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } - - // DOM UTILITIES - - function elt(tag, content, className, style) { - var e = document.createElement(tag); - if (className) e.className = className; - if (style) e.style.cssText = style; - if (typeof content == "string") e.appendChild(document.createTextNode(content)); - else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); - return e; - } - - var range; - if (document.createRange) range = function(node, start, end, endNode) { - var r = document.createRange(); - r.setEnd(endNode || node, end); - r.setStart(node, start); - return r; - }; - else range = function(node, start, end) { - var r = document.body.createTextRange(); - try { r.moveToElementText(node.parentNode); } - catch(e) { return r; } - r.collapse(true); - r.moveEnd("character", end); - r.moveStart("character", start); - return r; - }; - - function removeChildren(e) { - for (var count = e.childNodes.length; count > 0; --count) - e.removeChild(e.firstChild); - return e; - } - - function removeChildrenAndAdd(parent, e) { - return removeChildren(parent).appendChild(e); - } - - var contains = CodeMirror.contains = function(parent, child) { - if (child.nodeType == 3) // Android browser always returns false when child is a textnode - child = child.parentNode; - if (parent.contains) - return parent.contains(child); - do { - if (child.nodeType == 11) child = child.host; - if (child == parent) return true; - } while (child = child.parentNode); - }; - - function activeElt() { - var activeElement = document.activeElement; - while (activeElement && activeElement.root && activeElement.root.activeElement) - activeElement = activeElement.root.activeElement; - return activeElement; - } - // Older versions of IE throws unspecified error when touching - // document.activeElement in some cases (during loading, in iframe) - if (ie && ie_version < 11) activeElt = function() { - try { return document.activeElement; } - catch(e) { return document.body; } - }; - - function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); } - var rmClass = CodeMirror.rmClass = function(node, cls) { - var current = node.className; - var match = classTest(cls).exec(current); - if (match) { - var after = current.slice(match.index + match[0].length); - node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); - } - }; - var addClass = CodeMirror.addClass = function(node, cls) { - var current = node.className; - if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls; - }; - function joinClasses(a, b) { - var as = a.split(" "); - for (var i = 0; i < as.length; i++) - if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; - return b; - } - - // WINDOW-WIDE EVENTS - - // These must be handled carefully, because naively registering a - // handler for each editor will cause the editors to never be - // garbage collected. - - function forEachCodeMirror(f) { - if (!document.body.getElementsByClassName) return; - var byClass = document.body.getElementsByClassName("CodeMirror"); - for (var i = 0; i < byClass.length; i++) { - var cm = byClass[i].CodeMirror; - if (cm) f(cm); - } - } - - var globalsRegistered = false; - function ensureGlobalHandlers() { - if (globalsRegistered) return; - registerGlobalHandlers(); - globalsRegistered = true; - } - function registerGlobalHandlers() { - // When the window resizes, we need to refresh active editors. - var resizeTimer; - on(window, "resize", function() { - if (resizeTimer == null) resizeTimer = setTimeout(function() { - resizeTimer = null; - forEachCodeMirror(onResize); - }, 100); - }); - // When the window loses focus, we want to show the editor as blurred - on(window, "blur", function() { - forEachCodeMirror(onBlur); - }); - } - - // FEATURE DETECTION - - // Detect drag-and-drop - var dragAndDrop = function() { - // There is *some* kind of drag-and-drop support in IE6-8, but I - // couldn't get it to work yet. - if (ie && ie_version < 9) return false; - var div = elt('div'); - return "draggable" in div || "dragDrop" in div; - }(); - - var zwspSupported; - function zeroWidthElement(measure) { - if (zwspSupported == null) { - var test = elt("span", "\u200b"); - removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); - if (measure.firstChild.offsetHeight != 0) - zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); - } - var node = zwspSupported ? elt("span", "\u200b") : - elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); - node.setAttribute("cm-text", ""); - return node; - } - - // Feature-detect IE's crummy client rect reporting for bidi text - var badBidiRects; - function hasBadBidiRects(measure) { - if (badBidiRects != null) return badBidiRects; - var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); - var r0 = range(txt, 0, 1).getBoundingClientRect(); - var r1 = range(txt, 1, 2).getBoundingClientRect(); - removeChildren(measure); - if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780) - return badBidiRects = (r1.right - r0.right < 3); - } - - // See if "".split is the broken IE version, if so, provide an - // alternative way to split lines. - var splitLinesAuto = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { - var pos = 0, result = [], l = string.length; - while (pos <= l) { - var nl = string.indexOf("\n", pos); - if (nl == -1) nl = string.length; - var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); - var rt = line.indexOf("\r"); - if (rt != -1) { - result.push(line.slice(0, rt)); - pos += rt + 1; - } else { - result.push(line); - pos = nl + 1; - } - } - return result; - } : function(string){return string.split(/\r\n?|\n/);}; - - var hasSelection = window.getSelection ? function(te) { - try { return te.selectionStart != te.selectionEnd; } - catch(e) { return false; } - } : function(te) { - try {var range = te.ownerDocument.selection.createRange();} - catch(e) {} - if (!range || range.parentElement() != te) return false; - return range.compareEndPoints("StartToEnd", range) != 0; - }; - - var hasCopyEvent = (function() { - var e = elt("div"); - if ("oncopy" in e) return true; - e.setAttribute("oncopy", "return;"); - return typeof e.oncopy == "function"; - })(); - - var badZoomedRects = null; - function hasBadZoomedRects(measure) { - if (badZoomedRects != null) return badZoomedRects; - var node = removeChildrenAndAdd(measure, elt("span", "x")); - var normal = node.getBoundingClientRect(); - var fromRange = range(node, 0, 1).getBoundingClientRect(); - return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; - } - - // KEY NAMES - - var keyNames = CodeMirror.keyNames = { - 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", - 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", - 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", - 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", - 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", - 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", - 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" - }; - (function() { - // Number keys - for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); - // Alphabetic keys - for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); - // Function keys - for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; - })(); - - // BIDI HELPERS - - function iterateBidiSections(order, from, to, f) { - if (!order) return f(from, to, "ltr"); - var found = false; - for (var i = 0; i < order.length; ++i) { - var part = order[i]; - if (part.from < to && part.to > from || from == to && part.to == from) { - f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); - found = true; - } - } - if (!found) f(from, to, "ltr"); - } - - function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } - function bidiRight(part) { return part.level % 2 ? part.from : part.to; } - - function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } - function lineRight(line) { - var order = getOrder(line); - if (!order) return line.text.length; - return bidiRight(lst(order)); - } - - function lineStart(cm, lineN) { - var line = getLine(cm.doc, lineN); - var visual = visualLine(line); - if (visual != line) lineN = lineNo(visual); - var order = getOrder(visual); - var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); - return Pos(lineN, ch); - } - function lineEnd(cm, lineN) { - var merged, line = getLine(cm.doc, lineN); - while (merged = collapsedSpanAtEnd(line)) { - line = merged.find(1, true).line; - lineN = null; - } - var order = getOrder(line); - var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); - return Pos(lineN == null ? lineNo(line) : lineN, ch); - } - function lineStartSmart(cm, pos) { - var start = lineStart(cm, pos.line); - var line = getLine(cm.doc, start.line); - var order = getOrder(line); - if (!order || order[0].level == 0) { - var firstNonWS = Math.max(0, line.text.search(/\S/)); - var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; - return Pos(start.line, inWS ? 0 : firstNonWS); - } - return start; - } - - function compareBidiLevel(order, a, b) { - var linedir = order[0].level; - if (a == linedir) return true; - if (b == linedir) return false; - return a < b; - } - var bidiOther; - function getBidiPartAt(order, pos) { - bidiOther = null; - for (var i = 0, found; i < order.length; ++i) { - var cur = order[i]; - if (cur.from < pos && cur.to > pos) return i; - if ((cur.from == pos || cur.to == pos)) { - if (found == null) { - found = i; - } else if (compareBidiLevel(order, cur.level, order[found].level)) { - if (cur.from != cur.to) bidiOther = found; - return i; - } else { - if (cur.from != cur.to) bidiOther = i; - return found; - } - } - } - return found; - } - - function moveInLine(line, pos, dir, byUnit) { - if (!byUnit) return pos + dir; - do pos += dir; - while (pos > 0 && isExtendingChar(line.text.charAt(pos))); - return pos; - } - - // This is needed in order to move 'visually' through bi-directional - // text -- i.e., pressing left should make the cursor go left, even - // when in RTL text. The tricky part is the 'jumps', where RTL and - // LTR text touch each other. This often requires the cursor offset - // to move more than one unit, in order to visually move one unit. - function moveVisually(line, start, dir, byUnit) { - var bidi = getOrder(line); - if (!bidi) return moveLogically(line, start, dir, byUnit); - var pos = getBidiPartAt(bidi, start), part = bidi[pos]; - var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); - - for (;;) { - if (target > part.from && target < part.to) return target; - if (target == part.from || target == part.to) { - if (getBidiPartAt(bidi, target) == pos) return target; - part = bidi[pos += dir]; - return (dir > 0) == part.level % 2 ? part.to : part.from; - } else { - part = bidi[pos += dir]; - if (!part) return null; - if ((dir > 0) == part.level % 2) - target = moveInLine(line, part.to, -1, byUnit); - else - target = moveInLine(line, part.from, 1, byUnit); - } - } - } - - function moveLogically(line, start, dir, byUnit) { - var target = start + dir; - if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; - return target < 0 || target > line.text.length ? null : target; - } - - // Bidirectional ordering algorithm - // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm - // that this (partially) implements. - - // One-char codes used for character types: - // L (L): Left-to-Right - // R (R): Right-to-Left - // r (AL): Right-to-Left Arabic - // 1 (EN): European Number - // + (ES): European Number Separator - // % (ET): European Number Terminator - // n (AN): Arabic Number - // , (CS): Common Number Separator - // m (NSM): Non-Spacing Mark - // b (BN): Boundary Neutral - // s (B): Paragraph Separator - // t (S): Segment Separator - // w (WS): Whitespace - // N (ON): Other Neutrals - - // Returns null if characters are ordered as they appear - // (left-to-right), or an array of sections ({from, to, level} - // objects) in the order in which they occur visually. - var bidiOrdering = (function() { - // Character types for codepoints 0 to 0xff - var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; - // Character types for codepoints 0x600 to 0x6ff - var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm"; - function charType(code) { - if (code <= 0xf7) return lowTypes.charAt(code); - else if (0x590 <= code && code <= 0x5f4) return "R"; - else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600); - else if (0x6ee <= code && code <= 0x8ac) return "r"; - else if (0x2000 <= code && code <= 0x200b) return "w"; - else if (code == 0x200c) return "b"; - else return "L"; - } - - var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; - var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; - // Browsers seem to always treat the boundaries of block elements as being L. - var outerType = "L"; - - function BidiSpan(level, from, to) { - this.level = level; - this.from = from; this.to = to; - } - - return function(str) { - if (!bidiRE.test(str)) return false; - var len = str.length, types = []; - for (var i = 0, type; i < len; ++i) - types.push(type = charType(str.charCodeAt(i))); - - // W1. Examine each non-spacing mark (NSM) in the level run, and - // change the type of the NSM to the type of the previous - // character. If the NSM is at the start of the level run, it will - // get the type of sor. - for (var i = 0, prev = outerType; i < len; ++i) { - var type = types[i]; - if (type == "m") types[i] = prev; - else prev = type; - } - - // W2. Search backwards from each instance of a European number - // until the first strong type (R, L, AL, or sor) is found. If an - // AL is found, change the type of the European number to Arabic - // number. - // W3. Change all ALs to R. - for (var i = 0, cur = outerType; i < len; ++i) { - var type = types[i]; - if (type == "1" && cur == "r") types[i] = "n"; - else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } - } - - // W4. A single European separator between two European numbers - // changes to a European number. A single common separator between - // two numbers of the same type changes to that type. - for (var i = 1, prev = types[0]; i < len - 1; ++i) { - var type = types[i]; - if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; - else if (type == "," && prev == types[i+1] && - (prev == "1" || prev == "n")) types[i] = prev; - prev = type; - } - - // W5. A sequence of European terminators adjacent to European - // numbers changes to all European numbers. - // W6. Otherwise, separators and terminators change to Other - // Neutral. - for (var i = 0; i < len; ++i) { - var type = types[i]; - if (type == ",") types[i] = "N"; - else if (type == "%") { - for (var end = i + 1; end < len && types[end] == "%"; ++end) {} - var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // W7. Search backwards from each instance of a European number - // until the first strong type (R, L, or sor) is found. If an L is - // found, then change the type of the European number to L. - for (var i = 0, cur = outerType; i < len; ++i) { - var type = types[i]; - if (cur == "L" && type == "1") types[i] = "L"; - else if (isStrong.test(type)) cur = type; - } - - // N1. A sequence of neutrals takes the direction of the - // surrounding strong text if the text on both sides has the same - // direction. European and Arabic numbers act as if they were R in - // terms of their influence on neutrals. Start-of-level-run (sor) - // and end-of-level-run (eor) are used at level run boundaries. - // N2. Any remaining neutrals take the embedding direction. - for (var i = 0; i < len; ++i) { - if (isNeutral.test(types[i])) { - for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} - var before = (i ? types[i-1] : outerType) == "L"; - var after = (end < len ? types[end] : outerType) == "L"; - var replace = before || after ? "L" : "R"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // Here we depart from the documented algorithm, in order to avoid - // building up an actual levels array. Since there are only three - // levels (0, 1, 2) in an implementation that doesn't take - // explicit embedding into account, we can build up the order on - // the fly, without following the level-based algorithm. - var order = [], m; - for (var i = 0; i < len;) { - if (countsAsLeft.test(types[i])) { - var start = i; - for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} - order.push(new BidiSpan(0, start, i)); - } else { - var pos = i, at = order.length; - for (++i; i < len && types[i] != "L"; ++i) {} - for (var j = pos; j < i;) { - if (countsAsNum.test(types[j])) { - if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); - var nstart = j; - for (++j; j < i && countsAsNum.test(types[j]); ++j) {} - order.splice(at, 0, new BidiSpan(2, nstart, j)); - pos = j; - } else ++j; - } - if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); - } - } - if (order[0].level == 1 && (m = str.match(/^\s+/))) { - order[0].from = m[0].length; - order.unshift(new BidiSpan(0, 0, m[0].length)); - } - if (lst(order).level == 1 && (m = str.match(/\s+$/))) { - lst(order).to -= m[0].length; - order.push(new BidiSpan(0, len - m[0].length, len)); - } - if (order[0].level == 2) - order.unshift(new BidiSpan(1, order[0].to, order[0].to)); - if (order[0].level != lst(order).level) - order.push(new BidiSpan(order[0].level, len, len)); - - return order; - }; - })(); - - // THE END - - CodeMirror.version = "5.17.0"; - - return CodeMirror; -}); - - - -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -// TODO actually recognize syntax of TypeScript constructs - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { -"use strict"; - -function expressionAllowed(stream, state, backUp) { - return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) || - (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) -} - -CodeMirror.defineMode("javascript", function(config, parserConfig) { - var indentUnit = config.indentUnit; - var statementIndent = parserConfig.statementIndent; - var jsonldMode = parserConfig.jsonld; - var jsonMode = parserConfig.json || jsonldMode; - var isTS = parserConfig.typescript; - var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; - - // Tokenizer - - var keywords = function(){ - function kw(type) {return {type: type, style: "keyword"};} - var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); - var operator = kw("operator"), atom = {type: "atom", style: "atom"}; - - var jsKeywords = { - "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, - "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C, - "var": kw("var"), "const": kw("var"), "let": kw("var"), - "function": kw("function"), "catch": kw("catch"), - "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), - "in": operator, "typeof": operator, "instanceof": operator, - "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, - "this": kw("this"), "class": kw("class"), "super": kw("atom"), - "yield": C, "export": kw("export"), "import": kw("import"), "extends": C, - "await": C, "async": kw("async") - }; - - // Extend the 'normal' keywords with the TypeScript language extensions - if (isTS) { - var type = {type: "variable", style: "variable-3"}; - var tsKeywords = { - // object-like things - "interface": kw("class"), - "implements": C, - "namespace": C, - "module": kw("module"), - "enum": kw("module"), - - // scope modifiers - "public": kw("modifier"), - "private": kw("modifier"), - "protected": kw("modifier"), - "abstract": kw("modifier"), - - // operators - "as": operator, - - // types - "string": type, "number": type, "boolean": type, "any": type - }; - - for (var attr in tsKeywords) { - jsKeywords[attr] = tsKeywords[attr]; - } - } - - return jsKeywords; - }(); - - var isOperatorChar = /[+\-*&%=<>!?|~^]/; - var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; - - function readRegexp(stream) { - var escaped = false, next, inSet = false; - while ((next = stream.next()) != null) { - if (!escaped) { - if (next == "/" && !inSet) return; - if (next == "[") inSet = true; - else if (inSet && next == "]") inSet = false; - } - escaped = !escaped && next == "\\"; - } - } - - // Used as scratch variables to communicate multiple values without - // consing up tons of objects. - var type, content; - function ret(tp, style, cont) { - type = tp; content = cont; - return style; - } - function tokenBase(stream, state) { - var ch = stream.next(); - if (ch == '"' || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { - return ret("number", "number"); - } else if (ch == "." && stream.match("..")) { - return ret("spread", "meta"); - } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - return ret(ch); - } else if (ch == "=" && stream.eat(">")) { - return ret("=>", "operator"); - } else if (ch == "0" && stream.eat(/x/i)) { - stream.eatWhile(/[\da-f]/i); - return ret("number", "number"); - } else if (ch == "0" && stream.eat(/o/i)) { - stream.eatWhile(/[0-7]/i); - return ret("number", "number"); - } else if (ch == "0" && stream.eat(/b/i)) { - stream.eatWhile(/[01]/i); - return ret("number", "number"); - } else if (/\d/.test(ch)) { - stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); - return ret("number", "number"); - } else if (ch == "/") { - if (stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } else if (stream.eat("/")) { - stream.skipToEnd(); - return ret("comment", "comment"); - } else if (expressionAllowed(stream, state, 1)) { - readRegexp(stream); - stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); - return ret("regexp", "string-2"); - } else { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator", stream.current()); - } - } else if (ch == "`") { - state.tokenize = tokenQuasi; - return tokenQuasi(stream, state); - } else if (ch == "#") { - stream.skipToEnd(); - return ret("error", "error"); - } else if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator", stream.current()); - } else if (wordRE.test(ch)) { - stream.eatWhile(wordRE); - var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; - return (known && state.lastType != ".") ? ret(known.type, known.style, word) : - ret("variable", "variable", word); - } - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next; - if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ - state.tokenize = tokenBase; - return ret("jsonld-keyword", "meta"); - } - while ((next = stream.next()) != null) { - if (next == quote && !escaped) break; - escaped = !escaped && next == "\\"; - } - if (!escaped) state.tokenize = tokenBase; - return ret("string", "string"); - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return ret("comment", "comment"); - } - - function tokenQuasi(stream, state) { - var escaped = false, next; - while ((next = stream.next()) != null) { - if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { - state.tokenize = tokenBase; - break; - } - escaped = !escaped && next == "\\"; - } - return ret("quasi", "string-2", stream.current()); - } - - var brackets = "([{}])"; - // This is a crude lookahead trick to try and notice that we're - // parsing the argument patterns for a fat-arrow function before we - // actually hit the arrow token. It only works if the arrow is on - // the same line as the arguments and there's no strange noise - // (comments) in between. Fallback is to only notice when we hit the - // arrow, and not declare the arguments as locals for the arrow - // body. - function findFatArrow(stream, state) { - if (state.fatArrowAt) state.fatArrowAt = null; - var arrow = stream.string.indexOf("=>", stream.start); - if (arrow < 0) return; - - var depth = 0, sawSomething = false; - for (var pos = arrow - 1; pos >= 0; --pos) { - var ch = stream.string.charAt(pos); - var bracket = brackets.indexOf(ch); - if (bracket >= 0 && bracket < 3) { - if (!depth) { ++pos; break; } - if (--depth == 0) break; - } else if (bracket >= 3 && bracket < 6) { - ++depth; - } else if (wordRE.test(ch)) { - sawSomething = true; - } else if (/["'\/]/.test(ch)) { - return; - } else if (sawSomething && !depth) { - ++pos; - break; - } - } - if (sawSomething && !depth) state.fatArrowAt = pos; - } - - // Parser - - var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; - - function JSLexical(indented, column, type, align, prev, info) { - this.indented = indented; - this.column = column; - this.type = type; - this.prev = prev; - this.info = info; - if (align != null) this.align = align; - } - - function inScope(state, varname) { - for (var v = state.localVars; v; v = v.next) - if (v.name == varname) return true; - for (var cx = state.context; cx; cx = cx.prev) { - for (var v = cx.vars; v; v = v.next) - if (v.name == varname) return true; - } - } - - function parseJS(state, style, type, content, stream) { - var cc = state.cc; - // Communicate our context to the combinators. - // (Less wasteful than consing up a hundred closures on every call.) - cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; - - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = true; - - while(true) { - var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; - if (combinator(type, content)) { - while(cc.length && cc[cc.length - 1].lex) - cc.pop()(); - if (cx.marked) return cx.marked; - if (type == "variable" && inScope(state, content)) return "variable-2"; - return style; - } - } - } - - // Combinator utils - - var cx = {state: null, column: null, marked: null, cc: null}; - function pass() { - for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); - } - function cont() { - pass.apply(null, arguments); - return true; - } - function register(varname) { - function inList(list) { - for (var v = list; v; v = v.next) - if (v.name == varname) return true; - return false; - } - var state = cx.state; - cx.marked = "def"; - if (state.context) { - if (inList(state.localVars)) return; - state.localVars = {name: varname, next: state.localVars}; - } else { - if (inList(state.globalVars)) return; - if (parserConfig.globalVars) - state.globalVars = {name: varname, next: state.globalVars}; - } - } - - // Combinators - - var defaultVars = {name: "this", next: {name: "arguments"}}; - function pushcontext() { - cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; - cx.state.localVars = defaultVars; - } - function popcontext() { - cx.state.localVars = cx.state.context.vars; - cx.state.context = cx.state.context.prev; - } - function pushlex(type, info) { - var result = function() { - var state = cx.state, indent = state.indented; - if (state.lexical.type == "stat") indent = state.lexical.indented; - else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) - indent = outer.indented; - state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); - }; - result.lex = true; - return result; - } - function poplex() { - var state = cx.state; - if (state.lexical.prev) { - if (state.lexical.type == ")") - state.indented = state.lexical.indented; - state.lexical = state.lexical.prev; - } - } - poplex.lex = true; - - function expect(wanted) { - function exp(type) { - if (type == wanted) return cont(); - else if (wanted == ";") return pass(); - else return cont(exp); - }; - return exp; - } - - function statement(type, value) { - if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); - if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); - if (type == "keyword b") return cont(pushlex("form"), statement, poplex); - if (type == "{") return cont(pushlex("}"), block, poplex); - if (type == ";") return cont(); - if (type == "if") { - if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) - cx.state.cc.pop()(); - return cont(pushlex("form"), expression, statement, poplex, maybeelse); - } - if (type == "function") return cont(functiondef); - if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); - if (type == "variable") return cont(pushlex("stat"), maybelabel); - if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), - block, poplex, poplex); - if (type == "case") return cont(expression, expect(":")); - if (type == "default") return cont(expect(":")); - if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), - statement, poplex, popcontext); - if (type == "class") return cont(pushlex("form"), className, poplex); - if (type == "export") return cont(pushlex("stat"), afterExport, poplex); - if (type == "import") return cont(pushlex("stat"), afterImport, poplex); - if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex) - if (type == "async") return cont(statement) - return pass(pushlex("stat"), expression, expect(";"), poplex); - } - function expression(type) { - return expressionInner(type, false); - } - function expressionNoComma(type) { - return expressionInner(type, true); - } - function expressionInner(type, noComma) { - if (cx.state.fatArrowAt == cx.stream.start) { - var body = noComma ? arrowBodyNoComma : arrowBody; - if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); - else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); - } - - var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; - if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); - if (type == "function") return cont(functiondef, maybeop); - if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression); - if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); - if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); - if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); - if (type == "{") return contCommasep(objprop, "}", null, maybeop); - if (type == "quasi") return pass(quasi, maybeop); - if (type == "new") return cont(maybeTarget(noComma)); - return cont(); - } - function maybeexpression(type) { - if (type.match(/[;\}\)\],]/)) return pass(); - return pass(expression); - } - function maybeexpressionNoComma(type) { - if (type.match(/[;\}\)\],]/)) return pass(); - return pass(expressionNoComma); - } - - function maybeoperatorComma(type, value) { - if (type == ",") return cont(expression); - return maybeoperatorNoComma(type, value, false); - } - function maybeoperatorNoComma(type, value, noComma) { - var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; - var expr = noComma == false ? expression : expressionNoComma; - if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); - if (type == "operator") { - if (/\+\+|--/.test(value)) return cont(me); - if (value == "?") return cont(expression, expect(":"), expr); - return cont(expr); - } - if (type == "quasi") { return pass(quasi, me); } - if (type == ";") return; - if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); - if (type == ".") return cont(property, me); - if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); - } - function quasi(type, value) { - if (type != "quasi") return pass(); - if (value.slice(value.length - 2) != "${") return cont(quasi); - return cont(expression, continueQuasi); - } - function continueQuasi(type) { - if (type == "}") { - cx.marked = "string-2"; - cx.state.tokenize = tokenQuasi; - return cont(quasi); - } - } - function arrowBody(type) { - findFatArrow(cx.stream, cx.state); - return pass(type == "{" ? statement : expression); - } - function arrowBodyNoComma(type) { - findFatArrow(cx.stream, cx.state); - return pass(type == "{" ? statement : expressionNoComma); - } - function maybeTarget(noComma) { - return function(type) { - if (type == ".") return cont(noComma ? targetNoComma : target); - else return pass(noComma ? expressionNoComma : expression); - }; - } - function target(_, value) { - if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } - } - function targetNoComma(_, value) { - if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } - } - function maybelabel(type) { - if (type == ":") return cont(poplex, statement); - return pass(maybeoperatorComma, expect(";"), poplex); - } - function property(type) { - if (type == "variable") {cx.marked = "property"; return cont();} - } - function objprop(type, value) { - if (type == "async") return cont(objprop); - if (type == "variable" || cx.style == "keyword") { - cx.marked = "property"; - if (value == "get" || value == "set") return cont(getterSetter); - return cont(afterprop); - } else if (type == "number" || type == "string") { - cx.marked = jsonldMode ? "property" : (cx.style + " property"); - return cont(afterprop); - } else if (type == "jsonld-keyword") { - return cont(afterprop); - } else if (type == "modifier") { - return cont(objprop) - } else if (type == "[") { - return cont(expression, expect("]"), afterprop); - } else if (type == "spread") { - return cont(expression); - } - } - function getterSetter(type) { - if (type != "variable") return pass(afterprop); - cx.marked = "property"; - return cont(functiondef); - } - function afterprop(type) { - if (type == ":") return cont(expressionNoComma); - if (type == "(") return pass(functiondef); - } - function commasep(what, end) { - function proceed(type, value) { - if (type == ",") { - var lex = cx.state.lexical; - if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; - return cont(function(type, value) { - if (type == end || value == end) return pass() - return pass(what) - }, proceed); - } - if (type == end || value == end) return cont(); - return cont(expect(end)); - } - return function(type, value) { - if (type == end || value == end) return cont(); - return pass(what, proceed); - }; - } - function contCommasep(what, end, info) { - for (var i = 3; i < arguments.length; i++) - cx.cc.push(arguments[i]); - return cont(pushlex(end, info), commasep(what, end), poplex); - } - function block(type) { - if (type == "}") return cont(); - return pass(statement, block); - } - function maybetype(type) { - if (isTS && type == ":") return cont(typeexpr); - } - function maybedefault(_, value) { - if (value == "=") return cont(expressionNoComma); - } - function typeexpr(type) { - if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);} - } - function afterType(type, value) { - if (value == "<") return cont(commasep(typeexpr, ">"), afterType) - if (type == "[") return cont(expect("]"), afterType) - } - function vardef() { - return pass(pattern, maybetype, maybeAssign, vardefCont); - } - function pattern(type, value) { - if (type == "modifier") return cont(pattern) - if (type == "variable") { register(value); return cont(); } - if (type == "spread") return cont(pattern); - if (type == "[") return contCommasep(pattern, "]"); - if (type == "{") return contCommasep(proppattern, "}"); - } - function proppattern(type, value) { - if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { - register(value); - return cont(maybeAssign); - } - if (type == "variable") cx.marked = "property"; - if (type == "spread") return cont(pattern); - if (type == "}") return pass(); - return cont(expect(":"), pattern, maybeAssign); - } - function maybeAssign(_type, value) { - if (value == "=") return cont(expressionNoComma); - } - function vardefCont(type) { - if (type == ",") return cont(vardef); - } - function maybeelse(type, value) { - if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); - } - function forspec(type) { - if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); - } - function forspec1(type) { - if (type == "var") return cont(vardef, expect(";"), forspec2); - if (type == ";") return cont(forspec2); - if (type == "variable") return cont(formaybeinof); - return pass(expression, expect(";"), forspec2); - } - function formaybeinof(_type, value) { - if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } - return cont(maybeoperatorComma, forspec2); - } - function forspec2(type, value) { - if (type == ";") return cont(forspec3); - if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } - return pass(expression, expect(";"), forspec3); - } - function forspec3(type) { - if (type != ")") cont(expression); - } - function functiondef(type, value) { - if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} - if (type == "variable") {register(value); return cont(functiondef);} - if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext); - } - function funarg(type) { - if (type == "spread") return cont(funarg); - return pass(pattern, maybetype, maybedefault); - } - function className(type, value) { - if (type == "variable") {register(value); return cont(classNameAfter);} - } - function classNameAfter(type, value) { - if (value == "extends") return cont(expression, classNameAfter); - if (type == "{") return cont(pushlex("}"), classBody, poplex); - } - function classBody(type, value) { - if (type == "variable" || cx.style == "keyword") { - if (value == "static") { - cx.marked = "keyword"; - return cont(classBody); - } - cx.marked = "property"; - if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); - return cont(functiondef, classBody); - } - if (value == "*") { - cx.marked = "keyword"; - return cont(classBody); - } - if (type == ";") return cont(classBody); - if (type == "}") return cont(); - } - function classGetterSetter(type) { - if (type != "variable") return pass(); - cx.marked = "property"; - return cont(); - } - function afterExport(_type, value) { - if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } - if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } - return pass(statement); - } - function afterImport(type) { - if (type == "string") return cont(); - return pass(importSpec, maybeFrom); - } - function importSpec(type, value) { - if (type == "{") return contCommasep(importSpec, "}"); - if (type == "variable") register(value); - if (value == "*") cx.marked = "keyword"; - return cont(maybeAs); - } - function maybeAs(_type, value) { - if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } - } - function maybeFrom(_type, value) { - if (value == "from") { cx.marked = "keyword"; return cont(expression); } - } - function arrayLiteral(type) { - if (type == "]") return cont(); - return pass(expressionNoComma, commasep(expressionNoComma, "]")); - } - - function isContinuedStatement(state, textAfter) { - return state.lastType == "operator" || state.lastType == "," || - isOperatorChar.test(textAfter.charAt(0)) || - /[,.]/.test(textAfter.charAt(0)); - } - - // Interface - - return { - startState: function(basecolumn) { - var state = { - tokenize: tokenBase, - lastType: "sof", - cc: [], - lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), - localVars: parserConfig.localVars, - context: parserConfig.localVars && {vars: parserConfig.localVars}, - indented: basecolumn || 0 - }; - if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") - state.globalVars = parserConfig.globalVars; - return state; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = false; - state.indented = stream.indentation(); - findFatArrow(stream, state); - } - if (state.tokenize != tokenComment && stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - if (type == "comment") return style; - state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; - return parseJS(state, style, type, content, stream); - }, - - indent: function(state, textAfter) { - if (state.tokenize == tokenComment) return CodeMirror.Pass; - if (state.tokenize != tokenBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; - // Kludge to prevent 'maybelse' from blocking lexical scope pops - if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { - var c = state.cc[i]; - if (c == poplex) lexical = lexical.prev; - else if (c != maybeelse) break; - } - if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; - if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") - lexical = lexical.prev; - var type = lexical.type, closing = firstChar == type; - - if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); - else if (type == "form" && firstChar == "{") return lexical.indented; - else if (type == "form") return lexical.indented + indentUnit; - else if (type == "stat") - return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); - else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) - return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); - else if (lexical.align) return lexical.column + (closing ? 0 : 1); - else return lexical.indented + (closing ? 0 : indentUnit); - }, - - electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, - blockCommentStart: jsonMode ? null : "/*", - blockCommentEnd: jsonMode ? null : "*/", - lineComment: jsonMode ? null : "//", - fold: "brace", - closeBrackets: "()[]{}''\"\"``", - - helperType: jsonMode ? "json" : "javascript", - jsonldMode: jsonldMode, - jsonMode: jsonMode, - - expressionAllowed: expressionAllowed, - skipExpression: function(state) { - var top = state.cc[state.cc.length - 1] - if (top == expression || top == expressionNoComma) state.cc.pop() - } - }; -}); - -CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); - -CodeMirror.defineMIME("text/javascript", "javascript"); -CodeMirror.defineMIME("text/ecmascript", "javascript"); -CodeMirror.defineMIME("application/javascript", "javascript"); -CodeMirror.defineMIME("application/x-javascript", "javascript"); -CodeMirror.defineMIME("application/ecmascript", "javascript"); -CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); -CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); -CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true}); -CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); -CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); - -}); - -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - var defaults = { - pairs: "()[]{}''\"\"", - triples: "", - explode: "[]{}" - }; - - var Pos = CodeMirror.Pos; - - CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) { - if (old && old != CodeMirror.Init) { - cm.removeKeyMap(keyMap); - cm.state.closeBrackets = null; - } - if (val) { - cm.state.closeBrackets = val; - cm.addKeyMap(keyMap); - } - }); - - function getOption(conf, name) { - if (name == "pairs" && typeof conf == "string") return conf; - if (typeof conf == "object" && conf[name] != null) return conf[name]; - return defaults[name]; - } - - var bind = defaults.pairs + "`"; - var keyMap = {Backspace: handleBackspace, Enter: handleEnter}; - for (var i = 0; i < bind.length; i++) - keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i)); - - function handler(ch) { - return function(cm) { return handleChar(cm, ch); }; - } - - function getConfig(cm) { - var deflt = cm.state.closeBrackets; - if (!deflt) return null; - var mode = cm.getModeAt(cm.getCursor()); - return mode.closeBrackets || deflt; - } - - function handleBackspace(cm) { - var conf = getConfig(cm); - if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; - - var pairs = getOption(conf, "pairs"); - var ranges = cm.listSelections(); - for (var i = 0; i < ranges.length; i++) { - if (!ranges[i].empty()) return CodeMirror.Pass; - var around = charsAround(cm, ranges[i].head); - if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass; - } - for (var i = ranges.length - 1; i >= 0; i--) { - var cur = ranges[i].head; - cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete"); - } - } - - function handleEnter(cm) { - var conf = getConfig(cm); - var explode = conf && getOption(conf, "explode"); - if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass; - - var ranges = cm.listSelections(); - for (var i = 0; i < ranges.length; i++) { - if (!ranges[i].empty()) return CodeMirror.Pass; - var around = charsAround(cm, ranges[i].head); - if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass; - } - cm.operation(function() { - cm.replaceSelection("\n\n", null); - cm.execCommand("goCharLeft"); - ranges = cm.listSelections(); - for (var i = 0; i < ranges.length; i++) { - var line = ranges[i].head.line; - cm.indentLine(line, null, true); - cm.indentLine(line + 1, null, true); - } - }); - } - - function contractSelection(sel) { - var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0; - return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)), - head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))}; - } - - function handleChar(cm, ch) { - var conf = getConfig(cm); - if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; - - var pairs = getOption(conf, "pairs"); - var pos = pairs.indexOf(ch); - if (pos == -1) return CodeMirror.Pass; - var triples = getOption(conf, "triples"); - - var identical = pairs.charAt(pos + 1) == ch; - var ranges = cm.listSelections(); - var opening = pos % 2 == 0; - - var type; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i], cur = range.head, curType; - var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1)); - if (opening && !range.empty()) { - curType = "surround"; - } else if ((identical || !opening) && next == ch) { - if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) - curType = "skipThree"; - else - curType = "skip"; - } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 && - cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch && - (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) { - curType = "addFour"; - } else if (identical) { - if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both"; - else return CodeMirror.Pass; - } else if (opening && (cm.getLine(cur.line).length == cur.ch || - isClosingBracket(next, pairs) || - /\s/.test(next))) { - curType = "both"; - } else { - return CodeMirror.Pass; - } - if (!type) type = curType; - else if (type != curType) return CodeMirror.Pass; - } - - var left = pos % 2 ? pairs.charAt(pos - 1) : ch; - var right = pos % 2 ? ch : pairs.charAt(pos + 1); - cm.operation(function() { - if (type == "skip") { - cm.execCommand("goCharRight"); - } else if (type == "skipThree") { - for (var i = 0; i < 3; i++) - cm.execCommand("goCharRight"); - } else if (type == "surround") { - var sels = cm.getSelections(); - for (var i = 0; i < sels.length; i++) - sels[i] = left + sels[i] + right; - cm.replaceSelections(sels, "around"); - sels = cm.listSelections().slice(); - for (var i = 0; i < sels.length; i++) - sels[i] = contractSelection(sels[i]); - cm.setSelections(sels); - } else if (type == "both") { - cm.replaceSelection(left + right, null); - cm.triggerElectric(left + right); - cm.execCommand("goCharLeft"); - } else if (type == "addFour") { - cm.replaceSelection(left + left + left + left, "before"); - cm.execCommand("goCharRight"); - } - }); - } - - function isClosingBracket(ch, pairs) { - var pos = pairs.lastIndexOf(ch); - return pos > -1 && pos % 2 == 1; - } - - function charsAround(cm, pos) { - var str = cm.getRange(Pos(pos.line, pos.ch - 1), - Pos(pos.line, pos.ch + 1)); - return str.length == 2 ? str : null; - } - - // Project the token type that will exists after the given char is - // typed, and use it to determine whether it would cause the start - // of a string token. - function enteringString(cm, pos, ch) { - var line = cm.getLine(pos.line); - var token = cm.getTokenAt(pos); - if (/\bstring2?\b/.test(token.type)) return false; - var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4); - stream.pos = stream.start = token.start; - for (;;) { - var type1 = cm.getMode().token(stream, token.state); - if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1); - stream.start = stream.pos; - } - } -}); +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// This is CodeMirror (http://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + module.exports = mod(); + else if (typeof define == "function" && define.amd) // AMD + return define([], mod); + else // Plain browser env + (this || window).CodeMirror = mod(); +})(function() { + "use strict"; + + // BROWSER SNIFFING + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + var userAgent = navigator.userAgent; + var platform = navigator.platform; + + var gecko = /gecko\/\d/i.test(userAgent); + var ie_upto10 = /MSIE \d/.test(userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); + var ie = ie_upto10 || ie_11up; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); + var webkit = /WebKit\//.test(userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); + var chrome = /Chrome\//.test(userAgent); + var presto = /Opera\//.test(userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); + var phantom = /PhantomJS/.test(userAgent); + + var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); + var mac = ios || /Mac/.test(platform); + var chromeOS = /\bCrOS\b/.test(userAgent); + var windows = /win/i.test(platform); + + var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) presto_version = Number(presto_version[1]); + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && ie_version >= 9); + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + // EDITOR CONSTRUCTOR + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); + + this.options = options = options ? copyObj(options) : {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + setGuttersForLineNumbers(options); + + var doc = options.value; + if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator); + this.doc = doc; + + var input = new CodeMirror.inputStyles[options.inputStyle](this); + var display = this.display = new Display(place, doc, input); + display.wrapper.CodeMirror = this; + updateGutters(this); + themeChanged(this); + if (options.lineWrapping) + this.display.wrapper.className += " CodeMirror-wrap"; + if (options.autofocus && !mobile) display.input.focus(); + initScrollbars(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll + selectingText: false, + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + }; + + var cm = this; + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20); + + registerEventHandlers(this); + ensureGlobalHandlers(); + + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); + + if ((options.autofocus && !mobile) || cm.hasFocus()) + setTimeout(bind(onFocus, this), 20); + else + onBlur(this); + + for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) + optionHandlers[opt](this, options[opt], Init); + maybeUpdateLineNumberWidth(this); + if (options.finishInit) options.finishInit(this); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); + endOperation(this); + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + display.lineDiv.style.textRendering = "auto"; + } + + // DISPLAY CONSTRUCTOR + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc, input) { + var d = this; + this.input = input; + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.scrollbarFiller.setAttribute("cm-not-content", "true"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + d.gutterFiller.setAttribute("cm-not-content", "true"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = elt("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + d.sizerWidth = null; + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (!webkit && !(gecko && mobile)) d.scroller.draggable = true; + + if (place) { + if (place.appendChild) place.appendChild(d.wrapper); + else place(d.wrapper); + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + d.reportedViewFrom = d.reportedViewTo = doc.first; + // Information about the rendered lines. + d.view = []; + d.renderedView = null; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastWrapHeight = d.lastWrapWidth = 0; + d.updateLineNumbers = null; + + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; + + d.activeTouch = null; + + input.init(d); + } + + // STATE UPDATES + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function(line) { + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + }); + cm.doc.frontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) regChange(cm); + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + cm.display.sizerWidth = null; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function(){updateScrollbars(cm);}, 100); + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function(line) { + if (lineIsHidden(cm.doc, line)) return 0; + + var widgetsHeight = 0; + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; + } + + if (wrapping) + return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; + else + return widgetsHeight + th; + }; + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function(line) { + var estHeight = est(line); + if (estHeight != line.height) updateLineHeight(line, estHeight); + }); + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + for (var i = 0; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + updateGutterSpace(cm); + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; + cm.display.sizer.style.marginLeft = width + "px"; + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) return 0; + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found = merged.find(0, true); + len -= cur.text.length - found.from.ch; + cur = found.to.line; + len += cur.text.length - found.to.ch; + } + return len; + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function(line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); + } + } + + // SCROLLBARS + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth; + var docH = Math.round(cm.doc.height + paddingVert(cm.display)); + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + }; + } + + function NativeScrollbars(place, scroll, cm) { + this.cm = cm; + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + place(vert); place(horiz); + + on(vert, "scroll", function() { + if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); + }); + on(horiz, "scroll", function() { + if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); + }); + + this.checkedZeroWidth = false; + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; + } + + NativeScrollbars.prototype = copyObj({ + update: function(measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1; + var needsV = measure.scrollHeight > measure.clientHeight + 1; + var sWidth = measure.nativeBarWidth; + + if (needsV) { + this.vert.style.display = "block"; + this.vert.style.bottom = needsH ? sWidth + "px" : "0"; + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; + } else { + this.vert.style.display = ""; + this.vert.firstChild.style.height = "0"; + } + + if (needsH) { + this.horiz.style.display = "block"; + this.horiz.style.right = needsV ? sWidth + "px" : "0"; + this.horiz.style.left = measure.barLeft + "px"; + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); + this.horiz.firstChild.style.width = + (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; + } else { + this.horiz.style.display = ""; + this.horiz.firstChild.style.width = "0"; + } + + if (!this.checkedZeroWidth && measure.clientHeight > 0) { + if (sWidth == 0) this.zeroWidthHack(); + this.checkedZeroWidth = true; + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; + }, + setScrollLeft: function(pos) { + if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; + if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz); + }, + setScrollTop: function(pos) { + if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; + if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert); + }, + zeroWidthHack: function() { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + this.horiz.style.height = this.vert.style.width = w; + this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; + this.disableHoriz = new Delayed; + this.disableVert = new Delayed; + }, + enableZeroWidthBar: function(bar, delay) { + bar.style.pointerEvents = "auto"; + function maybeDisable() { + // To find out whether the scrollbar is still visible, we + // check whether the element under the pixel in the bottom + // left corner of the scrollbar box is the scrollbar box + // itself (when the bar is still visible) or its filler child + // (when the bar is hidden). If it is still visible, we keep + // it enabled, if it's hidden, we disable pointer events. + var box = bar.getBoundingClientRect(); + var elt = document.elementFromPoint(box.left + 1, box.bottom - 1); + if (elt != bar) bar.style.pointerEvents = "none"; + else delay.set(1000, maybeDisable); + } + delay.set(1000, maybeDisable); + }, + clear: function() { + var parent = this.horiz.parentNode; + parent.removeChild(this.horiz); + parent.removeChild(this.vert); + } + }, NativeScrollbars.prototype); + + function NullScrollbars() {} + + NullScrollbars.prototype = copyObj({ + update: function() { return {bottom: 0, right: 0}; }, + setScrollLeft: function() {}, + setScrollTop: function() {}, + clear: function() {} + }, NullScrollbars.prototype); + + CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; + + function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear(); + if (cm.display.scrollbars.addClass) + rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function() { + if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0); + }); + node.setAttribute("cm-not-content", "true"); + }, function(pos, axis) { + if (axis == "horizontal") setScrollLeft(cm, pos); + else setScrollTop(cm, pos); + }, cm); + if (cm.display.scrollbars.addClass) + addClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + function updateScrollbars(cm, measure) { + if (!measure) measure = measureForScrollbars(cm); + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + updateHeightsInViewport(cm); + updateScrollbarsInner(cm, measureForScrollbars(cm)); + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; + } + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbarsInner(cm, measure) { + var d = cm.display; + var sizes = d.scrollbars.update(measure); + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = sizes.bottom + "px"; + d.scrollbarFiller.style.width = sizes.right + "px"; + } else d.scrollbarFiller.style.display = ""; + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sizes.bottom + "px"; + d.gutterFiller.style.width = measure.gutterWidth + "px"; + } else d.gutterFiller.style.display = ""; + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewport may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) { + from = ensureFrom; + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); + to = ensureTo; + } + } + return {from: from, to: Math.max(to, from + 1)}; + } + + // LINE NUMBERS + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) if (!view[i].hidden) { + if (cm.options.fixedGutter && view[i].gutter) + view[i].gutter.style.left = left; + var align = view[i].alignable; + if (align) for (var j = 0; j < align.length; j++) + align[j].style.left = left; + } + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px"; + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) return false; + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm); + return true; + } + return false; + } + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)); + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; + } + + // DISPLAY DRAWING + + function DisplayUpdate(cm, viewport, force) { + var display = cm.display; + + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.wrapperWidth = display.wrapper.clientWidth; + this.oldDisplayWidth = displayWidth(cm); + this.force = force; + this.dims = getDimensions(cm); + this.events = []; + } + + DisplayUpdate.prototype.signal = function(emitter, type) { + if (hasHandler(emitter, type)) + this.events.push(arguments); + }; + DisplayUpdate.prototype.finish = function() { + for (var i = 0; i < this.events.length; i++) + signal.apply(null, this.events[i]); + }; + + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; + } + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc; + + if (update.editorIsHidden) { + resetView(cm); + return false; + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + return false; + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm); + update.dims = getDimensions(cm); + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false; + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var focused = activeElt(); + if (toUpdate > 4) display.lineDiv.style.display = "none"; + patchDisplay(cm, display.updateLineNumbers, update.dims); + if (toUpdate > 4) display.lineDiv.style.display = ""; + display.renderedView = display.view; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + display.gutters.style.height = display.sizer.style.minHeight = 0; + + if (different) { + display.lastWrapHeight = update.wrapperHeight; + display.lastWrapWidth = update.wrapperWidth; + startWorker(cm, 400); + } + + display.updateLineNumbers = null; + + return true; + } + + function postUpdateDisplay(cm, update) { + var viewport = update.viewport; + + for (var first = true;; first = false) { + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + break; + } + if (!updateDisplayIfNeeded(cm, update)) break; + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); + } + + update.signal(cm, "update", cm); + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; + } + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); + update.finish(); + } + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px"; + cm.display.heightForcer.style.top = measure.docHeight + "px"; + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"; + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height; + if (cur.hidden) continue; + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + } + var diff = cur.line.height - height; + if (height < 2) height = textHeight(display); + if (diff > .001 || diff < -.001) { + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) for (var j = 0; j < cur.rest.length; j++) + updateWidgetHeight(cur.rest[j]); + } + } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) + line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight; + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; + width[cm.options.gutters[i]] = n.clientWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth}; + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + node.style.display = "none"; + else + node.parentNode.removeChild(node); + return next; + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) { + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) cur = rm(cur); + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) cur = rm(cur); + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") updateLineText(cm, lineView); + else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); + else if (type == "class") updateLineClasses(lineView); + else if (type == "widget") updateLineWidgets(cm, lineView, dims); + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + lineView.text.parentNode.replaceChild(lineView.node, lineView.text); + lineView.node.appendChild(lineView.text); + if (ie && ie_version < 8) lineView.node.style.zIndex = 2; + } + return lineView.node; + } + + function updateLineBackground(lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) cls += " CodeMirror-linebackground"; + if (lineView.background) { + if (cls) lineView.background.className = cls; + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built; + } + return buildLineContent(cm, lineView); + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) lineView.node = built.pre; + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(lineView) { + updateLineBackground(lineView); + if (lineView.line.wrapClass) + ensureLineWrapped(lineView).className = lineView.line.wrapClass; + else if (lineView.node != lineView.text) + lineView.node.className = ""; + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + if (lineView.gutterBackground) { + lineView.node.removeChild(lineView.gutterBackground); + lineView.gutterBackground = null; + } + if (lineView.line.gutterClass) { + var wrap = ensureLineWrapped(lineView); + lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, + "left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + + "px; width: " + dims.gutterTotalWidth + "px"); + wrap.insertBefore(lineView.gutterBackground, lineView.text); + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " + + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"); + cm.display.input.setUneditable(gutterWrap); + wrap.insertBefore(gutterWrap, lineView.text); + if (lineView.line.gutterClass) + gutterWrap.className += " " + lineView.line.gutterClass; + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + + cm.display.lineNumInnerWidth + "px")); + if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); + } + } + } + + function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) lineView.alignable = null; + for (var node = lineView.node.firstChild, next; node; node = next) { + var next = node.nextSibling; + if (node.className == "CodeMirror-linewidget") + lineView.node.removeChild(node); + } + insertLineWidgets(cm, lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) lineView.bgClass = built.bgClass; + if (built.textClass) lineView.textClass = built.textClass; + + updateLineClasses(lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(cm, lineView, dims); + return lineView.node; + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); + } + + function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) return; + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true"); + positionLineWidget(widget, node, lineView, dims); + cm.display.input.setUneditable(node); + if (allowAbove && widget.above) + wrap.insertBefore(node, lineView.gutter || lineView.text); + else + wrap.appendChild(node); + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; + } + } + + // POSITION OBJECT + + // A Pos instance represents a position within the text. + var Pos = CodeMirror.Pos = function(line, ch) { + if (!(this instanceof Pos)) return new Pos(line, ch); + this.line = line; this.ch = ch; + }; + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; }; + + function copyPos(x) {return Pos(x.line, x.ch);} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } + + // INPUT HANDLING + + function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } + } + + // This will be set to a {lineWise: bool, text: [string]} object, so + // that, when pasting, we know what kind of selections the copied + // text was made out of. + var lastCopied = null; + + function applyTextInput(cm, inserted, deleted, sel, origin) { + var doc = cm.doc; + cm.display.shift = false; + if (!sel) sel = doc.sel; + + var paste = cm.state.pasteIncoming || origin == "paste"; + var textLines = doc.splitLines(inserted), multiPaste = null + // When pasing N lines into N selections, insert one line per selection + if (paste && sel.ranges.length > 1) { + if (lastCopied && lastCopied.text.join("\n") == inserted) { + if (sel.ranges.length % lastCopied.text.length == 0) { + multiPaste = []; + for (var i = 0; i < lastCopied.text.length; i++) + multiPaste.push(doc.splitLines(lastCopied.text[i])); + } + } else if (textLines.length == sel.ranges.length) { + multiPaste = map(textLines, function(l) { return [l]; }); + } + } + + // Normal behavior is to insert the new text into every selection + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i]; + var from = range.from(), to = range.to(); + if (range.empty()) { + if (deleted && deleted > 0) // Handle deletion + from = Pos(from.line, from.ch - deleted); + else if (cm.state.overwrite && !paste) // Handle overwrite + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); + else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) + from = to = Pos(from.line, 0) + } + var updateInput = cm.curOp.updateInput; + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, + origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + } + if (inserted && !paste) + triggerElectric(cm, inserted); + + ensureCursorVisible(cm); + cm.curOp.updateInput = updateInput; + cm.curOp.typing = true; + cm.state.pasteIncoming = cm.state.cutIncoming = false; + } + + function handlePaste(e, cm) { + var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); + if (pasted) { + e.preventDefault(); + if (!cm.isReadOnly() && !cm.options.disableInput) + runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); }); + return true; + } + } + + function triggerElectric(cm, inserted) { + // When an 'electric' character is inserted, immediately trigger a reindent + if (!cm.options.electricChars || !cm.options.smartIndent) return; + var sel = cm.doc.sel; + + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i]; + if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue; + var mode = cm.getModeAt(range.head); + var indented = false; + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indented = indentLine(cm, range.head.line, "smart"); + break; + } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) + indented = indentLine(cm, range.head.line, "smart"); + } + if (indented) signalLater(cm, "electricInput", cm, range.head.line); + } + } + + function copyableRanges(cm) { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + return {text: text, ranges: ranges}; + } + + function disableBrowserMagic(field) { + field.setAttribute("autocorrect", "off"); + field.setAttribute("autocapitalize", "off"); + field.setAttribute("spellcheck", "false"); + } + + // TEXTAREA INPUT STYLE + + function TextareaInput(cm) { + this.cm = cm; + // See input.poll and input.reset + this.prevInput = ""; + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false; + // Self-resetting timeout for the poller + this.polling = new Delayed(); + // Tracks when input.reset has punted to just putting a short + // string into the textarea instead of the full selection. + this.inaccurateSelection = false; + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false; + this.composing = null; + }; + + function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none"); + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) te.style.width = "1000px"; + else te.setAttribute("wrap", "off"); + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) te.style.border = "1px solid black"; + disableBrowserMagic(te); + return div; + } + + TextareaInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = this.cm; + + // Wraps and hides input textarea + var div = this.wrapper = hiddenTextarea(); + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + var te = this.textarea = div.firstChild; + display.wrapper.insertBefore(div, display.wrapper.firstChild); + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) te.style.width = "0px"; + + on(te, "input", function() { + if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null; + input.poll(); + }); + + on(te, "paste", function(e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return + + cm.state.pasteIncoming = true; + input.fastPoll(); + }); + + function prepareCopyCut(e) { + if (signalDOMEvent(cm, e)) return + if (cm.somethingSelected()) { + lastCopied = {lineWise: false, text: cm.getSelections()}; + if (input.inaccurateSelection) { + input.prevInput = ""; + input.inaccurateSelection = false; + te.value = lastCopied.text.join("\n"); + selectInput(te); + } + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = {lineWise: true, text: ranges.text}; + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll); + } else { + input.prevInput = ""; + te.value = ranges.text.join("\n"); + selectInput(te); + } + } + if (e.type == "cut") cm.state.cutIncoming = true; + } + on(te, "cut", prepareCopyCut); + on(te, "copy", prepareCopyCut); + + on(display.scroller, "paste", function(e) { + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return; + cm.state.pasteIncoming = true; + input.focus(); + }); + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function(e) { + if (!eventInWidget(display, e)) e_preventDefault(e); + }); + + on(te, "compositionstart", function() { + var start = cm.getCursor("from"); + if (input.composing) input.composing.range.clear() + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + }; + }); + on(te, "compositionend", function() { + if (input.composing) { + input.poll(); + input.composing.range.clear(); + input.composing = null; + } + }); + }, + + prepareSelection: function() { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc; + var result = prepareSelection(cm); + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + } + + return result; + }, + + showSelection: function(drawn) { + var cm = this.cm, display = cm.display; + removeChildrenAndAdd(display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px"; + this.wrapper.style.left = drawn.teLeft + "px"; + } + }, + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + reset: function(typing) { + if (this.contextMenuPending) return; + var minimal, selected, cm = this.cm, doc = cm.doc; + if (cm.somethingSelected()) { + this.prevInput = ""; + var range = doc.sel.primary(); + minimal = hasCopyEvent && + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); + var content = minimal ? "-" : selected || cm.getSelection(); + this.textarea.value = content; + if (cm.state.focused) selectInput(this.textarea); + if (ie && ie_version >= 9) this.hasSelection = content; + } else if (!typing) { + this.prevInput = this.textarea.value = ""; + if (ie && ie_version >= 9) this.hasSelection = null; + } + this.inaccurateSelection = minimal; + }, + + getField: function() { return this.textarea; }, + + supportsTouch: function() { return false; }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus(); } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + }, + + blur: function() { this.textarea.blur(); }, + + resetPosition: function() { + this.wrapper.style.top = this.wrapper.style.left = 0; + }, + + receivedFocus: function() { this.slowPoll(); }, + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + slowPoll: function() { + var input = this; + if (input.pollingFast) return; + input.polling.set(this.cm.options.pollInterval, function() { + input.poll(); + if (input.cm.state.focused) input.slowPoll(); + }); + }, + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + fastPoll: function() { + var missed = false, input = this; + input.pollingFast = true; + function p() { + var changed = input.poll(); + if (!changed && !missed) {missed = true; input.polling.set(60, p);} + else {input.pollingFast = false; input.slowPoll();} + } + input.polling.set(20, p); + }, + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + poll: function() { + var cm = this.cm, input = this.textarea, prevInput = this.prevInput; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (this.contextMenuPending || !cm.state.focused || + (hasSelection(input) && !prevInput && !this.composing) || + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) + return false; + + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) return false; + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset(); + return false; + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0); + if (first == 0x200b && !prevInput) prevInput = "\u200b"; + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; + + var self = this; + runInOp(cm, function() { + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, self.composing ? "*compose" : null); + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = ""; + else self.prevInput = text; + + if (self.composing) { + self.composing.range.clear(); + self.composing.range = cm.markText(self.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}); + } + }); + return true; + }, + + ensurePolled: function() { + if (this.pollingFast && this.poll()) this.pollingFast = false; + }, + + onKeyPress: function() { + if (ie && ie_version >= 9) this.hasSelection = null; + this.fastPoll(); + }, + + onContextMenu: function(e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea; + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) return; // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); + + var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText; + input.wrapper.style.cssText = "position: absolute" + var wrapperBox = input.wrapper.getBoundingClientRect() + te.style.cssText = "position: absolute; width: 30px; height: 30px; top: " + (e.clientY - wrapperBox.top - 5) + + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px; z-index: 1000; background: " + + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) + display.input.focus(); + if (webkit) window.scrollTo(null, oldScrollY); + display.input.reset(); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) te.value = input.prevInput = " "; + input.contextMenuPending = true; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = "\u200b" + (selected ? te.value : ""); + te.value = "\u21da"; // Used to catch context-menu undo + te.value = extval; + input.prevInput = selected ? "" : "\u200b"; + te.selectionStart = 1; te.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; + } + } + function rehide() { + input.contextMenuPending = false; + input.wrapper.style.cssText = oldWrapperCSS + te.style.cssText = oldCSS; + if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); + var i = 0, poll = function() { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") + operation(cm, commands.selectAll)(cm); + else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); + else display.input.reset(); + }; + display.detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && ie_version >= 9) prepareSelectAllHack(); + if (captureRightClick) { + e_stop(e); + var mouseup = function() { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + }, + + readOnlyChanged: function(val) { + if (!val) this.reset(); + }, + + setUneditable: nothing, + + needsContentAttribute: false + }, TextareaInput.prototype); + + // CONTENTEDITABLE INPUT STYLE + + function ContentEditableInput(cm) { + this.cm = cm; + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; + this.polling = new Delayed(); + this.gracePeriod = false; + } + + ContentEditableInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = input.cm; + var div = input.div = display.lineDiv; + disableBrowserMagic(div); + + on(div, "paste", function(e) { + if (!signalDOMEvent(cm, e)) handlePaste(e, cm); + }) + + on(div, "compositionstart", function(e) { + var data = e.data; + input.composing = {sel: cm.doc.sel, data: data, startData: data}; + if (!data) return; + var prim = cm.doc.sel.primary(); + var line = cm.getLine(prim.head.line); + var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length)); + if (found > -1 && found <= prim.head.ch) + input.composing.sel = simpleSelection(Pos(prim.head.line, found), + Pos(prim.head.line, found + data.length)); + }); + on(div, "compositionupdate", function(e) { + input.composing.data = e.data; + }); + on(div, "compositionend", function(e) { + var ours = input.composing; + if (!ours) return; + if (e.data != ours.startData && !/\u200b/.test(e.data)) + ours.data = e.data; + // Need a small delay to prevent other code (input event, + // selection polling) from doing damage when fired right after + // compositionend. + setTimeout(function() { + if (!ours.handled) + input.applyComposition(ours); + if (input.composing == ours) + input.composing = null; + }, 50); + }); + + on(div, "touchstart", function() { + input.forceCompositionEnd(); + }); + + on(div, "input", function() { + if (input.composing) return; + if (cm.isReadOnly() || !input.pollContent()) + runInOp(input.cm, function() {regChange(cm);}); + }); + + function onCopyCut(e) { + if (signalDOMEvent(cm, e)) return + if (cm.somethingSelected()) { + lastCopied = {lineWise: false, text: cm.getSelections()}; + if (e.type == "cut") cm.replaceSelection("", null, "cut"); + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = {lineWise: true, text: ranges.text}; + if (e.type == "cut") { + cm.operation(function() { + cm.setSelections(ranges.ranges, 0, sel_dontScroll); + cm.replaceSelection("", null, "cut"); + }); + } + } + // iOS exposes the clipboard API, but seems to discard content inserted into it + if (e.clipboardData && !ios) { + e.preventDefault(); + e.clipboardData.clearData(); + e.clipboardData.setData("text/plain", lastCopied.text.join("\n")); + } else { + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild; + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); + te.value = lastCopied.text.join("\n"); + var hadFocus = document.activeElement; + selectInput(te); + setTimeout(function() { + cm.display.lineSpace.removeChild(kludge); + hadFocus.focus(); + }, 50); + } + } + on(div, "copy", onCopyCut); + on(div, "cut", onCopyCut); + }, + + prepareSelection: function() { + var result = prepareSelection(this.cm, false); + result.focus = this.cm.state.focused; + return result; + }, + + showSelection: function(info, takeFocus) { + if (!info || !this.cm.display.view.length) return; + if (info.focus || takeFocus) this.showPrimarySelection(); + this.showMultipleSelections(info); + }, + + showPrimarySelection: function() { + var sel = window.getSelection(), prim = this.cm.doc.sel.primary(); + var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset); + var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset); + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && + cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) + return; + + var start = posToDOM(this.cm, prim.from()); + var end = posToDOM(this.cm, prim.to()); + if (!start && !end) return; + + var view = this.cm.display.view; + var old = sel.rangeCount && sel.getRangeAt(0); + if (!start) { + start = {node: view[0].measure.map[2], offset: 0}; + } else if (!end) { // FIXME dangerously hacky + var measure = view[view.length - 1].measure; + var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}; + } + + try { var rng = range(start.node, start.offset, end.offset, end.node); } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + if (!gecko && this.cm.state.focused) { + sel.collapse(start.node, start.offset); + if (!rng.collapsed) sel.addRange(rng); + } else { + sel.removeAllRanges(); + sel.addRange(rng); + } + if (old && sel.anchorNode == null) sel.addRange(old); + else if (gecko) this.startGracePeriod(); + } + this.rememberSelection(); + }, + + startGracePeriod: function() { + var input = this; + clearTimeout(this.gracePeriod); + this.gracePeriod = setTimeout(function() { + input.gracePeriod = false; + if (input.selectionChanged()) + input.cm.operation(function() { input.cm.curOp.selectionChanged = true; }); + }, 20); + }, + + showMultipleSelections: function(info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); + }, + + rememberSelection: function() { + var sel = window.getSelection(); + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; + }, + + selectionInEditor: function() { + var sel = window.getSelection(); + if (!sel.rangeCount) return false; + var node = sel.getRangeAt(0).commonAncestorContainer; + return contains(this.div, node); + }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor") this.div.focus(); + }, + blur: function() { this.div.blur(); }, + getField: function() { return this.div; }, + + supportsTouch: function() { return true; }, + + receivedFocus: function() { + var input = this; + if (this.selectionInEditor()) + this.pollSelection(); + else + runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; }); + + function poll() { + if (input.cm.state.focused) { + input.pollSelection(); + input.polling.set(input.cm.options.pollInterval, poll); + } + } + this.polling.set(this.cm.options.pollInterval, poll); + }, + + selectionChanged: function() { + var sel = window.getSelection(); + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset; + }, + + pollSelection: function() { + if (!this.composing && !this.gracePeriod && this.selectionChanged()) { + var sel = window.getSelection(), cm = this.cm; + this.rememberSelection(); + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var head = domToPos(cm, sel.focusNode, sel.focusOffset); + if (anchor && head) runInOp(cm, function() { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); + if (anchor.bad || head.bad) cm.curOp.selectionChanged = true; + }); + } + }, + + pollContent: function() { + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); + var from = sel.from(), to = sel.to(); + if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false; + + var fromIndex; + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + var fromLine = lineNo(display.view[0].line); + var fromNode = display.view[0].node; + } else { + var fromLine = lineNo(display.view[fromIndex].line); + var fromNode = display.view[fromIndex - 1].node.nextSibling; + } + var toIndex = findViewIndex(cm, to.line); + if (toIndex == display.view.length - 1) { + var toLine = display.viewTo - 1; + var toNode = display.lineDiv.lastChild; + } else { + var toLine = lineNo(display.view[toIndex + 1].line) - 1; + var toNode = display.view[toIndex + 1].node.previousSibling; + } + + var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } + else break; + } + + var cutFront = 0, cutEnd = 0; + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + ++cutFront; + var newBot = lst(newText), oldBot = lst(oldText); + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)); + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + ++cutEnd; + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd); + newText[0] = newText[0].slice(cutFront); + + var chFrom = Pos(fromLine, cutFront); + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input"); + return true; + } + }, + + ensurePolled: function() { + this.forceCompositionEnd(); + }, + reset: function() { + this.forceCompositionEnd(); + }, + forceCompositionEnd: function() { + if (!this.composing || this.composing.handled) return; + this.applyComposition(this.composing); + this.composing.handled = true; + this.div.blur(); + this.div.focus(); + }, + applyComposition: function(composing) { + if (this.cm.isReadOnly()) + operation(this.cm, regChange)(this.cm) + else if (composing.data && composing.data != composing.startData) + operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel); + }, + + setUneditable: function(node) { + node.contentEditable = "false" + }, + + onKeyPress: function(e) { + e.preventDefault(); + if (!this.cm.isReadOnly()) + operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); + }, + + readOnlyChanged: function(val) { + this.div.contentEditable = String(val != "nocursor") + }, + + onContextMenu: nothing, + resetPosition: nothing, + + needsContentAttribute: true + }, ContentEditableInput.prototype); + + function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line); + if (!view || view.hidden) return null; + var line = getLine(cm.doc, pos.line); + var info = mapFromLineView(view, line, pos.line); + + var order = getOrder(line), side = "left"; + if (order) { + var partPos = getBidiPartAt(order, pos.ch); + side = partPos % 2 ? "right" : "left"; + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); + result.offset = result.collapse == "right" ? result.end : result.start; + return result; + } + + function badPos(pos, bad) { if (bad) pos.bad = true; return pos; } + + function domToPos(cm, node, offset) { + var lineNode; + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset]; + if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true); + node = null; offset = 0; + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) return null; + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break; + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i]; + if (lineView.node == lineNode) + return locateNodeInLineView(lineView, node, offset); + } + } + + function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false; + if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true); + if (node == wrapper) { + bad = true; + node = wrapper.childNodes[offset]; + offset = 0; + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line; + return badPos(Pos(lineNo(line), line.text.length), bad); + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node; + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild; + if (offset) offset = textNode.nodeValue.length; + } + while (topNode.parentNode != wrapper) topNode = topNode.parentNode; + var measure = lineView.measure, maps = measure.maps; + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map = i < 0 ? measure.map : maps[i]; + for (var j = 0; j < map.length; j += 3) { + var curNode = map[j + 2]; + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); + var ch = map[j] + offset; + if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)]; + return Pos(line, ch); + } + } + } + } + var found = find(textNode, topNode, offset); + if (found) return badPos(found, bad); + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0); + if (found) + return badPos(Pos(found.line, found.ch - dist), bad); + else + dist += after.textContent.length; + } + for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1); + if (found) + return badPos(Pos(found.line, found.ch + dist), bad); + else + dist += after.textContent.length; + } + } + + function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false, lineSep = cm.doc.lineSeparator(); + function recognizeMarker(id) { return function(marker) { return marker.id == id; }; } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text"); + if (cmText != null) { + if (cmText == "") cmText = node.textContent.replace(/\u200b/g, ""); + text += cmText; + return; + } + var markerID = node.getAttribute("cm-marker"), range; + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); + if (found.length && (range = found[0].find())) + text += getBetween(cm.doc, range.from, range.to).join(lineSep); + return; + } + if (node.getAttribute("contenteditable") == "false") return; + for (var i = 0; i < node.childNodes.length; i++) + walk(node.childNodes[i]); + if (/^(pre|div|p)$/i.test(node.nodeName)) + closing = true; + } else if (node.nodeType == 3) { + var val = node.nodeValue; + if (!val) return; + if (closing) { + text += lineSep; + closing = false; + } + text += val; + } + } + for (;;) { + walk(from); + if (from == to) break; + from = from.nextSibling; + } + return text; + } + + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; + + // SELECTION / CURSOR + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + function Selection(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + } + + Selection.prototype = { + primary: function() { return this.ranges[this.primIndex]; }, + equals: function(other) { + if (other == this) return true; + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false; + for (var i = 0; i < this.ranges.length; i++) { + var here = this.ranges[i], there = other.ranges[i]; + if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false; + } + return true; + }, + deepCopy: function() { + for (var out = [], i = 0; i < this.ranges.length; i++) + out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); + return new Selection(out, this.primIndex); + }, + somethingSelected: function() { + for (var i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].empty()) return true; + return false; + }, + contains: function(pos, end) { + if (!end) end = pos; + for (var i = 0; i < this.ranges.length; i++) { + var range = this.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + return i; + } + return -1; + } + }; + + function Range(anchor, head) { + this.anchor = anchor; this.head = head; + } + + Range.prototype = { + from: function() { return minPos(this.anchor, this.head); }, + to: function() { return maxPos(this.anchor, this.head); }, + empty: function() { + return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; + } + }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(ranges, primIndex) { + var prim = ranges[primIndex]; + ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + if (cmp(prev.to(), cur.from()) >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) --primIndex; + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex); + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0); + } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} + function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0); + var last = doc.first + doc.size - 1; + if (pos.line > last) return Pos(last, getLine(doc, last).text.length); + return clipToLen(pos, getLine(doc, pos.line).text.length); + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) return Pos(pos.line, linelen); + else if (ch < 0) return Pos(pos.line, 0); + else return pos; + } + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} + function clipPosArray(doc, array) { + for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]); + return out; + } + + // SELECTION UPDATES + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(doc, range, head, other) { + if (doc.cm && doc.cm.display.shift || doc.extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head); + } else { + return new Range(other || head, head); + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options) { + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + for (var out = [], i = 0; i < doc.sel.ranges.length; i++) + out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); + var newSel = normalizeSelection(out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel, options) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); + }, + origin: options && options.origin + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); + if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1); + else return sel; + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + sel = filterSelectionChange(doc, sel, options); + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm) + ensureCursorVisible(doc.cm); + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) return; + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) out = sel.ranges.slice(0, i); + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel; + } + + function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { + var line = getLine(doc, pos.line); + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} + } + } + if (!m.atomic) continue; + + if (oldPos) { + var near = m.find(dir < 0 ? 1 : -1), diff; + if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) + near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) + return skipAtomicInner(doc, near, pos, dir, mayClear); + } + + var far = m.find(dir < 0 ? -1 : 1); + if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) + far = movePos(doc, far, dir, far.line == pos.line ? line : null); + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null; + } + } + return pos; + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, oldPos, bias, mayClear) { + var dir = bias || 1; + var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); + if (!found) { + doc.cantEdit = true; + return Pos(doc.first, 0); + } + return found; + } + + function movePos(doc, pos, dir, line) { + if (dir < 0 && pos.ch == 0) { + if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)); + else return null; + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { + if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0); + else return null; + } else { + return new Pos(pos.line, pos.ch + dir); + } + } + + // SELECTION DRAWING + + function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()); + } + + function prepareSelection(cm, primary) { + var doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); + + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (primary === false && i == doc.sel.primIndex) continue; + var range = doc.sel.ranges[i]; + if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue; + var collapsed = range.empty(); + if (collapsed || cm.options.showCursorWhenSelecting) + drawSelectionCursor(cm, range.head, curFragment); + if (!collapsed) + drawSelectionRange(cm, range, selFragment); + } + return result; + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, head, output) { + var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left; + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; + + function add(left, top, width, bottom) { + if (top < 0) top = 0; + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + + "px; height: " + (bottom - top) + "px")); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); + } + + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = leftSide; + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom); + left = leftSide; + if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); + } + if (toArg == null && to == lineLen) right = rightSide; + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; + if (left < leftSide + 1) left = leftSide; + add(left, rightPos.top, right - left, rightPos.bottom); + }); + return {start: start, end: end}; + } + + var sFrom = range.from(), sTo = range.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + add(leftSide, leftEnd.bottom, null, rightStart.top); + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) return; + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); + else if (cm.options.cursorBlinkRate < 0) + display.cursorDiv.style.visibility = "hidden"; + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)); + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.frontier < doc.first) doc.frontier = doc.first; + if (doc.frontier >= cm.display.viewTo) return; + var end = +new Date + cm.options.workTime; + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + var changedLines = []; + + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { + if (doc.frontier >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHighlightLength; + var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true); + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) line.styleClasses = newCls; + else if (oldCls) line.styleClasses = null; + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); + for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; + if (ischange) changedLines.push(doc.frontier); + line.stateAfter = tooLong ? state : copyState(doc.mode, state); + } else { + if (line.text.length <= cm.options.maxHighlightLength) + processLine(cm, line.text, state); + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; + } + ++doc.frontier; + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true; + } + }); + if (changedLines.length) runInOp(cm, function() { + for (var i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text"); + }); + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) return doc.first; + var line = getLine(doc, search - 1); + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + + function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!state) state = startState(doc.mode); + else state = copyState(doc.mode, state); + doc.iter(pos, n, function(line) { + processLine(cm, line.text, state); + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo; + line.stateAfter = save ? copyState(doc.mode, state) : null; + ++pos; + }); + if (precise) doc.frontier = pos; + return state; + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop;} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} + function paddingH(display) { + if (display.cachedPaddingH) return display.cachedPaddingH; + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; + return data; + } + + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && displayWidth(cm); + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + heights.push((cur.bottom + next.top) / 2 - rect.top); + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + return {map: lineView.measure.map, cache: lineView.measure.cache}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineView.rest[i] == line) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineNo(lineView.rest[i]) > lineN) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}; + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view; + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + return cm.display.view[findViewIndex(cm, lineN)]; + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + return ext; + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) { + view = null; + } else if (view && view.changes) { + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + cm.curOp.forceUpdate = true; + } + if (!view) + view = updateExternalMeasurement(cm, line); + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + }; + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) ch = -1; + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + prepared.rect = prepared.view.text.getBoundingClientRect(); + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) prepared.cache[key] = found; + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom}; + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function nodeAndOffsetInLineMap(map, ch, bias) { + var node, start, end, collapse; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + var mStart = map[i], mEnd = map[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) collapse = "right"; + } + if (start != null) { + node = map[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + collapse = bias; + if (bias == "left" && start == 0) + while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2]; + collapse = "left"; + } + if (bias == "right" && start == mEnd - mStart) + while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2]; + collapse = "right"; + } + break; + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}; + } + + function getUsefulRect(rects, bias) { + var rect = nullRect + if (bias == "left") for (var i = 0; i < rects.length; i++) { + if ((rect = rects[i]).left != rect.right) break + } else for (var i = rects.length - 1; i >= 0; i--) { + if ((rect = rects[i]).left != rect.right) break + } + return rect + } + + function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); + var node = place.node, start = place.start, end = place.end, collapse = place.collapse; + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start; + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end; + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) + rect = node.parentNode.getBoundingClientRect(); + else + rect = getUsefulRect(range(node, start, end).getClientRects(), bias) + if (rect.left || rect.right || start == 0) break; + end = start; + start = start - 1; + collapse = "right"; + } + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) collapse = bias = "right"; + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = node.getBoundingClientRect(); + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; + else + rect = nullRect; + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; + var heights = prepared.view.measure.heights; + for (var i = 0; i < heights.length - 1; i++) + if (mid < heights[i]) break; + var top = i ? heights[i - 1] : 0, bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) result.bogus = true; + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + + return result; + } + + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + return rect; + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY}; + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + lineView.measure.caches[i] = {}; + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + clearLineMeasurementCacheFor(cm.display.view[i]); + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; + cm.display.lineNumChars = null; + } + + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"/null (editor), "window", + // or "page". + function intoCoordSystem(cm, lineObj, rect, context) { + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]); + rect.top += size; rect.bottom += size; + } + if (context == "line") return rect; + if (!context) context = "local"; + var yOff = heightAtLine(lineObj); + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect; + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"/null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords; + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) lineObj = getLine(cm.doc, pos.line); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); + if (right) m.left = m.right; else m.right = m.left; + return intoCoordSystem(cm, lineObj, m, context); + } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } + var order = getOrder(lineObj), ch = pos.ch; + if (!order) return get(ch); + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0, pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height}; + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, outside, xRel) { + var pos = Pos(line, ch); + pos.xRel = xRel; + if (outside) pos.outside = true; + return pos; + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); + if (x < 0) x = 0; + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var merged = collapsedSpanAtEnd(lineObj); + var mergedPos = merged && merged.find(0, true); + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + lineN = lineNo(lineObj = mergedPos.to.line); + else + return found; + } + } + + function coordsCharInner(cm, lineObj, lineNo, x, y) { + var innerOff = y - heightAtLine(lineObj); + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + + function getX(ch) { + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure); + wrongLine = true; + if (innerOff > sp.bottom) return sp.left - adjust; + else if (innerOff < sp.top) return sp.left + adjust; + else wrongLine = false; + return sp.left; + } + + var bidi = getOrder(lineObj), dist = lineObj.text.length; + var from = lineLeft(lineObj), to = lineRight(lineObj); + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; + + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); + // Do a binary search between these bounds. + for (;;) { + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var outside = ch == from ? fromOutside : toOutside + var xDiff = x - (ch == from ? fromX : toX); + // This is a kludge to handle the case where the coordinates + // are after a line-wrapped line. We should replace it with a + // more general handling of cursor positions around line + // breaks. (Issue #4078) + if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff > 0 && + ch < lineObj.text.length && preparedMeasure.view.measure.heights.length > 1) { + var charSize = measureCharPrepared(cm, preparedMeasure, ch, "right"); + if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.abs(x - charSize.right) < xDiff) { + outside = false + ch++ + xDiff = x - charSize.right + } + } + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; + var pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); + return pos; + } + var step = Math.ceil(dist / 2), middle = from + step; + if (bidi) { + middle = from; + for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); + } + var middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} + } + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) return display.cachedTextHeight; + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) display.cachedTextHeight = height; + removeChildren(display.measure); + return height || 1; + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) return display.cachedCharWidth; + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) display.cachedCharWidth = width; + return width || 10; + } + + // OPERATIONS + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var operationGroup = null; + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: null, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + }; + if (operationGroup) { + operationGroup.ops.push(cm.curOp); + } else { + cm.curOp.ownsGroup = operationGroup = { + ops: [cm.curOp], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + callbacks[i].call(null); + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); + } + } while (i < callbacks.length); + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp, group = op.ownsGroup; + if (!group) return; + + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + for (var i = 0; i < group.ops.length; i++) + group.ops[i].cm.curOp = null; + endOperations(group); + } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R1(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W1(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R2(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W2(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_finish(ops[i]); + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); + if (op.updateMaxLine) findMaxLine(cm); + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) updateHeightsInViewport(cm); + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + cm.display.sizerWidth = op.adjustWidthTo; + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); + } + + if (op.updatedDisplay || op.selectionChanged) + op.preparedSelection = display.input.prepareSelection(op.focus); + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); + cm.display.maxLineChanged = false; + } + + var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()) + if (op.preparedSelection) + cm.display.input.showSelection(op.preparedSelection, takeFocus); + if (op.updatedDisplay || op.startHeight != cm.doc.height) + updateScrollbars(cm, op.barMeasure); + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure); + + if (op.selectionChanged) restartBlink(cm); + + if (cm.state.focused && op.updateInput) + cm.display.input.reset(op.typing); + if (takeFocus) ensureFocus(op.cm); + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + display.wheelStartX = display.wheelStartY = null; + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { + doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); + display.scrollbars.setScrollTop(doc.scrollTop); + display.scroller.scrollTop = doc.scrollTop; + } + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)); + display.scrollbars.setScrollLeft(doc.scrollLeft); + display.scroller.scrollLeft = doc.scrollLeft; + alignHorizontally(cm); + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) for (var i = 0; i < hidden.length; ++i) + if (!hidden[i].lines.length) signal(hidden[i], "hide"); + if (unhidden) for (var i = 0; i < unhidden.length; ++i) + if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); + + if (display.wrapper.offsetHeight) + doc.scrollTop = cm.display.scroller.scrollTop; + + // Fire change events, and delayed event handlers + if (op.changeObjs) + signal(cm, "changes", cm, op.changeObjs); + if (op.update) + op.update.finish(); + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) return f(); + startOperation(cm); + try { return f(); } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) return f.apply(cm, arguments); + startOperation(cm); + try { return f.apply(cm, arguments); } + finally { endOperation(cm); } + }; + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) return f.apply(this, arguments); + startOperation(this); + try { return f.apply(this, arguments); } + finally { endOperation(this); } + }; + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) return f.apply(this, arguments); + startOperation(cm); + try { return f.apply(this, arguments); } + finally { endOperation(cm); } + }; + } + + // VIEW TRACKING + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array; + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) from = cm.doc.first; + if (to == null) to = cm.doc.first + cm.doc.size; + if (!lendiff) lendiff = 0; + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + display.updateLineNumbers = from; + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + resetView(cm); + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut = viewCuttingPoint(cm, from, from, -1); + if (cut) { + display.view = display.view.slice(0, cut.index); + display.viewTo = cut.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + ext.lineN += lendiff; + else if (from < ext.lineN + ext.size) + display.externalMeasured = null; + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + display.externalMeasured = null; + + if (line < display.viewFrom || line >= display.viewTo) return; + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) return; + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) arr.push(type); + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) return null; + n -= cm.display.viewFrom; + if (n < 0) return null; + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) return i; + } + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + return {index: index, lineN: newN}; + for (var i = 0, n = cm.display.viewFrom; i < index; i++) + n += view[i].size; + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) return null; + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) return null; + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN}; + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); + else if (display.viewFrom < from) + display.view = display.view.slice(findViewIndex(cm, from)); + display.viewFrom = from; + if (display.viewTo < to) + display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); + else if (display.viewTo > to) + display.view = display.view.slice(0, findViewIndex(cm, to)); + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; + } + return dirty; + } + + // EVENT HANDLERS + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = cm.findWordAt(pos); + extendSelection(cm.doc, word.anchor, word.head); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0}; + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000); + prevTouch = d.activeTouch; + prevTouch.end = +new Date; + } + }; + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) return false; + var touch = e.touches[0]; + return touch.radiusX <= 1 && touch.radiusY <= 1; + } + function farAway(touch, other) { + if (other.left == null) return true; + var dx = other.left - touch.left, dy = other.top - touch.top; + return dx * dx + dy * dy > 20 * 20; + } + on(d.scroller, "touchstart", function(e) { + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { + clearTimeout(touchFinished); + var now = +new Date; + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null}; + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX; + d.activeTouch.top = e.touches[0].pageY; + } + } + }); + on(d.scroller, "touchmove", function() { + if (d.activeTouch) d.activeTouch.moved = true; + }); + on(d.scroller, "touchend", function(e) { + var touch = d.activeTouch; + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range; + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + range = new Range(pos, pos); + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + range = cm.findWordAt(pos); + else // Triple tap + range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); + cm.setSelection(range.anchor, range.head); + cm.focus(); + e_preventDefault(e); + } + finishTouch(); + }); + on(d.scroller, "touchcancel", finishTouch); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function() { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + d.dragFunctions = { + enter: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, + over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }}, + start: function(e){onDragStart(cm, e);}, + drop: operation(cm, onDrop), + leave: function(e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} + }; + + var inp = d.input.getField(); + on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); + on(inp, "keydown", operation(cm, onKeyDown)); + on(inp, "keypress", operation(cm, onKeyPress)); + on(inp, "focus", bind(onFocus, cm)); + on(inp, "blur", bind(onBlur, cm)); + } + + function dragDropChanged(cm, value, old) { + var wasOn = old && old != CodeMirror.Init; + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions; + var toggle = value ? on : off; + toggle(cm.display.scroller, "dragstart", funcs.start); + toggle(cm.display.scroller, "dragenter", funcs.enter); + toggle(cm.display.scroller, "dragover", funcs.over); + toggle(cm.display.scroller, "dragleave", funcs.leave); + toggle(cm.display.scroller, "drop", funcs.drop); + } + } + + // Called when the window resizes + function onResize(cm) { + var d = cm.display; + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) + return; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + d.scrollbarsClipped = false; + cm.setSize(); + } + + // MOUSE EVENTS + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + return true; + } + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null; + + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e) { return null; } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords; + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + var cm = this, display = cm.display; + if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return; + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function(){display.scroller.draggable = true;}, 100); + } + return; + } + if (clickInGutter(cm, e)) return; + var start = posFromMouse(cm, e); + window.focus(); + + switch (e_button(e)) { + case 1: + // #3261: make sure, that we're not starting a second selection + if (cm.state.selectingText) + cm.state.selectingText(e); + else if (start) + leftButtonDown(cm, e, start); + else if (e_target(e) == display.scroller) + e_preventDefault(e); + break; + case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; + if (start) extendSelection(cm.doc, start); + setTimeout(function() {display.input.focus();}, 20); + e_preventDefault(e); + break; + case 3: + if (captureRightClick) onContextMenu(cm, e); + else delayBlurEvent(cm); + break; + } + } + + var lastClick, lastDoubleClick; + function leftButtonDown(cm, e, start) { + if (ie) setTimeout(bind(ensureFocus, cm), 0); + else cm.curOp.focus = activeElt(); + + var now = +new Date, type; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { + type = "triple"; + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { + type = "double"; + lastDoubleClick = {time: now, pos: start}; + } else { + type = "single"; + lastClick = {time: now, pos: start}; + } + + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && + type == "single" && (contained = sel.contains(start)) > -1 && + (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) && + (cmp(contained.to(), start) > 0 || start.xRel < 0)) + leftButtonStartDrag(cm, e, start, modifier); + else + leftButtonSelect(cm, e, start, type, modifier); + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, e, start, modifier) { + var display = cm.display, startTime = +new Date; + var dragEnd = operation(cm, function(e2) { + if (webkit) display.scroller.draggable = false; + cm.state.draggingText = false; + off(document, "mouseup", dragEnd); + off(display.scroller, "drop", dragEnd); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + if (!modifier && +new Date - 200 < startTime) + extendSelection(cm.doc, start); + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + setTimeout(function() {document.body.focus(); display.input.focus();}, 20); + else + display.input.focus(); + } + }); + // Let the drag handler handle this. + if (webkit) display.scroller.draggable = true; + cm.state.draggingText = dragEnd; + dragEnd.copy = mac ? e.altKey : e.ctrlKey + // IE's approach to draggable + if (display.scroller.dragDrop) display.scroller.dragDrop(); + on(document, "mouseup", dragEnd); + on(display.scroller, "drop", dragEnd); + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, e, start, type, addNew) { + var display = cm.display, doc = cm.doc; + e_preventDefault(e); + + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; + if (addNew && !e.shiftKey) { + ourIndex = doc.sel.contains(start); + if (ourIndex > -1) + ourRange = ranges[ourIndex]; + else + ourRange = new Range(start, start); + } else { + ourRange = doc.sel.primary(); + ourIndex = doc.sel.primIndex; + } + + if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) { + type = "rect"; + if (!addNew) ourRange = new Range(start, start); + start = posFromMouse(cm, e, true, true); + ourIndex = -1; + } else if (type == "double") { + var word = cm.findWordAt(start); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, word.anchor, word.head); + else + ourRange = word; + } else if (type == "triple") { + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, line.anchor, line.head); + else + ourRange = line; + } else { + ourRange = extendRange(doc, ourRange, start); + } + + if (!addNew) { + ourIndex = 0; + setSelection(doc, new Selection([ourRange], 0), sel_mouse); + startSel = doc.sel; + } else if (ourIndex == -1) { + ourIndex = ranges.length; + setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { + setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), + {scroll: false, origin: "*mouse"}); + startSel = doc.sel; + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) return; + lastPos = pos; + + if (type == "rect") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); + else if (text.length > leftPos) + ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); + } + if (!ranges.length) ranges.push(new Range(start, start)); + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); + } else { + var oldRange = ourRange; + var anchor = oldRange.anchor, head = pos; + if (type != "single") { + if (type == "double") + var range = cm.findWordAt(pos); + else + var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); + if (cmp(range.anchor, anchor) > 0) { + head = range.head; + anchor = minPos(oldRange.from(), range.anchor); + } else { + head = range.anchor; + anchor = maxPos(oldRange.to(), range.head); + } + } + var ranges = startSel.ranges.slice(0); + ranges[ourIndex] = new Range(clipPos(doc, anchor), head); + setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, type == "rect"); + if (!cur) return; + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt(); + extendTo(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) setTimeout(operation(cm, function() { + if (counter != curCount) return; + display.scroller.scrollTop += outside; + extend(e); + }), 50); + } + } + + function done(e) { + cm.state.selectingText = false; + counter = Infinity; + e_preventDefault(e); + display.input.focus(); + off(document, "mousemove", move); + off(document, "mouseup", up); + doc.history.lastSelOrigin = null; + } + + var move = operation(cm, function(e) { + if (!e_button(e)) done(e); + else extend(e); + }); + var up = operation(cm, done); + cm.state.selectingText = up; + on(document, "mousemove", move); + on(document, "mouseup", up); + } + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent) { + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; + if (prevent) e_preventDefault(e); + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signal(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true); + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + clearDragCursor(cm); + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + return; + e_preventDefault(e); + if (ie) lastDrop = +new Date; + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || cm.isReadOnly()) return; + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { + if (cm.options.allowDropFileTypes && + indexOf(cm.options.allowDropFileTypes, file.type) == -1) + return; + + var reader = new FileReader; + reader.onload = operation(cm, function() { + var content = reader.result; + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = ""; + text[i] = content; + if (++read == n) { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, + text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), + origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); + } + }); + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(function() {cm.display.input.focus();}, 20); + return; + } + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + if (cm.state.draggingText && !cm.state.draggingText.copy) + var selected = cm.listSelections(); + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) for (var i = 0; i < selected.length; ++i) + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); + cm.replaceSelection(text, "around", "paste"); + cm.display.input.focus(); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; + + e.dataTransfer.setData("Text", cm.getSelection()); + e.dataTransfer.effectAllowed = "copyMove" + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) img.parentNode.removeChild(img); + } + } + + function onDragOver(cm, e) { + var pos = posFromMouse(cm, e); + if (!pos) return; + var frag = document.createDocumentFragment(); + drawSelectionCursor(cm, pos, frag); + if (!cm.display.dragCursor) { + cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors"); + cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv); + } + removeChildrenAndAdd(cm.display.dragCursor, frag); + } + + function clearDragCursor(cm) { + if (cm.display.dragCursor) { + cm.display.lineSpace.removeChild(cm.display.dragCursor); + cm.display.dragCursor = null; + } + } + + // SCROLL EVENTS + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) return; + cm.doc.scrollTop = val; + if (!gecko) updateDisplaySimple(cm, {top: val}); + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; + cm.display.scrollbars.setScrollTop(val); + if (gecko) updateDisplaySimple(cm); + startWorker(cm, 100); + } + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; + cm.display.scrollbars.setScrollLeft(val); + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) wheelPixelsPerUnit = -.53; + else if (gecko) wheelPixelsPerUnit = 15; + else if (chrome) wheelPixelsPerUnit = -.7; + else if (safari) wheelPixelsPerUnit = -1/3; + + var wheelEventDelta = function(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; + else if (dy == null) dy = e.wheelDelta; + return {x: dx, y: dy}; + }; + CodeMirror.wheelEventPixels = function(e) { + var delta = wheelEventDelta(e); + delta.x *= wheelPixelsPerUnit; + delta.y *= wheelPixelsPerUnit; + return delta; + }; + + function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + var canScrollX = scroll.scrollWidth > scroll.clientWidth; + var canScrollY = scroll.scrollHeight > scroll.clientHeight; + if (!(dx && canScrollX || dy && canScrollY)) return; + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer; + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy && canScrollY) + setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + e_preventDefault(e); + display.wheelStartX = null; // Abort measurement, if in progress + return; + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) top = Math.max(0, top + pixels - 50); + else bot = Math.min(cm.doc.height, bot + pixels + 50); + updateDisplaySimple(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function() { + if (display.wheelStartX == null) return; + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) return; + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // KEY EVENTS + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled(); + var prevShift = cm.display.shift, done = false; + try { + if (cm.isReadOnly()) cm.state.suppressEdits = true; + if (dropShift) cm.display.shift = false; + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done; + } + + function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); + if (result) return result; + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm); + } + + var stopSeq = new Delayed; + function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq; + if (seq) { + if (isModifierKey(name)) return "handled"; + stopSeq.set(50, function() { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null; + cm.display.input.reset(); + } + }); + name = seq + " " + name; + } + var result = lookupKeyForEditor(cm, name, handle); + + if (result == "multi") + cm.state.keySeq = name; + if (result == "handled") + signalLater(cm, "keyHandled", cm, name, e); + + if (result == "handled" || result == "multi") { + e_preventDefault(e); + restartBlink(cm); + } + + if (seq && !result && /\'$/.test(name)) { + e_preventDefault(e); + return true; + } + return !!result; + } + + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + var name = keyName(e, true); + if (!name) return false; + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);}) + || dispatchKey(cm, name, e, function(b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); + }); + } else { + return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); }); + } + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, + function(b) { return doHandleBinding(cm, b, true); }); + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + cm.curOp.focus = activeElt(); + if (signalDOMEvent(cm, e)) return; + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + cm.replaceSelection("", null, "cut"); + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + showCrossHair(cm); + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (e.keyCode == 16) this.doc.sel.shift = false; + signalDOMEvent(this, e); + } + + function onKeyPress(e) { + var cm = this; + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (handleCharBinding(cm, e, ch)) return; + cm.display.input.onKeyPress(e); + } + + // FOCUS/BLUR EVENTS + + function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true; + setTimeout(function() { + if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false; + onBlur(cm); + } + }, 100); + } + + function onFocus(cm) { + if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; + + if (cm.options.readOnly == "nocursor") return; + if (!cm.state.focused) { + signal(cm, "focus", cm); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset(); + if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730 + } + cm.display.input.receivedFocus(); + } + restartBlink(cm); + } + function onBlur(cm) { + if (cm.state.delayingBlurEvent) return; + + if (cm.state.focused) { + signal(cm, "blur", cm); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); + } + + // CONTEXT MENU HANDLING + + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; + if (signalDOMEvent(cm, e, "contextmenu")) return; + cm.display.input.onContextMenu(e); + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false); + } + + // UPDATING + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); + }; + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) return pos; + if (cmp(pos, change.to) <= 0) return changeEnd(change); + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; + return Pos(line, ch); + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(out, doc.sel.primIndex); + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + return Pos(nw.line, pos.ch - old.ch + nw.ch); + else + return Pos(nw.line + (pos.line - old.line), pos.ch); + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex); + } + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function() { this.canceled = true; } + }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; + signal(doc, "beforeChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); + + if (obj.canceled) return null; + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); + if (doc.cm.state.suppressEdits) return; + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) return; + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}); + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return; + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) return; + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + for (var i = 0; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + break; + } + if (i == source.length) return; + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return; + } + selAfter = event; + } + else break; + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + for (var i = event.changes.length - 1; i >= 0; --i) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return; + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + if (distance == 0) return; + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function(range) { + return new Range(Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch)); + }), doc.sel.primIndex); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + regLineChange(doc.cm, l, "gutter"); + } + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return; + } + if (change.from.line > doc.lastLine()) return; + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) selAfter = computeSelAfterChange(doc, change); + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); + else updateDoc(doc, change, spans); + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function(line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true; + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + signalCursorActivity(cm); + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function(line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) cm.curOp.updateMaxLine = true; + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (change.full) + regChange(cm); + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + regLineChange(cm, from.line, "text"); + else + regChange(cm, from.line, to.line + 1, lendiff); + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) signalLater(cm, "change", cm, obj); + if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); + } + cm.display.selForContextMenu = null; + } + + function replaceRange(doc, code, from, to, origin) { + if (!to) to = from; + if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } + if (typeof code == "string") code = doc.splitLines(code); + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, coords) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) return; + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + + (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " + + coords.left + "px; width: 2px;"); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) margin = 0; + for (var limit = 0; limit < 5; limit++) { + var changed = false, coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; + } + if (!changed) break; + } + return coords; + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); + if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, x1, y1, x2, y2) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = displayHeight(cm), result = {}; + if (y2 - y1 > screen) y2 = y1 + screen; + var docBottom = cm.doc.height + paddingVert(display); + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); + var tooWide = x2 - x1 > screenw; + if (tooWide) x2 = x1 + screenw; + if (x1 < 10) + result.scrollLeft = 0; + else if (x1 < screenleft) + result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); + else if (x2 > screenw + screenleft - 3) + result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; + return result; + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollPos(cm, left, top) { + if (left != null || top != null) resolveScrollToPos(cm); + if (left != null) + cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left; + if (top != null) + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(), from = cur, to = cur; + if (!cm.options.lineWrapping) { + from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; + to = Pos(cur.line, cur.ch + 1); + } + cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true}; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos; + if (range) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); + var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), + Math.min(from.top, to.top) - range.margin, + Math.max(from.right, to.right), + Math.max(from.bottom, to.bottom) + range.margin); + cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + } + + // API UTILITIES + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) how = "add"; + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) how = "prev"; + else state = getStateBefore(cm, n); + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) line.stateAfter = null; + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { + if (!aggressive) return; + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); + else indentation = 0; + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + line.stateAfter = null; + return true; + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i, new Range(pos, pos)); + break; + } + } + } + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); + return line; + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break; + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function() { + for (var i = kill.length - 1; i >= 0; i--) + replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); + ensureCursorVisible(cm); + }); + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "char", "column" (like char, but doesn't + // cross line boundaries), "word" (across next word), or "group" (to + // the start of next group of word or non-word-non-whitespace + // chars). The visually param controls whether, in right-to-left + // text, direction 1 means to move towards the next index in the + // string, or towards the character to the right of the current + // position. The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var line = pos.line, ch = pos.ch, origDir = dir; + var lineObj = getLine(doc, line); + function findNextLine() { + var l = line + dir; + if (l < doc.first || l >= doc.first + doc.size) return false + line = l; + return lineObj = getLine(doc, l); + } + function moveOnce(boundToLine) { + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); + if (next == null) { + if (!boundToLine && findNextLine()) { + if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); + else ch = dir < 0 ? lineObj.text.length : 0; + } else return false + } else ch = next; + return true; + } + + if (unit == "char") { + moveOnce() + } else if (unit == "column") { + moveOnce(true) + } else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break; + var cur = lineObj.text.charAt(ch) || "\n"; + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) type = "s"; + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce();} + break; + } + + if (type) sawType = type; + if (dir > 0 && !moveOnce(!first)) break; + } + } + var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true); + if (!cmp(pos, result)) result.hitSide = true; + return result; + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + for (;;) { + var target = coordsChar(cm, x, y); + if (!target.outside) break; + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } + y += dir * 5; + } + return target; + } + + // EDITOR METHODS + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus();}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") return; + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + operation(this, optionHandlers[option])(this, value, old); + }, + + getOption: function(option) {return this.options[option];}, + getDoc: function() {return this.doc;}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1); + return true; + } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) throw new Error("Overlays may not be stateful."); + this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this.state.modeGen++; + regChange(this); + return; + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); + }), + indentSelection: methodOp(function(how) { + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (!range.empty()) { + var from = range.from(), to = range.to(); + var start = Math.max(end, from.line); + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + indentLine(this, j, how); + var newRanges = this.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true); + end = range.head.line; + if (i == this.doc.sel.primIndex) ensureCursorVisible(this); + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise); + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true); + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) type = styles[2]; + else for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else { type = styles[mid * 2 + 2]; break; } + } + var cut = type ? type.indexOf("cm-overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0]; + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) return found; + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]); + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) found.push(val); + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i = 0; i < help._global.length; i++) { + var cur = help._global[i]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val); + } + return found; + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getStateBefore(this, line + 1, precise); + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary(); + if (start == null) pos = range.head; + else if (typeof start == "object") pos = clipPos(this.doc, start); + else pos = start ? range.from() : range.to(); + return cursorCoords(this, pos, mode || "page"); + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page"); + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top); + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, lineObj; + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + lineObj = getLine(this.doc, line); + } else { + lineObj = line; + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + + (end ? this.doc.height - heightAtLine(lineObj) : 0); + }, + + defaultTextHeight: function() { return textHeight(this.display); }, + defaultCharWidth: function() { return charWidth(this.display); }, + + setGutterMarker: methodOp(function(line, gutterID, value) { + return changeLine(this.doc, line, "gutter", function(line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) line.gutterMarkers = null; + return true; + }); + }), + + clearGutter: methodOp(function(gutterID) { + var cm = this, doc = cm.doc, i = doc.first; + doc.iter(function(line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + line.gutterMarkers[gutterID] = null; + regLineChange(cm, i, "gutter"); + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; + } + ++i; + }); + }), + + lineInfo: function(line) { + if (typeof line == "number") { + if (!isLine(this.doc, line)) return null; + var n = line; + line = getLine(this.doc, line); + if (!line) return null; + } else { + var n = lineNo(line); + if (n == null) return null; + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets}; + }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); + this.display.input.setUneditable(node); + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + top = pos.top - node.offsetHeight; + else if (pos.bottom + node.offsetHeight <= vspace) + top = pos.bottom; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; + node.style.left = left + "px"; + } + if (scroll) + scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd].call(null, this); + }, + + triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), + + findPosH: function(from, amount, unit, visually) { + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually); + if (cur.hitSide) break; + } + return cur; + }, + + moveH: methodOp(function(dir, unit) { + var cm = this; + cm.extendSelectionsBy(function(range) { + if (cm.display.shift || cm.doc.extend || range.empty()) + return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually); + else + return dir < 0 ? range.from() : range.to(); + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + doc.replaceSelection("", null, "+delete"); + else + deleteNearSelection(this, function(range) { + var other = findPosH(doc, range.head, dir, unit, false); + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}; + }); + }), + + findPosV: function(from, amount, unit, goalColumn) { + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + var coords = cursorCoords(this, cur, "div"); + if (x == null) x = coords.left; + else coords.left = x; + cur = findPosV(this, coords, dir, unit); + if (cur.hitSide) break; + } + return cur; + }, + + moveV: methodOp(function(dir, unit) { + var cm = this, doc = this.doc, goals = []; + var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function(range) { + if (collapse) + return dir < 0 ? range.from() : range.to(); + var headPos = cursorCoords(cm, range.head, "div"); + if (range.goalColumn != null) headPos.left = range.goalColumn; + goals.push(headPos.left); + var pos = findPosV(cm, headPos, dir, unit); + if (unit == "page" && range == doc.sel.primary()) + addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top); + return pos; + }, sel_move); + if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) + doc.sel.ranges[i].goalColumn = goals[i]; + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function(ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)); + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; + if (this.state.overwrite = !this.state.overwrite) + addClass(this.display.cursorDiv, "CodeMirror-overwrite"); + else + rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return this.display.input.getField() == activeElt(); }, + isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit); }, + + scrollTo: methodOp(function(x, y) { + if (x != null || y != null) resolveScrollToPos(this); + if (x != null) this.curOp.scrollLeft = x; + if (y != null) this.curOp.scrollTop = y; + }), + getScrollInfo: function() { + var scroller = this.display.scroller; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)}; + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) margin = this.options.cursorScrollMargin; + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null}; + } else if (range.from == null) { + range = {from: range, to: null}; + } + if (!range.to) range.to = range.from; + range.margin = margin || 0; + + if (range.from.line != null) { + resolveScrollToPos(this); + this.curOp.scrollToPos = range; + } else { + var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), + Math.min(range.from.top, range.to.top) - range.margin, + Math.max(range.from.right, range.to.right), + Math.max(range.from.bottom, range.to.bottom) + range.margin); + this.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + }), + + setSize: methodOp(function(width, height) { + var cm = this; + function interpret(val) { + return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; + } + if (width != null) cm.display.wrapper.style.width = interpret(width); + if (height != null) cm.display.wrapper.style.height = interpret(height); + if (cm.options.lineWrapping) clearLineMeasurementCache(this); + var lineNo = cm.display.viewFrom; + cm.doc.iter(lineNo, cm.display.viewTo, function(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) + if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; } + ++lineNo; + }); + cm.curOp.forceUpdate = true; + signal(cm, "refresh", this); + }), + + operation: function(f){return runInOp(this, f);}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + estimateLineHeights(this); + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + this.display.input.reset(); + this.scrollTo(doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; + signalLater(this, "swapDoc", this, old); + return old; + }), + + getInputField: function(){return this.display.input.getField();}, + getWrapperElement: function(){return this.display.wrapper;}, + getScrollerElement: function(){return this.display.scroller;}, + getGutterElement: function(){return this.display.gutters;} + }; + eventMixin(CodeMirror); + + // OPTION DEFAULTS + + // The default configuration options. + var defaults = CodeMirror.defaults = {}; + // Functions to run when options are changed. + var optionHandlers = CodeMirror.optionHandlers = {}; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) optionHandlers[name] = + notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; + } + + // Passed to option handlers when there is no old value. + var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function(cm, val) { + cm.setValue(val); + }, true); + option("mode", null, function(cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function(cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + option("lineSeparator", null, function(cm, val) { + cm.doc.lineSep = val; + if (!val) return; + var newBreaks = [], lineNo = cm.doc.first; + cm.doc.iter(function(line) { + for (var pos = 0;;) { + var found = line.text.indexOf(val, pos); + if (found == -1) break; + pos = found + val.length; + newBreaks.push(Pos(lineNo, found)); + } + lineNo++; + }); + for (var i = newBreaks.length - 1; i >= 0; i--) + replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) + }); + option("specialChars", /[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + if (old != CodeMirror.Init) cm.refresh(); + }); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); + option("electricChars", true); + option("inputStyle", mobile ? "contenteditable" : "textarea", function() { + throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME + }, true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function(cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", function(cm, val, old) { + var next = getKeyMap(val); + var prev = old != CodeMirror.Init && getKeyMap(old); + if (prev && prev.detach) prev.detach(cm, next); + if (next.attach) next.attach(cm, prev || null); + }); + option("extraKeys", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function(cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm);}, true); + option("scrollbarStyle", "native", function(cm) { + initScrollbars(cm); + updateScrollbars(cm); + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); + }, true); + option("lineNumbers", false, function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + option("lineWiseCopyCut", true); + + option("readOnly", false, function(cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + cm.display.disabled = true; + } else { + cm.display.disabled = false; + } + cm.display.input.readOnlyChanged(val) + }); + option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true); + option("dragDrop", true, dragDropChanged); + option("allowDropFileTypes", null); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.input.resetPosition(); + }); + + option("tabindex", null, function(cm, val) { + cm.display.input.getField().tabIndex = val || ""; + }); + option("autofocus", null); + + // MODE DEFINITION AND QUERYING + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2); + modes[name] = mode; + }; + + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") found = {name: found}; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return CodeMirror.resolveMode("application/xml"); + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) continue; + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; + + return modeObj; + }; + + // Minimal default mode. + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + }; + + // EXTENSIONS + + CodeMirror.defineExtension = function(name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; + CodeMirror.defineOption = option; + + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + + // MODE STATE HANDLING + + // Utility functions for working with state. Exported because nested + // modes need to do this for their inner modes. + + var copyState = CodeMirror.copyState = function(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + }; + + var startState = CodeMirror.startState = function(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + }; + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; + + // STANDARD COMMANDS + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);}, + singleSelection: function(cm) { + cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); + }, + killLine: function(cm) { + deleteNearSelection(cm, function(range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + return {from: range.head, to: Pos(range.head.line + 1, 0)}; + else + return {from: range.head, to: Pos(range.head.line, len)}; + } else { + return {from: range.from(), to: range.to()}; + } + }); + }, + deleteLine: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; + }); + }, + delLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), to: range.from()}; + }); + }, + delWrappedLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()}; + }); + }, + delWrappedLineRight: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos }; + }); + }, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + undoSelection: function(cm) {cm.undoSelection();}, + redoSelection: function(cm) {cm.redoSelection();}, + goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, + goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, + goLineStart: function(cm) { + cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1}); + }, + goLineStartSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + return lineStartSmart(cm, range.head); + }, {origin: "+move", bias: 1}); + }, + goLineEnd: function(cm) { + cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1}); + }, + goLineRight: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + }, sel_move); + }, + goLineLeft: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div"); + }, sel_move); + }, + goLineLeftSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); + return pos; + }, sel_move); + }, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goGroupRight: function(cm) {cm.moveH(1, "group");}, + goGroupLeft: function(cm) {cm.moveH(-1, "group");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharBefore: function(cm) {cm.deleteH(-1, "char");}, + delCharAfter: function(cm) {cm.deleteH(1, "char");}, + delWordBefore: function(cm) {cm.deleteH(-1, "word");}, + delWordAfter: function(cm) {cm.deleteH(1, "word");}, + delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, + delGroupAfter: function(cm) {cm.deleteH(1, "group");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t");}, + insertSoftTab: function(cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(spaceStr(tabSize - col % tabSize)); + } + cm.replaceSelections(spaces); + }, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.execCommand("insertTab"); + }, + transposeChars: function(cm) { + runInOp(cm, function() { + var ranges = cm.listSelections(), newSel = []; + for (var i = 0; i < ranges.length; i++) { + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (line) { + if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) + cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose"); + } + } + newSel.push(new Range(cur, cur)); + } + cm.setSelections(newSel); + }); + }, + newlineAndIndent: function(cm) { + runInOp(cm, function() { + var len = cm.listSelections().length; + for (var i = 0; i < len; i++) { + var range = cm.listSelections()[i]; + cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+input"); + cm.indentLine(range.from().line + 1, null, true); + } + ensureCursorVisible(cm); + }); + }, + openLine: function(cm) {cm.replaceSelection("\n", "start")}, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + + // STANDARD KEYMAPS + + var keyMap = CodeMirror.keyMap = {}; + + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", + "Ctrl-O": "openLine" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/), name = parts[parts.length - 1]; + var alt, ctrl, shift, cmd; + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i]; + if (/^(cmd|meta|m)$/i.test(mod)) cmd = true; + else if (/^a(lt)?$/i.test(mod)) alt = true; + else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true; + else if (/^s(hift)$/i.test(mod)) shift = true; + else throw new Error("Unrecognized modifier name: " + mod); + } + if (alt) name = "Alt-" + name; + if (ctrl) name = "Ctrl-" + name; + if (cmd) name = "Cmd-" + name; + if (shift) name = "Shift-" + name; + return name; + } + + // This is a kludge to keep keymaps mostly working as raw objects + // (backwards compatibility) while at the same time support features + // like normalization and multi-stroke key bindings. It compiles a + // new normalized keymap, and then updates the old object to reflect + // this. + CodeMirror.normalizeKeyMap = function(keymap) { + var copy = {}; + for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname]; + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; + if (value == "...") { delete keymap[keyname]; continue; } + + var keys = map(keyname.split(" "), normalizeKeyName); + for (var i = 0; i < keys.length; i++) { + var val, name; + if (i == keys.length - 1) { + name = keys.join(" "); + val = value; + } else { + name = keys.slice(0, i + 1).join(" "); + val = "..."; + } + var prev = copy[name]; + if (!prev) copy[name] = val; + else if (prev != val) throw new Error("Inconsistent bindings for " + name); + } + delete keymap[keyname]; + } + for (var prop in copy) keymap[prop] = copy[prop]; + return keymap; + }; + + var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) { + map = getKeyMap(map); + var found = map.call ? map.call(key, context) : map[key]; + if (found === false) return "nothing"; + if (found === "...") return "multi"; + if (found != null && handle(found)) return "handled"; + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + return lookupKey(key, map.fallthrough, handle, context); + for (var i = 0; i < map.fallthrough.length; i++) { + var result = lookupKey(key, map.fallthrough[i], handle, context); + if (result) return result; + } + } + }; + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + var isModifierKey = CodeMirror.isModifierKey = function(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + }; + + // Look up the name of a key as indicated by an event object. + var keyName = CodeMirror.keyName = function(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) return false; + var base = keyNames[event.keyCode], name = base; + if (name == null || event.altGraphKey) return false; + if (event.altKey && base != "Alt") name = "Alt-" + name; + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name; + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name; + if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name; + return name; + }; + + function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val; + } + + // FROMTEXTAREA + + CodeMirror.fromTextArea = function(textarea, options) { + options = options ? copyObj(options) : {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabIndex) + options.tabindex = textarea.tabIndex; + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form, realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function() { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + options.finishInit = function(cm) { + cm.save = save; + cm.getTextArea = function() { return textarea; }; + cm.toTextArea = function() { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + }; + + textarea.style.display = "none"; + var cm = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + return cm; + }; + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = CodeMirror.StringStream = function(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + }; + + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == this.lineStart;}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } + }; + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + var nextMarkerId = 0; + + var TextMarker = CodeMirror.TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + this.id = ++nextMarkerId; + }; + eventMixin(TextMarker); + + // Clear the marker. + TextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); + else if (cm) { + if (span.to != null) max = lineNo(line); + if (span.from != null) min = lineNo(line); + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)); + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { + var visual = visualLine(this.lines[i]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } + + if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) reCheckSelection(cm.doc); + } + if (cm) signalLater(cm, "markerCleared", cm, this); + if (withOp) endOperation(cm); + if (this.parent) this.parent.clear(); + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function(side, lineObj) { + if (side == null && this.type == "bookmark") side = 1; + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) return from; + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) return to; + } + } + return from && {from: from, to: to}; + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function() { + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) return; + runInOp(cm, function() { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + updateLineHeight(line, line.height + dHeight); + } + }); + }; + + TextMarker.prototype.attachLine = function(line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); + } + this.lines.push(line); + }; + TextMarker.prototype.detachLine = function(line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) return markTextShared(doc, from, to, options, type); + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) copyObj(options, marker, false); + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + return marker; + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true"); + if (options.insertLeft) marker.widgetNode.insertLeft = true; + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + throw new Error("Inserting collapsed marker partially overlapping an existing one"); + sawCollapsedSpans = true; + } + + if (marker.addToHistory) + addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function(line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + updateMaxLine = true; + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { + if (lineIsHidden(doc, line)) updateLineHeight(line, 0); + }); + + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); + + if (marker.readOnly) { + sawReadOnlySpans = true; + if (doc.history.done.length || doc.history.undone.length) + doc.clearHistory(); + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) cm.curOp.updateMaxLine = true; + if (marker.collapsed) + regChange(cm, from.line, to.line + 1); + else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) + for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); + if (marker.atomic) reCheckSelection(cm.doc); + signalLater(cm, "markerAdded", cm, marker); + } + return marker; + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + markers[i].parent = this; + }; + eventMixin(SharedTextMarker); + + SharedTextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + this.markers[i].clear(); + signalLater(this, "clear"); + }; + SharedTextMarker.prototype.find = function(side, lineObj) { + return this.primary.find(side, lineObj); + }; + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function(doc) { + if (widget) options.widgetNode = widget.cloneNode(true); + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + if (doc.linked[i].isParent) return; + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary); + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), + function(m) { return m.parent; }); + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], linked = [marker.primary.doc];; + linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + } + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } + } + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + for (var r, i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + // Add a span to a line. + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } + return nw; + } + function markedSpansAfter(old, endCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } + return nw; + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + if (change.full) return null; + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) return null; + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first); + if (last && last != first) last = clearEmptySpans(last); + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)); + for (var i = 0; i < gap; ++i) + newMarkers.push(gapMarkers); + newMarkers.push(last); + } + return newMarkers; + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1); + } + if (!spans.length) return null; + return spans; + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) return stretched; + if (!stretched) return old; + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + if (oldCur[k].marker == span.marker) continue spans; + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old; + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function(line) { + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark); + } + }); + if (!markers) return null; + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}); + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}); + parts.splice.apply(parts, newParts); + j += newParts.length - 1; + } + } + return parts; + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line); + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line); + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) return lenDiff; + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) return -fromCmp; + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) return toCmp; + return b.id - a.id; + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker; + } + return found; + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) continue; + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; + if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || + fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) + return true; + } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line; + return line; + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + (lines || (lines = [])).push(line); + } + return lines; + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) return lineN; + return lineNo(vis); + } + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN; + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) return lineN; + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line; + return lineNo(line) + 1; + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) continue; + if (sp.from == null) return true; + if (sp.marker.widgetNode) continue; + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true; + } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true; + for (var sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true; + } + } + + // LINE WIDGETS + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt]; + this.doc = doc; + this.node = node; + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + addToScrollPos(cm, null, diff); + } + + LineWidget.prototype.clear = function() { + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) return; + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); + if (!ws.length) line.widgets = null; + var height = widgetHeight(this); + updateLineHeight(line, Math.max(0, line.height - height)); + if (cm) runInOp(cm, function() { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + }); + }; + LineWidget.prototype.changed = function() { + var oldH = this.height, cm = this.doc.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) return; + updateLineHeight(line, line.height + diff); + if (cm) runInOp(cm, function() { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + }); + }; + + function widgetHeight(widget) { + if (widget.height != null) return widget.height; + var cm = widget.doc.cm; + if (!cm) return 0; + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; + if (widget.noHScroll) + parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } + return widget.height = widget.node.parentNode.offsetHeight; + } + + function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options); + var cm = doc.cm; + if (cm && widget.noHScroll) cm.display.alignWidgets = true; + changeLine(doc, handle, "widget", function(line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); + widget.line = line; + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) addToScrollPos(cm, null, widget.height); + cm.curOp.forceUpdate = true; + } + return true; + }); + return widget; + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); + Line.prototype.lineNo = function() { return lineNo(this); }; + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + if (line.order != null) line.order = null; + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) updateLineHeight(line, estHeight); + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + function extractLineClasses(type, output) { + if (type) for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) break; + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + output[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2]; + } + return type; + } + + function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state); + if (!mode.innerMode) return; + var inner = CodeMirror.innerMode(mode, state); + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); + } + + function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode; + var style = mode.token(stream, state); + if (stream.pos > stream.start) return style; + } + throw new Error("Mode " + mode.name + " failed to advance stream."); + } + + // Utility for getTokenAt and getLineTokens + function takeToken(cm, pos, precise, asArray) { + function getObj(copy) { + return {start: stream.start, end: stream.pos, + string: stream.current(), + type: style || null, + state: copy ? copyState(doc.mode, state) : state}; + } + + var doc = cm.doc, mode = doc.mode, style; + pos = clipPos(doc, pos); + var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise); + var stream = new StringStream(line.text, cm.options.tabSize), tokens; + if (asArray) tokens = []; + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos; + style = readToken(mode, stream, state); + if (asArray) tokens.push(getObj(true)); + } + return asArray ? tokens : getObj(); + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; + var inner = cm.options.addModeClass && [null]; + if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) processLine(cm, text, state, stream.pos); + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses); + } + if (inner) { + var mName = inner[0].name; + if (mName) style = "m-" + (style ? mName + " " + style : mName); + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 50000); + f(curStart, curStyle); + } + curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; + } + } + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, state, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function(end, style) { + st.push(end, style); + }, lineClasses, forceToEnd); + + // Run overlays, adjust style array. + for (var o = 0; o < cm.state.overlays.length; ++o) { + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); + i += 2; + at = Math.min(end, i_end); + } + if (!style) return; + if (overlay.opaque) { + st.splice(start, i - start, end, "cm-overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; + } + } + }, lineClasses); + } + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; + } + + function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var state = getStateBefore(cm, lineNo(line)); + var result = highlightLine(cm, line, line.text.length > cm.options.maxHighlightLength ? copyState(cm.doc.mode, state) : state); + line.stateAfter = state; + line.styles = result.styles; + if (result.classes) line.styleClasses = result.classes; + else if (line.styleClasses) line.styleClasses = null; + if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; + } + return line.styles; + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, state, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize); + stream.start = stream.pos = startAt || 0; + if (text == "") callBlankLine(mode, state); + while (!stream.eol()) { + readToken(mode, stream, state); + stream.start = stream.pos; + } + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null; + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")); + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: content, + col: 0, pos: 0, cm: cm, + trailingSpace: false, + splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order; + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) + builder.addToken = buildTokenBadBidi(builder.addToken, order); + builder.map = []; + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); + (lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + // See issue #2901 + if (webkit) { + var last = builder.content.lastChild + if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) + builder.content.className = "cm-tab-wrap-hack"; + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); + + return builder; + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + token.setAttribute("aria-label", token.title); + return token; + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, title, css) { + if (!text) return; + var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text + var special = builder.cm.state.specialChars, mustWrap = false; + if (!special.test(text)) { + builder.col += text.length; + var content = document.createTextNode(displayText); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie && ie_version < 9) mustWrap = true; + builder.pos += text.length; + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + txt.setAttribute("role", "presentation"); + txt.setAttribute("cm-text", "\t"); + builder.col += tabWidth; + } else if (m[0] == "\r" || m[0] == "\n") { + var txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")); + txt.setAttribute("cm-text", m[0]); + builder.col += 1; + } else { + var txt = builder.cm.options.specialCharPlaceholder(m[0]); + txt.setAttribute("cm-text", m[0]); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt); + builder.pos++; + } + } + builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 + if (style || startStyle || endStyle || mustWrap || css) { + var fullStyle = style || ""; + if (startStyle) fullStyle += startStyle; + if (endStyle) fullStyle += endStyle; + var token = elt("span", [content], fullStyle, css); + if (title) token.title = title; + return builder.content.appendChild(token); + } + builder.content.appendChild(content); + } + + function splitSpaces(text, trailingBefore) { + if (text.length > 1 && !/ /.test(text)) return text + var spaceBefore = trailingBefore, result = "" + for (var i = 0; i < text.length; i++) { + var ch = text.charAt(i) + if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) + ch = "\u00a0" + result += ch + spaceBefore = ch == " " + } + return result + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function(builder, text, style, startStyle, endStyle, title, css) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + for (var i = 0; i < order.length; i++) { + var part = order[i]; + if (part.to > start && part.from <= start) break; + } + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css); + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + }; + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) builder.map.push(builder.pos, builder.pos + size, widget); + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + widget = builder.content.appendChild(document.createElement("span")); + widget.setAttribute("cm-marker", marker.id); + } + if (widget) { + builder.cm.display.input.setUneditable(widget); + builder.content.appendChild(widget); + } + builder.pos += size; + builder.trailingSpace = false + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); + return; + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = css = ""; + collapsed = null; nextChange = Infinity; + var foundBookmarks = [], endStyles + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m); + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to; + spanEndStyle = ""; + } + if (m.className) spanStyle += " " + m.className; + if (m.css) css = (css ? css + ";" : "") + m.css; + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; + if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to) + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + collapsed = sp; + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + } + if (endStyles) for (var j = 0; j < endStyles.length; j += 2) + if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j] + + if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) return; + if (collapsed.to == pos) collapsed = false; + } + } + if (pos >= len) break; + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore); + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null;} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } + function linesFor(start, end) { + for (var i = start, result = []; i < end; ++i) + result.push(new Line(text[i], spansFor(i), estimateHeight)); + return result; + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)); + doc.remove(text.length, doc.size - text.length); + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1); + update(lastLine, lastLine.text, lastSpans); + if (nlines) doc.remove(from.line, nlines); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + var added = linesFor(1, text.length - 1); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + var added = linesFor(1, text.length - 1); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1); + doc.insert(from.line + 1, added); + } + + signalLater(doc, "change", doc, change); + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, height = 0; i < lines.length; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) lines[i].parent = this; + }, + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + removeInner: function(at, n) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines); + }, + insertInner: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. + // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. + var remaining = child.lines.length % 25 + 25 + for (var pos = remaining; pos < child.lines.length;) { + var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); + child.height -= leaf.height; + this.children.splice(++i, 0, leaf); + leaf.parent = this; + } + child.lines = child.lines.slice(0, remaining); + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iterN: function(at, n, op) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + var nextDocId = 0; + var Doc = CodeMirror.Doc = function(text, mode, firstLine, lineSep) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep); + if (firstLine == null) firstLine = 0; + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.frontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + this.lineSep = lineSep; + this.extend = false; + + if (typeof text == "string") text = this.splitLines(text); + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) this.iterN(from - this.first, to - from, op); + else this.iterN(this.first, this.first + this.size, from); + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) height += lines[i].height; + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) return lines; + return lines.join(lineSep || this.lineSeparator()); + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: this.splitLines(code), origin: "setValue", full: true}, true); + setSelection(this, simpleSelection(top)); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) return lines; + return lines.join(lineSep || this.lineSeparator()); + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, + + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, + getLineNumber: function(line) {return lineNo(line);}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(line); + }, + + lineCount: function() {return this.size;}, + firstLine: function() {return this.first;}, + lastLine: function() {return this.first + this.size - 1;}, + + clipPos: function(pos) {return clipPos(this, pos);}, + + getCursor: function(start) { + var range = this.sel.primary(), pos; + if (start == null || start == "head") pos = range.head; + else if (start == "anchor") pos = range.anchor; + else if (start == "end" || start == "to" || start === false) pos = range.to(); + else pos = range.from(); + return pos; + }, + listSelections: function() { return this.sel.ranges; }, + somethingSelected: function() {return this.sel.somethingSelected();}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads), options); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + var heads = map(this.sel.ranges, f); + extendSelections(this, clipPosArray(this, heads), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return; + for (var i = 0, out = []; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)); + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex); + setSelection(this, normalizeSelection(out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) return lines; + else return lines.join(lineSep || this.lineSeparator()); + }, + getSelections: function(lineSep) { + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator()); + parts[i] = sel; + } + return parts; + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + dup[i] = code; + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]); + if (newSel) setSelectionReplaceHistory(this, newSel); + else if (this.cm) ensureCursorVisible(this.cm); + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend;}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done; + for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone; + return {undo: done, redo: undone}; + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)}; + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + if (!line[prop]) line[prop] = cls; + else if (classTest(cls).test(line[prop])) return false; + else line[prop] += " " + cls; + return true; + }); + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) return false; + else if (cls == null) line[prop] = null; + else { + var found = cur.match(classTest(cls)); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true; + }); + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options); + }), + removeLineWidget: function(widget) { widget.clear(); }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range"); + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark"); + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker.parent || span.marker); + } + return markers; + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo = from.line; + this.iter(from.line, to.line + 1, function(line) { + var spans = line.markedSpans; + if (spans) for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(span.to != null && lineNo == from.line && from.ch >= span.to || + span.from == null && lineNo != from.line || + span.from != null && lineNo == to.line && span.from >= to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker); + } + ++lineNo; + }); + return found; + }, + getAllMarks: function() { + var markers = []; + this.iter(function(line) { + var sps = line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker); + }); + return markers; + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first, sepSize = this.lineSeparator().length; + this.iter(function(line) { + var sz = line.text.length + sepSize; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos(this, Pos(lineNo, ch)); + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) return 0; + var sepSize = this.lineSeparator().length; + this.iter(this.first, coords.line, function (line) { + index += line.text.length + sepSize; + }); + return index; + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), + this.modeOption, this.first, this.lineSep); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc; + }, + + linkedDoc: function(options) { + if (!options) options = {}; + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) from = options.from; + if (options.to != null && options.to < to) to = options.to; + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep); + if (options.sharedHist) copy.history = this.history; + (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy; + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc; + if (this.linked) for (var i = 0; i < this.linked.length; ++i) { + var link = this.linked[i]; + if (link.doc != other) continue; + this.linked.splice(i, 1); + other.unlinkDoc(this); + detachSharedMarkers(findSharedMarkers(this)); + break; + } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode;}, + getEditor: function() {return this.cm;}, + + splitLines: function(str) { + if (this.lineSep) return str.split(this.lineSep); + return splitLinesAuto(str); + }, + lineSeparator: function() { return this.lineSep || "\n"; } + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); + for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments);}; + })(Doc.prototype[prop]); + + eventMixin(Doc); + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) continue; + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) continue; + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) throw new Error("This document is already in use."); + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + if (!cm.options.lineWrapping) findMaxLine(cm); + cm.options.mode = doc.modeOption; + regChange(cm); + } + + // LINE UTILITIES + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document."); + for (var chunk = doc; !chunk.lines;) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function(line) { + var text = line.text; + if (n == end.line) text = text.slice(0, end.ch); + if (n == start.line) text = text.slice(start.ch); + out.push(text); + ++n; + }); + return out; + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function(line) { out.push(line.text); }); + return out; + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) for (var n = line; n; n = n.parent) n.height += diff; + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first; + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i = 0; i < chunk.children.length; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) break; + else h += line.height; + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i = 0; i < p.children.length; ++i) { + var cur = p.children[i]; + if (cur == chunk) break; + else h += cur.height; + } + } + return h; + } + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line) { + var order = line.order; + if (order == null) order = line.order = bidiOrdering(line.text); + return order; + } + + // HISTORY + + function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = this.lastSelOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); + return histChange; + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) array.pop(); + else break; + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done); + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done); + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done); + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, ore are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + var last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + pushSelectionToHistory(doc.sel, hist.done); + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) hist.done.shift(); + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = hist.lastSelOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) signal(doc, "historyAdded"); + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + hist.done[hist.done.length - 1] = sel; + else + pushSelectionToHistory(sel, hist.done); + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastSelOp = opId; + if (options && options.clearRedo !== false) + clearSelectionEvents(hist.undone); + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + dest.push(sel); + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) return null; + for (var i = 0, out; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) return null; + for (var i = 0, nw = []; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])); + return nw; + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + for (var i = 0, copy = []; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue; + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m; + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } + } + } + return copy; + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue; + } + for (var j = 0; j < sub.changes.length; ++j) { + var cur = sub.changes[j]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break; + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // EVENT UTILITIES + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + var e_preventDefault = CodeMirror.e_preventDefault = function(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + }; + var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + }; + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } + var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);}; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var on = CodeMirror.on = function(emitter, type, f) { + if (emitter.addEventListener) + emitter.addEventListener(type, f, false); + else if (emitter.attachEvent) + emitter.attachEvent("on" + type, f); + else { + var map = emitter._handlers || (emitter._handlers = {}); + var arr = map[type] || (map[type] = []); + arr.push(f); + } + }; + + var noHandlers = [] + function getHandlers(emitter, type, copy) { + var arr = emitter._handlers && emitter._handlers[type] + if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers + else return arr || noHandlers + } + + var off = CodeMirror.off = function(emitter, type, f) { + if (emitter.removeEventListener) + emitter.removeEventListener(type, f, false); + else if (emitter.detachEvent) + emitter.detachEvent("on" + type, f); + else { + var handlers = getHandlers(emitter, type, false) + for (var i = 0; i < handlers.length; ++i) + if (handlers[i] == f) { handlers.splice(i, 1); break; } + } + }; + + var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { + var handlers = getHandlers(emitter, type, true) + if (!handlers.length) return; + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args); + }; + + var orphanDelayedCallbacks = null; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + function signalLater(emitter, type /*, values...*/) { + var arr = getHandlers(emitter, type, false) + if (!arr.length) return; + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); + } + function bnd(f) {return function(){f.apply(null, args);};}; + for (var i = 0; i < arr.length; ++i) + list.push(bnd(arr[i])); + } + + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) delayed[i](); + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) return; + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) + set.push(arr[i]); + } + + function hasHandler(emitter, type) { + return getHandlers(emitter, type).length > 0 + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // MISC UTILITIES + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerGap = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + function Delayed() {this.id = null;} + Delayed.prototype.set = function(ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + return n + (end - i); + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + }; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + var findColumn = CodeMirror.findColumn = function(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) nextTab = string.length; + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + return pos + Math.min(skipped, goal - col); + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) return pos; + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; + else if (ie) // Suppress mysterious IE10 errors + selectInput = function(node) { try { node.select(); } catch(_e) {} }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + if (array[i] == elt) return i; + return -1; + } + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); + return out; + } + + function nothing() {} + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + nothing.prototype = base; + inst = new nothing(); + } + if (props) copyObj(props, inst); + return inst; + }; + + function copyObj(obj, target, overwrite) { + if (!target) target = {}; + for (var prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop]; + return target; + } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args);}; + } + + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var isWordCharBasic = CodeMirror.isWordChar = function(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); + }; + function isWordChar(ch, helper) { + if (!helper) return isWordCharBasic(ch); + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; + return helper.test(ch); + } + + function isEmpty(obj) { + for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; + return true; + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } + + // DOM UTILITIES + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") e.appendChild(document.createTextNode(content)); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + var range; + if (document.createRange) range = function(node, start, end, endNode) { + var r = document.createRange(); + r.setEnd(endNode || node, end); + r.setStart(node, start); + return r; + }; + else range = function(node, start, end) { + var r = document.body.createTextRange(); + try { r.moveToElementText(node.parentNode); } + catch(e) { return r; } + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r; + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild); + return e; + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e); + } + + var contains = CodeMirror.contains = function(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + child = child.parentNode; + if (parent.contains) + return parent.contains(child); + do { + if (child.nodeType == 11) child = child.host; + if (child == parent) return true; + } while (child = child.parentNode); + }; + + function activeElt() { + var activeElement = document.activeElement; + while (activeElement && activeElement.root && activeElement.root.activeElement) + activeElement = activeElement.root.activeElement; + return activeElement; + } + // Older versions of IE throws unspecified error when touching + // document.activeElement in some cases (during loading, in iframe) + if (ie && ie_version < 11) activeElt = function() { + try { return document.activeElement; } + catch(e) { return document.body; } + }; + + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); } + var rmClass = CodeMirror.rmClass = function(node, cls) { + var current = node.className; + var match = classTest(cls).exec(current); + if (match) { + var after = current.slice(match.index + match[0].length); + node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); + } + }; + var addClass = CodeMirror.addClass = function(node, cls) { + var current = node.className; + if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls; + }; + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; + return b; + } + + // WINDOW-WIDE EVENTS + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.body.getElementsByClassName) return; + var byClass = document.body.getElementsByClassName("CodeMirror"); + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) f(cm); + } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) return; + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + forEachCodeMirror(onResize); + }, 100); + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function() { + forEachCodeMirror(onBlur); + }); + } + + // FEATURE DETECTION + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + node.setAttribute("cm-text", ""); + return node; + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) return badBidiRects; + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + var r1 = range(txt, 1, 2).getBoundingClientRect(); + removeChildren(measure); + if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780) + return badBidiRects = (r1.right - r0.right < 3); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLinesAuto = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result; + } : function(string){return string.split(/\r\n?|\n/);}; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + var hasCopyEvent = (function() { + var e = elt("div"); + if ("oncopy" in e) return true; + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function"; + })(); + + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects; + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; + } + + // KEY NAMES + + var keyNames = CodeMirror.keyNames = { + 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", + 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" + }; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr"); + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } + } + if (!found) f(from, to, "ltr"); + } + + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } + function bidiRight(part) { return part.level % 2 ? part.from : part.to; } + + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } + function lineRight(line) { + var order = getOrder(line); + if (!order) return line.text.length; + return bidiRight(lst(order)); + } + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) lineN = lineNo(visual); + var order = getOrder(visual); + var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); + return Pos(lineN, ch); + } + function lineEnd(cm, lineN) { + var merged, line = getLine(cm.doc, lineN); + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + lineN = null; + } + var order = getOrder(line); + var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); + return Pos(lineN == null ? lineNo(line) : lineN, ch); + } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS); + } + return start; + } + + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + bidiOther = null; + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) return i; + if ((cur.from == pos || cur.to == pos)) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + if (cur.from != cur.to) bidiOther = found; + return i; + } else { + if (cur.from != cur.to) bidiOther = i; + return found; + } + } + } + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar(line.text.charAt(pos))); + return pos; + } + + // This is needed in order to move 'visually' through bi-directional + // text -- i.e., pressing left should make the cursor go left, even + // when in RTL text. The tricky part is the 'jumps', where RTL and + // LTR text touch each other. This often requires the cursor offset + // to move more than one unit, in order to visually move one unit. + function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line); + if (!bidi) return moveLogically(line, start, dir, byUnit); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); + + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; + } else { + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); + } + } + } + + function moveLogically(line, start, dir, byUnit) { + var target = start + dir; + if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; + return target < 0 || target > line.text.length ? null : target; + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6ff + var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm"; + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code); + else if (0x590 <= code && code <= 0x5f4) return "R"; + else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600); + else if (0x6ee <= code && code <= 0x8ac) return "r"; + else if (0x2000 <= code && code <= 0x200b) return "w"; + else if (code == 0x200c) return "b"; + else return "L"; + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L"; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str) { + if (!bidiRE.test(str)) return false; + var len = str.length, types = []; + for (var i = 0, type; i < len; ++i) + types.push(type = charType(str.charCodeAt(i))); + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i = 0, prev = outerType; i < len; ++i) { + var type = types[i]; + if (type == "m") types[i] = prev; + else prev = type; + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (type == "1" && cur == "r") types[i] = "n"; + else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i = 1, prev = types[0]; i < len - 1; ++i) { + var type = types[i]; + if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; + else if (type == "," && prev == types[i+1] && + (prev == "1" || prev == "n")) types[i] = prev; + prev = type; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i = 0; i < len; ++i) { + var type = types[i]; + if (type == ",") types[i] = "N"; + else if (type == "%") { + for (var end = i + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (cur == "L" && type == "1") types[i] = "L"; + else if (isStrong.test(type)) cur = type; + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + var before = (i ? types[i-1] : outerType) == "L"; + var after = (end < len ? types[end] : outerType) == "L"; + var replace = before || after ? "L" : "R"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + var start = i; + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)); + } else { + var pos = i, at = order.length; + for (++i; i < len && types[i] != "L"; ++i) {} + for (var j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); + var nstart = j; + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)); + pos = j; + } else ++j; + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + if (order[0].level == 2) + order.unshift(new BidiSpan(1, order[0].to, order[0].to)); + if (order[0].level != lst(order).level) + order.push(new BidiSpan(order[0].level, len, len)); + + return order; + }; + })(); + + // THE END + + CodeMirror.version = "5.17.0"; + + return CodeMirror; +}); + + + +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// TODO actually recognize syntax of TypeScript constructs + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +function expressionAllowed(stream, state, backUp) { + return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) || + (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) +} + +CodeMirror.defineMode("javascript", function(config, parserConfig) { + var indentUnit = config.indentUnit; + var statementIndent = parserConfig.statementIndent; + var jsonldMode = parserConfig.jsonld; + var jsonMode = parserConfig.json || jsonldMode; + var isTS = parserConfig.typescript; + var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; + + // Tokenizer + + var keywords = function(){ + function kw(type) {return {type: type, style: "keyword"};} + var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); + var operator = kw("operator"), atom = {type: "atom", style: "atom"}; + + var jsKeywords = { + "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, + "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C, + "var": kw("var"), "const": kw("var"), "let": kw("var"), + "function": kw("function"), "catch": kw("catch"), + "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), + "in": operator, "typeof": operator, "instanceof": operator, + "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, + "this": kw("this"), "class": kw("class"), "super": kw("atom"), + "yield": C, "export": kw("export"), "import": kw("import"), "extends": C, + "await": C, "async": kw("async") + }; + + // Extend the 'normal' keywords with the TypeScript language extensions + if (isTS) { + var type = {type: "variable", style: "variable-3"}; + var tsKeywords = { + // object-like things + "interface": kw("class"), + "implements": C, + "namespace": C, + "module": kw("module"), + "enum": kw("module"), + + // scope modifiers + "public": kw("modifier"), + "private": kw("modifier"), + "protected": kw("modifier"), + "abstract": kw("modifier"), + + // operators + "as": operator, + + // types + "string": type, "number": type, "boolean": type, "any": type + }; + + for (var attr in tsKeywords) { + jsKeywords[attr] = tsKeywords[attr]; + } + } + + return jsKeywords; + }(); + + var isOperatorChar = /[+\-*&%=<>!?|~^]/; + var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; + + function readRegexp(stream) { + var escaped = false, next, inSet = false; + while ((next = stream.next()) != null) { + if (!escaped) { + if (next == "/" && !inSet) return; + if (next == "[") inSet = true; + else if (inSet && next == "]") inSet = false; + } + escaped = !escaped && next == "\\"; + } + } + + // Used as scratch variables to communicate multiple values without + // consing up tons of objects. + var type, content; + function ret(tp, style, cont) { + type = tp; content = cont; + return style; + } + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { + return ret("number", "number"); + } else if (ch == "." && stream.match("..")) { + return ret("spread", "meta"); + } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + return ret(ch); + } else if (ch == "=" && stream.eat(">")) { + return ret("=>", "operator"); + } else if (ch == "0" && stream.eat(/x/i)) { + stream.eatWhile(/[\da-f]/i); + return ret("number", "number"); + } else if (ch == "0" && stream.eat(/o/i)) { + stream.eatWhile(/[0-7]/i); + return ret("number", "number"); + } else if (ch == "0" && stream.eat(/b/i)) { + stream.eatWhile(/[01]/i); + return ret("number", "number"); + } else if (/\d/.test(ch)) { + stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); + return ret("number", "number"); + } else if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } else if (stream.eat("/")) { + stream.skipToEnd(); + return ret("comment", "comment"); + } else if (expressionAllowed(stream, state, 1)) { + readRegexp(stream); + stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); + return ret("regexp", "string-2"); + } else { + stream.eatWhile(isOperatorChar); + return ret("operator", "operator", stream.current()); + } + } else if (ch == "`") { + state.tokenize = tokenQuasi; + return tokenQuasi(stream, state); + } else if (ch == "#") { + stream.skipToEnd(); + return ret("error", "error"); + } else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return ret("operator", "operator", stream.current()); + } else if (wordRE.test(ch)) { + stream.eatWhile(wordRE); + var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; + return (known && state.lastType != ".") ? ret(known.type, known.style, word) : + ret("variable", "variable", word); + } + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next; + if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ + state.tokenize = tokenBase; + return ret("jsonld-keyword", "meta"); + } + while ((next = stream.next()) != null) { + if (next == quote && !escaped) break; + escaped = !escaped && next == "\\"; + } + if (!escaped) state.tokenize = tokenBase; + return ret("string", "string"); + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + function tokenQuasi(stream, state) { + var escaped = false, next; + while ((next = stream.next()) != null) { + if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && next == "\\"; + } + return ret("quasi", "string-2", stream.current()); + } + + var brackets = "([{}])"; + // This is a crude lookahead trick to try and notice that we're + // parsing the argument patterns for a fat-arrow function before we + // actually hit the arrow token. It only works if the arrow is on + // the same line as the arguments and there's no strange noise + // (comments) in between. Fallback is to only notice when we hit the + // arrow, and not declare the arguments as locals for the arrow + // body. + function findFatArrow(stream, state) { + if (state.fatArrowAt) state.fatArrowAt = null; + var arrow = stream.string.indexOf("=>", stream.start); + if (arrow < 0) return; + + var depth = 0, sawSomething = false; + for (var pos = arrow - 1; pos >= 0; --pos) { + var ch = stream.string.charAt(pos); + var bracket = brackets.indexOf(ch); + if (bracket >= 0 && bracket < 3) { + if (!depth) { ++pos; break; } + if (--depth == 0) break; + } else if (bracket >= 3 && bracket < 6) { + ++depth; + } else if (wordRE.test(ch)) { + sawSomething = true; + } else if (/["'\/]/.test(ch)) { + return; + } else if (sawSomething && !depth) { + ++pos; + break; + } + } + if (sawSomething && !depth) state.fatArrowAt = pos; + } + + // Parser + + var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; + + function JSLexical(indented, column, type, align, prev, info) { + this.indented = indented; + this.column = column; + this.type = type; + this.prev = prev; + this.info = info; + if (align != null) this.align = align; + } + + function inScope(state, varname) { + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return true; + for (var cx = state.context; cx; cx = cx.prev) { + for (var v = cx.vars; v; v = v.next) + if (v.name == varname) return true; + } + } + + function parseJS(state, style, type, content, stream) { + var cc = state.cc; + // Communicate our context to the combinators. + // (Less wasteful than consing up a hundred closures on every call.) + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; + + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = true; + + while(true) { + var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; + if (combinator(type, content)) { + while(cc.length && cc[cc.length - 1].lex) + cc.pop()(); + if (cx.marked) return cx.marked; + if (type == "variable" && inScope(state, content)) return "variable-2"; + return style; + } + } + } + + // Combinator utils + + var cx = {state: null, column: null, marked: null, cc: null}; + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); + } + function cont() { + pass.apply(null, arguments); + return true; + } + function register(varname) { + function inList(list) { + for (var v = list; v; v = v.next) + if (v.name == varname) return true; + return false; + } + var state = cx.state; + cx.marked = "def"; + if (state.context) { + if (inList(state.localVars)) return; + state.localVars = {name: varname, next: state.localVars}; + } else { + if (inList(state.globalVars)) return; + if (parserConfig.globalVars) + state.globalVars = {name: varname, next: state.globalVars}; + } + } + + // Combinators + + var defaultVars = {name: "this", next: {name: "arguments"}}; + function pushcontext() { + cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; + cx.state.localVars = defaultVars; + } + function popcontext() { + cx.state.localVars = cx.state.context.vars; + cx.state.context = cx.state.context.prev; + } + function pushlex(type, info) { + var result = function() { + var state = cx.state, indent = state.indented; + if (state.lexical.type == "stat") indent = state.lexical.indented; + else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) + indent = outer.indented; + state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); + }; + result.lex = true; + return result; + } + function poplex() { + var state = cx.state; + if (state.lexical.prev) { + if (state.lexical.type == ")") + state.indented = state.lexical.indented; + state.lexical = state.lexical.prev; + } + } + poplex.lex = true; + + function expect(wanted) { + function exp(type) { + if (type == wanted) return cont(); + else if (wanted == ";") return pass(); + else return cont(exp); + }; + return exp; + } + + function statement(type, value) { + if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); + if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); + if (type == "keyword b") return cont(pushlex("form"), statement, poplex); + if (type == "{") return cont(pushlex("}"), block, poplex); + if (type == ";") return cont(); + if (type == "if") { + if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) + cx.state.cc.pop()(); + return cont(pushlex("form"), expression, statement, poplex, maybeelse); + } + if (type == "function") return cont(functiondef); + if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); + if (type == "variable") return cont(pushlex("stat"), maybelabel); + if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), + block, poplex, poplex); + if (type == "case") return cont(expression, expect(":")); + if (type == "default") return cont(expect(":")); + if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), + statement, poplex, popcontext); + if (type == "class") return cont(pushlex("form"), className, poplex); + if (type == "export") return cont(pushlex("stat"), afterExport, poplex); + if (type == "import") return cont(pushlex("stat"), afterImport, poplex); + if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex) + if (type == "async") return cont(statement) + return pass(pushlex("stat"), expression, expect(";"), poplex); + } + function expression(type) { + return expressionInner(type, false); + } + function expressionNoComma(type) { + return expressionInner(type, true); + } + function expressionInner(type, noComma) { + if (cx.state.fatArrowAt == cx.stream.start) { + var body = noComma ? arrowBodyNoComma : arrowBody; + if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); + else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); + } + + var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; + if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); + if (type == "function") return cont(functiondef, maybeop); + if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression); + if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); + if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); + if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); + if (type == "{") return contCommasep(objprop, "}", null, maybeop); + if (type == "quasi") return pass(quasi, maybeop); + if (type == "new") return cont(maybeTarget(noComma)); + return cont(); + } + function maybeexpression(type) { + if (type.match(/[;\}\)\],]/)) return pass(); + return pass(expression); + } + function maybeexpressionNoComma(type) { + if (type.match(/[;\}\)\],]/)) return pass(); + return pass(expressionNoComma); + } + + function maybeoperatorComma(type, value) { + if (type == ",") return cont(expression); + return maybeoperatorNoComma(type, value, false); + } + function maybeoperatorNoComma(type, value, noComma) { + var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; + var expr = noComma == false ? expression : expressionNoComma; + if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); + if (type == "operator") { + if (/\+\+|--/.test(value)) return cont(me); + if (value == "?") return cont(expression, expect(":"), expr); + return cont(expr); + } + if (type == "quasi") { return pass(quasi, me); } + if (type == ";") return; + if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); + if (type == ".") return cont(property, me); + if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); + } + function quasi(type, value) { + if (type != "quasi") return pass(); + if (value.slice(value.length - 2) != "${") return cont(quasi); + return cont(expression, continueQuasi); + } + function continueQuasi(type) { + if (type == "}") { + cx.marked = "string-2"; + cx.state.tokenize = tokenQuasi; + return cont(quasi); + } + } + function arrowBody(type) { + findFatArrow(cx.stream, cx.state); + return pass(type == "{" ? statement : expression); + } + function arrowBodyNoComma(type) { + findFatArrow(cx.stream, cx.state); + return pass(type == "{" ? statement : expressionNoComma); + } + function maybeTarget(noComma) { + return function(type) { + if (type == ".") return cont(noComma ? targetNoComma : target); + else return pass(noComma ? expressionNoComma : expression); + }; + } + function target(_, value) { + if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } + } + function targetNoComma(_, value) { + if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } + } + function maybelabel(type) { + if (type == ":") return cont(poplex, statement); + return pass(maybeoperatorComma, expect(";"), poplex); + } + function property(type) { + if (type == "variable") {cx.marked = "property"; return cont();} + } + function objprop(type, value) { + if (type == "async") return cont(objprop); + if (type == "variable" || cx.style == "keyword") { + cx.marked = "property"; + if (value == "get" || value == "set") return cont(getterSetter); + return cont(afterprop); + } else if (type == "number" || type == "string") { + cx.marked = jsonldMode ? "property" : (cx.style + " property"); + return cont(afterprop); + } else if (type == "jsonld-keyword") { + return cont(afterprop); + } else if (type == "modifier") { + return cont(objprop) + } else if (type == "[") { + return cont(expression, expect("]"), afterprop); + } else if (type == "spread") { + return cont(expression); + } + } + function getterSetter(type) { + if (type != "variable") return pass(afterprop); + cx.marked = "property"; + return cont(functiondef); + } + function afterprop(type) { + if (type == ":") return cont(expressionNoComma); + if (type == "(") return pass(functiondef); + } + function commasep(what, end) { + function proceed(type, value) { + if (type == ",") { + var lex = cx.state.lexical; + if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; + return cont(function(type, value) { + if (type == end || value == end) return pass() + return pass(what) + }, proceed); + } + if (type == end || value == end) return cont(); + return cont(expect(end)); + } + return function(type, value) { + if (type == end || value == end) return cont(); + return pass(what, proceed); + }; + } + function contCommasep(what, end, info) { + for (var i = 3; i < arguments.length; i++) + cx.cc.push(arguments[i]); + return cont(pushlex(end, info), commasep(what, end), poplex); + } + function block(type) { + if (type == "}") return cont(); + return pass(statement, block); + } + function maybetype(type) { + if (isTS && type == ":") return cont(typeexpr); + } + function maybedefault(_, value) { + if (value == "=") return cont(expressionNoComma); + } + function typeexpr(type) { + if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);} + } + function afterType(type, value) { + if (value == "<") return cont(commasep(typeexpr, ">"), afterType) + if (type == "[") return cont(expect("]"), afterType) + } + function vardef() { + return pass(pattern, maybetype, maybeAssign, vardefCont); + } + function pattern(type, value) { + if (type == "modifier") return cont(pattern) + if (type == "variable") { register(value); return cont(); } + if (type == "spread") return cont(pattern); + if (type == "[") return contCommasep(pattern, "]"); + if (type == "{") return contCommasep(proppattern, "}"); + } + function proppattern(type, value) { + if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { + register(value); + return cont(maybeAssign); + } + if (type == "variable") cx.marked = "property"; + if (type == "spread") return cont(pattern); + if (type == "}") return pass(); + return cont(expect(":"), pattern, maybeAssign); + } + function maybeAssign(_type, value) { + if (value == "=") return cont(expressionNoComma); + } + function vardefCont(type) { + if (type == ",") return cont(vardef); + } + function maybeelse(type, value) { + if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); + } + function forspec(type) { + if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); + } + function forspec1(type) { + if (type == "var") return cont(vardef, expect(";"), forspec2); + if (type == ";") return cont(forspec2); + if (type == "variable") return cont(formaybeinof); + return pass(expression, expect(";"), forspec2); + } + function formaybeinof(_type, value) { + if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } + return cont(maybeoperatorComma, forspec2); + } + function forspec2(type, value) { + if (type == ";") return cont(forspec3); + if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } + return pass(expression, expect(";"), forspec3); + } + function forspec3(type) { + if (type != ")") cont(expression); + } + function functiondef(type, value) { + if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} + if (type == "variable") {register(value); return cont(functiondef);} + if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext); + } + function funarg(type) { + if (type == "spread") return cont(funarg); + return pass(pattern, maybetype, maybedefault); + } + function className(type, value) { + if (type == "variable") {register(value); return cont(classNameAfter);} + } + function classNameAfter(type, value) { + if (value == "extends") return cont(expression, classNameAfter); + if (type == "{") return cont(pushlex("}"), classBody, poplex); + } + function classBody(type, value) { + if (type == "variable" || cx.style == "keyword") { + if (value == "static") { + cx.marked = "keyword"; + return cont(classBody); + } + cx.marked = "property"; + if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); + return cont(functiondef, classBody); + } + if (value == "*") { + cx.marked = "keyword"; + return cont(classBody); + } + if (type == ";") return cont(classBody); + if (type == "}") return cont(); + } + function classGetterSetter(type) { + if (type != "variable") return pass(); + cx.marked = "property"; + return cont(); + } + function afterExport(_type, value) { + if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } + if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } + return pass(statement); + } + function afterImport(type) { + if (type == "string") return cont(); + return pass(importSpec, maybeFrom); + } + function importSpec(type, value) { + if (type == "{") return contCommasep(importSpec, "}"); + if (type == "variable") register(value); + if (value == "*") cx.marked = "keyword"; + return cont(maybeAs); + } + function maybeAs(_type, value) { + if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } + } + function maybeFrom(_type, value) { + if (value == "from") { cx.marked = "keyword"; return cont(expression); } + } + function arrayLiteral(type) { + if (type == "]") return cont(); + return pass(expressionNoComma, commasep(expressionNoComma, "]")); + } + + function isContinuedStatement(state, textAfter) { + return state.lastType == "operator" || state.lastType == "," || + isOperatorChar.test(textAfter.charAt(0)) || + /[,.]/.test(textAfter.charAt(0)); + } + + // Interface + + return { + startState: function(basecolumn) { + var state = { + tokenize: tokenBase, + lastType: "sof", + cc: [], + lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), + localVars: parserConfig.localVars, + context: parserConfig.localVars && {vars: parserConfig.localVars}, + indented: basecolumn || 0 + }; + if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") + state.globalVars = parserConfig.globalVars; + return state; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = false; + state.indented = stream.indentation(); + findFatArrow(stream, state); + } + if (state.tokenize != tokenComment && stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + if (type == "comment") return style; + state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; + return parseJS(state, style, type, content, stream); + }, + + indent: function(state, textAfter) { + if (state.tokenize == tokenComment) return CodeMirror.Pass; + if (state.tokenize != tokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; + // Kludge to prevent 'maybelse' from blocking lexical scope pops + if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { + var c = state.cc[i]; + if (c == poplex) lexical = lexical.prev; + else if (c != maybeelse) break; + } + if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; + if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") + lexical = lexical.prev; + var type = lexical.type, closing = firstChar == type; + + if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); + else if (type == "form" && firstChar == "{") return lexical.indented; + else if (type == "form") return lexical.indented + indentUnit; + else if (type == "stat") + return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); + else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) + return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); + else if (lexical.align) return lexical.column + (closing ? 0 : 1); + else return lexical.indented + (closing ? 0 : indentUnit); + }, + + electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, + blockCommentStart: jsonMode ? null : "/*", + blockCommentEnd: jsonMode ? null : "*/", + lineComment: jsonMode ? null : "//", + fold: "brace", + closeBrackets: "()[]{}''\"\"``", + + helperType: jsonMode ? "json" : "javascript", + jsonldMode: jsonldMode, + jsonMode: jsonMode, + + expressionAllowed: expressionAllowed, + skipExpression: function(state) { + var top = state.cc[state.cc.length - 1] + if (top == expression || top == expressionNoComma) state.cc.pop() + } + }; +}); + +CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); + +CodeMirror.defineMIME("text/javascript", "javascript"); +CodeMirror.defineMIME("text/ecmascript", "javascript"); +CodeMirror.defineMIME("application/javascript", "javascript"); +CodeMirror.defineMIME("application/x-javascript", "javascript"); +CodeMirror.defineMIME("application/ecmascript", "javascript"); +CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); +CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); +CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true}); +CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); +CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); + +}); + +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + var defaults = { + pairs: "()[]{}''\"\"", + triples: "", + explode: "[]{}" + }; + + var Pos = CodeMirror.Pos; + + CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.removeKeyMap(keyMap); + cm.state.closeBrackets = null; + } + if (val) { + cm.state.closeBrackets = val; + cm.addKeyMap(keyMap); + } + }); + + function getOption(conf, name) { + if (name == "pairs" && typeof conf == "string") return conf; + if (typeof conf == "object" && conf[name] != null) return conf[name]; + return defaults[name]; + } + + var bind = defaults.pairs + "`"; + var keyMap = {Backspace: handleBackspace, Enter: handleEnter}; + for (var i = 0; i < bind.length; i++) + keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i)); + + function handler(ch) { + return function(cm) { return handleChar(cm, ch); }; + } + + function getConfig(cm) { + var deflt = cm.state.closeBrackets; + if (!deflt) return null; + var mode = cm.getModeAt(cm.getCursor()); + return mode.closeBrackets || deflt; + } + + function handleBackspace(cm) { + var conf = getConfig(cm); + if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; + + var pairs = getOption(conf, "pairs"); + var ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) return CodeMirror.Pass; + var around = charsAround(cm, ranges[i].head); + if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass; + } + for (var i = ranges.length - 1; i >= 0; i--) { + var cur = ranges[i].head; + cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete"); + } + } + + function handleEnter(cm) { + var conf = getConfig(cm); + var explode = conf && getOption(conf, "explode"); + if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass; + + var ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) return CodeMirror.Pass; + var around = charsAround(cm, ranges[i].head); + if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass; + } + cm.operation(function() { + cm.replaceSelection("\n\n", null); + cm.execCommand("goCharLeft"); + ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var line = ranges[i].head.line; + cm.indentLine(line, null, true); + cm.indentLine(line + 1, null, true); + } + }); + } + + function contractSelection(sel) { + var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0; + return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)), + head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))}; + } + + function handleChar(cm, ch) { + var conf = getConfig(cm); + if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; + + var pairs = getOption(conf, "pairs"); + var pos = pairs.indexOf(ch); + if (pos == -1) return CodeMirror.Pass; + var triples = getOption(conf, "triples"); + + var identical = pairs.charAt(pos + 1) == ch; + var ranges = cm.listSelections(); + var opening = pos % 2 == 0; + + var type; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i], cur = range.head, curType; + var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1)); + if (opening && !range.empty()) { + curType = "surround"; + } else if ((identical || !opening) && next == ch) { + if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) + curType = "skipThree"; + else + curType = "skip"; + } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 && + cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch && + (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) { + curType = "addFour"; + } else if (identical) { + if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both"; + else return CodeMirror.Pass; + } else if (opening && (cm.getLine(cur.line).length == cur.ch || + isClosingBracket(next, pairs) || + /\s/.test(next))) { + curType = "both"; + } else { + return CodeMirror.Pass; + } + if (!type) type = curType; + else if (type != curType) return CodeMirror.Pass; + } + + var left = pos % 2 ? pairs.charAt(pos - 1) : ch; + var right = pos % 2 ? ch : pairs.charAt(pos + 1); + cm.operation(function() { + if (type == "skip") { + cm.execCommand("goCharRight"); + } else if (type == "skipThree") { + for (var i = 0; i < 3; i++) + cm.execCommand("goCharRight"); + } else if (type == "surround") { + var sels = cm.getSelections(); + for (var i = 0; i < sels.length; i++) + sels[i] = left + sels[i] + right; + cm.replaceSelections(sels, "around"); + sels = cm.listSelections().slice(); + for (var i = 0; i < sels.length; i++) + sels[i] = contractSelection(sels[i]); + cm.setSelections(sels); + } else if (type == "both") { + cm.replaceSelection(left + right, null); + cm.triggerElectric(left + right); + cm.execCommand("goCharLeft"); + } else if (type == "addFour") { + cm.replaceSelection(left + left + left + left, "before"); + cm.execCommand("goCharRight"); + } + }); + } + + function isClosingBracket(ch, pairs) { + var pos = pairs.lastIndexOf(ch); + return pos > -1 && pos % 2 == 1; + } + + function charsAround(cm, pos) { + var str = cm.getRange(Pos(pos.line, pos.ch - 1), + Pos(pos.line, pos.ch + 1)); + return str.length == 2 ? str : null; + } + + // Project the token type that will exists after the given char is + // typed, and use it to determine whether it would cause the start + // of a string token. + function enteringString(cm, pos, ch) { + var line = cm.getLine(pos.line); + var token = cm.getTokenAt(pos); + if (/\bstring2?\b/.test(token.type)) return false; + var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4); + stream.pos = stream.start = token.start; + for (;;) { + var type1 = cm.getMode().token(stream, token.state); + if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1); + stream.start = stream.pos; + } + } +}); diff --git a/game/config.js b/game/config.js index f2c9406e5..ee59c5f90 100644 --- a/game/config.js +++ b/game/config.js @@ -1,233 +1,233 @@ -window.config={ - forbidai:['ns_liuzhang','xin_yuji','re_yuji'], - forbidai_user:[], - forbidall:[], - forbidstone:['zhugedan','pal_xuanxiao','hs_malfurion','lusu','chenlin','hs_siwangzhiyi', - 'gjqt_bailitusu','yuanshao','swd_anka','swd_nicole','daqiao','re_daqiao','hs_xuanzhuanjijia', - 'zhuran','huatuo','swd_tuwei','hs_guldan','wangyi','caoang','swd_guyue','swd_rongshuang', - 'swd_jiangziya','guojia','re_guojia','shen_caocao','swd_qiner','caopi','hs_yngvar','guansuo', - 'gjqt_aruan','swd_hanluo','hs_anduin','swd_huanglei','yxs_yujix','yxs_luzhishen','swd_muyun','ow_tianshi', - 'pal_yuejinzhao','hs_antonidas','xushi','hs_lreno' - ], - forbidchess:['hetaihou','swd_kangnalishi'], - forbidboss:['caiwenji','gjqt_aruan','pal_xuanxiao','swd_hupo'], - forbiddouble:['zhugedan','swd_kangnalishi','dongzhuo','wutugu','hs_siwangzhiyi','hs_ronghejuren','hs_shanlingjuren'], - forbidthreecard:['qiankunbiao','shenhuofeiya','gw_ciguhanshuang','gw_birinongwu','gw_qinpendayu','gw_poxiao'], - all:{ - sgscharacters:['standard','shenhua','xinghuoliaoyuan','refresh','yijiang','sp','extra','old','mobile','tw'], - sgscards:['standard','extra','sp','guozhan'], - sgsmodes:['identity','guozhan','versus','doudizhu','single','brawl','connect'], - stockmode:['identity','guozhan','versus','boss','doudizhu','single','chess','stone','connect','brawl','tafang'], - stockextension:['boss','cardpile','coin','wuxing'], - layout:['default','newlayout'], - theme:['woodden','music','simple'], - card_font:['xiaozhuan','huangcao','caoshu','xingshu'], - double_hp:['hejiansan','pingjun','zuidazhi','zuixiaozhi','zonghe'], - image_background_filter:['default','blur','gray','sepia','invert','saturate','contrast','hue','brightness'], - }, - - game:'sgs', - duration:500, - hoveration:1000, - doubleclick_intro:true, - cheat:false, - volumn_background:8, - volumn_audio:8, - - connect_avatar:'caocao', - connect_nickname:'无名玩家', - config_menu:true, - auto_popped_config:true, - auto_popped_history:false, - auto_skill:true, - auto_confirm:true, - enable_drag:true, - enable_pressure:false, - pressure_taptic:true, - hover_handcard:true, - hover_all:true, - right_info:true, - longpress_info:true, - long_info:true, - background_music:'music_default', - background_audio:true, - background_speak:true, - glow_phase:'yellow', - die_move:'flip', - - skin:{}, - gameRecord:{}, - extensionInfo:{}, - autoskilllist:[], - hiddenModePack:[], - hiddenCharacterPack:[], - hiddenCardPack:[], - hiddenPlayPack:[], - hiddenBackgroundPack:[], - customBackgroundPack:[], - favouriteCharacter:[], - favouriteMode:[], - recentIP:[], - vintageSkills:[], - alteredSkills:[], - brokenFile:[], - - theme:'woodden', - layout:'mobile', - card_style:'default', - cardback_style:'default', - hp_style:'default', - - image_character:'default', - image_background:'default', - - asset_image:true, - asset_font:true, - - card_font:'xiaozhuan', - show_statusbar_ios:'off', - show_statusbar_android:false, - show_name:true, - show_replay:false, - show_round_menu:true, - show_pause:true, - show_auto:true, - show_volumn:true, - show_cardpile:true, - only_fullskin:true, - show_connect:true, - show_wuxie:false, - show_wuxie_self:true, - show_stat:true, - show_playerids:true, - show_scrollbar:false, - mousewheel:true, - fold_card:true, - threed_card:false, - vertical_scroll:false, - handcard_scroll:0, - animation:true, - skill_animation_type:'default', - paused:false, - title:false, - button_press:true, - damage_shake:true, - log_highlight:true, - player_border:'normal', - radius_size:'default', - - modeconfig:false, - gameconfig:false, - appearence:false, - video:'20', - coin:0, - - intro:'i', - right_click:'pause', - sort:'type_sort', - - cards:['standard','ex','extra','sp','classic','basic'], - characters:['standard','shenhua','sp','yijiang','refresh','xinghuoliaoyuan','mobile','extra'], - connect_characters:[], - connect_cards:[], - plays:[], - extensions:[], - banned:[], - bannedcards:[], - forbidlist:[], - bannedpile:{}, - customcardpile:{}, - addedpile:{}, - - mode:'identity', - mode_config:{ - global:{ - player_number:8, - auto_identity:'off', - double_character:false, - save_progress:true, - free_choose:true, - swap:true, - change_identity:true, - battle_number:3, - double_hp:'pingjun', - }, - identity:{ - identity:[ - ['zhu','fan'], - ['zhu','nei','fan'], - ['zhu','zhong','nei','fan'], - ['zhu','zhong','nei','fan','fan'], - ['zhu','zhong','nei','fan','fan','fan'], - ['zhu','zhong','zhong','nei','fan','fan','fan'], - ['zhu','zhong','zhong','nei','fan','fan','fan','fan'], - ], - choice:{ - zhu:3, - zhong:4, - nei:5, - fan:3, - }, - show_identity:true, - difficulty:'normal', - dierestart:true - }, - guozhan:{ - difficulty:'normal', - initshow_draw:'mark', - dierestart:true - }, - }, - current_mode:{}, - customforbid:[], - forbid:[ - ['huashen'], - ['rehuashen'], - ['xinmanjuan'], - //['xinleiji','fuji'], - ['xinleiji','xinfu_jijun'], - ['reluanji','jueqing'], - ['lianying','rende'], - ['lianying','anxian'], - ['lianying','yinguo'], - ['lianying','qingjian'], - ['boss_juejing','rende'], - ['boss_juejing','anxian'], - ['boss_juejing','yinguo'], - ['boss_juejing','qingjian'], - ['shangshi','rende'], - ['shangshi','anxian'], - ['shangshi','yinguo'], - ['shangshi','qingjian'], - ['rende','relianying'], - ['anxian','relianying'], - ['yinguo','relianying'], - ['shenxing','relianying'], - ['qingjian','relianying'], - ['rende','yuling'], - ['anxian','yuling'], - ['yinguo','yuling'], - ['qingjian','yuling'], - //['qingnang','yiji'], - //['qingnang','reyiji'], - //['qingjian','tuntian'], - // ['yiji','tuntian'], - // ['reyiji','tuntian'], - ['tuntian','guidao'], - ['tuntian','tiandao'], - ['tuntian','huanshi'], - // ['tuntian','guicai'], - // ['jiang','chongzhen'], - // ['fenji','yuling'], - ['jiushi','guixin'], - ['xiuhua','qiaoxie'], - ['xiuhua','xuanfeng'], - ['xiuhua','duanxing'], - ['xiuhua','xiaoji'], - ['xiuhua','xiaoji'], - // ['jiushi','jushou'], - // ['jiushi','kuiwei'], - ['zishu','xinfu_songsang'], - ['zishu','shenxing'], - ] -}; +window.config={ + forbidai:['ns_liuzhang','xin_yuji','re_yuji'], + forbidai_user:[], + forbidall:[], + forbidstone:['zhugedan','pal_xuanxiao','hs_malfurion','lusu','chenlin','hs_siwangzhiyi', + 'gjqt_bailitusu','yuanshao','swd_anka','swd_nicole','daqiao','re_daqiao','hs_xuanzhuanjijia', + 'zhuran','huatuo','swd_tuwei','hs_guldan','wangyi','caoang','swd_guyue','swd_rongshuang', + 'swd_jiangziya','guojia','re_guojia','shen_caocao','swd_qiner','caopi','hs_yngvar','guansuo', + 'gjqt_aruan','swd_hanluo','hs_anduin','swd_huanglei','yxs_yujix','yxs_luzhishen','swd_muyun','ow_tianshi', + 'pal_yuejinzhao','hs_antonidas','xushi','hs_lreno' + ], + forbidchess:['hetaihou','swd_kangnalishi'], + forbidboss:['caiwenji','gjqt_aruan','pal_xuanxiao','swd_hupo'], + forbiddouble:['zhugedan','swd_kangnalishi','dongzhuo','wutugu','hs_siwangzhiyi','hs_ronghejuren','hs_shanlingjuren'], + forbidthreecard:['qiankunbiao','shenhuofeiya','gw_ciguhanshuang','gw_birinongwu','gw_qinpendayu','gw_poxiao'], + all:{ + sgscharacters:['standard','shenhua','xinghuoliaoyuan','refresh','yijiang','sp','extra','old','mobile','tw'], + sgscards:['standard','extra','sp','guozhan','zhulu'], + sgsmodes:['identity','guozhan','versus','doudizhu','single','brawl','connect'], + stockmode:['identity','guozhan','versus','boss','doudizhu','single','chess','stone','connect','brawl','tafang'], + stockextension:['boss','cardpile','coin','wuxing'], + layout:['default','newlayout'], + theme:['woodden','music','simple'], + card_font:['xiaozhuan','huangcao','caoshu','xingshu'], + double_hp:['hejiansan','pingjun','zuidazhi','zuixiaozhi','zonghe'], + image_background_filter:['default','blur','gray','sepia','invert','saturate','contrast','hue','brightness'], + }, + + game:'sgs', + duration:500, + hoveration:1000, + doubleclick_intro:true, + cheat:false, + volumn_background:8, + volumn_audio:8, + + connect_avatar:'caocao', + connect_nickname:'无名玩家', + config_menu:true, + auto_popped_config:true, + auto_popped_history:false, + auto_skill:true, + auto_confirm:true, + enable_drag:true, + enable_pressure:false, + pressure_taptic:true, + hover_handcard:true, + hover_all:true, + right_info:true, + longpress_info:true, + long_info:true, + background_music:'music_default', + background_audio:true, + background_speak:true, + glow_phase:'yellow', + die_move:'flip', + + skin:{}, + gameRecord:{}, + extensionInfo:{}, + autoskilllist:[], + hiddenModePack:[], + hiddenCharacterPack:[], + hiddenCardPack:[], + hiddenPlayPack:[], + hiddenBackgroundPack:[], + customBackgroundPack:[], + favouriteCharacter:[], + favouriteMode:[], + recentIP:[], + vintageSkills:[], + alteredSkills:[], + brokenFile:[], + + theme:'woodden', + layout:'mobile', + card_style:'default', + cardback_style:'default', + hp_style:'default', + + image_character:'default', + image_background:'default', + + asset_image:true, + asset_font:true, + + card_font:'xiaozhuan', + show_statusbar_ios:'off', + show_statusbar_android:false, + show_name:true, + show_replay:false, + show_round_menu:true, + show_pause:true, + show_auto:true, + show_volumn:true, + show_cardpile:true, + only_fullskin:true, + show_connect:true, + show_wuxie:false, + show_wuxie_self:true, + show_stat:true, + show_playerids:true, + show_scrollbar:false, + mousewheel:true, + fold_card:true, + threed_card:false, + vertical_scroll:false, + handcard_scroll:0, + animation:true, + skill_animation_type:'default', + paused:false, + title:false, + button_press:true, + damage_shake:true, + log_highlight:true, + player_border:'normal', + radius_size:'default', + + modeconfig:false, + gameconfig:false, + appearence:false, + video:'20', + coin:0, + + intro:'i', + right_click:'pause', + sort:'type_sort', + + cards:['standard','ex','extra','sp','classic','basic'], + characters:['standard','shenhua','sp','yijiang','refresh','xinghuoliaoyuan','mobile','extra'], + connect_characters:[], + connect_cards:[], + plays:[], + extensions:[], + banned:[], + bannedcards:[], + forbidlist:[], + bannedpile:{}, + customcardpile:{}, + addedpile:{}, + + mode:'identity', + mode_config:{ + global:{ + player_number:8, + auto_identity:'off', + double_character:false, + save_progress:true, + free_choose:true, + swap:true, + change_identity:true, + battle_number:3, + double_hp:'pingjun', + }, + identity:{ + identity:[ + ['zhu','fan'], + ['zhu','nei','fan'], + ['zhu','zhong','nei','fan'], + ['zhu','zhong','nei','fan','fan'], + ['zhu','zhong','nei','fan','fan','fan'], + ['zhu','zhong','zhong','nei','fan','fan','fan'], + ['zhu','zhong','zhong','nei','fan','fan','fan','fan'], + ], + choice:{ + zhu:3, + zhong:4, + nei:5, + fan:3, + }, + show_identity:true, + difficulty:'normal', + dierestart:true + }, + guozhan:{ + difficulty:'normal', + initshow_draw:'mark', + dierestart:true + }, + }, + current_mode:{}, + customforbid:[], + forbid:[ + ['huashen'], + ['rehuashen'], + ['xinmanjuan'], + //['xinleiji','fuji'], + ['xinleiji','xinfu_jijun'], + ['reluanji','jueqing'], + ['lianying','rende'], + ['lianying','anxian'], + ['lianying','yinguo'], + ['lianying','qingjian'], + ['boss_juejing','rende'], + ['boss_juejing','anxian'], + ['boss_juejing','yinguo'], + ['boss_juejing','qingjian'], + ['shangshi','rende'], + ['shangshi','anxian'], + ['shangshi','yinguo'], + ['shangshi','qingjian'], + ['rende','relianying'], + ['anxian','relianying'], + ['yinguo','relianying'], + ['shenxing','relianying'], + ['qingjian','relianying'], + ['rende','yuling'], + ['anxian','yuling'], + ['yinguo','yuling'], + ['qingjian','yuling'], + //['qingnang','yiji'], + //['qingnang','reyiji'], + //['qingjian','tuntian'], + // ['yiji','tuntian'], + // ['reyiji','tuntian'], + ['tuntian','guidao'], + ['tuntian','tiandao'], + ['tuntian','huanshi'], + // ['tuntian','guicai'], + // ['jiang','chongzhen'], + // ['fenji','yuling'], + ['jiushi','guixin'], + ['xiuhua','qiaoxie'], + ['xiuhua','xuanfeng'], + ['xiuhua','duanxing'], + ['xiuhua','xiaoji'], + ['xiuhua','xiaoji'], + // ['jiushi','jushou'], + // ['jiushi','kuiwei'], + ['zishu','xinfu_songsang'], + ['zishu','shenxing'], + ] +}; diff --git a/game/directory.js b/game/directory.js index 8c7a486b3..cdabbb940 100644 --- a/game/directory.js +++ b/game/directory.js @@ -1,176 +1,176 @@ -var fs=require('fs'); -var path=require('path'); -var exec = require('child_process').exec; -global.window=global; -require(__dirname+'/update.js'); -require(__dirname+'/asset.js'); - -var updates=window.noname_update; -var newversion=false; -var commit=false -if(process.argv[2]){ - if(/[0-9]/.test(process.argv[2][0])){ - newversion=true; - updates.update = updates.version; - updates.version = '1.9.' + process.argv[2]; - commit=updates.version; - } - else{ - commit=process.argv[2]; - } -} -var assetlist=''; -var skinlist='window.noname_skin_list={\n'; -var entrylist=[]; -var entrymap={}; -var get = function(dir,callback){ - fs.readdir(dir,function(err,list){ - var shift=function(){ - if(list.length){ - var filename=list.shift(); - var delay=false; - if(!/\.|~|_/.test(filename[0])){ - var url=dir+'/'+filename; - var stat=fs.statSync(url); - if(stat.isFile()){ - if(['.jpg','.png','.mp3','.ttf'].indexOf(path.extname(url))!=-1){ - var assetentry=path.relative(path.dirname(__dirname),url); - assetlist+=',\n\t\''+assetentry+'\''; - entrylist.push(assetentry); - } - } - else if(stat.isDirectory()){ - if(dir==path.dirname(__dirname)+'/image/skin'){ - fs.readdir(url,function(err,list){ - var num=0; - for(var i=0;i { - var updatelist='window.noname_update={\n\tversion:\''+updates.version+'\','; - updatelist+='\n\tupdate:\''+(updates.update||'')+'\','; - var apply=function(name,list){ - updatelist+='\n\t'+name+':[\n'; - for(var i=0;ib) return 1; - if (a { + var updatelist='window.noname_update={\n\tversion:\''+updates.version+'\','; + updatelist+='\n\tupdate:\''+(updates.update||'')+'\','; + var apply=function(name,list){ + updatelist+='\n\t'+name+':[\n'; + for(var i=0;ib) return 1; + if (a - -(c) 2009-2014 Stuart Knightley -Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown. - -JSZip uses the library pako released under the MIT license : -https://github.com/nodeca/pako/blob/master/LICENSE -*/ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.JSZip=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } - else if (isNaN(chr3)) { - enc4 = 64; - } - - output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); - - } - - return output; -}; - -// public method for decoding -exports.decode = function(input, utf8) { - var output = ""; - var chr1, chr2, chr3; - var enc1, enc2, enc3, enc4; - var i = 0; - - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - - while (i < input.length) { - - enc1 = _keyStr.indexOf(input.charAt(i++)); - enc2 = _keyStr.indexOf(input.charAt(i++)); - enc3 = _keyStr.indexOf(input.charAt(i++)); - enc4 = _keyStr.indexOf(input.charAt(i++)); - - chr1 = (enc1 << 2) | (enc2 >> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - output = output + String.fromCharCode(chr1); - - if (enc3 != 64) { - output = output + String.fromCharCode(chr2); - } - if (enc4 != 64) { - output = output + String.fromCharCode(chr3); - } - - } - - return output; - -}; - -},{}],2:[function(_dereq_,module,exports){ -'use strict'; -function CompressedObject() { - this.compressedSize = 0; - this.uncompressedSize = 0; - this.crc32 = 0; - this.compressionMethod = null; - this.compressedContent = null; -} - -CompressedObject.prototype = { - /** - * Return the decompressed content in an unspecified format. - * The format will depend on the decompressor. - * @return {Object} the decompressed content. - */ - getContent: function() { - return null; // see implementation - }, - /** - * Return the compressed content in an unspecified format. - * The format will depend on the compressed conten source. - * @return {Object} the compressed content. - */ - getCompressedContent: function() { - return null; // see implementation - } -}; -module.exports = CompressedObject; - -},{}],3:[function(_dereq_,module,exports){ -'use strict'; -exports.STORE = { - magic: "\x00\x00", - compress: function(content, compressionOptions) { - return content; // no compression - }, - uncompress: function(content) { - return content; // no compression - }, - compressInputType: null, - uncompressInputType: null -}; -exports.DEFLATE = _dereq_('./flate'); - -},{"./flate":8}],4:[function(_dereq_,module,exports){ -'use strict'; - -var utils = _dereq_('./utils'); - -var table = [ - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, - 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, - 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, - 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, - 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, - 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, - 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, - 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, - 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, - 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, - 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, - 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, - 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, - 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, - 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, - 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, - 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, - 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, - 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, - 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, - 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, - 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, - 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, - 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, - 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, - 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, - 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, - 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, - 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, - 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, - 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, - 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, - 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D -]; - -/** - * - * Javascript crc32 - * http://www.webtoolkit.info/ - * - */ -module.exports = function crc32(input, crc) { - if (typeof input === "undefined" || !input.length) { - return 0; - } - - var isArray = utils.getTypeOf(input) !== "string"; - - if (typeof(crc) == "undefined") { - crc = 0; - } - var x = 0; - var y = 0; - var b = 0; - - crc = crc ^ (-1); - for (var i = 0, iTop = input.length; i < iTop; i++) { - b = isArray ? input[i] : input.charCodeAt(i); - y = (crc ^ b) & 0xFF; - x = table[y]; - crc = (crc >>> 8) ^ x; - } - - return crc ^ (-1); -}; -// vim: set shiftwidth=4 softtabstop=4: - -},{"./utils":21}],5:[function(_dereq_,module,exports){ -'use strict'; -var utils = _dereq_('./utils'); - -function DataReader(data) { - this.data = null; // type : see implementation - this.length = 0; - this.index = 0; -} -DataReader.prototype = { - /** - * Check that the offset will not go too far. - * @param {string} offset the additional offset to check. - * @throws {Error} an Error if the offset is out of bounds. - */ - checkOffset: function(offset) { - this.checkIndex(this.index + offset); - }, - /** - * Check that the specifed index will not be too far. - * @param {string} newIndex the index to check. - * @throws {Error} an Error if the index is out of bounds. - */ - checkIndex: function(newIndex) { - if (this.length < newIndex || newIndex < 0) { - throw new Error("End of data reached (data length = " + this.length + ", asked index = " + (newIndex) + "). Corrupted zip ?"); - } - }, - /** - * Change the index. - * @param {number} newIndex The new index. - * @throws {Error} if the new index is out of the data. - */ - setIndex: function(newIndex) { - this.checkIndex(newIndex); - this.index = newIndex; - }, - /** - * Skip the next n bytes. - * @param {number} n the number of bytes to skip. - * @throws {Error} if the new index is out of the data. - */ - skip: function(n) { - this.setIndex(this.index + n); - }, - /** - * Get the byte at the specified index. - * @param {number} i the index to use. - * @return {number} a byte. - */ - byteAt: function(i) { - // see implementations - }, - /** - * Get the next number with a given byte size. - * @param {number} size the number of bytes to read. - * @return {number} the corresponding number. - */ - readInt: function(size) { - var result = 0, - i; - this.checkOffset(size); - for (i = this.index + size - 1; i >= this.index; i--) { - result = (result << 8) + this.byteAt(i); - } - this.index += size; - return result; - }, - /** - * Get the next string with a given byte size. - * @param {number} size the number of bytes to read. - * @return {string} the corresponding string. - */ - readString: function(size) { - return utils.transformTo("string", this.readData(size)); - }, - /** - * Get raw data without conversion, bytes. - * @param {number} size the number of bytes to read. - * @return {Object} the raw data, implementation specific. - */ - readData: function(size) { - // see implementations - }, - /** - * Find the last occurence of a zip signature (4 bytes). - * @param {string} sig the signature to find. - * @return {number} the index of the last occurence, -1 if not found. - */ - lastIndexOfSignature: function(sig) { - // see implementations - }, - /** - * Get the next date. - * @return {Date} the date. - */ - readDate: function() { - var dostime = this.readInt(4); - return new Date( - ((dostime >> 25) & 0x7f) + 1980, // year - ((dostime >> 21) & 0x0f) - 1, // month - (dostime >> 16) & 0x1f, // day - (dostime >> 11) & 0x1f, // hour - (dostime >> 5) & 0x3f, // minute - (dostime & 0x1f) << 1); // second - } -}; -module.exports = DataReader; - -},{"./utils":21}],6:[function(_dereq_,module,exports){ -'use strict'; -exports.base64 = false; -exports.binary = false; -exports.dir = false; -exports.createFolders = false; -exports.date = null; -exports.compression = null; -exports.compressionOptions = null; -exports.comment = null; -exports.unixPermissions = null; -exports.dosPermissions = null; - -},{}],7:[function(_dereq_,module,exports){ -'use strict'; -var utils = _dereq_('./utils'); - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.string2binary = function(str) { - return utils.string2binary(str); -}; - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.string2Uint8Array = function(str) { - return utils.transformTo("uint8array", str); -}; - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.uint8Array2String = function(array) { - return utils.transformTo("string", array); -}; - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.string2Blob = function(str) { - var buffer = utils.transformTo("arraybuffer", str); - return utils.arrayBuffer2Blob(buffer); -}; - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.arrayBuffer2Blob = function(buffer) { - return utils.arrayBuffer2Blob(buffer); -}; - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.transformTo = function(outputType, input) { - return utils.transformTo(outputType, input); -}; - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.getTypeOf = function(input) { - return utils.getTypeOf(input); -}; - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.checkSupport = function(type) { - return utils.checkSupport(type); -}; - -/** - * @deprecated - * This value will be removed in a future version without replacement. - */ -exports.MAX_VALUE_16BITS = utils.MAX_VALUE_16BITS; - -/** - * @deprecated - * This value will be removed in a future version without replacement. - */ -exports.MAX_VALUE_32BITS = utils.MAX_VALUE_32BITS; - - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.pretty = function(str) { - return utils.pretty(str); -}; - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.findCompression = function(compressionMethod) { - return utils.findCompression(compressionMethod); -}; - -/** - * @deprecated - * This function will be removed in a future version without replacement. - */ -exports.isRegExp = function (object) { - return utils.isRegExp(object); -}; - - -},{"./utils":21}],8:[function(_dereq_,module,exports){ -'use strict'; -var USE_TYPEDARRAY = (typeof Uint8Array !== 'undefined') && (typeof Uint16Array !== 'undefined') && (typeof Uint32Array !== 'undefined'); - -var pako = _dereq_("pako"); -exports.uncompressInputType = USE_TYPEDARRAY ? "uint8array" : "array"; -exports.compressInputType = USE_TYPEDARRAY ? "uint8array" : "array"; - -exports.magic = "\x08\x00"; -exports.compress = function(input, compressionOptions) { - return pako.deflateRaw(input, { - level : compressionOptions.level || -1 // default compression - }); -}; -exports.uncompress = function(input) { - return pako.inflateRaw(input); -}; - -},{"pako":24}],9:[function(_dereq_,module,exports){ -'use strict'; - -var base64 = _dereq_('./base64'); - -/** -Usage: - zip = new JSZip(); - zip.file("hello.txt", "Hello, World!").file("tempfile", "nothing"); - zip.folder("images").file("smile.gif", base64Data, {base64: true}); - zip.file("Xmas.txt", "Ho ho ho !", {date : new Date("December 25, 2007 00:00:01")}); - zip.remove("tempfile"); - - base64zip = zip.generate(); - -**/ - -/** - * Representation a of zip file in js - * @constructor - * @param {String=|ArrayBuffer=|Uint8Array=} data the data to load, if any (optional). - * @param {Object=} options the options for creating this objects (optional). - */ -function JSZip(data, options) { - // if this constructor is used without `new`, it adds `new` before itself: - if(!(this instanceof JSZip)) return new JSZip(data, options); - - // object containing the files : - // { - // "folder/" : {...}, - // "folder/data.txt" : {...} - // } - this.files = {}; - - this.comment = null; - - // Where we are in the hierarchy - this.root = ""; - if (data) { - this.load(data, options); - } - this.clone = function() { - var newObj = new JSZip(); - for (var i in this) { - if (typeof this[i] !== "function") { - newObj[i] = this[i]; - } - } - return newObj; - }; -} -JSZip.prototype = _dereq_('./object'); -JSZip.prototype.load = _dereq_('./load'); -JSZip.support = _dereq_('./support'); -JSZip.defaults = _dereq_('./defaults'); - -/** - * @deprecated - * This namespace will be removed in a future version without replacement. - */ -JSZip.utils = _dereq_('./deprecatedPublicUtils'); - -JSZip.base64 = { - /** - * @deprecated - * This method will be removed in a future version without replacement. - */ - encode : function(input) { - return base64.encode(input); - }, - /** - * @deprecated - * This method will be removed in a future version without replacement. - */ - decode : function(input) { - return base64.decode(input); - } -}; -JSZip.compressions = _dereq_('./compressions'); -module.exports = JSZip; - -},{"./base64":1,"./compressions":3,"./defaults":6,"./deprecatedPublicUtils":7,"./load":10,"./object":13,"./support":17}],10:[function(_dereq_,module,exports){ -'use strict'; -var base64 = _dereq_('./base64'); -var ZipEntries = _dereq_('./zipEntries'); -module.exports = function(data, options) { - var files, zipEntries, i, input; - options = options || {}; - if (options.base64) { - data = base64.decode(data); - } - - zipEntries = new ZipEntries(data, options); - files = zipEntries.files; - for (i = 0; i < files.length; i++) { - input = files[i]; - this.file(input.fileName, input.decompressed, { - binary: true, - optimizedBinaryString: true, - date: input.date, - dir: input.dir, - comment : input.fileComment.length ? input.fileComment : null, - unixPermissions : input.unixPermissions, - dosPermissions : input.dosPermissions, - createFolders: options.createFolders - }); - } - if (zipEntries.zipComment.length) { - this.comment = zipEntries.zipComment; - } - - return this; -}; - -},{"./base64":1,"./zipEntries":22}],11:[function(_dereq_,module,exports){ -(function (Buffer){ -'use strict'; -module.exports = function(data, encoding){ - return new Buffer(data, encoding); -}; -module.exports.test = function(b){ - return Buffer.isBuffer(b); -}; - -}).call(this,(typeof Buffer !== "undefined" ? Buffer : undefined)) -},{}],12:[function(_dereq_,module,exports){ -'use strict'; -var Uint8ArrayReader = _dereq_('./uint8ArrayReader'); - -function NodeBufferReader(data) { - this.data = data; - this.length = this.data.length; - this.index = 0; -} -NodeBufferReader.prototype = new Uint8ArrayReader(); - -/** - * @see DataReader.readData - */ -NodeBufferReader.prototype.readData = function(size) { - this.checkOffset(size); - var result = this.data.slice(this.index, this.index + size); - this.index += size; - return result; -}; -module.exports = NodeBufferReader; - -},{"./uint8ArrayReader":18}],13:[function(_dereq_,module,exports){ -'use strict'; -var support = _dereq_('./support'); -var utils = _dereq_('./utils'); -var crc32 = _dereq_('./crc32'); -var signature = _dereq_('./signature'); -var defaults = _dereq_('./defaults'); -var base64 = _dereq_('./base64'); -var compressions = _dereq_('./compressions'); -var CompressedObject = _dereq_('./compressedObject'); -var nodeBuffer = _dereq_('./nodeBuffer'); -var utf8 = _dereq_('./utf8'); -var StringWriter = _dereq_('./stringWriter'); -var Uint8ArrayWriter = _dereq_('./uint8ArrayWriter'); - -/** - * Returns the raw data of a ZipObject, decompress the content if necessary. - * @param {ZipObject} file the file to use. - * @return {String|ArrayBuffer|Uint8Array|Buffer} the data. - */ -var getRawData = function(file) { - if (file._data instanceof CompressedObject) { - file._data = file._data.getContent(); - file.options.binary = true; - file.options.base64 = false; - - if (utils.getTypeOf(file._data) === "uint8array") { - var copy = file._data; - // when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array. - // if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file). - file._data = new Uint8Array(copy.length); - // with an empty Uint8Array, Opera fails with a "Offset larger than array size" - if (copy.length !== 0) { - file._data.set(copy, 0); - } - } - } - return file._data; -}; - -/** - * Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it. - * @param {ZipObject} file the file to use. - * @return {String|ArrayBuffer|Uint8Array|Buffer} the data. - */ -var getBinaryData = function(file) { - var result = getRawData(file), - type = utils.getTypeOf(result); - if (type === "string") { - if (!file.options.binary) { - // unicode text ! - // unicode string => binary string is a painful process, check if we can avoid it. - if (support.nodebuffer) { - return nodeBuffer(result, "utf-8"); - } - } - return file.asBinary(); - } - return result; -}; - -/** - * Transform this._data into a string. - * @param {function} filter a function String -> String, applied if not null on the result. - * @return {String} the string representing this._data. - */ -var dataToString = function(asUTF8) { - var result = getRawData(this); - if (result === null || typeof result === "undefined") { - return ""; - } - // if the data is a base64 string, we decode it before checking the encoding ! - if (this.options.base64) { - result = base64.decode(result); - } - if (asUTF8 && this.options.binary) { - // JSZip.prototype.utf8decode supports arrays as input - // skip to array => string step, utf8decode will do it. - result = out.utf8decode(result); - } - else { - // no utf8 transformation, do the array => string step. - result = utils.transformTo("string", result); - } - - if (!asUTF8 && !this.options.binary) { - result = utils.transformTo("string", out.utf8encode(result)); - } - return result; -}; -/** - * A simple object representing a file in the zip file. - * @constructor - * @param {string} name the name of the file - * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data - * @param {Object} options the options of the file - */ -var ZipObject = function(name, data, options) { - this.name = name; - this.dir = options.dir; - this.date = options.date; - this.comment = options.comment; - this.unixPermissions = options.unixPermissions; - this.dosPermissions = options.dosPermissions; - - this._data = data; - this.options = options; - - /* - * This object contains initial values for dir and date. - * With them, we can check if the user changed the deprecated metadata in - * `ZipObject#options` or not. - */ - this._initialMetadata = { - dir : options.dir, - date : options.date - }; -}; - -ZipObject.prototype = { - /** - * Return the content as UTF8 string. - * @return {string} the UTF8 string. - */ - asText: function() { - return dataToString.call(this, true); - }, - /** - * Returns the binary content. - * @return {string} the content as binary. - */ - asBinary: function() { - return dataToString.call(this, false); - }, - /** - * Returns the content as a nodejs Buffer. - * @return {Buffer} the content as a Buffer. - */ - asNodeBuffer: function() { - var result = getBinaryData(this); - return utils.transformTo("nodebuffer", result); - }, - /** - * Returns the content as an Uint8Array. - * @return {Uint8Array} the content as an Uint8Array. - */ - asUint8Array: function() { - var result = getBinaryData(this); - return utils.transformTo("uint8array", result); - }, - /** - * Returns the content as an ArrayBuffer. - * @return {ArrayBuffer} the content as an ArrayBufer. - */ - asArrayBuffer: function() { - return this.asUint8Array().buffer; - } -}; - -/** - * Transform an integer into a string in hexadecimal. - * @private - * @param {number} dec the number to convert. - * @param {number} bytes the number of bytes to generate. - * @returns {string} the result. - */ -var decToHex = function(dec, bytes) { - var hex = "", - i; - for (i = 0; i < bytes; i++) { - hex += String.fromCharCode(dec & 0xff); - dec = dec >>> 8; - } - return hex; -}; - -/** - * Merge the objects passed as parameters into a new one. - * @private - * @param {...Object} var_args All objects to merge. - * @return {Object} a new object with the data of the others. - */ -var extend = function() { - var result = {}, i, attr; - for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers - for (attr in arguments[i]) { - if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") { - result[attr] = arguments[i][attr]; - } - } - } - return result; -}; - -/** - * Transforms the (incomplete) options from the user into the complete - * set of options to create a file. - * @private - * @param {Object} o the options from the user. - * @return {Object} the complete set of options. - */ -var prepareFileAttrs = function(o) { - o = o || {}; - if (o.base64 === true && (o.binary === null || o.binary === undefined)) { - o.binary = true; - } - o = extend(o, defaults); - o.date = o.date || new Date(); - if (o.compression !== null) o.compression = o.compression.toUpperCase(); - - return o; -}; - -/** - * Add a file in the current folder. - * @private - * @param {string} name the name of the file - * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file - * @param {Object} o the options of the file - * @return {Object} the new file. - */ -var fileAdd = function(name, data, o) { - // be sure sub folders exist - var dataType = utils.getTypeOf(data), - parent; - - o = prepareFileAttrs(o); - - if (typeof o.unixPermissions === "string") { - o.unixPermissions = parseInt(o.unixPermissions, 8); - } - - // UNX_IFDIR 0040000 see zipinfo.c - if (o.unixPermissions && (o.unixPermissions & 0x4000)) { - o.dir = true; - } - // Bit 4 Directory - if (o.dosPermissions && (o.dosPermissions & 0x0010)) { - o.dir = true; - } - - if (o.dir) { - name = forceTrailingSlash(name); - } - - if (o.createFolders && (parent = parentFolder(name))) { - folderAdd.call(this, parent, true); - } - - if (o.dir || data === null || typeof data === "undefined") { - o.base64 = false; - o.binary = false; - data = null; - dataType = null; - } - else if (dataType === "string") { - if (o.binary && !o.base64) { - // optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask - if (o.optimizedBinaryString !== true) { - // this is a string, not in a base64 format. - // Be sure that this is a correct "binary string" - data = utils.string2binary(data); - } - } - } - else { // arraybuffer, uint8array, ... - o.base64 = false; - o.binary = true; - - if (!dataType && !(data instanceof CompressedObject)) { - throw new Error("The data of '" + name + "' is in an unsupported format !"); - } - - // special case : it's way easier to work with Uint8Array than with ArrayBuffer - if (dataType === "arraybuffer") { - data = utils.transformTo("uint8array", data); - } - } - - var object = new ZipObject(name, data, o); - this.files[name] = object; - return object; -}; - -/** - * Find the parent folder of the path. - * @private - * @param {string} path the path to use - * @return {string} the parent folder, or "" - */ -var parentFolder = function (path) { - if (path.slice(-1) == '/') { - path = path.substring(0, path.length - 1); - } - var lastSlash = path.lastIndexOf('/'); - return (lastSlash > 0) ? path.substring(0, lastSlash) : ""; -}; - - -/** - * Returns the path with a slash at the end. - * @private - * @param {String} path the path to check. - * @return {String} the path with a trailing slash. - */ -var forceTrailingSlash = function(path) { - // Check the name ends with a / - if (path.slice(-1) != "/") { - path += "/"; // IE doesn't like substr(-1) - } - return path; -}; -/** - * Add a (sub) folder in the current folder. - * @private - * @param {string} name the folder's name - * @param {boolean=} [createFolders] If true, automatically create sub - * folders. Defaults to false. - * @return {Object} the new folder. - */ -var folderAdd = function(name, createFolders) { - createFolders = (typeof createFolders !== 'undefined') ? createFolders : false; - - name = forceTrailingSlash(name); - - // Does this folder already exist? - if (!this.files[name]) { - fileAdd.call(this, name, null, { - dir: true, - createFolders: createFolders - }); - } - return this.files[name]; -}; - -/** - * Generate a JSZip.CompressedObject for a given zipOject. - * @param {ZipObject} file the object to read. - * @param {JSZip.compression} compression the compression to use. - * @param {Object} compressionOptions the options to use when compressing. - * @return {JSZip.CompressedObject} the compressed result. - */ -var generateCompressedObjectFrom = function(file, compression, compressionOptions) { - var result = new CompressedObject(), - content; - - // the data has not been decompressed, we might reuse things ! - if (file._data instanceof CompressedObject) { - result.uncompressedSize = file._data.uncompressedSize; - result.crc32 = file._data.crc32; - - if (result.uncompressedSize === 0 || file.dir) { - compression = compressions['STORE']; - result.compressedContent = ""; - result.crc32 = 0; - } - else if (file._data.compressionMethod === compression.magic) { - result.compressedContent = file._data.getCompressedContent(); - } - else { - content = file._data.getContent(); - // need to decompress / recompress - result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions); - } - } - else { - // have uncompressed data - content = getBinaryData(file); - if (!content || content.length === 0 || file.dir) { - compression = compressions['STORE']; - content = ""; - } - result.uncompressedSize = content.length; - result.crc32 = crc32(content); - result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions); - } - - result.compressedSize = result.compressedContent.length; - result.compressionMethod = compression.magic; - - return result; -}; - - - - -/** - * Generate the UNIX part of the external file attributes. - * @param {Object} unixPermissions the unix permissions or null. - * @param {Boolean} isDir true if the entry is a directory, false otherwise. - * @return {Number} a 32 bit integer. - * - * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute : - * - * TTTTsstrwxrwxrwx0000000000ADVSHR - * ^^^^____________________________ file type, see zipinfo.c (UNX_*) - * ^^^_________________________ setuid, setgid, sticky - * ^^^^^^^^^________________ permissions - * ^^^^^^^^^^______ not used ? - * ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only - */ -var generateUnixExternalFileAttr = function (unixPermissions, isDir) { - - var result = unixPermissions; - if (!unixPermissions) { - // I can't use octal values in strict mode, hence the hexa. - // 040775 => 0x41fd - // 0100664 => 0x81b4 - result = isDir ? 0x41fd : 0x81b4; - } - - return (result & 0xFFFF) << 16; -}; - -/** - * Generate the DOS part of the external file attributes. - * @param {Object} dosPermissions the dos permissions or null. - * @param {Boolean} isDir true if the entry is a directory, false otherwise. - * @return {Number} a 32 bit integer. - * - * Bit 0 Read-Only - * Bit 1 Hidden - * Bit 2 System - * Bit 3 Volume Label - * Bit 4 Directory - * Bit 5 Archive - */ -var generateDosExternalFileAttr = function (dosPermissions, isDir) { - - // the dir flag is already set for compatibility - - return (dosPermissions || 0) & 0x3F; -}; - -/** - * Generate the various parts used in the construction of the final zip file. - * @param {string} name the file name. - * @param {ZipObject} file the file content. - * @param {JSZip.CompressedObject} compressedObject the compressed object. - * @param {number} offset the current offset from the start of the zip file. - * @param {String} platform let's pretend we are this platform (change platform dependents fields) - * @return {object} the zip parts. - */ -var generateZipParts = function(name, file, compressedObject, offset, platform) { - var data = compressedObject.compressedContent, - utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)), - comment = file.comment || "", - utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)), - useUTF8ForFileName = utfEncodedFileName.length !== file.name.length, - useUTF8ForComment = utfEncodedComment.length !== comment.length, - o = file.options, - dosTime, - dosDate, - extraFields = "", - unicodePathExtraField = "", - unicodeCommentExtraField = "", - dir, date; - - - // handle the deprecated options.dir - if (file._initialMetadata.dir !== file.dir) { - dir = file.dir; - } else { - dir = o.dir; - } - - // handle the deprecated options.date - if(file._initialMetadata.date !== file.date) { - date = file.date; - } else { - date = o.date; - } - - var extFileAttr = 0; - var versionMadeBy = 0; - if (dir) { - // dos or unix, we set the dos dir flag - extFileAttr |= 0x00010; - } - if(platform === "UNIX") { - versionMadeBy = 0x031E; // UNIX, version 3.0 - extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir); - } else { // DOS or other, fallback to DOS - versionMadeBy = 0x0014; // DOS, version 2.0 - extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir); - } - - // date - // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html - // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html - // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html - - dosTime = date.getHours(); - dosTime = dosTime << 6; - dosTime = dosTime | date.getMinutes(); - dosTime = dosTime << 5; - dosTime = dosTime | date.getSeconds() / 2; - - dosDate = date.getFullYear() - 1980; - dosDate = dosDate << 4; - dosDate = dosDate | (date.getMonth() + 1); - dosDate = dosDate << 5; - dosDate = dosDate | date.getDate(); - - if (useUTF8ForFileName) { - // set the unicode path extra field. unzip needs at least one extra - // field to correctly handle unicode path, so using the path is as good - // as any other information. This could improve the situation with - // other archive managers too. - // This field is usually used without the utf8 flag, with a non - // unicode path in the header (winrar, winzip). This helps (a bit) - // with the messy Windows' default compressed folders feature but - // breaks on p7zip which doesn't seek the unicode path extra field. - // So for now, UTF-8 everywhere ! - unicodePathExtraField = - // Version - decToHex(1, 1) + - // NameCRC32 - decToHex(crc32(utfEncodedFileName), 4) + - // UnicodeName - utfEncodedFileName; - - extraFields += - // Info-ZIP Unicode Path Extra Field - "\x75\x70" + - // size - decToHex(unicodePathExtraField.length, 2) + - // content - unicodePathExtraField; - } - - if(useUTF8ForComment) { - - unicodeCommentExtraField = - // Version - decToHex(1, 1) + - // CommentCRC32 - decToHex(this.crc32(utfEncodedComment), 4) + - // UnicodeName - utfEncodedComment; - - extraFields += - // Info-ZIP Unicode Path Extra Field - "\x75\x63" + - // size - decToHex(unicodeCommentExtraField.length, 2) + - // content - unicodeCommentExtraField; - } - - var header = ""; - - // version needed to extract - header += "\x0A\x00"; - // general purpose bit flag - // set bit 11 if utf8 - header += (useUTF8ForFileName || useUTF8ForComment) ? "\x00\x08" : "\x00\x00"; - // compression method - header += compressedObject.compressionMethod; - // last mod file time - header += decToHex(dosTime, 2); - // last mod file date - header += decToHex(dosDate, 2); - // crc-32 - header += decToHex(compressedObject.crc32, 4); - // compressed size - header += decToHex(compressedObject.compressedSize, 4); - // uncompressed size - header += decToHex(compressedObject.uncompressedSize, 4); - // file name length - header += decToHex(utfEncodedFileName.length, 2); - // extra field length - header += decToHex(extraFields.length, 2); - - - var fileRecord = signature.LOCAL_FILE_HEADER + header + utfEncodedFileName + extraFields; - - var dirRecord = signature.CENTRAL_FILE_HEADER + - // version made by (00: DOS) - decToHex(versionMadeBy, 2) + - // file header (common to file and central directory) - header + - // file comment length - decToHex(utfEncodedComment.length, 2) + - // disk number start - "\x00\x00" + - // internal file attributes TODO - "\x00\x00" + - // external file attributes - decToHex(extFileAttr, 4) + - // relative offset of local header - decToHex(offset, 4) + - // file name - utfEncodedFileName + - // extra field - extraFields + - // file comment - utfEncodedComment; - - return { - fileRecord: fileRecord, - dirRecord: dirRecord, - compressedObject: compressedObject - }; -}; - - -// return the actual prototype of JSZip -var out = { - /** - * Read an existing zip and merge the data in the current JSZip object. - * The implementation is in jszip-load.js, don't forget to include it. - * @param {String|ArrayBuffer|Uint8Array|Buffer} stream The stream to load - * @param {Object} options Options for loading the stream. - * options.base64 : is the stream in base64 ? default : false - * @return {JSZip} the current JSZip object - */ - load: function(stream, options) { - throw new Error("Load method is not defined. Is the file jszip-load.js included ?"); - }, - - /** - * Filter nested files/folders with the specified function. - * @param {Function} search the predicate to use : - * function (relativePath, file) {...} - * It takes 2 arguments : the relative path and the file. - * @return {Array} An array of matching elements. - */ - filter: function(search) { - var result = [], - filename, relativePath, file, fileClone; - for (filename in this.files) { - if (!this.files.hasOwnProperty(filename)) { - continue; - } - file = this.files[filename]; - // return a new object, don't let the user mess with our internal objects :) - fileClone = new ZipObject(file.name, file._data, extend(file.options)); - relativePath = filename.slice(this.root.length, filename.length); - if (filename.slice(0, this.root.length) === this.root && // the file is in the current root - search(relativePath, fileClone)) { // and the file matches the function - result.push(fileClone); - } - } - return result; - }, - - /** - * Add a file to the zip file, or search a file. - * @param {string|RegExp} name The name of the file to add (if data is defined), - * the name of the file to find (if no data) or a regex to match files. - * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded - * @param {Object} o File options - * @return {JSZip|Object|Array} this JSZip object (when adding a file), - * a file (when searching by string) or an array of files (when searching by regex). - */ - file: function(name, data, o) { - if (arguments.length === 1) { - if (utils.isRegExp(name)) { - var regexp = name; - return this.filter(function(relativePath, file) { - return !file.dir && regexp.test(relativePath); - }); - } - else { // text - return this.filter(function(relativePath, file) { - return !file.dir && relativePath === name; - })[0] || null; - } - } - else { // more than one argument : we have data ! - name = this.root + name; - fileAdd.call(this, name, data, o); - } - return this; - }, - - /** - * Add a directory to the zip file, or search. - * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders. - * @return {JSZip} an object with the new directory as the root, or an array containing matching folders. - */ - folder: function(arg) { - if (!arg) { - return this; - } - - if (utils.isRegExp(arg)) { - return this.filter(function(relativePath, file) { - return file.dir && arg.test(relativePath); - }); - } - - // else, name is a new folder - var name = this.root + arg; - var newFolder = folderAdd.call(this, name); - - // Allow chaining by returning a new object with this folder as the root - var ret = this.clone(); - ret.root = newFolder.name; - return ret; - }, - - /** - * Delete a file, or a directory and all sub-files, from the zip - * @param {string} name the name of the file to delete - * @return {JSZip} this JSZip object - */ - remove: function(name) { - name = this.root + name; - var file = this.files[name]; - if (!file) { - // Look for any folders - if (name.slice(-1) != "/") { - name += "/"; - } - file = this.files[name]; - } - - if (file && !file.dir) { - // file - delete this.files[name]; - } else { - // maybe a folder, delete recursively - var kids = this.filter(function(relativePath, file) { - return file.name.slice(0, name.length) === name; - }); - for (var i = 0; i < kids.length; i++) { - delete this.files[kids[i].name]; - } - } - - return this; - }, - - /** - * Generate the complete zip file - * @param {Object} options the options to generate the zip file : - * - base64, (deprecated, use type instead) true to generate base64. - * - compression, "STORE" by default. - * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. - * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file - */ - generate: function(options) { - options = extend(options || {}, { - base64: true, - compression: "STORE", - compressionOptions : null, - type: "base64", - platform: "DOS", - comment: null, - mimeType: 'application/zip' - }); - - utils.checkSupport(options.type); - - // accept nodejs `process.platform` - if( - options.platform === 'darwin' || - options.platform === 'freebsd' || - options.platform === 'linux' || - options.platform === 'sunos' - ) { - options.platform = "UNIX"; - } - if (options.platform === 'win32') { - options.platform = "DOS"; - } - - var zipData = [], - localDirLength = 0, - centralDirLength = 0, - writer, i, - utfEncodedComment = utils.transformTo("string", this.utf8encode(options.comment || this.comment || "")); - - // first, generate all the zip parts. - for (var name in this.files) { - if (!this.files.hasOwnProperty(name)) { - continue; - } - var file = this.files[name]; - - var compressionName = file.options.compression || options.compression.toUpperCase(); - var compression = compressions[compressionName]; - if (!compression) { - throw new Error(compressionName + " is not a valid compression method !"); - } - var compressionOptions = file.options.compressionOptions || options.compressionOptions || {}; - - var compressedObject = generateCompressedObjectFrom.call(this, file, compression, compressionOptions); - - var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength, options.platform); - localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize; - centralDirLength += zipPart.dirRecord.length; - zipData.push(zipPart); - } - - var dirEnd = ""; - - // end of central dir signature - dirEnd = signature.CENTRAL_DIRECTORY_END + - // number of this disk - "\x00\x00" + - // number of the disk with the start of the central directory - "\x00\x00" + - // total number of entries in the central directory on this disk - decToHex(zipData.length, 2) + - // total number of entries in the central directory - decToHex(zipData.length, 2) + - // size of the central directory 4 bytes - decToHex(centralDirLength, 4) + - // offset of start of central directory with respect to the starting disk number - decToHex(localDirLength, 4) + - // .ZIP file comment length - decToHex(utfEncodedComment.length, 2) + - // .ZIP file comment - utfEncodedComment; - - - // we have all the parts (and the total length) - // time to create a writer ! - var typeName = options.type.toLowerCase(); - if(typeName==="uint8array"||typeName==="arraybuffer"||typeName==="blob"||typeName==="nodebuffer") { - writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length); - }else{ - writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length); - } - - for (i = 0; i < zipData.length; i++) { - writer.append(zipData[i].fileRecord); - writer.append(zipData[i].compressedObject.compressedContent); - } - for (i = 0; i < zipData.length; i++) { - writer.append(zipData[i].dirRecord); - } - - writer.append(dirEnd); - - var zip = writer.finalize(); - - - - switch(options.type.toLowerCase()) { - // case "zip is an Uint8Array" - case "uint8array" : - case "arraybuffer" : - case "nodebuffer" : - return utils.transformTo(options.type.toLowerCase(), zip); - case "blob" : - return utils.arrayBuffer2Blob(utils.transformTo("arraybuffer", zip), options.mimeType); - // case "zip is a string" - case "base64" : - return (options.base64) ? base64.encode(zip) : zip; - default : // case "string" : - return zip; - } - - }, - - /** - * @deprecated - * This method will be removed in a future version without replacement. - */ - crc32: function (input, crc) { - return crc32(input, crc); - }, - - /** - * @deprecated - * This method will be removed in a future version without replacement. - */ - utf8encode: function (string) { - return utils.transformTo("string", utf8.utf8encode(string)); - }, - - /** - * @deprecated - * This method will be removed in a future version without replacement. - */ - utf8decode: function (input) { - return utf8.utf8decode(input); - } -}; -module.exports = out; - -},{"./base64":1,"./compressedObject":2,"./compressions":3,"./crc32":4,"./defaults":6,"./nodeBuffer":11,"./signature":14,"./stringWriter":16,"./support":17,"./uint8ArrayWriter":19,"./utf8":20,"./utils":21}],14:[function(_dereq_,module,exports){ -'use strict'; -exports.LOCAL_FILE_HEADER = "PK\x03\x04"; -exports.CENTRAL_FILE_HEADER = "PK\x01\x02"; -exports.CENTRAL_DIRECTORY_END = "PK\x05\x06"; -exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x06\x07"; -exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\x06\x06"; -exports.DATA_DESCRIPTOR = "PK\x07\x08"; - -},{}],15:[function(_dereq_,module,exports){ -'use strict'; -var DataReader = _dereq_('./dataReader'); -var utils = _dereq_('./utils'); - -function StringReader(data, optimizedBinaryString) { - this.data = data; - if (!optimizedBinaryString) { - this.data = utils.string2binary(this.data); - } - this.length = this.data.length; - this.index = 0; -} -StringReader.prototype = new DataReader(); -/** - * @see DataReader.byteAt - */ -StringReader.prototype.byteAt = function(i) { - return this.data.charCodeAt(i); -}; -/** - * @see DataReader.lastIndexOfSignature - */ -StringReader.prototype.lastIndexOfSignature = function(sig) { - return this.data.lastIndexOf(sig); -}; -/** - * @see DataReader.readData - */ -StringReader.prototype.readData = function(size) { - this.checkOffset(size); - // this will work because the constructor applied the "& 0xff" mask. - var result = this.data.slice(this.index, this.index + size); - this.index += size; - return result; -}; -module.exports = StringReader; - -},{"./dataReader":5,"./utils":21}],16:[function(_dereq_,module,exports){ -'use strict'; - -var utils = _dereq_('./utils'); - -/** - * An object to write any content to a string. - * @constructor - */ -var StringWriter = function() { - this.data = []; -}; -StringWriter.prototype = { - /** - * Append any content to the current string. - * @param {Object} input the content to add. - */ - append: function(input) { - input = utils.transformTo("string", input); - this.data.push(input); - }, - /** - * Finalize the construction an return the result. - * @return {string} the generated string. - */ - finalize: function() { - return this.data.join(""); - } -}; - -module.exports = StringWriter; - -},{"./utils":21}],17:[function(_dereq_,module,exports){ -(function (Buffer){ -'use strict'; -exports.base64 = true; -exports.array = true; -exports.string = true; -exports.arraybuffer = typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined"; -// contains true if JSZip can read/generate nodejs Buffer, false otherwise. -// Browserify will provide a Buffer implementation for browsers, which is -// an augmented Uint8Array (i.e., can be used as either Buffer or U8). -exports.nodebuffer = typeof Buffer !== "undefined"; -// contains true if JSZip can read/generate Uint8Array, false otherwise. -exports.uint8array = typeof Uint8Array !== "undefined"; - -if (typeof ArrayBuffer === "undefined") { - exports.blob = false; -} -else { - var buffer = new ArrayBuffer(0); - try { - exports.blob = new Blob([buffer], { - type: "application/zip" - }).size === 0; - } - catch (e) { - try { - var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; - var builder = new Builder(); - builder.append(buffer); - exports.blob = builder.getBlob('application/zip').size === 0; - } - catch (e) { - exports.blob = false; - } - } -} - -}).call(this,(typeof Buffer !== "undefined" ? Buffer : undefined)) -},{}],18:[function(_dereq_,module,exports){ -'use strict'; -var DataReader = _dereq_('./dataReader'); - -function Uint8ArrayReader(data) { - if (data) { - this.data = data; - this.length = this.data.length; - this.index = 0; - } -} -Uint8ArrayReader.prototype = new DataReader(); -/** - * @see DataReader.byteAt - */ -Uint8ArrayReader.prototype.byteAt = function(i) { - return this.data[i]; -}; -/** - * @see DataReader.lastIndexOfSignature - */ -Uint8ArrayReader.prototype.lastIndexOfSignature = function(sig) { - var sig0 = sig.charCodeAt(0), - sig1 = sig.charCodeAt(1), - sig2 = sig.charCodeAt(2), - sig3 = sig.charCodeAt(3); - for (var i = this.length - 4; i >= 0; --i) { - if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) { - return i; - } - } - - return -1; -}; -/** - * @see DataReader.readData - */ -Uint8ArrayReader.prototype.readData = function(size) { - this.checkOffset(size); - if(size === 0) { - // in IE10, when using subarray(idx, idx), we get the array [0x00] instead of []. - return new Uint8Array(0); - } - var result = this.data.subarray(this.index, this.index + size); - this.index += size; - return result; -}; -module.exports = Uint8ArrayReader; - -},{"./dataReader":5}],19:[function(_dereq_,module,exports){ -'use strict'; - -var utils = _dereq_('./utils'); - -/** - * An object to write any content to an Uint8Array. - * @constructor - * @param {number} length The length of the array. - */ -var Uint8ArrayWriter = function(length) { - this.data = new Uint8Array(length); - this.index = 0; -}; -Uint8ArrayWriter.prototype = { - /** - * Append any content to the current array. - * @param {Object} input the content to add. - */ - append: function(input) { - if (input.length !== 0) { - // with an empty Uint8Array, Opera fails with a "Offset larger than array size" - input = utils.transformTo("uint8array", input); - this.data.set(input, this.index); - this.index += input.length; - } - }, - /** - * Finalize the construction an return the result. - * @return {Uint8Array} the generated array. - */ - finalize: function() { - return this.data; - } -}; - -module.exports = Uint8ArrayWriter; - -},{"./utils":21}],20:[function(_dereq_,module,exports){ -'use strict'; - -var utils = _dereq_('./utils'); -var support = _dereq_('./support'); -var nodeBuffer = _dereq_('./nodeBuffer'); - -/** - * The following functions come from pako, from pako/lib/utils/strings - * released under the MIT license, see pako https://github.com/nodeca/pako/ - */ - -// Table with utf8 lengths (calculated by first byte of sequence) -// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, -// because max possible codepoint is 0x10ffff -var _utf8len = new Array(256); -for (var i=0; i<256; i++) { - _utf8len[i] = (i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1); -} -_utf8len[254]=_utf8len[254]=1; // Invalid sequence start - -// convert string to array (typed, when possible) -var string2buf = function (str) { - var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; - - // count binary size - for (m_pos = 0; m_pos < str_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { - c2 = str.charCodeAt(m_pos+1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; - } - - // allocate buffer - if (support.uint8array) { - buf = new Uint8Array(buf_len); - } else { - buf = new Array(buf_len); - } - - // convert - for (i=0, m_pos = 0; i < buf_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { - c2 = str.charCodeAt(m_pos+1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - if (c < 0x80) { - /* one byte */ - buf[i++] = c; - } else if (c < 0x800) { - /* two bytes */ - buf[i++] = 0xC0 | (c >>> 6); - buf[i++] = 0x80 | (c & 0x3f); - } else if (c < 0x10000) { - /* three bytes */ - buf[i++] = 0xE0 | (c >>> 12); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } else { - /* four bytes */ - buf[i++] = 0xf0 | (c >>> 18); - buf[i++] = 0x80 | (c >>> 12 & 0x3f); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } - } - - return buf; -}; - -// Calculate max possible position in utf8 buffer, -// that will not break sequence. If that's not possible -// - (very small limits) return max size as is. -// -// buf[] - utf8 bytes array -// max - length limit (mandatory); -var utf8border = function(buf, max) { - var pos; - - max = max || buf.length; - if (max > buf.length) { max = buf.length; } - - // go back from last position, until start of sequence found - pos = max-1; - while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } - - // Fuckup - very small and broken sequence, - // return max, because we should return something anyway. - if (pos < 0) { return max; } - - // If we came to start of buffer - that means vuffer is too small, - // return max too. - if (pos === 0) { return max; } - - return (pos + _utf8len[buf[pos]] > max) ? pos : max; -}; - -// convert array to string -var buf2string = function (buf) { - var str, i, out, c, c_len; - var len = buf.length; - - // Reserve max possible length (2 words per char) - // NB: by unknown reasons, Array is significantly faster for - // String.fromCharCode.apply than Uint16Array. - var utf16buf = new Array(len*2); - - for (out=0, i=0; i 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; } - - // apply mask on first byte - c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; - // join the rest - while (c_len > 1 && i < len) { - c = (c << 6) | (buf[i++] & 0x3f); - c_len--; - } - - // terminated by end of string? - if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } - - if (c < 0x10000) { - utf16buf[out++] = c; - } else { - c -= 0x10000; - utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); - utf16buf[out++] = 0xdc00 | (c & 0x3ff); - } - } - - // shrinkBuf(utf16buf, out) - if (utf16buf.length !== out) { - if(utf16buf.subarray) { - utf16buf = utf16buf.subarray(0, out); - } else { - utf16buf.length = out; - } - } - - // return String.fromCharCode.apply(null, utf16buf); - return utils.applyFromCharCode(utf16buf); -}; - - -// That's all for the pako functions. - - -/** - * Transform a javascript string into an array (typed if possible) of bytes, - * UTF-8 encoded. - * @param {String} str the string to encode - * @return {Array|Uint8Array|Buffer} the UTF-8 encoded string. - */ -exports.utf8encode = function utf8encode(str) { - if (support.nodebuffer) { - return nodeBuffer(str, "utf-8"); - } - - return string2buf(str); -}; - - -/** - * Transform a bytes array (or a representation) representing an UTF-8 encoded - * string into a javascript string. - * @param {Array|Uint8Array|Buffer} buf the data de decode - * @return {String} the decoded string. - */ -exports.utf8decode = function utf8decode(buf) { - if (support.nodebuffer) { - return utils.transformTo("nodebuffer", buf).toString("utf-8"); - } - - buf = utils.transformTo(support.uint8array ? "uint8array" : "array", buf); - - // return buf2string(buf); - // Chrome prefers to work with "small" chunks of data - // for the method buf2string. - // Firefox and Chrome has their own shortcut, IE doesn't seem to really care. - var result = [], k = 0, len = buf.length, chunk = 65536; - while (k < len) { - var nextBoundary = utf8border(buf, Math.min(k + chunk, len)); - if (support.uint8array) { - result.push(buf2string(buf.subarray(k, nextBoundary))); - } else { - result.push(buf2string(buf.slice(k, nextBoundary))); - } - k = nextBoundary; - } - return result.join(""); - -}; -// vim: set shiftwidth=4 softtabstop=4: - -},{"./nodeBuffer":11,"./support":17,"./utils":21}],21:[function(_dereq_,module,exports){ -'use strict'; -var support = _dereq_('./support'); -var compressions = _dereq_('./compressions'); -var nodeBuffer = _dereq_('./nodeBuffer'); -/** - * Convert a string to a "binary string" : a string containing only char codes between 0 and 255. - * @param {string} str the string to transform. - * @return {String} the binary string. - */ -exports.string2binary = function(str) { - var result = ""; - for (var i = 0; i < str.length; i++) { - result += String.fromCharCode(str.charCodeAt(i) & 0xff); - } - return result; -}; -exports.arrayBuffer2Blob = function(buffer, mimeType) { - exports.checkSupport("blob"); - mimeType = mimeType || 'application/zip'; - - try { - // Blob constructor - return new Blob([buffer], { - type: mimeType - }); - } - catch (e) { - - try { - // deprecated, browser only, old way - var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; - var builder = new Builder(); - builder.append(buffer); - return builder.getBlob(mimeType); - } - catch (e) { - - // well, fuck ?! - throw new Error("Bug : can't construct the Blob."); - } - } - - -}; -/** - * The identity function. - * @param {Object} input the input. - * @return {Object} the same input. - */ -function identity(input) { - return input; -} - -/** - * Fill in an array with a string. - * @param {String} str the string to use. - * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated). - * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array. - */ -function stringToArrayLike(str, array) { - for (var i = 0; i < str.length; ++i) { - array[i] = str.charCodeAt(i) & 0xFF; - } - return array; -} - -/** - * Transform an array-like object to a string. - * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. - * @return {String} the result. - */ -function arrayLikeToString(array) { - // Performances notes : - // -------------------- - // String.fromCharCode.apply(null, array) is the fastest, see - // see http://jsperf.com/converting-a-uint8array-to-a-string/2 - // but the stack is limited (and we can get huge arrays !). - // - // result += String.fromCharCode(array[i]); generate too many strings ! - // - // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2 - var chunk = 65536; - var result = [], - len = array.length, - type = exports.getTypeOf(array), - k = 0, - canUseApply = true; - try { - switch(type) { - case "uint8array": - String.fromCharCode.apply(null, new Uint8Array(0)); - break; - case "nodebuffer": - String.fromCharCode.apply(null, nodeBuffer(0)); - break; - } - } catch(e) { - canUseApply = false; - } - - // no apply : slow and painful algorithm - // default browser on android 4.* - if (!canUseApply) { - var resultStr = ""; - for(var i = 0; i < array.length;i++) { - resultStr += String.fromCharCode(array[i]); - } - return resultStr; - } - while (k < len && chunk > 1) { - try { - if (type === "array" || type === "nodebuffer") { - result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len)))); - } - else { - result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len)))); - } - k += chunk; - } - catch (e) { - chunk = Math.floor(chunk / 2); - } - } - return result.join(""); -} - -exports.applyFromCharCode = arrayLikeToString; - - -/** - * Copy the data from an array-like to an other array-like. - * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array. - * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated. - * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array. - */ -function arrayLikeToArrayLike(arrayFrom, arrayTo) { - for (var i = 0; i < arrayFrom.length; i++) { - arrayTo[i] = arrayFrom[i]; - } - return arrayTo; -} - -// a matrix containing functions to transform everything into everything. -var transform = {}; - -// string to ? -transform["string"] = { - "string": identity, - "array": function(input) { - return stringToArrayLike(input, new Array(input.length)); - }, - "arraybuffer": function(input) { - return transform["string"]["uint8array"](input).buffer; - }, - "uint8array": function(input) { - return stringToArrayLike(input, new Uint8Array(input.length)); - }, - "nodebuffer": function(input) { - return stringToArrayLike(input, nodeBuffer(input.length)); - } -}; - -// array to ? -transform["array"] = { - "string": arrayLikeToString, - "array": identity, - "arraybuffer": function(input) { - return (new Uint8Array(input)).buffer; - }, - "uint8array": function(input) { - return new Uint8Array(input); - }, - "nodebuffer": function(input) { - return nodeBuffer(input); - } -}; - -// arraybuffer to ? -transform["arraybuffer"] = { - "string": function(input) { - return arrayLikeToString(new Uint8Array(input)); - }, - "array": function(input) { - return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength)); - }, - "arraybuffer": identity, - "uint8array": function(input) { - return new Uint8Array(input); - }, - "nodebuffer": function(input) { - return nodeBuffer(new Uint8Array(input)); - } -}; - -// uint8array to ? -transform["uint8array"] = { - "string": arrayLikeToString, - "array": function(input) { - return arrayLikeToArrayLike(input, new Array(input.length)); - }, - "arraybuffer": function(input) { - return input.buffer; - }, - "uint8array": identity, - "nodebuffer": function(input) { - return nodeBuffer(input); - } -}; - -// nodebuffer to ? -transform["nodebuffer"] = { - "string": arrayLikeToString, - "array": function(input) { - return arrayLikeToArrayLike(input, new Array(input.length)); - }, - "arraybuffer": function(input) { - return transform["nodebuffer"]["uint8array"](input).buffer; - }, - "uint8array": function(input) { - return arrayLikeToArrayLike(input, new Uint8Array(input.length)); - }, - "nodebuffer": identity -}; - -/** - * Transform an input into any type. - * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer. - * If no output type is specified, the unmodified input will be returned. - * @param {String} outputType the output type. - * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert. - * @throws {Error} an Error if the browser doesn't support the requested output type. - */ -exports.transformTo = function(outputType, input) { - if (!input) { - // undefined, null, etc - // an empty string won't harm. - input = ""; - } - if (!outputType) { - return input; - } - exports.checkSupport(outputType); - var inputType = exports.getTypeOf(input); - var result = transform[inputType][outputType](input); - return result; -}; - -/** - * Return the type of the input. - * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer. - * @param {Object} input the input to identify. - * @return {String} the (lowercase) type of the input. - */ -exports.getTypeOf = function(input) { - if (typeof input === "string") { - return "string"; - } - if (Object.prototype.toString.call(input) === "[object Array]") { - return "array"; - } - if (support.nodebuffer && nodeBuffer.test(input)) { - return "nodebuffer"; - } - if (support.uint8array && input instanceof Uint8Array) { - return "uint8array"; - } - if (support.arraybuffer && input instanceof ArrayBuffer) { - return "arraybuffer"; - } -}; - -/** - * Throw an exception if the type is not supported. - * @param {String} type the type to check. - * @throws {Error} an Error if the browser doesn't support the requested type. - */ -exports.checkSupport = function(type) { - var supported = support[type.toLowerCase()]; - if (!supported) { - throw new Error(type + " is not supported by this browser"); - } -}; -exports.MAX_VALUE_16BITS = 65535; -exports.MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1 - -/** - * Prettify a string read as binary. - * @param {string} str the string to prettify. - * @return {string} a pretty string. - */ -exports.pretty = function(str) { - var res = '', - code, i; - for (i = 0; i < (str || "").length; i++) { - code = str.charCodeAt(i); - res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase(); - } - return res; -}; - -/** - * Find a compression registered in JSZip. - * @param {string} compressionMethod the method magic to find. - * @return {Object|null} the JSZip compression object, null if none found. - */ -exports.findCompression = function(compressionMethod) { - for (var method in compressions) { - if (!compressions.hasOwnProperty(method)) { - continue; - } - if (compressions[method].magic === compressionMethod) { - return compressions[method]; - } - } - return null; -}; -/** -* Cross-window, cross-Node-context regular expression detection -* @param {Object} object Anything -* @return {Boolean} true if the object is a regular expression, -* false otherwise -*/ -exports.isRegExp = function (object) { - return Object.prototype.toString.call(object) === "[object RegExp]"; -}; - - -},{"./compressions":3,"./nodeBuffer":11,"./support":17}],22:[function(_dereq_,module,exports){ -'use strict'; -var StringReader = _dereq_('./stringReader'); -var NodeBufferReader = _dereq_('./nodeBufferReader'); -var Uint8ArrayReader = _dereq_('./uint8ArrayReader'); -var utils = _dereq_('./utils'); -var sig = _dereq_('./signature'); -var ZipEntry = _dereq_('./zipEntry'); -var support = _dereq_('./support'); -var jszipProto = _dereq_('./object'); -// class ZipEntries {{{ -/** - * All the entries in the zip file. - * @constructor - * @param {String|ArrayBuffer|Uint8Array} data the binary stream to load. - * @param {Object} loadOptions Options for loading the stream. - */ -function ZipEntries(data, loadOptions) { - this.files = []; - this.loadOptions = loadOptions; - if (data) { - this.load(data); - } -} -ZipEntries.prototype = { - /** - * Check that the reader is on the speficied signature. - * @param {string} expectedSignature the expected signature. - * @throws {Error} if it is an other signature. - */ - checkSignature: function(expectedSignature) { - var signature = this.reader.readString(4); - if (signature !== expectedSignature) { - throw new Error("Corrupted zip or bug : unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")"); - } - }, - /** - * Read the end of the central directory. - */ - readBlockEndOfCentral: function() { - this.diskNumber = this.reader.readInt(2); - this.diskWithCentralDirStart = this.reader.readInt(2); - this.centralDirRecordsOnThisDisk = this.reader.readInt(2); - this.centralDirRecords = this.reader.readInt(2); - this.centralDirSize = this.reader.readInt(4); - this.centralDirOffset = this.reader.readInt(4); - - this.zipCommentLength = this.reader.readInt(2); - // warning : the encoding depends of the system locale - // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded. - // On a windows machine, this field is encoded with the localized windows code page. - this.zipComment = this.reader.readString(this.zipCommentLength); - // To get consistent behavior with the generation part, we will assume that - // this is utf8 encoded. - this.zipComment = jszipProto.utf8decode(this.zipComment); - }, - /** - * Read the end of the Zip 64 central directory. - * Not merged with the method readEndOfCentral : - * The end of central can coexist with its Zip64 brother, - * I don't want to read the wrong number of bytes ! - */ - readBlockZip64EndOfCentral: function() { - this.zip64EndOfCentralSize = this.reader.readInt(8); - this.versionMadeBy = this.reader.readString(2); - this.versionNeeded = this.reader.readInt(2); - this.diskNumber = this.reader.readInt(4); - this.diskWithCentralDirStart = this.reader.readInt(4); - this.centralDirRecordsOnThisDisk = this.reader.readInt(8); - this.centralDirRecords = this.reader.readInt(8); - this.centralDirSize = this.reader.readInt(8); - this.centralDirOffset = this.reader.readInt(8); - - this.zip64ExtensibleData = {}; - var extraDataSize = this.zip64EndOfCentralSize - 44, - index = 0, - extraFieldId, - extraFieldLength, - extraFieldValue; - while (index < extraDataSize) { - extraFieldId = this.reader.readInt(2); - extraFieldLength = this.reader.readInt(4); - extraFieldValue = this.reader.readString(extraFieldLength); - this.zip64ExtensibleData[extraFieldId] = { - id: extraFieldId, - length: extraFieldLength, - value: extraFieldValue - }; - } - }, - /** - * Read the end of the Zip 64 central directory locator. - */ - readBlockZip64EndOfCentralLocator: function() { - this.diskWithZip64CentralDirStart = this.reader.readInt(4); - this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8); - this.disksCount = this.reader.readInt(4); - if (this.disksCount > 1) { - throw new Error("Multi-volumes zip are not supported"); - } - }, - /** - * Read the local files, based on the offset read in the central part. - */ - readLocalFiles: function() { - var i, file; - for (i = 0; i < this.files.length; i++) { - file = this.files[i]; - this.reader.setIndex(file.localHeaderOffset); - this.checkSignature(sig.LOCAL_FILE_HEADER); - file.readLocalPart(this.reader); - file.handleUTF8(); - file.processAttributes(); - } - }, - /** - * Read the central directory. - */ - readCentralDir: function() { - var file; - - this.reader.setIndex(this.centralDirOffset); - while (this.reader.readString(4) === sig.CENTRAL_FILE_HEADER) { - file = new ZipEntry({ - zip64: this.zip64 - }, this.loadOptions); - file.readCentralPart(this.reader); - this.files.push(file); - } - }, - /** - * Read the end of central directory. - */ - readEndOfCentral: function() { - var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END); - if (offset === -1) { - // Check if the content is a truncated zip or complete garbage. - // A "LOCAL_FILE_HEADER" is not required at the beginning (auto - // extractible zip for example) but it can give a good hint. - // If an ajax request was used without responseType, we will also - // get unreadable data. - var isGarbage = true; - try { - this.reader.setIndex(0); - this.checkSignature(sig.LOCAL_FILE_HEADER); - isGarbage = false; - } catch (e) {} - - if (isGarbage) { - throw new Error("Can't find end of central directory : is this a zip file ? " + - "If it is, see http://stuk.github.io/jszip/documentation/howto/read_zip.html"); - } else { - throw new Error("Corrupted zip : can't find end of central directory"); - } - } - this.reader.setIndex(offset); - this.checkSignature(sig.CENTRAL_DIRECTORY_END); - this.readBlockEndOfCentral(); - - - /* extract from the zip spec : - 4) If one of the fields in the end of central directory - record is too small to hold required data, the field - should be set to -1 (0xFFFF or 0xFFFFFFFF) and the - ZIP64 format record should be created. - 5) The end of central directory record and the - Zip64 end of central directory locator record must - reside on the same disk when splitting or spanning - an archive. - */ - if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) { - this.zip64 = true; - - /* - Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from - the zip file can fit into a 32bits integer. This cannot be solved : Javascript represents - all numbers as 64-bit double precision IEEE 754 floating point numbers. - So, we have 53bits for integers and bitwise operations treat everything as 32bits. - see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators - and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5 - */ - - // should look for a zip64 EOCD locator - offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); - if (offset === -1) { - throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator"); - } - this.reader.setIndex(offset); - this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); - this.readBlockZip64EndOfCentralLocator(); - - // now the zip64 EOCD record - this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir); - this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); - this.readBlockZip64EndOfCentral(); - } - }, - prepareReader: function(data) { - var type = utils.getTypeOf(data); - if (type === "string" && !support.uint8array) { - this.reader = new StringReader(data, this.loadOptions.optimizedBinaryString); - } - else if (type === "nodebuffer") { - this.reader = new NodeBufferReader(data); - } - else { - this.reader = new Uint8ArrayReader(utils.transformTo("uint8array", data)); - } - }, - /** - * Read a zip file and create ZipEntries. - * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file. - */ - load: function(data) { - this.prepareReader(data); - this.readEndOfCentral(); - this.readCentralDir(); - this.readLocalFiles(); - } -}; -// }}} end of ZipEntries -module.exports = ZipEntries; - -},{"./nodeBufferReader":12,"./object":13,"./signature":14,"./stringReader":15,"./support":17,"./uint8ArrayReader":18,"./utils":21,"./zipEntry":23}],23:[function(_dereq_,module,exports){ -'use strict'; -var StringReader = _dereq_('./stringReader'); -var utils = _dereq_('./utils'); -var CompressedObject = _dereq_('./compressedObject'); -var jszipProto = _dereq_('./object'); - -var MADE_BY_DOS = 0x00; -var MADE_BY_UNIX = 0x03; - -// class ZipEntry {{{ -/** - * An entry in the zip file. - * @constructor - * @param {Object} options Options of the current file. - * @param {Object} loadOptions Options for loading the stream. - */ -function ZipEntry(options, loadOptions) { - this.options = options; - this.loadOptions = loadOptions; -} -ZipEntry.prototype = { - /** - * say if the file is encrypted. - * @return {boolean} true if the file is encrypted, false otherwise. - */ - isEncrypted: function() { - // bit 1 is set - return (this.bitFlag & 0x0001) === 0x0001; - }, - /** - * say if the file has utf-8 filename/comment. - * @return {boolean} true if the filename/comment is in utf-8, false otherwise. - */ - useUTF8: function() { - // bit 11 is set - return (this.bitFlag & 0x0800) === 0x0800; - }, - /** - * Prepare the function used to generate the compressed content from this ZipFile. - * @param {DataReader} reader the reader to use. - * @param {number} from the offset from where we should read the data. - * @param {number} length the length of the data to read. - * @return {Function} the callback to get the compressed content (the type depends of the DataReader class). - */ - prepareCompressedContent: function(reader, from, length) { - return function() { - var previousIndex = reader.index; - reader.setIndex(from); - var compressedFileData = reader.readData(length); - reader.setIndex(previousIndex); - - return compressedFileData; - }; - }, - /** - * Prepare the function used to generate the uncompressed content from this ZipFile. - * @param {DataReader} reader the reader to use. - * @param {number} from the offset from where we should read the data. - * @param {number} length the length of the data to read. - * @param {JSZip.compression} compression the compression used on this file. - * @param {number} uncompressedSize the uncompressed size to expect. - * @return {Function} the callback to get the uncompressed content (the type depends of the DataReader class). - */ - prepareContent: function(reader, from, length, compression, uncompressedSize) { - return function() { - - var compressedFileData = utils.transformTo(compression.uncompressInputType, this.getCompressedContent()); - var uncompressedFileData = compression.uncompress(compressedFileData); - - if (uncompressedFileData.length !== uncompressedSize) { - throw new Error("Bug : uncompressed data size mismatch"); - } - - return uncompressedFileData; - }; - }, - /** - * Read the local part of a zip file and add the info in this object. - * @param {DataReader} reader the reader to use. - */ - readLocalPart: function(reader) { - var compression, localExtraFieldsLength; - - // we already know everything from the central dir ! - // If the central dir data are false, we are doomed. - // On the bright side, the local part is scary : zip64, data descriptors, both, etc. - // The less data we get here, the more reliable this should be. - // Let's skip the whole header and dash to the data ! - reader.skip(22); - // in some zip created on windows, the filename stored in the central dir contains \ instead of /. - // Strangely, the filename here is OK. - // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes - // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators... - // Search "unzip mismatching "local" filename continuing with "central" filename version" on - // the internet. - // - // I think I see the logic here : the central directory is used to display - // content and the local directory is used to extract the files. Mixing / and \ - // may be used to display \ to windows users and use / when extracting the files. - // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394 - this.fileNameLength = reader.readInt(2); - localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir - this.fileName = reader.readString(this.fileNameLength); - reader.skip(localExtraFieldsLength); - - if (this.compressedSize == -1 || this.uncompressedSize == -1) { - throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory " + "(compressedSize == -1 || uncompressedSize == -1)"); - } - - compression = utils.findCompression(this.compressionMethod); - if (compression === null) { // no compression found - throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + this.fileName + ")"); - } - this.decompressed = new CompressedObject(); - this.decompressed.compressedSize = this.compressedSize; - this.decompressed.uncompressedSize = this.uncompressedSize; - this.decompressed.crc32 = this.crc32; - this.decompressed.compressionMethod = this.compressionMethod; - this.decompressed.getCompressedContent = this.prepareCompressedContent(reader, reader.index, this.compressedSize, compression); - this.decompressed.getContent = this.prepareContent(reader, reader.index, this.compressedSize, compression, this.uncompressedSize); - - // we need to compute the crc32... - if (this.loadOptions.checkCRC32) { - this.decompressed = utils.transformTo("string", this.decompressed.getContent()); - if (jszipProto.crc32(this.decompressed) !== this.crc32) { - throw new Error("Corrupted zip : CRC32 mismatch"); - } - } - }, - - /** - * Read the central part of a zip file and add the info in this object. - * @param {DataReader} reader the reader to use. - */ - readCentralPart: function(reader) { - this.versionMadeBy = reader.readInt(2); - this.versionNeeded = reader.readInt(2); - this.bitFlag = reader.readInt(2); - this.compressionMethod = reader.readString(2); - this.date = reader.readDate(); - this.crc32 = reader.readInt(4); - this.compressedSize = reader.readInt(4); - this.uncompressedSize = reader.readInt(4); - this.fileNameLength = reader.readInt(2); - this.extraFieldsLength = reader.readInt(2); - this.fileCommentLength = reader.readInt(2); - this.diskNumberStart = reader.readInt(2); - this.internalFileAttributes = reader.readInt(2); - this.externalFileAttributes = reader.readInt(4); - this.localHeaderOffset = reader.readInt(4); - - if (this.isEncrypted()) { - throw new Error("Encrypted zip are not supported"); - } - - this.fileName = reader.readString(this.fileNameLength); - this.readExtraFields(reader); - this.parseZIP64ExtraField(reader); - this.fileComment = reader.readString(this.fileCommentLength); - }, - - /** - * Parse the external file attributes and get the unix/dos permissions. - */ - processAttributes: function () { - this.unixPermissions = null; - this.dosPermissions = null; - var madeBy = this.versionMadeBy >> 8; - - // Check if we have the DOS directory flag set. - // We look for it in the DOS and UNIX permissions - // but some unknown platform could set it as a compatibility flag. - this.dir = this.externalFileAttributes & 0x0010 ? true : false; - - if(madeBy === MADE_BY_DOS) { - // first 6 bits (0 to 5) - this.dosPermissions = this.externalFileAttributes & 0x3F; - } - - if(madeBy === MADE_BY_UNIX) { - this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF; - // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8); - } - - // fail safe : if the name ends with a / it probably means a folder - if (!this.dir && this.fileName.slice(-1) === '/') { - this.dir = true; - } - }, - - /** - * Parse the ZIP64 extra field and merge the info in the current ZipEntry. - * @param {DataReader} reader the reader to use. - */ - parseZIP64ExtraField: function(reader) { - - if (!this.extraFields[0x0001]) { - return; - } - - // should be something, preparing the extra reader - var extraReader = new StringReader(this.extraFields[0x0001].value); - - // I really hope that these 64bits integer can fit in 32 bits integer, because js - // won't let us have more. - if (this.uncompressedSize === utils.MAX_VALUE_32BITS) { - this.uncompressedSize = extraReader.readInt(8); - } - if (this.compressedSize === utils.MAX_VALUE_32BITS) { - this.compressedSize = extraReader.readInt(8); - } - if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) { - this.localHeaderOffset = extraReader.readInt(8); - } - if (this.diskNumberStart === utils.MAX_VALUE_32BITS) { - this.diskNumberStart = extraReader.readInt(4); - } - }, - /** - * Read the central part of a zip file and add the info in this object. - * @param {DataReader} reader the reader to use. - */ - readExtraFields: function(reader) { - var start = reader.index, - extraFieldId, - extraFieldLength, - extraFieldValue; - - this.extraFields = this.extraFields || {}; - - while (reader.index < start + this.extraFieldsLength) { - extraFieldId = reader.readInt(2); - extraFieldLength = reader.readInt(2); - extraFieldValue = reader.readString(extraFieldLength); - - this.extraFields[extraFieldId] = { - id: extraFieldId, - length: extraFieldLength, - value: extraFieldValue - }; - } - }, - /** - * Apply an UTF8 transformation if needed. - */ - handleUTF8: function() { - if (this.useUTF8()) { - this.fileName = jszipProto.utf8decode(this.fileName); - this.fileComment = jszipProto.utf8decode(this.fileComment); - } else { - var upath = this.findExtraFieldUnicodePath(); - if (upath !== null) { - this.fileName = upath; - } - var ucomment = this.findExtraFieldUnicodeComment(); - if (ucomment !== null) { - this.fileComment = ucomment; - } - } - }, - - /** - * Find the unicode path declared in the extra field, if any. - * @return {String} the unicode path, null otherwise. - */ - findExtraFieldUnicodePath: function() { - var upathField = this.extraFields[0x7075]; - if (upathField) { - var extraReader = new StringReader(upathField.value); - - // wrong version - if (extraReader.readInt(1) !== 1) { - return null; - } - - // the crc of the filename changed, this field is out of date. - if (jszipProto.crc32(this.fileName) !== extraReader.readInt(4)) { - return null; - } - - return jszipProto.utf8decode(extraReader.readString(upathField.length - 5)); - } - return null; - }, - - /** - * Find the unicode comment declared in the extra field, if any. - * @return {String} the unicode comment, null otherwise. - */ - findExtraFieldUnicodeComment: function() { - var ucommentField = this.extraFields[0x6375]; - if (ucommentField) { - var extraReader = new StringReader(ucommentField.value); - - // wrong version - if (extraReader.readInt(1) !== 1) { - return null; - } - - // the crc of the comment changed, this field is out of date. - if (jszipProto.crc32(this.fileComment) !== extraReader.readInt(4)) { - return null; - } - - return jszipProto.utf8decode(extraReader.readString(ucommentField.length - 5)); - } - return null; - } -}; -module.exports = ZipEntry; - -},{"./compressedObject":2,"./object":13,"./stringReader":15,"./utils":21}],24:[function(_dereq_,module,exports){ -// Top level file is just a mixin of submodules & constants -'use strict'; - -var assign = _dereq_('./lib/utils/common').assign; - -var deflate = _dereq_('./lib/deflate'); -var inflate = _dereq_('./lib/inflate'); -var constants = _dereq_('./lib/zlib/constants'); - -var pako = {}; - -assign(pako, deflate, inflate, constants); - -module.exports = pako; -},{"./lib/deflate":25,"./lib/inflate":26,"./lib/utils/common":27,"./lib/zlib/constants":30}],25:[function(_dereq_,module,exports){ -'use strict'; - - -var zlib_deflate = _dereq_('./zlib/deflate.js'); -var utils = _dereq_('./utils/common'); -var strings = _dereq_('./utils/strings'); -var msg = _dereq_('./zlib/messages'); -var zstream = _dereq_('./zlib/zstream'); - - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -var Z_NO_FLUSH = 0; -var Z_FINISH = 4; - -var Z_OK = 0; -var Z_STREAM_END = 1; - -var Z_DEFAULT_COMPRESSION = -1; - -var Z_DEFAULT_STRATEGY = 0; - -var Z_DEFLATED = 8; - -/* ===========================================================================*/ - - -/** - * class Deflate - * - * Generic JS-style wrapper for zlib calls. If you don't need - * streaming behaviour - use more simple functions: [[deflate]], - * [[deflateRaw]] and [[gzip]]. - **/ - -/* internal - * Deflate.chunks -> Array - * - * Chunks of output data, if [[Deflate#onData]] not overriden. - **/ - -/** - * Deflate.result -> Uint8Array|Array - * - * Compressed result, generated by default [[Deflate#onData]] - * and [[Deflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Deflate#push]] with `Z_FINISH` / `true` param). - **/ - -/** - * Deflate.err -> Number - * - * Error code after deflate finished. 0 (Z_OK) on success. - * You will not need it in real life, because deflate errors - * are possible only on wrong options or bad `onData` / `onEnd` - * custom handlers. - **/ - -/** - * Deflate.msg -> String - * - * Error message, if [[Deflate.err]] != 0 - **/ - - -/** - * new Deflate(options) - * - options (Object): zlib deflate options. - * - * Creates new deflator instance with specified params. Throws exception - * on bad params. Supported options: - * - * - `level` - * - `windowBits` - * - `memLevel` - * - `strategy` - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Additional options, for internal needs: - * - * - `chunkSize` - size of generated data chunks (16K by default) - * - `raw` (Boolean) - do raw deflate - * - `gzip` (Boolean) - create gzip wrapper - * - `to` (String) - if equal to 'string', then result will be "binary string" - * (each char code [0..255]) - * - `header` (Object) - custom header for gzip - * - `text` (Boolean) - true if compressed data believed to be text - * - `time` (Number) - modification time, unix timestamp - * - `os` (Number) - operation system code - * - `extra` (Array) - array of bytes with extra data (max 65536) - * - `name` (String) - file name (binary string) - * - `comment` (String) - comment (binary string) - * - `hcrc` (Boolean) - true if header crc should be added - * - * ##### Example: - * - * ```javascript - * var pako = require('pako') - * , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9]) - * , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]); - * - * var deflate = new pako.Deflate({ level: 3}); - * - * deflate.push(chunk1, false); - * deflate.push(chunk2, true); // true -> last chunk - * - * if (deflate.err) { throw new Error(deflate.err); } - * - * console.log(deflate.result); - * ``` - **/ -var Deflate = function(options) { - - this.options = utils.assign({ - level: Z_DEFAULT_COMPRESSION, - method: Z_DEFLATED, - chunkSize: 16384, - windowBits: 15, - memLevel: 8, - strategy: Z_DEFAULT_STRATEGY, - to: '' - }, options || {}); - - var opt = this.options; - - if (opt.raw && (opt.windowBits > 0)) { - opt.windowBits = -opt.windowBits; - } - - else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) { - opt.windowBits += 16; - } - - this.err = 0; // error code, if happens (0 = Z_OK) - this.msg = ''; // error message - this.ended = false; // used to avoid multiple onEnd() calls - this.chunks = []; // chunks of compressed data - - this.strm = new zstream(); - this.strm.avail_out = 0; - - var status = zlib_deflate.deflateInit2( - this.strm, - opt.level, - opt.method, - opt.windowBits, - opt.memLevel, - opt.strategy - ); - - if (status !== Z_OK) { - throw new Error(msg[status]); - } - - if (opt.header) { - zlib_deflate.deflateSetHeader(this.strm, opt.header); - } -}; - -/** - * Deflate#push(data[, mode]) -> Boolean - * - data (Uint8Array|Array|String): input data. Strings will be converted to - * utf8 byte sequence. - * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. - * See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH. - * - * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with - * new compressed chunks. Returns `true` on success. The last data block must have - * mode Z_FINISH (or `true`). That flush internal pending buffers and call - * [[Deflate#onEnd]]. - * - * On fail call [[Deflate#onEnd]] with error code and return false. - * - * We strongly recommend to use `Uint8Array` on input for best speed (output - * array format is detected automatically). Also, don't skip last param and always - * use the same type in your code (boolean or number). That will improve JS speed. - * - * For regular `Array`-s make sure all elements are [0..255]. - * - * ##### Example - * - * ```javascript - * push(chunk, false); // push one of data chunks - * ... - * push(chunk, true); // push last chunk - * ``` - **/ -Deflate.prototype.push = function(data, mode) { - var strm = this.strm; - var chunkSize = this.options.chunkSize; - var status, _mode; - - if (this.ended) { return false; } - - _mode = (mode === ~~mode) ? mode : ((mode === true) ? Z_FINISH : Z_NO_FLUSH); - - // Convert data if needed - if (typeof data === 'string') { - // If we need to compress text, change encoding to utf8. - strm.input = strings.string2buf(data); - } else { - strm.input = data; - } - - strm.next_in = 0; - strm.avail_in = strm.input.length; - - do { - if (strm.avail_out === 0) { - strm.output = new utils.Buf8(chunkSize); - strm.next_out = 0; - strm.avail_out = chunkSize; - } - status = zlib_deflate.deflate(strm, _mode); /* no bad return value */ - - if (status !== Z_STREAM_END && status !== Z_OK) { - this.onEnd(status); - this.ended = true; - return false; - } - if (strm.avail_out === 0 || (strm.avail_in === 0 && _mode === Z_FINISH)) { - if (this.options.to === 'string') { - this.onData(strings.buf2binstring(utils.shrinkBuf(strm.output, strm.next_out))); - } else { - this.onData(utils.shrinkBuf(strm.output, strm.next_out)); - } - } - } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== Z_STREAM_END); - - // Finalize on the last chunk. - if (_mode === Z_FINISH) { - status = zlib_deflate.deflateEnd(this.strm); - this.onEnd(status); - this.ended = true; - return status === Z_OK; - } - - return true; -}; - - -/** - * Deflate#onData(chunk) -> Void - * - chunk (Uint8Array|Array|String): ouput data. Type of array depends - * on js engine support. When string output requested, each chunk - * will be string. - * - * By default, stores data blocks in `chunks[]` property and glue - * those in `onEnd`. Override this handler, if you need another behaviour. - **/ -Deflate.prototype.onData = function(chunk) { - this.chunks.push(chunk); -}; - - -/** - * Deflate#onEnd(status) -> Void - * - status (Number): deflate status. 0 (Z_OK) on success, - * other if not. - * - * Called once after you tell deflate that input stream complete - * or error happenned. By default - join collected chunks, - * free memory and fill `results` / `err` properties. - **/ -Deflate.prototype.onEnd = function(status) { - // On success - join - if (status === Z_OK) { - if (this.options.to === 'string') { - this.result = this.chunks.join(''); - } else { - this.result = utils.flattenChunks(this.chunks); - } - } - this.chunks = []; - this.err = status; - this.msg = this.strm.msg; -}; - - -/** - * deflate(data[, options]) -> Uint8Array|Array|String - * - data (Uint8Array|Array|String): input data to compress. - * - options (Object): zlib deflate options. - * - * Compress `data` with deflate alrorythm and `options`. - * - * Supported options are: - * - * - level - * - windowBits - * - memLevel - * - strategy - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Sugar (options): - * - * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify - * negative windowBits implicitly. - * - `to` (String) - if equal to 'string', then result will be "binary string" - * (each char code [0..255]) - * - * ##### Example: - * - * ```javascript - * var pako = require('pako') - * , data = Uint8Array([1,2,3,4,5,6,7,8,9]); - * - * console.log(pako.deflate(data)); - * ``` - **/ -function deflate(input, options) { - var deflator = new Deflate(options); - - deflator.push(input, true); - - // That will never happens, if you don't cheat with options :) - if (deflator.err) { throw deflator.msg; } - - return deflator.result; -} - - -/** - * deflateRaw(data[, options]) -> Uint8Array|Array|String - * - data (Uint8Array|Array|String): input data to compress. - * - options (Object): zlib deflate options. - * - * The same as [[deflate]], but creates raw data, without wrapper - * (header and adler32 crc). - **/ -function deflateRaw(input, options) { - options = options || {}; - options.raw = true; - return deflate(input, options); -} - - -/** - * gzip(data[, options]) -> Uint8Array|Array|String - * - data (Uint8Array|Array|String): input data to compress. - * - options (Object): zlib deflate options. - * - * The same as [[deflate]], but create gzip wrapper instead of - * deflate one. - **/ -function gzip(input, options) { - options = options || {}; - options.gzip = true; - return deflate(input, options); -} - - -exports.Deflate = Deflate; -exports.deflate = deflate; -exports.deflateRaw = deflateRaw; -exports.gzip = gzip; -},{"./utils/common":27,"./utils/strings":28,"./zlib/deflate.js":32,"./zlib/messages":37,"./zlib/zstream":39}],26:[function(_dereq_,module,exports){ -'use strict'; - - -var zlib_inflate = _dereq_('./zlib/inflate.js'); -var utils = _dereq_('./utils/common'); -var strings = _dereq_('./utils/strings'); -var c = _dereq_('./zlib/constants'); -var msg = _dereq_('./zlib/messages'); -var zstream = _dereq_('./zlib/zstream'); -var gzheader = _dereq_('./zlib/gzheader'); - - -/** - * class Inflate - * - * Generic JS-style wrapper for zlib calls. If you don't need - * streaming behaviour - use more simple functions: [[inflate]] - * and [[inflateRaw]]. - **/ - -/* internal - * inflate.chunks -> Array - * - * Chunks of output data, if [[Inflate#onData]] not overriden. - **/ - -/** - * Inflate.result -> Uint8Array|Array|String - * - * Uncompressed result, generated by default [[Inflate#onData]] - * and [[Inflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Inflate#push]] with `Z_FINISH` / `true` param). - **/ - -/** - * Inflate.err -> Number - * - * Error code after inflate finished. 0 (Z_OK) on success. - * Should be checked if broken data possible. - **/ - -/** - * Inflate.msg -> String - * - * Error message, if [[Inflate.err]] != 0 - **/ - - -/** - * new Inflate(options) - * - options (Object): zlib inflate options. - * - * Creates new inflator instance with specified params. Throws exception - * on bad params. Supported options: - * - * - `windowBits` - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Additional options, for internal needs: - * - * - `chunkSize` - size of generated data chunks (16K by default) - * - `raw` (Boolean) - do raw inflate - * - `to` (String) - if equal to 'string', then result will be converted - * from utf8 to utf16 (javascript) string. When string output requested, - * chunk length can differ from `chunkSize`, depending on content. - * - * By default, when no options set, autodetect deflate/gzip data format via - * wrapper header. - * - * ##### Example: - * - * ```javascript - * var pako = require('pako') - * , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9]) - * , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]); - * - * var inflate = new pako.Inflate({ level: 3}); - * - * inflate.push(chunk1, false); - * inflate.push(chunk2, true); // true -> last chunk - * - * if (inflate.err) { throw new Error(inflate.err); } - * - * console.log(inflate.result); - * ``` - **/ -var Inflate = function(options) { - - this.options = utils.assign({ - chunkSize: 16384, - windowBits: 0, - to: '' - }, options || {}); - - var opt = this.options; - - // Force window size for `raw` data, if not set directly, - // because we have no header for autodetect. - if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { - opt.windowBits = -opt.windowBits; - if (opt.windowBits === 0) { opt.windowBits = -15; } - } - - // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate - if ((opt.windowBits >= 0) && (opt.windowBits < 16) && - !(options && options.windowBits)) { - opt.windowBits += 32; - } - - // Gzip header has no info about windows size, we can do autodetect only - // for deflate. So, if window size not set, force it to max when gzip possible - if ((opt.windowBits > 15) && (opt.windowBits < 48)) { - // bit 3 (16) -> gzipped data - // bit 4 (32) -> autodetect gzip/deflate - if ((opt.windowBits & 15) === 0) { - opt.windowBits |= 15; - } - } - - this.err = 0; // error code, if happens (0 = Z_OK) - this.msg = ''; // error message - this.ended = false; // used to avoid multiple onEnd() calls - this.chunks = []; // chunks of compressed data - - this.strm = new zstream(); - this.strm.avail_out = 0; - - var status = zlib_inflate.inflateInit2( - this.strm, - opt.windowBits - ); - - if (status !== c.Z_OK) { - throw new Error(msg[status]); - } - - this.header = new gzheader(); - - zlib_inflate.inflateGetHeader(this.strm, this.header); -}; - -/** - * Inflate#push(data[, mode]) -> Boolean - * - data (Uint8Array|Array|String): input data - * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. - * See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH. - * - * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with - * new output chunks. Returns `true` on success. The last data block must have - * mode Z_FINISH (or `true`). That flush internal pending buffers and call - * [[Inflate#onEnd]]. - * - * On fail call [[Inflate#onEnd]] with error code and return false. - * - * We strongly recommend to use `Uint8Array` on input for best speed (output - * format is detected automatically). Also, don't skip last param and always - * use the same type in your code (boolean or number). That will improve JS speed. - * - * For regular `Array`-s make sure all elements are [0..255]. - * - * ##### Example - * - * ```javascript - * push(chunk, false); // push one of data chunks - * ... - * push(chunk, true); // push last chunk - * ``` - **/ -Inflate.prototype.push = function(data, mode) { - var strm = this.strm; - var chunkSize = this.options.chunkSize; - var status, _mode; - var next_out_utf8, tail, utf8str; - - if (this.ended) { return false; } - _mode = (mode === ~~mode) ? mode : ((mode === true) ? c.Z_FINISH : c.Z_NO_FLUSH); - - // Convert data if needed - if (typeof data === 'string') { - // Only binary strings can be decompressed on practice - strm.input = strings.binstring2buf(data); - } else { - strm.input = data; - } - - strm.next_in = 0; - strm.avail_in = strm.input.length; - - do { - if (strm.avail_out === 0) { - strm.output = new utils.Buf8(chunkSize); - strm.next_out = 0; - strm.avail_out = chunkSize; - } - - status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH); /* no bad return value */ - - if (status !== c.Z_STREAM_END && status !== c.Z_OK) { - this.onEnd(status); - this.ended = true; - return false; - } - - if (strm.next_out) { - if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && _mode === c.Z_FINISH)) { - - if (this.options.to === 'string') { - - next_out_utf8 = strings.utf8border(strm.output, strm.next_out); - - tail = strm.next_out - next_out_utf8; - utf8str = strings.buf2string(strm.output, next_out_utf8); - - // move tail - strm.next_out = tail; - strm.avail_out = chunkSize - tail; - if (tail) { utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0); } - - this.onData(utf8str); - - } else { - this.onData(utils.shrinkBuf(strm.output, strm.next_out)); - } - } - } - } while ((strm.avail_in > 0) && status !== c.Z_STREAM_END); - - if (status === c.Z_STREAM_END) { - _mode = c.Z_FINISH; - } - // Finalize on the last chunk. - if (_mode === c.Z_FINISH) { - status = zlib_inflate.inflateEnd(this.strm); - this.onEnd(status); - this.ended = true; - return status === c.Z_OK; - } - - return true; -}; - - -/** - * Inflate#onData(chunk) -> Void - * - chunk (Uint8Array|Array|String): ouput data. Type of array depends - * on js engine support. When string output requested, each chunk - * will be string. - * - * By default, stores data blocks in `chunks[]` property and glue - * those in `onEnd`. Override this handler, if you need another behaviour. - **/ -Inflate.prototype.onData = function(chunk) { - this.chunks.push(chunk); -}; - - -/** - * Inflate#onEnd(status) -> Void - * - status (Number): inflate status. 0 (Z_OK) on success, - * other if not. - * - * Called once after you tell inflate that input stream complete - * or error happenned. By default - join collected chunks, - * free memory and fill `results` / `err` properties. - **/ -Inflate.prototype.onEnd = function(status) { - // On success - join - if (status === c.Z_OK) { - if (this.options.to === 'string') { - // Glue & convert here, until we teach pako to send - // utf8 alligned strings to onData - this.result = this.chunks.join(''); - } else { - this.result = utils.flattenChunks(this.chunks); - } - } - this.chunks = []; - this.err = status; - this.msg = this.strm.msg; -}; - - -/** - * inflate(data[, options]) -> Uint8Array|Array|String - * - data (Uint8Array|Array|String): input data to decompress. - * - options (Object): zlib inflate options. - * - * Decompress `data` with inflate/ungzip and `options`. Autodetect - * format via wrapper header by default. That's why we don't provide - * separate `ungzip` method. - * - * Supported options are: - * - * - windowBits - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information. - * - * Sugar (options): - * - * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify - * negative windowBits implicitly. - * - `to` (String) - if equal to 'string', then result will be converted - * from utf8 to utf16 (javascript) string. When string output requested, - * chunk length can differ from `chunkSize`, depending on content. - * - * - * ##### Example: - * - * ```javascript - * var pako = require('pako') - * , input = pako.deflate([1,2,3,4,5,6,7,8,9]) - * , output; - * - * try { - * output = pako.inflate(input); - * } catch (err) - * console.log(err); - * } - * ``` - **/ -function inflate(input, options) { - var inflator = new Inflate(options); - - inflator.push(input, true); - - // That will never happens, if you don't cheat with options :) - if (inflator.err) { throw inflator.msg; } - - return inflator.result; -} - - -/** - * inflateRaw(data[, options]) -> Uint8Array|Array|String - * - data (Uint8Array|Array|String): input data to decompress. - * - options (Object): zlib inflate options. - * - * The same as [[inflate]], but creates raw data, without wrapper - * (header and adler32 crc). - **/ -function inflateRaw(input, options) { - options = options || {}; - options.raw = true; - return inflate(input, options); -} - - -/** - * ungzip(data[, options]) -> Uint8Array|Array|String - * - data (Uint8Array|Array|String): input data to decompress. - * - options (Object): zlib inflate options. - * - * Just shortcut to [[inflate]], because it autodetects format - * by header.content. Done for convenience. - **/ - - -exports.Inflate = Inflate; -exports.inflate = inflate; -exports.inflateRaw = inflateRaw; -exports.ungzip = inflate; - -},{"./utils/common":27,"./utils/strings":28,"./zlib/constants":30,"./zlib/gzheader":33,"./zlib/inflate.js":35,"./zlib/messages":37,"./zlib/zstream":39}],27:[function(_dereq_,module,exports){ -'use strict'; - - -var TYPED_OK = (typeof Uint8Array !== 'undefined') && - (typeof Uint16Array !== 'undefined') && - (typeof Int32Array !== 'undefined'); - - -exports.assign = function (obj /*from1, from2, from3, ...*/) { - var sources = Array.prototype.slice.call(arguments, 1); - while (sources.length) { - var source = sources.shift(); - if (!source) { continue; } - - if (typeof(source) !== 'object') { - throw new TypeError(source + 'must be non-object'); - } - - for (var p in source) { - if (source.hasOwnProperty(p)) { - obj[p] = source[p]; - } - } - } - - return obj; -}; - - -// reduce buffer size, avoiding mem copy -exports.shrinkBuf = function (buf, size) { - if (buf.length === size) { return buf; } - if (buf.subarray) { return buf.subarray(0, size); } - buf.length = size; - return buf; -}; - - -var fnTyped = { - arraySet: function (dest, src, src_offs, len, dest_offs) { - if (src.subarray && dest.subarray) { - dest.set(src.subarray(src_offs, src_offs+len), dest_offs); - return; - } - // Fallback to ordinary array - for(var i=0; i= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1); -} -_utf8len[254]=_utf8len[254]=1; // Invalid sequence start - - -// convert string to array (typed, when possible) -exports.string2buf = function (str) { - var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; - - // count binary size - for (m_pos = 0; m_pos < str_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { - c2 = str.charCodeAt(m_pos+1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; - } - - // allocate buffer - buf = new utils.Buf8(buf_len); - - // convert - for (i=0, m_pos = 0; i < buf_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { - c2 = str.charCodeAt(m_pos+1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - if (c < 0x80) { - /* one byte */ - buf[i++] = c; - } else if (c < 0x800) { - /* two bytes */ - buf[i++] = 0xC0 | (c >>> 6); - buf[i++] = 0x80 | (c & 0x3f); - } else if (c < 0x10000) { - /* three bytes */ - buf[i++] = 0xE0 | (c >>> 12); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } else { - /* four bytes */ - buf[i++] = 0xf0 | (c >>> 18); - buf[i++] = 0x80 | (c >>> 12 & 0x3f); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } - } - - return buf; -}; - -// Helper (used in 2 places) -function buf2binstring(buf, len) { - // use fallback for big arrays to avoid stack overflow - if (len < 65537) { - if ((buf.subarray && STR_APPLY_UIA_OK) || (!buf.subarray && STR_APPLY_OK)) { - return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len)); - } - } - - var result = ''; - for(var i=0; i < len; i++) { - result += String.fromCharCode(buf[i]); - } - return result; -} - - -// Convert byte array to binary string -exports.buf2binstring = function(buf) { - return buf2binstring(buf, buf.length); -}; - - -// Convert binary string (typed, when possible) -exports.binstring2buf = function(str) { - var buf = new utils.Buf8(str.length); - for(var i=0, len=buf.length; i < len; i++) { - buf[i] = str.charCodeAt(i); - } - return buf; -}; - - -// convert array to string -exports.buf2string = function (buf, max) { - var i, out, c, c_len; - var len = max || buf.length; - - // Reserve max possible length (2 words per char) - // NB: by unknown reasons, Array is significantly faster for - // String.fromCharCode.apply than Uint16Array. - var utf16buf = new Array(len*2); - - for (out=0, i=0; i 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; } - - // apply mask on first byte - c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; - // join the rest - while (c_len > 1 && i < len) { - c = (c << 6) | (buf[i++] & 0x3f); - c_len--; - } - - // terminated by end of string? - if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } - - if (c < 0x10000) { - utf16buf[out++] = c; - } else { - c -= 0x10000; - utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); - utf16buf[out++] = 0xdc00 | (c & 0x3ff); - } - } - - return buf2binstring(utf16buf, out); -}; - - -// Calculate max possible position in utf8 buffer, -// that will not break sequence. If that's not possible -// - (very small limits) return max size as is. -// -// buf[] - utf8 bytes array -// max - length limit (mandatory); -exports.utf8border = function(buf, max) { - var pos; - - max = max || buf.length; - if (max > buf.length) { max = buf.length; } - - // go back from last position, until start of sequence found - pos = max-1; - while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } - - // Fuckup - very small and broken sequence, - // return max, because we should return something anyway. - if (pos < 0) { return max; } - - // If we came to start of buffer - that means vuffer is too small, - // return max too. - if (pos === 0) { return max; } - - return (pos + _utf8len[buf[pos]] > max) ? pos : max; -}; - -},{"./common":27}],29:[function(_dereq_,module,exports){ -'use strict'; - -// Note: adler32 takes 12% for level 0 and 2% for level 6. -// It doesn't worth to make additional optimizationa as in original. -// Small size is preferable. - -function adler32(adler, buf, len, pos) { - var s1 = (adler & 0xffff) |0 - , s2 = ((adler >>> 16) & 0xffff) |0 - , n = 0; - - while (len !== 0) { - // Set limit ~ twice less than 5552, to keep - // s2 in 31-bits, because we force signed ints. - // in other case %= will fail. - n = len > 2000 ? 2000 : len; - len -= n; - - do { - s1 = (s1 + buf[pos++]) |0; - s2 = (s2 + s1) |0; - } while (--n); - - s1 %= 65521; - s2 %= 65521; - } - - return (s1 | (s2 << 16)) |0; -} - - -module.exports = adler32; -},{}],30:[function(_dereq_,module,exports){ -module.exports = { - - /* Allowed flush values; see deflate() and inflate() below for details */ - Z_NO_FLUSH: 0, - Z_PARTIAL_FLUSH: 1, - Z_SYNC_FLUSH: 2, - Z_FULL_FLUSH: 3, - Z_FINISH: 4, - Z_BLOCK: 5, - Z_TREES: 6, - - /* Return codes for the compression/decompression functions. Negative values - * are errors, positive values are used for special but normal events. - */ - Z_OK: 0, - Z_STREAM_END: 1, - Z_NEED_DICT: 2, - Z_ERRNO: -1, - Z_STREAM_ERROR: -2, - Z_DATA_ERROR: -3, - //Z_MEM_ERROR: -4, - Z_BUF_ERROR: -5, - //Z_VERSION_ERROR: -6, - - /* compression levels */ - Z_NO_COMPRESSION: 0, - Z_BEST_SPEED: 1, - Z_BEST_COMPRESSION: 9, - Z_DEFAULT_COMPRESSION: -1, - - - Z_FILTERED: 1, - Z_HUFFMAN_ONLY: 2, - Z_RLE: 3, - Z_FIXED: 4, - Z_DEFAULT_STRATEGY: 0, - - /* Possible values of the data_type field (though see inflate()) */ - Z_BINARY: 0, - Z_TEXT: 1, - //Z_ASCII: 1, // = Z_TEXT (deprecated) - Z_UNKNOWN: 2, - - /* The deflate compression method */ - Z_DEFLATED: 8 - //Z_NULL: null // Use -1 or null inline, depending on var type -}; -},{}],31:[function(_dereq_,module,exports){ -'use strict'; - -// Note: we can't get significant speed boost here. -// So write code to minimize size - no pregenerated tables -// and array tools dependencies. - - -// Use ordinary array, since untyped makes no boost here -function makeTable() { - var c, table = []; - - for(var n =0; n < 256; n++){ - c = n; - for(var k =0; k < 8; k++){ - c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); - } - table[n] = c; - } - - return table; -} - -// Create table on load. Just 255 signed longs. Not a problem. -var crcTable = makeTable(); - - -function crc32(crc, buf, len, pos) { - var t = crcTable - , end = pos + len; - - crc = crc ^ (-1); - - for (var i = pos; i < end; i++ ) { - crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; - } - - return (crc ^ (-1)); // >>> 0; -} - - -module.exports = crc32; -},{}],32:[function(_dereq_,module,exports){ -'use strict'; - -var utils = _dereq_('../utils/common'); -var trees = _dereq_('./trees'); -var adler32 = _dereq_('./adler32'); -var crc32 = _dereq_('./crc32'); -var msg = _dereq_('./messages'); - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - - -/* Allowed flush values; see deflate() and inflate() below for details */ -var Z_NO_FLUSH = 0; -var Z_PARTIAL_FLUSH = 1; -//var Z_SYNC_FLUSH = 2; -var Z_FULL_FLUSH = 3; -var Z_FINISH = 4; -var Z_BLOCK = 5; -//var Z_TREES = 6; - - -/* Return codes for the compression/decompression functions. Negative values - * are errors, positive values are used for special but normal events. - */ -var Z_OK = 0; -var Z_STREAM_END = 1; -//var Z_NEED_DICT = 2; -//var Z_ERRNO = -1; -var Z_STREAM_ERROR = -2; -var Z_DATA_ERROR = -3; -//var Z_MEM_ERROR = -4; -var Z_BUF_ERROR = -5; -//var Z_VERSION_ERROR = -6; - - -/* compression levels */ -//var Z_NO_COMPRESSION = 0; -//var Z_BEST_SPEED = 1; -//var Z_BEST_COMPRESSION = 9; -var Z_DEFAULT_COMPRESSION = -1; - - -var Z_FILTERED = 1; -var Z_HUFFMAN_ONLY = 2; -var Z_RLE = 3; -var Z_FIXED = 4; -var Z_DEFAULT_STRATEGY = 0; - -/* Possible values of the data_type field (though see inflate()) */ -//var Z_BINARY = 0; -//var Z_TEXT = 1; -//var Z_ASCII = 1; // = Z_TEXT -var Z_UNKNOWN = 2; - - -/* The deflate compression method */ -var Z_DEFLATED = 8; - -/*============================================================================*/ - - -var MAX_MEM_LEVEL = 9; -/* Maximum value for memLevel in deflateInit2 */ -var MAX_WBITS = 15; -/* 32K LZ77 window */ -var DEF_MEM_LEVEL = 8; - - -var LENGTH_CODES = 29; -/* number of length codes, not counting the special END_BLOCK code */ -var LITERALS = 256; -/* number of literal bytes 0..255 */ -var L_CODES = LITERALS + 1 + LENGTH_CODES; -/* number of Literal or Length codes, including the END_BLOCK code */ -var D_CODES = 30; -/* number of distance codes */ -var BL_CODES = 19; -/* number of codes used to transfer the bit lengths */ -var HEAP_SIZE = 2*L_CODES + 1; -/* maximum heap size */ -var MAX_BITS = 15; -/* All codes must not exceed MAX_BITS bits */ - -var MIN_MATCH = 3; -var MAX_MATCH = 258; -var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); - -var PRESET_DICT = 0x20; - -var INIT_STATE = 42; -var EXTRA_STATE = 69; -var NAME_STATE = 73; -var COMMENT_STATE = 91; -var HCRC_STATE = 103; -var BUSY_STATE = 113; -var FINISH_STATE = 666; - -var BS_NEED_MORE = 1; /* block not completed, need more input or more output */ -var BS_BLOCK_DONE = 2; /* block flush performed */ -var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ -var BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ - -var OS_CODE = 0x03; // Unix :) . Don't detect, use this default. - -function err(strm, errorCode) { - strm.msg = msg[errorCode]; - return errorCode; -} - -function rank(f) { - return ((f) << 1) - ((f) > 4 ? 9 : 0); -} - -function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } - - -/* ========================================================================= - * Flush as much pending output as possible. All deflate() output goes - * through this function so some applications may wish to modify it - * to avoid allocating a large strm->output buffer and copying into it. - * (See also read_buf()). - */ -function flush_pending(strm) { - var s = strm.state; - - //_tr_flush_bits(s); - var len = s.pending; - if (len > strm.avail_out) { - len = strm.avail_out; - } - if (len === 0) { return; } - - utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out); - strm.next_out += len; - s.pending_out += len; - strm.total_out += len; - strm.avail_out -= len; - s.pending -= len; - if (s.pending === 0) { - s.pending_out = 0; - } -} - - -function flush_block_only (s, last) { - trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); - s.block_start = s.strstart; - flush_pending(s.strm); -} - - -function put_byte(s, b) { - s.pending_buf[s.pending++] = b; -} - - -/* ========================================================================= - * Put a short in the pending buffer. The 16-bit value is put in MSB order. - * IN assertion: the stream state is correct and there is enough room in - * pending_buf. - */ -function putShortMSB(s, b) { -// put_byte(s, (Byte)(b >> 8)); -// put_byte(s, (Byte)(b & 0xff)); - s.pending_buf[s.pending++] = (b >>> 8) & 0xff; - s.pending_buf[s.pending++] = b & 0xff; -} - - -/* =========================================================================== - * Read a new buffer from the current input stream, update the adler32 - * and total number of bytes read. All deflate() input goes through - * this function so some applications may wish to modify it to avoid - * allocating a large strm->input buffer and copying from it. - * (See also flush_pending()). - */ -function read_buf(strm, buf, start, size) { - var len = strm.avail_in; - - if (len > size) { len = size; } - if (len === 0) { return 0; } - - strm.avail_in -= len; - - utils.arraySet(buf, strm.input, strm.next_in, len, start); - if (strm.state.wrap === 1) { - strm.adler = adler32(strm.adler, buf, len, start); - } - - else if (strm.state.wrap === 2) { - strm.adler = crc32(strm.adler, buf, len, start); - } - - strm.next_in += len; - strm.total_in += len; - - return len; -} - - -/* =========================================================================== - * Set match_start to the longest match starting at the given string and - * return its length. Matches shorter or equal to prev_length are discarded, - * in which case the result is equal to prev_length and match_start is - * garbage. - * IN assertions: cur_match is the head of the hash chain for the current - * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 - * OUT assertion: the match length is not greater than s->lookahead. - */ -function longest_match(s, cur_match) { - var chain_length = s.max_chain_length; /* max hash chain length */ - var scan = s.strstart; /* current string */ - var match; /* matched string */ - var len; /* length of current match */ - var best_len = s.prev_length; /* best match length so far */ - var nice_match = s.nice_match; /* stop if match long enough */ - var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? - s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; - - var _win = s.window; // shortcut - - var wmask = s.w_mask; - var prev = s.prev; - - /* Stop when cur_match becomes <= limit. To simplify the code, - * we prevent matches with the string of window index 0. - */ - - var strend = s.strstart + MAX_MATCH; - var scan_end1 = _win[scan + best_len - 1]; - var scan_end = _win[scan + best_len]; - - /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. - * It is easy to get rid of this optimization if necessary. - */ - // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); - - /* Do not waste too much time if we already have a good match: */ - if (s.prev_length >= s.good_match) { - chain_length >>= 2; - } - /* Do not look for matches beyond the end of the input. This is necessary - * to make deflate deterministic. - */ - if (nice_match > s.lookahead) { nice_match = s.lookahead; } - - // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); - - do { - // Assert(cur_match < s->strstart, "no future"); - match = cur_match; - - /* Skip to next match if the match length cannot increase - * or if the match length is less than 2. Note that the checks below - * for insufficient lookahead only occur occasionally for performance - * reasons. Therefore uninitialized memory will be accessed, and - * conditional jumps will be made that depend on those values. - * However the length of the match is limited to the lookahead, so - * the output of deflate is not affected by the uninitialized values. - */ - - if (_win[match + best_len] !== scan_end || - _win[match + best_len - 1] !== scan_end1 || - _win[match] !== _win[scan] || - _win[++match] !== _win[scan + 1]) { - continue; - } - - /* The check at best_len-1 can be removed because it will be made - * again later. (This heuristic is not always a win.) - * It is not necessary to compare scan[2] and match[2] since they - * are always equal when the other bytes match, given that - * the hash keys are equal and that HASH_BITS >= 8. - */ - scan += 2; - match++; - // Assert(*scan == *match, "match[2]?"); - - /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. - */ - do { - /*jshint noempty:false*/ - } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - scan < strend); - - // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); - - len = MAX_MATCH - (strend - scan); - scan = strend - MAX_MATCH; - - if (len > best_len) { - s.match_start = cur_match; - best_len = len; - if (len >= nice_match) { - break; - } - scan_end1 = _win[scan + best_len - 1]; - scan_end = _win[scan + best_len]; - } - } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); - - if (best_len <= s.lookahead) { - return best_len; - } - return s.lookahead; -} - - -/* =========================================================================== - * Fill the window when the lookahead becomes insufficient. - * Updates strstart and lookahead. - * - * IN assertion: lookahead < MIN_LOOKAHEAD - * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD - * At least one byte has been read, or avail_in == 0; reads are - * performed for at least two bytes (required for the zip translate_eol - * option -- not supported here). - */ -function fill_window(s) { - var _w_size = s.w_size; - var p, n, m, more, str; - - //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); - - do { - more = s.window_size - s.lookahead - s.strstart; - - // JS ints have 32 bit, block below not needed - /* Deal with !@#$% 64K limit: */ - //if (sizeof(int) <= 2) { - // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { - // more = wsize; - // - // } else if (more == (unsigned)(-1)) { - // /* Very unlikely, but possible on 16 bit machine if - // * strstart == 0 && lookahead == 1 (input done a byte at time) - // */ - // more--; - // } - //} - - - /* If the window is almost full and there is insufficient lookahead, - * move the upper half to the lower one to make room in the upper half. - */ - if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { - - utils.arraySet(s.window, s.window, _w_size, _w_size, 0); - s.match_start -= _w_size; - s.strstart -= _w_size; - /* we now have strstart >= MAX_DIST */ - s.block_start -= _w_size; - - /* Slide the hash table (could be avoided with 32 bit values - at the expense of memory usage). We slide even when level == 0 - to keep the hash table consistent if we switch back to level > 0 - later. (Using level 0 permanently is not an optimal usage of - zlib, so we don't care about this pathological case.) - */ - - n = s.hash_size; - p = n; - do { - m = s.head[--p]; - s.head[p] = (m >= _w_size ? m - _w_size : 0); - } while (--n); - - n = _w_size; - p = n; - do { - m = s.prev[--p]; - s.prev[p] = (m >= _w_size ? m - _w_size : 0); - /* If n is not on any hash chain, prev[n] is garbage but - * its value will never be used. - */ - } while (--n); - - more += _w_size; - } - if (s.strm.avail_in === 0) { - break; - } - - /* If there was no sliding: - * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && - * more == window_size - lookahead - strstart - * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) - * => more >= window_size - 2*WSIZE + 2 - * In the BIG_MEM or MMAP case (not yet supported), - * window_size == input_size + MIN_LOOKAHEAD && - * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. - * Otherwise, window_size == 2*WSIZE so more >= 2. - * If there was sliding, more >= WSIZE. So in all cases, more >= 2. - */ - //Assert(more >= 2, "more < 2"); - n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); - s.lookahead += n; - - /* Initialize the hash value now that we have some input: */ - if (s.lookahead + s.insert >= MIN_MATCH) { - str = s.strstart - s.insert; - s.ins_h = s.window[str]; - - /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ - s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask; -//#if MIN_MATCH != 3 -// Call update_hash() MIN_MATCH-3 more times -//#endif - while (s.insert) { - /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ - s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH-1]) & s.hash_mask; - - s.prev[str & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = str; - str++; - s.insert--; - if (s.lookahead + s.insert < MIN_MATCH) { - break; - } - } - } - /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, - * but this is not important since only literal bytes will be emitted. - */ - - } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); - - /* If the WIN_INIT bytes after the end of the current data have never been - * written, then zero those bytes in order to avoid memory check reports of - * the use of uninitialized (or uninitialised as Julian writes) bytes by - * the longest match routines. Update the high water mark for the next - * time through here. WIN_INIT is set to MAX_MATCH since the longest match - * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. - */ -// if (s.high_water < s.window_size) { -// var curr = s.strstart + s.lookahead; -// var init = 0; -// -// if (s.high_water < curr) { -// /* Previous high water mark below current data -- zero WIN_INIT -// * bytes or up to end of window, whichever is less. -// */ -// init = s.window_size - curr; -// if (init > WIN_INIT) -// init = WIN_INIT; -// zmemzero(s->window + curr, (unsigned)init); -// s->high_water = curr + init; -// } -// else if (s->high_water < (ulg)curr + WIN_INIT) { -// /* High water mark at or above current data, but below current data -// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up -// * to end of window, whichever is less. -// */ -// init = (ulg)curr + WIN_INIT - s->high_water; -// if (init > s->window_size - s->high_water) -// init = s->window_size - s->high_water; -// zmemzero(s->window + s->high_water, (unsigned)init); -// s->high_water += init; -// } -// } -// -// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, -// "not enough room for search"); -} - -/* =========================================================================== - * Copy without compression as much as possible from the input stream, return - * the current block state. - * This function does not insert new strings in the dictionary since - * uncompressible data is probably not useful. This function is used - * only for the level=0 compression option. - * NOTE: this function should be optimized to avoid extra copying from - * window to pending_buf. - */ -function deflate_stored(s, flush) { - /* Stored blocks are limited to 0xffff bytes, pending_buf is limited - * to pending_buf_size, and each stored block has a 5 byte header: - */ - var max_block_size = 0xffff; - - if (max_block_size > s.pending_buf_size - 5) { - max_block_size = s.pending_buf_size - 5; - } - - /* Copy as much as possible from input to output: */ - for (;;) { - /* Fill the window as much as possible: */ - if (s.lookahead <= 1) { - - //Assert(s->strstart < s->w_size+MAX_DIST(s) || - // s->block_start >= (long)s->w_size, "slide too late"); -// if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) || -// s.block_start >= s.w_size)) { -// throw new Error("slide too late"); -// } - - fill_window(s); - if (s.lookahead === 0 && flush === Z_NO_FLUSH) { - return BS_NEED_MORE; - } - - if (s.lookahead === 0) { - break; - } - /* flush the current block */ - } - //Assert(s->block_start >= 0L, "block gone"); -// if (s.block_start < 0) throw new Error("block gone"); - - s.strstart += s.lookahead; - s.lookahead = 0; - - /* Emit a stored block if pending_buf will be full: */ - var max_start = s.block_start + max_block_size; - - if (s.strstart === 0 || s.strstart >= max_start) { - /* strstart == 0 is possible when wraparound on 16-bit machine */ - s.lookahead = s.strstart - max_start; - s.strstart = max_start; - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - - - } - /* Flush if we may have to slide, otherwise block_start may become - * negative and the data will be gone: - */ - if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - - s.insert = 0; - - if (flush === Z_FINISH) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - - if (s.strstart > s.block_start) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - - return BS_NEED_MORE; -} - -/* =========================================================================== - * Compress as much as possible from the input stream, return the current - * block state. - * This function does not perform lazy evaluation of matches and inserts - * new strings in the dictionary only for unmatched strings or for short - * matches. It is used only for the fast compression options. - */ -function deflate_fast(s, flush) { - var hash_head; /* head of the hash chain */ - var bflush; /* set if current block must be flushed */ - - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the next match, plus MIN_MATCH bytes to insert the - * string following the next match. - */ - if (s.lookahead < MIN_LOOKAHEAD) { - fill_window(s); - if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { - break; /* flush the current block */ - } - } - - /* Insert the string window[strstart .. strstart+2] in the - * dictionary, and set hash_head to the head of the hash chain: - */ - hash_head = 0/*NIL*/; - if (s.lookahead >= MIN_MATCH) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - - /* Find the longest match, discarding those <= prev_length. - * At this point we have always match_length < MIN_MATCH - */ - if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { - /* To simplify the code, we prevent matches with the string - * of window index 0 (in particular we have to avoid a match - * of the string with itself at the start of the input file). - */ - s.match_length = longest_match(s, hash_head); - /* longest_match() sets match_start */ - } - if (s.match_length >= MIN_MATCH) { - // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only - - /*** _tr_tally_dist(s, s.strstart - s.match_start, - s.match_length - MIN_MATCH, bflush); ***/ - bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); - - s.lookahead -= s.match_length; - - /* Insert new strings in the hash table only if the match length - * is not too large. This saves time but degrades compression. - */ - if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { - s.match_length--; /* string at strstart already in table */ - do { - s.strstart++; - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - /* strstart never exceeds WSIZE-MAX_MATCH, so there are - * always MIN_MATCH bytes ahead. - */ - } while (--s.match_length !== 0); - s.strstart++; - } else - { - s.strstart += s.match_length; - s.match_length = 0; - s.ins_h = s.window[s.strstart]; - /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ - s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask; - -//#if MIN_MATCH != 3 -// Call UPDATE_HASH() MIN_MATCH-3 more times -//#endif - /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not - * matter since it will be recomputed at next deflate call. - */ - } - } else { - /* No match, output a literal byte */ - //Tracevv((stderr,"%c", s.window[s.strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = trees._tr_tally(s, 0, s.window[s.strstart]); - - s.lookahead--; - s.strstart++; - } - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = ((s.strstart < (MIN_MATCH-1)) ? s.strstart : MIN_MATCH-1); - if (flush === Z_FINISH) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.last_lit) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -} - -/* =========================================================================== - * Same as above, but achieves better compression. We use a lazy - * evaluation for matches: a match is finally adopted only if there is - * no better match at the next window position. - */ -function deflate_slow(s, flush) { - var hash_head; /* head of hash chain */ - var bflush; /* set if current block must be flushed */ - - var max_insert; - - /* Process the input block. */ - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the next match, plus MIN_MATCH bytes to insert the - * string following the next match. - */ - if (s.lookahead < MIN_LOOKAHEAD) { - fill_window(s); - if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { break; } /* flush the current block */ - } - - /* Insert the string window[strstart .. strstart+2] in the - * dictionary, and set hash_head to the head of the hash chain: - */ - hash_head = 0/*NIL*/; - if (s.lookahead >= MIN_MATCH) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - - /* Find the longest match, discarding those <= prev_length. - */ - s.prev_length = s.match_length; - s.prev_match = s.match_start; - s.match_length = MIN_MATCH-1; - - if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && - s.strstart - hash_head <= (s.w_size-MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { - /* To simplify the code, we prevent matches with the string - * of window index 0 (in particular we have to avoid a match - * of the string with itself at the start of the input file). - */ - s.match_length = longest_match(s, hash_head); - /* longest_match() sets match_start */ - - if (s.match_length <= 5 && - (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { - - /* If prev_match is also MIN_MATCH, match_start is garbage - * but we will ignore the current match anyway. - */ - s.match_length = MIN_MATCH-1; - } - } - /* If there was a match at the previous step and the current - * match is not better, output the previous match: - */ - if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { - max_insert = s.strstart + s.lookahead - MIN_MATCH; - /* Do not insert strings in hash table beyond this. */ - - //check_match(s, s.strstart-1, s.prev_match, s.prev_length); - - /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, - s.prev_length - MIN_MATCH, bflush);***/ - bflush = trees._tr_tally(s, s.strstart - 1- s.prev_match, s.prev_length - MIN_MATCH); - /* Insert in hash table all strings up to the end of the match. - * strstart-1 and strstart are already inserted. If there is not - * enough lookahead, the last two strings are not inserted in - * the hash table. - */ - s.lookahead -= s.prev_length-1; - s.prev_length -= 2; - do { - if (++s.strstart <= max_insert) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - } while (--s.prev_length !== 0); - s.match_available = 0; - s.match_length = MIN_MATCH-1; - s.strstart++; - - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - - } else if (s.match_available) { - /* If there was no match at the previous position, output a - * single literal. If there was a match but the current match - * is longer, truncate the previous match to a single literal. - */ - //Tracevv((stderr,"%c", s->window[s->strstart-1])); - /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = trees._tr_tally(s, 0, s.window[s.strstart-1]); - - if (bflush) { - /*** FLUSH_BLOCK_ONLY(s, 0) ***/ - flush_block_only(s, false); - /***/ - } - s.strstart++; - s.lookahead--; - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - } else { - /* There is no previous match to compare with, wait for - * the next step to decide. - */ - s.match_available = 1; - s.strstart++; - s.lookahead--; - } - } - //Assert (flush != Z_NO_FLUSH, "no flush?"); - if (s.match_available) { - //Tracevv((stderr,"%c", s->window[s->strstart-1])); - /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = trees._tr_tally(s, 0, s.window[s.strstart-1]); - - s.match_available = 0; - } - s.insert = s.strstart < MIN_MATCH-1 ? s.strstart : MIN_MATCH-1; - if (flush === Z_FINISH) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.last_lit) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - - return BS_BLOCK_DONE; -} - - -/* =========================================================================== - * For Z_RLE, simply look for runs of bytes, generate matches only of distance - * one. Do not maintain a hash table. (It will be regenerated if this run of - * deflate switches away from Z_RLE.) - */ -function deflate_rle(s, flush) { - var bflush; /* set if current block must be flushed */ - var prev; /* byte at distance one to match */ - var scan, strend; /* scan goes up to strend for length of run */ - - var _win = s.window; - - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the longest run, plus one for the unrolled loop. - */ - if (s.lookahead <= MAX_MATCH) { - fill_window(s); - if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { break; } /* flush the current block */ - } - - /* See how many times the previous byte repeats */ - s.match_length = 0; - if (s.lookahead >= MIN_MATCH && s.strstart > 0) { - scan = s.strstart - 1; - prev = _win[scan]; - if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { - strend = s.strstart + MAX_MATCH; - do { - /*jshint noempty:false*/ - } while (prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - scan < strend); - s.match_length = MAX_MATCH - (strend - scan); - if (s.match_length > s.lookahead) { - s.match_length = s.lookahead; - } - } - //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); - } - - /* Emit match if have run of MIN_MATCH or longer, else emit literal */ - if (s.match_length >= MIN_MATCH) { - //check_match(s, s.strstart, s.strstart - 1, s.match_length); - - /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ - bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH); - - s.lookahead -= s.match_length; - s.strstart += s.match_length; - s.match_length = 0; - } else { - /* No match, output a literal byte */ - //Tracevv((stderr,"%c", s->window[s->strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = trees._tr_tally(s, 0, s.window[s.strstart]); - - s.lookahead--; - s.strstart++; - } - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = 0; - if (flush === Z_FINISH) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.last_lit) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -} - -/* =========================================================================== - * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. - * (It will be regenerated if this run of deflate switches away from Huffman.) - */ -function deflate_huff(s, flush) { - var bflush; /* set if current block must be flushed */ - - for (;;) { - /* Make sure that we have a literal to write. */ - if (s.lookahead === 0) { - fill_window(s); - if (s.lookahead === 0) { - if (flush === Z_NO_FLUSH) { - return BS_NEED_MORE; - } - break; /* flush the current block */ - } - } - - /* Output a literal byte */ - s.match_length = 0; - //Tracevv((stderr,"%c", s->window[s->strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = trees._tr_tally(s, 0, s.window[s.strstart]); - s.lookahead--; - s.strstart++; - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = 0; - if (flush === Z_FINISH) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.last_lit) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -} - -/* Values for max_lazy_match, good_match and max_chain_length, depending on - * the desired pack level (0..9). The values given below have been tuned to - * exclude worst case performance for pathological files. Better values may be - * found for specific files. - */ -var Config = function (good_length, max_lazy, nice_length, max_chain, func) { - this.good_length = good_length; - this.max_lazy = max_lazy; - this.nice_length = nice_length; - this.max_chain = max_chain; - this.func = func; -}; - -var configuration_table; - -configuration_table = [ - /* good lazy nice chain */ - new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ - new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ - new Config(4, 5, 16, 8, deflate_fast), /* 2 */ - new Config(4, 6, 32, 32, deflate_fast), /* 3 */ - - new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ - new Config(8, 16, 32, 32, deflate_slow), /* 5 */ - new Config(8, 16, 128, 128, deflate_slow), /* 6 */ - new Config(8, 32, 128, 256, deflate_slow), /* 7 */ - new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ - new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ -]; - - -/* =========================================================================== - * Initialize the "longest match" routines for a new zlib stream - */ -function lm_init(s) { - s.window_size = 2 * s.w_size; - - /*** CLEAR_HASH(s); ***/ - zero(s.head); // Fill with NIL (= 0); - - /* Set the default configuration parameters: - */ - s.max_lazy_match = configuration_table[s.level].max_lazy; - s.good_match = configuration_table[s.level].good_length; - s.nice_match = configuration_table[s.level].nice_length; - s.max_chain_length = configuration_table[s.level].max_chain; - - s.strstart = 0; - s.block_start = 0; - s.lookahead = 0; - s.insert = 0; - s.match_length = s.prev_length = MIN_MATCH - 1; - s.match_available = 0; - s.ins_h = 0; -} - - -function DeflateState() { - this.strm = null; /* pointer back to this zlib stream */ - this.status = 0; /* as the name implies */ - this.pending_buf = null; /* output still pending */ - this.pending_buf_size = 0; /* size of pending_buf */ - this.pending_out = 0; /* next pending byte to output to the stream */ - this.pending = 0; /* nb of bytes in the pending buffer */ - this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ - this.gzhead = null; /* gzip header information to write */ - this.gzindex = 0; /* where in extra, name, or comment */ - this.method = Z_DEFLATED; /* can only be DEFLATED */ - this.last_flush = -1; /* value of flush param for previous deflate call */ - - this.w_size = 0; /* LZ77 window size (32K by default) */ - this.w_bits = 0; /* log2(w_size) (8..16) */ - this.w_mask = 0; /* w_size - 1 */ - - this.window = null; - /* Sliding window. Input bytes are read into the second half of the window, - * and move to the first half later to keep a dictionary of at least wSize - * bytes. With this organization, matches are limited to a distance of - * wSize-MAX_MATCH bytes, but this ensures that IO is always - * performed with a length multiple of the block size. - */ - - this.window_size = 0; - /* Actual size of window: 2*wSize, except when the user input buffer - * is directly used as sliding window. - */ - - this.prev = null; - /* Link to older string with same hash index. To limit the size of this - * array to 64K, this link is maintained only for the last 32K strings. - * An index in this array is thus a window index modulo 32K. - */ - - this.head = null; /* Heads of the hash chains or NIL. */ - - this.ins_h = 0; /* hash index of string to be inserted */ - this.hash_size = 0; /* number of elements in hash table */ - this.hash_bits = 0; /* log2(hash_size) */ - this.hash_mask = 0; /* hash_size-1 */ - - this.hash_shift = 0; - /* Number of bits by which ins_h must be shifted at each input - * step. It must be such that after MIN_MATCH steps, the oldest - * byte no longer takes part in the hash key, that is: - * hash_shift * MIN_MATCH >= hash_bits - */ - - this.block_start = 0; - /* Window position at the beginning of the current output block. Gets - * negative when the window is moved backwards. - */ - - this.match_length = 0; /* length of best match */ - this.prev_match = 0; /* previous match */ - this.match_available = 0; /* set if previous match exists */ - this.strstart = 0; /* start of string to insert */ - this.match_start = 0; /* start of matching string */ - this.lookahead = 0; /* number of valid bytes ahead in window */ - - this.prev_length = 0; - /* Length of the best match at previous step. Matches not greater than this - * are discarded. This is used in the lazy match evaluation. - */ - - this.max_chain_length = 0; - /* To speed up deflation, hash chains are never searched beyond this - * length. A higher limit improves compression ratio but degrades the - * speed. - */ - - this.max_lazy_match = 0; - /* Attempt to find a better match only when the current match is strictly - * smaller than this value. This mechanism is used only for compression - * levels >= 4. - */ - // That's alias to max_lazy_match, don't use directly - //this.max_insert_length = 0; - /* Insert new strings in the hash table only if the match length is not - * greater than this length. This saves time but degrades compression. - * max_insert_length is used only for compression levels <= 3. - */ - - this.level = 0; /* compression level (1..9) */ - this.strategy = 0; /* favor or force Huffman coding*/ - - this.good_match = 0; - /* Use a faster search when the previous match is longer than this */ - - this.nice_match = 0; /* Stop searching when current match exceeds this */ - - /* used by trees.c: */ - - /* Didn't use ct_data typedef below to suppress compiler warning */ - - // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ - // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ - // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ - - // Use flat array of DOUBLE size, with interleaved fata, - // because JS does not support effective - this.dyn_ltree = new utils.Buf16(HEAP_SIZE * 2); - this.dyn_dtree = new utils.Buf16((2*D_CODES+1) * 2); - this.bl_tree = new utils.Buf16((2*BL_CODES+1) * 2); - zero(this.dyn_ltree); - zero(this.dyn_dtree); - zero(this.bl_tree); - - this.l_desc = null; /* desc. for literal tree */ - this.d_desc = null; /* desc. for distance tree */ - this.bl_desc = null; /* desc. for bit length tree */ - - //ush bl_count[MAX_BITS+1]; - this.bl_count = new utils.Buf16(MAX_BITS+1); - /* number of codes at each bit length for an optimal tree */ - - //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ - this.heap = new utils.Buf16(2*L_CODES+1); /* heap used to build the Huffman trees */ - zero(this.heap); - - this.heap_len = 0; /* number of elements in the heap */ - this.heap_max = 0; /* element of largest frequency */ - /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. - * The same heap array is used to build all trees. - */ - - this.depth = new utils.Buf16(2*L_CODES+1); //uch depth[2*L_CODES+1]; - zero(this.depth); - /* Depth of each subtree used as tie breaker for trees of equal frequency - */ - - this.l_buf = 0; /* buffer index for literals or lengths */ - - this.lit_bufsize = 0; - /* Size of match buffer for literals/lengths. There are 4 reasons for - * limiting lit_bufsize to 64K: - * - frequencies can be kept in 16 bit counters - * - if compression is not successful for the first block, all input - * data is still in the window so we can still emit a stored block even - * when input comes from standard input. (This can also be done for - * all blocks if lit_bufsize is not greater than 32K.) - * - if compression is not successful for a file smaller than 64K, we can - * even emit a stored file instead of a stored block (saving 5 bytes). - * This is applicable only for zip (not gzip or zlib). - * - creating new Huffman trees less frequently may not provide fast - * adaptation to changes in the input data statistics. (Take for - * example a binary file with poorly compressible code followed by - * a highly compressible string table.) Smaller buffer sizes give - * fast adaptation but have of course the overhead of transmitting - * trees more frequently. - * - I can't count above 4 - */ - - this.last_lit = 0; /* running index in l_buf */ - - this.d_buf = 0; - /* Buffer index for distances. To simplify the code, d_buf and l_buf have - * the same number of elements. To use different lengths, an extra flag - * array would be necessary. - */ - - this.opt_len = 0; /* bit length of current block with optimal trees */ - this.static_len = 0; /* bit length of current block with static trees */ - this.matches = 0; /* number of string matches in current block */ - this.insert = 0; /* bytes at end of window left to insert */ - - - this.bi_buf = 0; - /* Output buffer. bits are inserted starting at the bottom (least - * significant bits). - */ - this.bi_valid = 0; - /* Number of valid bits in bi_buf. All bits above the last valid bit - * are always zero. - */ - - // Used for window memory init. We safely ignore it for JS. That makes - // sense only for pointers and memory check tools. - //this.high_water = 0; - /* High water mark offset in window for initialized bytes -- bytes above - * this are set to zero in order to avoid memory check warnings when - * longest match routines access bytes past the input. This is then - * updated to the new high water mark. - */ -} - - -function deflateResetKeep(strm) { - var s; - - if (!strm || !strm.state) { - return err(strm, Z_STREAM_ERROR); - } - - strm.total_in = strm.total_out = 0; - strm.data_type = Z_UNKNOWN; - - s = strm.state; - s.pending = 0; - s.pending_out = 0; - - if (s.wrap < 0) { - s.wrap = -s.wrap; - /* was made negative by deflate(..., Z_FINISH); */ - } - s.status = (s.wrap ? INIT_STATE : BUSY_STATE); - strm.adler = (s.wrap === 2) ? - 0 // crc32(0, Z_NULL, 0) - : - 1; // adler32(0, Z_NULL, 0) - s.last_flush = Z_NO_FLUSH; - trees._tr_init(s); - return Z_OK; -} - - -function deflateReset(strm) { - var ret = deflateResetKeep(strm); - if (ret === Z_OK) { - lm_init(strm.state); - } - return ret; -} - - -function deflateSetHeader(strm, head) { - if (!strm || !strm.state) { return Z_STREAM_ERROR; } - if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; } - strm.state.gzhead = head; - return Z_OK; -} - - -function deflateInit2(strm, level, method, windowBits, memLevel, strategy) { - if (!strm) { // === Z_NULL - return Z_STREAM_ERROR; - } - var wrap = 1; - - if (level === Z_DEFAULT_COMPRESSION) { - level = 6; - } - - if (windowBits < 0) { /* suppress zlib wrapper */ - wrap = 0; - windowBits = -windowBits; - } - - else if (windowBits > 15) { - wrap = 2; /* write gzip wrapper instead */ - windowBits -= 16; - } - - - if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED || - windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || - strategy < 0 || strategy > Z_FIXED) { - return err(strm, Z_STREAM_ERROR); - } - - - if (windowBits === 8) { - windowBits = 9; - } - /* until 256-byte window bug fixed */ - - var s = new DeflateState(); - - strm.state = s; - s.strm = strm; - - s.wrap = wrap; - s.gzhead = null; - s.w_bits = windowBits; - s.w_size = 1 << s.w_bits; - s.w_mask = s.w_size - 1; - - s.hash_bits = memLevel + 7; - s.hash_size = 1 << s.hash_bits; - s.hash_mask = s.hash_size - 1; - s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); - - s.window = new utils.Buf8(s.w_size * 2); - s.head = new utils.Buf16(s.hash_size); - s.prev = new utils.Buf16(s.w_size); - - // Don't need mem init magic for JS. - //s.high_water = 0; /* nothing written to s->window yet */ - - s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ - - s.pending_buf_size = s.lit_bufsize * 4; - s.pending_buf = new utils.Buf8(s.pending_buf_size); - - s.d_buf = s.lit_bufsize >> 1; - s.l_buf = (1 + 2) * s.lit_bufsize; - - s.level = level; - s.strategy = strategy; - s.method = method; - - return deflateReset(strm); -} - -function deflateInit(strm, level) { - return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); -} - - -function deflate(strm, flush) { - var old_flush, s; - var beg, val; // for gzip header write only - - if (!strm || !strm.state || - flush > Z_BLOCK || flush < 0) { - return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR; - } - - s = strm.state; - - if (!strm.output || - (!strm.input && strm.avail_in !== 0) || - (s.status === FINISH_STATE && flush !== Z_FINISH)) { - return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR); - } - - s.strm = strm; /* just in case */ - old_flush = s.last_flush; - s.last_flush = flush; - - /* Write the header */ - if (s.status === INIT_STATE) { - - if (s.wrap === 2) { // GZIP header - strm.adler = 0; //crc32(0L, Z_NULL, 0); - put_byte(s, 31); - put_byte(s, 139); - put_byte(s, 8); - if (!s.gzhead) { // s->gzhead == Z_NULL - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, s.level === 9 ? 2 : - (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? - 4 : 0)); - put_byte(s, OS_CODE); - s.status = BUSY_STATE; - } - else { - put_byte(s, (s.gzhead.text ? 1 : 0) + - (s.gzhead.hcrc ? 2 : 0) + - (!s.gzhead.extra ? 0 : 4) + - (!s.gzhead.name ? 0 : 8) + - (!s.gzhead.comment ? 0 : 16) - ); - put_byte(s, s.gzhead.time & 0xff); - put_byte(s, (s.gzhead.time >> 8) & 0xff); - put_byte(s, (s.gzhead.time >> 16) & 0xff); - put_byte(s, (s.gzhead.time >> 24) & 0xff); - put_byte(s, s.level === 9 ? 2 : - (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? - 4 : 0)); - put_byte(s, s.gzhead.os & 0xff); - if (s.gzhead.extra && s.gzhead.extra.length) { - put_byte(s, s.gzhead.extra.length & 0xff); - put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); - } - if (s.gzhead.hcrc) { - strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0); - } - s.gzindex = 0; - s.status = EXTRA_STATE; - } - } - else // DEFLATE header - { - var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8; - var level_flags = -1; - - if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { - level_flags = 0; - } else if (s.level < 6) { - level_flags = 1; - } else if (s.level === 6) { - level_flags = 2; - } else { - level_flags = 3; - } - header |= (level_flags << 6); - if (s.strstart !== 0) { header |= PRESET_DICT; } - header += 31 - (header % 31); - - s.status = BUSY_STATE; - putShortMSB(s, header); - - /* Save the adler32 of the preset dictionary: */ - if (s.strstart !== 0) { - putShortMSB(s, strm.adler >>> 16); - putShortMSB(s, strm.adler & 0xffff); - } - strm.adler = 1; // adler32(0L, Z_NULL, 0); - } - } - -//#ifdef GZIP - if (s.status === EXTRA_STATE) { - if (s.gzhead.extra/* != Z_NULL*/) { - beg = s.pending; /* start of bytes to update crc */ - - while (s.gzindex < (s.gzhead.extra.length & 0xffff)) { - if (s.pending === s.pending_buf_size) { - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); - } - flush_pending(strm); - beg = s.pending; - if (s.pending === s.pending_buf_size) { - break; - } - } - put_byte(s, s.gzhead.extra[s.gzindex] & 0xff); - s.gzindex++; - } - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); - } - if (s.gzindex === s.gzhead.extra.length) { - s.gzindex = 0; - s.status = NAME_STATE; - } - } - else { - s.status = NAME_STATE; - } - } - if (s.status === NAME_STATE) { - if (s.gzhead.name/* != Z_NULL*/) { - beg = s.pending; /* start of bytes to update crc */ - //int val; - - do { - if (s.pending === s.pending_buf_size) { - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); - } - flush_pending(strm); - beg = s.pending; - if (s.pending === s.pending_buf_size) { - val = 1; - break; - } - } - // JS specific: little magic to add zero terminator to end of string - if (s.gzindex < s.gzhead.name.length) { - val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; - } else { - val = 0; - } - put_byte(s, val); - } while (val !== 0); - - if (s.gzhead.hcrc && s.pending > beg){ - strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); - } - if (val === 0) { - s.gzindex = 0; - s.status = COMMENT_STATE; - } - } - else { - s.status = COMMENT_STATE; - } - } - if (s.status === COMMENT_STATE) { - if (s.gzhead.comment/* != Z_NULL*/) { - beg = s.pending; /* start of bytes to update crc */ - //int val; - - do { - if (s.pending === s.pending_buf_size) { - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); - } - flush_pending(strm); - beg = s.pending; - if (s.pending === s.pending_buf_size) { - val = 1; - break; - } - } - // JS specific: little magic to add zero terminator to end of string - if (s.gzindex < s.gzhead.comment.length) { - val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; - } else { - val = 0; - } - put_byte(s, val); - } while (val !== 0); - - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); - } - if (val === 0) { - s.status = HCRC_STATE; - } - } - else { - s.status = HCRC_STATE; - } - } - if (s.status === HCRC_STATE) { - if (s.gzhead.hcrc) { - if (s.pending + 2 > s.pending_buf_size) { - flush_pending(strm); - } - if (s.pending + 2 <= s.pending_buf_size) { - put_byte(s, strm.adler & 0xff); - put_byte(s, (strm.adler >> 8) & 0xff); - strm.adler = 0; //crc32(0L, Z_NULL, 0); - s.status = BUSY_STATE; - } - } - else { - s.status = BUSY_STATE; - } - } -//#endif - - /* Flush as much pending output as possible */ - if (s.pending !== 0) { - flush_pending(strm); - if (strm.avail_out === 0) { - /* Since avail_out is 0, deflate will be called again with - * more output space, but possibly with both pending and - * avail_in equal to zero. There won't be anything to do, - * but this is not an error situation so make sure we - * return OK instead of BUF_ERROR at next call of deflate: - */ - s.last_flush = -1; - return Z_OK; - } - - /* Make sure there is something to do and avoid duplicate consecutive - * flushes. For repeated and useless calls with Z_FINISH, we keep - * returning Z_STREAM_END instead of Z_BUF_ERROR. - */ - } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && - flush !== Z_FINISH) { - return err(strm, Z_BUF_ERROR); - } - - /* User must not provide more input after the first FINISH: */ - if (s.status === FINISH_STATE && strm.avail_in !== 0) { - return err(strm, Z_BUF_ERROR); - } - - /* Start a new block or continue the current one. - */ - if (strm.avail_in !== 0 || s.lookahead !== 0 || - (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) { - var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) : - (s.strategy === Z_RLE ? deflate_rle(s, flush) : - configuration_table[s.level].func(s, flush)); - - if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { - s.status = FINISH_STATE; - } - if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { - if (strm.avail_out === 0) { - s.last_flush = -1; - /* avoid BUF_ERROR next call, see above */ - } - return Z_OK; - /* If flush != Z_NO_FLUSH && avail_out == 0, the next call - * of deflate should use the same flush parameter to make sure - * that the flush is complete. So we don't have to output an - * empty block here, this will be done at next call. This also - * ensures that for a very small output buffer, we emit at most - * one empty block. - */ - } - if (bstate === BS_BLOCK_DONE) { - if (flush === Z_PARTIAL_FLUSH) { - trees._tr_align(s); - } - else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */ - - trees._tr_stored_block(s, 0, 0, false); - /* For a full flush, this empty block will be recognized - * as a special marker by inflate_sync(). - */ - if (flush === Z_FULL_FLUSH) { - /*** CLEAR_HASH(s); ***/ /* forget history */ - zero(s.head); // Fill with NIL (= 0); - - if (s.lookahead === 0) { - s.strstart = 0; - s.block_start = 0; - s.insert = 0; - } - } - } - flush_pending(strm); - if (strm.avail_out === 0) { - s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ - return Z_OK; - } - } - } - //Assert(strm->avail_out > 0, "bug2"); - //if (strm.avail_out <= 0) { throw new Error("bug2");} - - if (flush !== Z_FINISH) { return Z_OK; } - if (s.wrap <= 0) { return Z_STREAM_END; } - - /* Write the trailer */ - if (s.wrap === 2) { - put_byte(s, strm.adler & 0xff); - put_byte(s, (strm.adler >> 8) & 0xff); - put_byte(s, (strm.adler >> 16) & 0xff); - put_byte(s, (strm.adler >> 24) & 0xff); - put_byte(s, strm.total_in & 0xff); - put_byte(s, (strm.total_in >> 8) & 0xff); - put_byte(s, (strm.total_in >> 16) & 0xff); - put_byte(s, (strm.total_in >> 24) & 0xff); - } - else - { - putShortMSB(s, strm.adler >>> 16); - putShortMSB(s, strm.adler & 0xffff); - } - - flush_pending(strm); - /* If avail_out is zero, the application will call deflate again - * to flush the rest. - */ - if (s.wrap > 0) { s.wrap = -s.wrap; } - /* write the trailer only once! */ - return s.pending !== 0 ? Z_OK : Z_STREAM_END; -} - -function deflateEnd(strm) { - var status; - - if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) { - return Z_STREAM_ERROR; - } - - status = strm.state.status; - if (status !== INIT_STATE && - status !== EXTRA_STATE && - status !== NAME_STATE && - status !== COMMENT_STATE && - status !== HCRC_STATE && - status !== BUSY_STATE && - status !== FINISH_STATE - ) { - return err(strm, Z_STREAM_ERROR); - } - - strm.state = null; - - return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK; -} - -/* ========================================================================= - * Copy the source state to the destination state - */ -//function deflateCopy(dest, source) { -// -//} - -exports.deflateInit = deflateInit; -exports.deflateInit2 = deflateInit2; -exports.deflateReset = deflateReset; -exports.deflateResetKeep = deflateResetKeep; -exports.deflateSetHeader = deflateSetHeader; -exports.deflate = deflate; -exports.deflateEnd = deflateEnd; -exports.deflateInfo = 'pako deflate (from Nodeca project)'; - -/* Not implemented -exports.deflateBound = deflateBound; -exports.deflateCopy = deflateCopy; -exports.deflateSetDictionary = deflateSetDictionary; -exports.deflateParams = deflateParams; -exports.deflatePending = deflatePending; -exports.deflatePrime = deflatePrime; -exports.deflateTune = deflateTune; -*/ -},{"../utils/common":27,"./adler32":29,"./crc32":31,"./messages":37,"./trees":38}],33:[function(_dereq_,module,exports){ -'use strict'; - - -function GZheader() { - /* true if compressed data believed to be text */ - this.text = 0; - /* modification time */ - this.time = 0; - /* extra flags (not used when writing a gzip file) */ - this.xflags = 0; - /* operating system */ - this.os = 0; - /* pointer to extra field or Z_NULL if none */ - this.extra = null; - /* extra field length (valid if extra != Z_NULL) */ - this.extra_len = 0; // Actually, we don't need it in JS, - // but leave for few code modifications - - // - // Setup limits is not necessary because in js we should not preallocate memory - // for inflate use constant limit in 65536 bytes - // - - /* space at extra (only when reading header) */ - // this.extra_max = 0; - /* pointer to zero-terminated file name or Z_NULL */ - this.name = ''; - /* space at name (only when reading header) */ - // this.name_max = 0; - /* pointer to zero-terminated comment or Z_NULL */ - this.comment = ''; - /* space at comment (only when reading header) */ - // this.comm_max = 0; - /* true if there was or will be a header crc */ - this.hcrc = 0; - /* true when done reading gzip header (not used when writing a gzip file) */ - this.done = false; -} - -module.exports = GZheader; -},{}],34:[function(_dereq_,module,exports){ -'use strict'; - -// See state defs from inflate.js -var BAD = 30; /* got a data error -- remain here until reset */ -var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ - -/* - Decode literal, length, and distance codes and write out the resulting - literal and match bytes until either not enough input or output is - available, an end-of-block is encountered, or a data error is encountered. - When large enough input and output buffers are supplied to inflate(), for - example, a 16K input buffer and a 64K output buffer, more than 95% of the - inflate execution time is spent in this routine. - - Entry assumptions: - - state.mode === LEN - strm.avail_in >= 6 - strm.avail_out >= 258 - start >= strm.avail_out - state.bits < 8 - - On return, state.mode is one of: - - LEN -- ran out of enough output space or enough available input - TYPE -- reached end of block code, inflate() to interpret next block - BAD -- error in block data - - Notes: - - - The maximum input bits used by a length/distance pair is 15 bits for the - length code, 5 bits for the length extra, 15 bits for the distance code, - and 13 bits for the distance extra. This totals 48 bits, or six bytes. - Therefore if strm.avail_in >= 6, then there is enough input to avoid - checking for available input while decoding. - - - The maximum bytes that a single length/distance pair can output is 258 - bytes, which is the maximum length that can be coded. inflate_fast() - requires strm.avail_out >= 258 for each loop to avoid checking for - output space. - */ -module.exports = function inflate_fast(strm, start) { - var state; - var _in; /* local strm.input */ - var last; /* have enough input while in < last */ - var _out; /* local strm.output */ - var beg; /* inflate()'s initial strm.output */ - var end; /* while out < end, enough space available */ -//#ifdef INFLATE_STRICT - var dmax; /* maximum distance from zlib header */ -//#endif - var wsize; /* window size or zero if not using window */ - var whave; /* valid bytes in the window */ - var wnext; /* window write index */ - var window; /* allocated sliding window, if wsize != 0 */ - var hold; /* local strm.hold */ - var bits; /* local strm.bits */ - var lcode; /* local strm.lencode */ - var dcode; /* local strm.distcode */ - var lmask; /* mask for first level of length codes */ - var dmask; /* mask for first level of distance codes */ - var here; /* retrieved table entry */ - var op; /* code bits, operation, extra bits, or */ - /* window position, window bytes to copy */ - var len; /* match length, unused bytes */ - var dist; /* match distance */ - var from; /* where to copy match from */ - var from_source; - - - var input, output; // JS specific, because we have no pointers - - /* copy state to local variables */ - state = strm.state; - //here = state.here; - _in = strm.next_in; - input = strm.input; - last = _in + (strm.avail_in - 5); - _out = strm.next_out; - output = strm.output; - beg = _out - (start - strm.avail_out); - end = _out + (strm.avail_out - 257); -//#ifdef INFLATE_STRICT - dmax = state.dmax; -//#endif - wsize = state.wsize; - whave = state.whave; - wnext = state.wnext; - window = state.window; - hold = state.hold; - bits = state.bits; - lcode = state.lencode; - dcode = state.distcode; - lmask = (1 << state.lenbits) - 1; - dmask = (1 << state.distbits) - 1; - - - /* decode literals and length/distances until end-of-block or not enough - input data or output space */ - - top: - do { - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - - here = lcode[hold & lmask]; - - dolen: - for (;;) { // Goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - if (op === 0) { /* literal */ - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - output[_out++] = here & 0xffff/*here.val*/; - } - else if (op & 16) { /* length base */ - len = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (op) { - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - len += hold & ((1 << op) - 1); - hold >>>= op; - bits -= op; - } - //Tracevv((stderr, "inflate: length %u\n", len)); - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - here = dcode[hold & dmask]; - - dodist: - for (;;) { // goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - - if (op & 16) { /* distance base */ - dist = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - } - dist += hold & ((1 << op) - 1); -//#ifdef INFLATE_STRICT - if (dist > dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break top; - } -//#endif - hold >>>= op; - bits -= op; - //Tracevv((stderr, "inflate: distance %u\n", dist)); - op = _out - beg; /* max distance in output */ - if (dist > op) { /* see if copy from window */ - op = dist - op; /* distance back in window */ - if (op > whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break top; - } - -// (!) This block is disabled in zlib defailts, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// if (len <= op - whave) { -// do { -// output[_out++] = 0; -// } while (--len); -// continue top; -// } -// len -= op - whave; -// do { -// output[_out++] = 0; -// } while (--op > whave); -// if (op === 0) { -// from = _out - dist; -// do { -// output[_out++] = output[from++]; -// } while (--len); -// continue top; -// } -//#endif - } - from = 0; // window index - from_source = window; - if (wnext === 0) { /* very common case */ - from += wsize - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - else if (wnext < op) { /* wrap around window */ - from += wsize + wnext - op; - op -= wnext; - if (op < len) { /* some from end of window */ - len -= op; - do { - output[_out++] = window[from++]; - } while (--op); - from = 0; - if (wnext < len) { /* some from start of window */ - op = wnext; - len -= op; - do { - output[_out++] = window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - } - else { /* contiguous in window */ - from += wnext - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - while (len > 2) { - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - len -= 3; - } - if (len) { - output[_out++] = from_source[from++]; - if (len > 1) { - output[_out++] = from_source[from++]; - } - } - } - else { - from = _out - dist; /* copy direct from output */ - do { /* minimum length is three */ - output[_out++] = output[from++]; - output[_out++] = output[from++]; - output[_out++] = output[from++]; - len -= 3; - } while (len > 2); - if (len) { - output[_out++] = output[from++]; - if (len > 1) { - output[_out++] = output[from++]; - } - } - } - } - else if ((op & 64) === 0) { /* 2nd level distance code */ - here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dodist; - } - else { - strm.msg = 'invalid distance code'; - state.mode = BAD; - break top; - } - - break; // need to emulate goto via "continue" - } - } - else if ((op & 64) === 0) { /* 2nd level length code */ - here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dolen; - } - else if (op & 32) { /* end-of-block */ - //Tracevv((stderr, "inflate: end of block\n")); - state.mode = TYPE; - break top; - } - else { - strm.msg = 'invalid literal/length code'; - state.mode = BAD; - break top; - } - - break; // need to emulate goto via "continue" - } - } while (_in < last && _out < end); - - /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ - len = bits >> 3; - _in -= len; - bits -= len << 3; - hold &= (1 << bits) - 1; - - /* update state and return */ - strm.next_in = _in; - strm.next_out = _out; - strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); - strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); - state.hold = hold; - state.bits = bits; - return; -}; - -},{}],35:[function(_dereq_,module,exports){ -'use strict'; - - -var utils = _dereq_('../utils/common'); -var adler32 = _dereq_('./adler32'); -var crc32 = _dereq_('./crc32'); -var inflate_fast = _dereq_('./inffast'); -var inflate_table = _dereq_('./inftrees'); - -var CODES = 0; -var LENS = 1; -var DISTS = 2; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - - -/* Allowed flush values; see deflate() and inflate() below for details */ -//var Z_NO_FLUSH = 0; -//var Z_PARTIAL_FLUSH = 1; -//var Z_SYNC_FLUSH = 2; -//var Z_FULL_FLUSH = 3; -var Z_FINISH = 4; -var Z_BLOCK = 5; -var Z_TREES = 6; - - -/* Return codes for the compression/decompression functions. Negative values - * are errors, positive values are used for special but normal events. - */ -var Z_OK = 0; -var Z_STREAM_END = 1; -var Z_NEED_DICT = 2; -//var Z_ERRNO = -1; -var Z_STREAM_ERROR = -2; -var Z_DATA_ERROR = -3; -var Z_MEM_ERROR = -4; -var Z_BUF_ERROR = -5; -//var Z_VERSION_ERROR = -6; - -/* The deflate compression method */ -var Z_DEFLATED = 8; - - -/* STATES ====================================================================*/ -/* ===========================================================================*/ - - -var HEAD = 1; /* i: waiting for magic header */ -var FLAGS = 2; /* i: waiting for method and flags (gzip) */ -var TIME = 3; /* i: waiting for modification time (gzip) */ -var OS = 4; /* i: waiting for extra flags and operating system (gzip) */ -var EXLEN = 5; /* i: waiting for extra length (gzip) */ -var EXTRA = 6; /* i: waiting for extra bytes (gzip) */ -var NAME = 7; /* i: waiting for end of file name (gzip) */ -var COMMENT = 8; /* i: waiting for end of comment (gzip) */ -var HCRC = 9; /* i: waiting for header crc (gzip) */ -var DICTID = 10; /* i: waiting for dictionary check value */ -var DICT = 11; /* waiting for inflateSetDictionary() call */ -var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ -var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */ -var STORED = 14; /* i: waiting for stored size (length and complement) */ -var COPY_ = 15; /* i/o: same as COPY below, but only first time in */ -var COPY = 16; /* i/o: waiting for input or output to copy stored block */ -var TABLE = 17; /* i: waiting for dynamic block table lengths */ -var LENLENS = 18; /* i: waiting for code length code lengths */ -var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */ -var LEN_ = 20; /* i: same as LEN below, but only first time in */ -var LEN = 21; /* i: waiting for length/lit/eob code */ -var LENEXT = 22; /* i: waiting for length extra bits */ -var DIST = 23; /* i: waiting for distance code */ -var DISTEXT = 24; /* i: waiting for distance extra bits */ -var MATCH = 25; /* o: waiting for output space to copy string */ -var LIT = 26; /* o: waiting for output space to write literal */ -var CHECK = 27; /* i: waiting for 32-bit check value */ -var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */ -var DONE = 29; /* finished check, done -- remain here until reset */ -var BAD = 30; /* got a data error -- remain here until reset */ -var MEM = 31; /* got an inflate() memory error -- remain here until reset */ -var SYNC = 32; /* looking for synchronization bytes to restart inflate() */ - -/* ===========================================================================*/ - - - -var ENOUGH_LENS = 852; -var ENOUGH_DISTS = 592; -//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -var MAX_WBITS = 15; -/* 32K LZ77 window */ -var DEF_WBITS = MAX_WBITS; - - -function ZSWAP32(q) { - return (((q >>> 24) & 0xff) + - ((q >>> 8) & 0xff00) + - ((q & 0xff00) << 8) + - ((q & 0xff) << 24)); -} - - -function InflateState() { - this.mode = 0; /* current inflate mode */ - this.last = false; /* true if processing last block */ - this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ - this.havedict = false; /* true if dictionary provided */ - this.flags = 0; /* gzip header method and flags (0 if zlib) */ - this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ - this.check = 0; /* protected copy of check value */ - this.total = 0; /* protected copy of output count */ - // TODO: may be {} - this.head = null; /* where to save gzip header information */ - - /* sliding window */ - this.wbits = 0; /* log base 2 of requested window size */ - this.wsize = 0; /* window size or zero if not using window */ - this.whave = 0; /* valid bytes in the window */ - this.wnext = 0; /* window write index */ - this.window = null; /* allocated sliding window, if needed */ - - /* bit accumulator */ - this.hold = 0; /* input bit accumulator */ - this.bits = 0; /* number of bits in "in" */ - - /* for string and stored block copying */ - this.length = 0; /* literal or length of data to copy */ - this.offset = 0; /* distance back to copy string from */ - - /* for table and code decoding */ - this.extra = 0; /* extra bits needed */ - - /* fixed and dynamic code tables */ - this.lencode = null; /* starting table for length/literal codes */ - this.distcode = null; /* starting table for distance codes */ - this.lenbits = 0; /* index bits for lencode */ - this.distbits = 0; /* index bits for distcode */ - - /* dynamic table building */ - this.ncode = 0; /* number of code length code lengths */ - this.nlen = 0; /* number of length code lengths */ - this.ndist = 0; /* number of distance code lengths */ - this.have = 0; /* number of code lengths in lens[] */ - this.next = null; /* next available space in codes[] */ - - this.lens = new utils.Buf16(320); /* temporary storage for code lengths */ - this.work = new utils.Buf16(288); /* work area for code table building */ - - /* - because we don't have pointers in js, we use lencode and distcode directly - as buffers so we don't need codes - */ - //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */ - this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ - this.distdyn = null; /* dynamic table for distance codes (JS specific) */ - this.sane = 0; /* if false, allow invalid distance too far */ - this.back = 0; /* bits back of last unprocessed length/lit */ - this.was = 0; /* initial length of match */ -} - -function inflateResetKeep(strm) { - var state; - - if (!strm || !strm.state) { return Z_STREAM_ERROR; } - state = strm.state; - strm.total_in = strm.total_out = state.total = 0; - strm.msg = ''; /*Z_NULL*/ - if (state.wrap) { /* to support ill-conceived Java test suite */ - strm.adler = state.wrap & 1; - } - state.mode = HEAD; - state.last = 0; - state.havedict = 0; - state.dmax = 32768; - state.head = null/*Z_NULL*/; - state.hold = 0; - state.bits = 0; - //state.lencode = state.distcode = state.next = state.codes; - state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS); - state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS); - - state.sane = 1; - state.back = -1; - //Tracev((stderr, "inflate: reset\n")); - return Z_OK; -} - -function inflateReset(strm) { - var state; - - if (!strm || !strm.state) { return Z_STREAM_ERROR; } - state = strm.state; - state.wsize = 0; - state.whave = 0; - state.wnext = 0; - return inflateResetKeep(strm); - -} - -function inflateReset2(strm, windowBits) { - var wrap; - var state; - - /* get the state */ - if (!strm || !strm.state) { return Z_STREAM_ERROR; } - state = strm.state; - - /* extract wrap request from windowBits parameter */ - if (windowBits < 0) { - wrap = 0; - windowBits = -windowBits; - } - else { - wrap = (windowBits >> 4) + 1; - if (windowBits < 48) { - windowBits &= 15; - } - } - - /* set number of window bits, free window if different */ - if (windowBits && (windowBits < 8 || windowBits > 15)) { - return Z_STREAM_ERROR; - } - if (state.window !== null && state.wbits !== windowBits) { - state.window = null; - } - - /* update state and reset the rest of it */ - state.wrap = wrap; - state.wbits = windowBits; - return inflateReset(strm); -} - -function inflateInit2(strm, windowBits) { - var ret; - var state; - - if (!strm) { return Z_STREAM_ERROR; } - //strm.msg = Z_NULL; /* in case we return an error */ - - state = new InflateState(); - - //if (state === Z_NULL) return Z_MEM_ERROR; - //Tracev((stderr, "inflate: allocated\n")); - strm.state = state; - state.window = null/*Z_NULL*/; - ret = inflateReset2(strm, windowBits); - if (ret !== Z_OK) { - strm.state = null/*Z_NULL*/; - } - return ret; -} - -function inflateInit(strm) { - return inflateInit2(strm, DEF_WBITS); -} - - -/* - Return state with length and distance decoding tables and index sizes set to - fixed code decoding. Normally this returns fixed tables from inffixed.h. - If BUILDFIXED is defined, then instead this routine builds the tables the - first time it's called, and returns those tables the first time and - thereafter. This reduces the size of the code by about 2K bytes, in - exchange for a little execution time. However, BUILDFIXED should not be - used for threaded applications, since the rewriting of the tables and virgin - may not be thread-safe. - */ -var virgin = true; - -var lenfix, distfix; // We have no pointers in JS, so keep tables separate - -function fixedtables(state) { - /* build fixed huffman tables if first call (may not be thread safe) */ - if (virgin) { - var sym; - - lenfix = new utils.Buf32(512); - distfix = new utils.Buf32(32); - - /* literal/length table */ - sym = 0; - while (sym < 144) { state.lens[sym++] = 8; } - while (sym < 256) { state.lens[sym++] = 9; } - while (sym < 280) { state.lens[sym++] = 7; } - while (sym < 288) { state.lens[sym++] = 8; } - - inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, {bits: 9}); - - /* distance table */ - sym = 0; - while (sym < 32) { state.lens[sym++] = 5; } - - inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, {bits: 5}); - - /* do this just once */ - virgin = false; - } - - state.lencode = lenfix; - state.lenbits = 9; - state.distcode = distfix; - state.distbits = 5; -} - - -/* - Update the window with the last wsize (normally 32K) bytes written before - returning. If window does not exist yet, create it. This is only called - when a window is already in use, or when output has been written during this - inflate call, but the end of the deflate stream has not been reached yet. - It is also called to create a window for dictionary data when a dictionary - is loaded. - - Providing output buffers larger than 32K to inflate() should provide a speed - advantage, since only the last 32K of output is copied to the sliding window - upon return from inflate(), and since all distances after the first 32K of - output will fall in the output data, making match copies simpler and faster. - The advantage may be dependent on the size of the processor's data caches. - */ -function updatewindow(strm, src, end, copy) { - var dist; - var state = strm.state; - - /* if it hasn't been done already, allocate space for the window */ - if (state.window === null) { - state.wsize = 1 << state.wbits; - state.wnext = 0; - state.whave = 0; - - state.window = new utils.Buf8(state.wsize); - } - - /* copy state->wsize or less output bytes into the circular window */ - if (copy >= state.wsize) { - utils.arraySet(state.window,src, end - state.wsize, state.wsize, 0); - state.wnext = 0; - state.whave = state.wsize; - } - else { - dist = state.wsize - state.wnext; - if (dist > copy) { - dist = copy; - } - //zmemcpy(state->window + state->wnext, end - copy, dist); - utils.arraySet(state.window,src, end - copy, dist, state.wnext); - copy -= dist; - if (copy) { - //zmemcpy(state->window, end - copy, copy); - utils.arraySet(state.window,src, end - copy, copy, 0); - state.wnext = copy; - state.whave = state.wsize; - } - else { - state.wnext += dist; - if (state.wnext === state.wsize) { state.wnext = 0; } - if (state.whave < state.wsize) { state.whave += dist; } - } - } - return 0; -} - -function inflate(strm, flush) { - var state; - var input, output; // input/output buffers - var next; /* next input INDEX */ - var put; /* next output INDEX */ - var have, left; /* available input and output */ - var hold; /* bit buffer */ - var bits; /* bits in bit buffer */ - var _in, _out; /* save starting available input and output */ - var copy; /* number of stored or match bytes to copy */ - var from; /* where to copy match bytes from */ - var from_source; - var here = 0; /* current decoding table entry */ - var here_bits, here_op, here_val; // paked "here" denormalized (JS specific) - //var last; /* parent table entry */ - var last_bits, last_op, last_val; // paked "last" denormalized (JS specific) - var len; /* length to copy for repeats, bits to drop */ - var ret; /* return code */ - var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */ - var opts; - - var n; // temporary var for NEED_BITS - - var order = /* permutation of code lengths */ - [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; - - - if (!strm || !strm.state || !strm.output || - (!strm.input && strm.avail_in !== 0)) { - return Z_STREAM_ERROR; - } - - state = strm.state; - if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ - - - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - _in = have; - _out = left; - ret = Z_OK; - - inf_leave: // goto emulation - for (;;) { - switch (state.mode) { - case HEAD: - if (state.wrap === 0) { - state.mode = TYPEDO; - break; - } - //=== NEEDBITS(16); - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ - state.check = 0/*crc32(0L, Z_NULL, 0)*/; - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32(state.check, hbuf, 2, 0); - //===// - - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = FLAGS; - break; - } - state.flags = 0; /* expect zlib header */ - if (state.head) { - state.head.done = false; - } - if (!(state.wrap & 1) || /* check if zlib header allowed */ - (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { - strm.msg = 'incorrect header check'; - state.mode = BAD; - break; - } - if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// - len = (hold & 0x0f)/*BITS(4)*/ + 8; - if (state.wbits === 0) { - state.wbits = len; - } - else if (len > state.wbits) { - strm.msg = 'invalid window size'; - state.mode = BAD; - break; - } - state.dmax = 1 << len; - //Tracev((stderr, "inflate: zlib header ok\n")); - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = hold & 0x200 ? DICTID : TYPE; - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - break; - case FLAGS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.flags = hold; - if ((state.flags & 0xff) !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - if (state.flags & 0xe000) { - strm.msg = 'unknown header flags set'; - state.mode = BAD; - break; - } - if (state.head) { - state.head.text = ((hold >> 8) & 1); - } - if (state.flags & 0x0200) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = TIME; - /* falls through */ - case TIME: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.time = hold; - } - if (state.flags & 0x0200) { - //=== CRC4(state.check, hold) - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - hbuf[2] = (hold >>> 16) & 0xff; - hbuf[3] = (hold >>> 24) & 0xff; - state.check = crc32(state.check, hbuf, 4, 0); - //=== - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = OS; - /* falls through */ - case OS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.xflags = (hold & 0xff); - state.head.os = (hold >> 8); - } - if (state.flags & 0x0200) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = EXLEN; - /* falls through */ - case EXLEN: - if (state.flags & 0x0400) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length = hold; - if (state.head) { - state.head.extra_len = hold; - } - if (state.flags & 0x0200) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - else if (state.head) { - state.head.extra = null/*Z_NULL*/; - } - state.mode = EXTRA; - /* falls through */ - case EXTRA: - if (state.flags & 0x0400) { - copy = state.length; - if (copy > have) { copy = have; } - if (copy) { - if (state.head) { - len = state.head.extra_len - state.length; - if (!state.head.extra) { - // Use untyped array for more conveniend processing later - state.head.extra = new Array(state.head.extra_len); - } - utils.arraySet( - state.head.extra, - input, - next, - // extra field is limited to 65536 bytes - // - no need for additional size check - copy, - /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ - len - ); - //zmemcpy(state.head.extra + len, next, - // len + copy > state.head.extra_max ? - // state.head.extra_max - len : copy); - } - if (state.flags & 0x0200) { - state.check = crc32(state.check, input, copy, next); - } - have -= copy; - next += copy; - state.length -= copy; - } - if (state.length) { break inf_leave; } - } - state.length = 0; - state.mode = NAME; - /* falls through */ - case NAME: - if (state.flags & 0x0800) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - // TODO: 2 or 1 bytes? - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.name_max*/)) { - state.head.name += String.fromCharCode(len); - } - } while (len && copy < have); - - if (state.flags & 0x0200) { - state.check = crc32(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.name = null; - } - state.length = 0; - state.mode = COMMENT; - /* falls through */ - case COMMENT: - if (state.flags & 0x1000) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.comm_max*/)) { - state.head.comment += String.fromCharCode(len); - } - } while (len && copy < have); - if (state.flags & 0x0200) { - state.check = crc32(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.comment = null; - } - state.mode = HCRC; - /* falls through */ - case HCRC: - if (state.flags & 0x0200) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (hold !== (state.check & 0xffff)) { - strm.msg = 'header crc mismatch'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - if (state.head) { - state.head.hcrc = ((state.flags >> 9) & 1); - state.head.done = true; - } - strm.adler = state.check = 0 /*crc32(0L, Z_NULL, 0)*/; - state.mode = TYPE; - break; - case DICTID: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - strm.adler = state.check = ZSWAP32(hold); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = DICT; - /* falls through */ - case DICT: - if (state.havedict === 0) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - return Z_NEED_DICT; - } - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = TYPE; - /* falls through */ - case TYPE: - if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } - /* falls through */ - case TYPEDO: - if (state.last) { - //--- BYTEBITS() ---// - hold >>>= bits & 7; - bits -= bits & 7; - //---// - state.mode = CHECK; - break; - } - //=== NEEDBITS(3); */ - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.last = (hold & 0x01)/*BITS(1)*/; - //--- DROPBITS(1) ---// - hold >>>= 1; - bits -= 1; - //---// - - switch ((hold & 0x03)/*BITS(2)*/) { - case 0: /* stored block */ - //Tracev((stderr, "inflate: stored block%s\n", - // state.last ? " (last)" : "")); - state.mode = STORED; - break; - case 1: /* fixed block */ - fixedtables(state); - //Tracev((stderr, "inflate: fixed codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = LEN_; /* decode codes */ - if (flush === Z_TREES) { - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break inf_leave; - } - break; - case 2: /* dynamic block */ - //Tracev((stderr, "inflate: dynamic codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = TABLE; - break; - case 3: - strm.msg = 'invalid block type'; - state.mode = BAD; - } - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break; - case STORED: - //--- BYTEBITS() ---// /* go to byte boundary */ - hold >>>= bits & 7; - bits -= bits & 7; - //---// - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { - strm.msg = 'invalid stored block lengths'; - state.mode = BAD; - break; - } - state.length = hold & 0xffff; - //Tracev((stderr, "inflate: stored length %u\n", - // state.length)); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = COPY_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case COPY_: - state.mode = COPY; - /* falls through */ - case COPY: - copy = state.length; - if (copy) { - if (copy > have) { copy = have; } - if (copy > left) { copy = left; } - if (copy === 0) { break inf_leave; } - //--- zmemcpy(put, next, copy); --- - utils.arraySet(output, input, next, copy, put); - //---// - have -= copy; - next += copy; - left -= copy; - put += copy; - state.length -= copy; - break; - } - //Tracev((stderr, "inflate: stored end\n")); - state.mode = TYPE; - break; - case TABLE: - //=== NEEDBITS(14); */ - while (bits < 14) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// -//#ifndef PKZIP_BUG_WORKAROUND - if (state.nlen > 286 || state.ndist > 30) { - strm.msg = 'too many length or distance symbols'; - state.mode = BAD; - break; - } -//#endif - //Tracev((stderr, "inflate: table sizes ok\n")); - state.have = 0; - state.mode = LENLENS; - /* falls through */ - case LENLENS: - while (state.have < state.ncode) { - //=== NEEDBITS(3); - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - while (state.have < 19) { - state.lens[order[state.have++]] = 0; - } - // We have separate tables & no pointers. 2 commented lines below not needed. - //state.next = state.codes; - //state.lencode = state.next; - // Switch to use dynamic table - state.lencode = state.lendyn; - state.lenbits = 7; - - opts = {bits: state.lenbits}; - ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); - state.lenbits = opts.bits; - - if (ret) { - strm.msg = 'invalid code lengths set'; - state.mode = BAD; - break; - } - //Tracev((stderr, "inflate: code lengths ok\n")); - state.have = 0; - state.mode = CODELENS; - /* falls through */ - case CODELENS: - while (state.have < state.nlen + state.ndist) { - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_val < 16) { - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.lens[state.have++] = here_val; - } - else { - if (here_val === 16) { - //=== NEEDBITS(here.bits + 2); - n = here_bits + 2; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - if (state.have === 0) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - len = state.lens[state.have - 1]; - copy = 3 + (hold & 0x03);//BITS(2); - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - } - else if (here_val === 17) { - //=== NEEDBITS(here.bits + 3); - n = here_bits + 3; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 3 + (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - else { - //=== NEEDBITS(here.bits + 7); - n = here_bits + 7; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 11 + (hold & 0x7f);//BITS(7); - //--- DROPBITS(7) ---// - hold >>>= 7; - bits -= 7; - //---// - } - if (state.have + copy > state.nlen + state.ndist) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - while (copy--) { - state.lens[state.have++] = len; - } - } - } - - /* handle error breaks in while */ - if (state.mode === BAD) { break; } - - /* check for end-of-block code (better have one) */ - if (state.lens[256] === 0) { - strm.msg = 'invalid code -- missing end-of-block'; - state.mode = BAD; - break; - } - - /* build code tables -- note: do not change the lenbits or distbits - values here (9 and 6) without reading the comments in inftrees.h - concerning the ENOUGH constants, which depend on those values */ - state.lenbits = 9; - - opts = {bits: state.lenbits}; - ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.lenbits = opts.bits; - // state.lencode = state.next; - - if (ret) { - strm.msg = 'invalid literal/lengths set'; - state.mode = BAD; - break; - } - - state.distbits = 6; - //state.distcode.copy(state.codes); - // Switch to use dynamic table - state.distcode = state.distdyn; - opts = {bits: state.distbits}; - ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.distbits = opts.bits; - // state.distcode = state.next; - - if (ret) { - strm.msg = 'invalid distances set'; - state.mode = BAD; - break; - } - //Tracev((stderr, 'inflate: codes ok\n')); - state.mode = LEN_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case LEN_: - state.mode = LEN; - /* falls through */ - case LEN: - if (have >= 6 && left >= 258) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - inflate_fast(strm, _out); - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - if (state.mode === TYPE) { - state.back = -1; - } - break; - } - state.back = 0; - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) -1)]; /*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if (here_bits <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_op && (here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.lencode[last_val + - ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - state.length = here_val; - if (here_op === 0) { - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - state.mode = LIT; - break; - } - if (here_op & 32) { - //Tracevv((stderr, "inflate: end of block\n")); - state.back = -1; - state.mode = TYPE; - break; - } - if (here_op & 64) { - strm.msg = 'invalid literal/length code'; - state.mode = BAD; - break; - } - state.extra = here_op & 15; - state.mode = LENEXT; - /* falls through */ - case LENEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } - //Tracevv((stderr, "inflate: length %u\n", state.length)); - state.was = state.length; - state.mode = DIST; - /* falls through */ - case DIST: - for (;;) { - here = state.distcode[hold & ((1 << state.distbits) -1)];/*BITS(state.distbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if ((here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.distcode[last_val + - ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - if (here_op & 64) { - strm.msg = 'invalid distance code'; - state.mode = BAD; - break; - } - state.offset = here_val; - state.extra = (here_op) & 15; - state.mode = DISTEXT; - /* falls through */ - case DISTEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.offset += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } -//#ifdef INFLATE_STRICT - if (state.offset > state.dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -//#endif - //Tracevv((stderr, "inflate: distance %u\n", state.offset)); - state.mode = MATCH; - /* falls through */ - case MATCH: - if (left === 0) { break inf_leave; } - copy = _out - left; - if (state.offset > copy) { /* copy from window */ - copy = state.offset - copy; - if (copy > state.whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -// (!) This block is disabled in zlib defailts, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// Trace((stderr, "inflate.c too far\n")); -// copy -= state.whave; -// if (copy > state.length) { copy = state.length; } -// if (copy > left) { copy = left; } -// left -= copy; -// state.length -= copy; -// do { -// output[put++] = 0; -// } while (--copy); -// if (state.length === 0) { state.mode = LEN; } -// break; -//#endif - } - if (copy > state.wnext) { - copy -= state.wnext; - from = state.wsize - copy; - } - else { - from = state.wnext - copy; - } - if (copy > state.length) { copy = state.length; } - from_source = state.window; - } - else { /* copy from output */ - from_source = output; - from = put - state.offset; - copy = state.length; - } - if (copy > left) { copy = left; } - left -= copy; - state.length -= copy; - do { - output[put++] = from_source[from++]; - } while (--copy); - if (state.length === 0) { state.mode = LEN; } - break; - case LIT: - if (left === 0) { break inf_leave; } - output[put++] = state.length; - left--; - state.mode = LEN; - break; - case CHECK: - if (state.wrap) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - // Use '|' insdead of '+' to make sure that result is signed - hold |= input[next++] << bits; - bits += 8; - } - //===// - _out -= left; - strm.total_out += _out; - state.total += _out; - if (_out) { - strm.adler = state.check = - /*UPDATE(state.check, put - _out, _out);*/ - (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out)); - - } - _out = left; - // NB: crc32 stored as signed 32-bit int, ZSWAP32 returns signed too - if ((state.flags ? hold : ZSWAP32(hold)) !== state.check) { - strm.msg = 'incorrect data check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: check matches trailer\n")); - } - state.mode = LENGTH; - /* falls through */ - case LENGTH: - if (state.wrap && state.flags) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (hold !== (state.total & 0xffffffff)) { - strm.msg = 'incorrect length check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: length matches trailer\n")); - } - state.mode = DONE; - /* falls through */ - case DONE: - ret = Z_STREAM_END; - break inf_leave; - case BAD: - ret = Z_DATA_ERROR; - break inf_leave; - case MEM: - return Z_MEM_ERROR; - case SYNC: - /* falls through */ - default: - return Z_STREAM_ERROR; - } - } - - // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" - - /* - Return from inflate(), updating the total counts and the check value. - If there was no progress during the inflate() call, return a buffer - error. Call updatewindow() to create and/or update the window state. - Note: a memory error from inflate() is non-recoverable. - */ - - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - - if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && - (state.mode < CHECK || flush !== Z_FINISH))) { - if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) { - state.mode = MEM; - return Z_MEM_ERROR; - } - } - _in -= strm.avail_in; - _out -= strm.avail_out; - strm.total_in += _in; - strm.total_out += _out; - state.total += _out; - if (state.wrap && _out) { - strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/ - (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out)); - } - strm.data_type = state.bits + (state.last ? 64 : 0) + - (state.mode === TYPE ? 128 : 0) + - (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); - if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) { - ret = Z_BUF_ERROR; - } - return ret; -} - -function inflateEnd(strm) { - - if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) { - return Z_STREAM_ERROR; - } - - var state = strm.state; - if (state.window) { - state.window = null; - } - strm.state = null; - return Z_OK; -} - -function inflateGetHeader(strm, head) { - var state; - - /* check state */ - if (!strm || !strm.state) { return Z_STREAM_ERROR; } - state = strm.state; - if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; } - - /* save header structure */ - state.head = head; - head.done = false; - return Z_OK; -} - - -exports.inflateReset = inflateReset; -exports.inflateReset2 = inflateReset2; -exports.inflateResetKeep = inflateResetKeep; -exports.inflateInit = inflateInit; -exports.inflateInit2 = inflateInit2; -exports.inflate = inflate; -exports.inflateEnd = inflateEnd; -exports.inflateGetHeader = inflateGetHeader; -exports.inflateInfo = 'pako inflate (from Nodeca project)'; - -/* Not implemented -exports.inflateCopy = inflateCopy; -exports.inflateGetDictionary = inflateGetDictionary; -exports.inflateMark = inflateMark; -exports.inflatePrime = inflatePrime; -exports.inflateSetDictionary = inflateSetDictionary; -exports.inflateSync = inflateSync; -exports.inflateSyncPoint = inflateSyncPoint; -exports.inflateUndermine = inflateUndermine; -*/ -},{"../utils/common":27,"./adler32":29,"./crc32":31,"./inffast":34,"./inftrees":36}],36:[function(_dereq_,module,exports){ -'use strict'; - - -var utils = _dereq_('../utils/common'); - -var MAXBITS = 15; -var ENOUGH_LENS = 852; -var ENOUGH_DISTS = 592; -//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -var CODES = 0; -var LENS = 1; -var DISTS = 2; - -var lbase = [ /* Length codes 257..285 base */ - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, - 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 -]; - -var lext = [ /* Length codes 257..285 extra */ - 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 -]; - -var dbase = [ /* Distance codes 0..29 base */ - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, - 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, - 8193, 12289, 16385, 24577, 0, 0 -]; - -var dext = [ /* Distance codes 0..29 extra */ - 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, - 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, - 28, 28, 29, 29, 64, 64 -]; - -module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) -{ - var bits = opts.bits; - //here = opts.here; /* table entry for duplication */ - - var len = 0; /* a code's length in bits */ - var sym = 0; /* index of code symbols */ - var min = 0, max = 0; /* minimum and maximum code lengths */ - var root = 0; /* number of index bits for root table */ - var curr = 0; /* number of index bits for current table */ - var drop = 0; /* code bits to drop for sub-table */ - var left = 0; /* number of prefix codes available */ - var used = 0; /* code entries in table used */ - var huff = 0; /* Huffman code */ - var incr; /* for incrementing code, index */ - var fill; /* index for replicating entries */ - var low; /* low bits for current root entry */ - var mask; /* mask for low root bits */ - var next; /* next available space in table */ - var base = null; /* base value table to use */ - var base_index = 0; -// var shoextra; /* extra bits table to use */ - var end; /* use base and extra for symbol > end */ - var count = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* number of codes of each length */ - var offs = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* offsets in table for each length */ - var extra = null; - var extra_index = 0; - - var here_bits, here_op, here_val; - - /* - Process a set of code lengths to create a canonical Huffman code. The - code lengths are lens[0..codes-1]. Each length corresponds to the - symbols 0..codes-1. The Huffman code is generated by first sorting the - symbols by length from short to long, and retaining the symbol order - for codes with equal lengths. Then the code starts with all zero bits - for the first code of the shortest length, and the codes are integer - increments for the same length, and zeros are appended as the length - increases. For the deflate format, these bits are stored backwards - from their more natural integer increment ordering, and so when the - decoding tables are built in the large loop below, the integer codes - are incremented backwards. - - This routine assumes, but does not check, that all of the entries in - lens[] are in the range 0..MAXBITS. The caller must assure this. - 1..MAXBITS is interpreted as that code length. zero means that that - symbol does not occur in this code. - - The codes are sorted by computing a count of codes for each length, - creating from that a table of starting indices for each length in the - sorted table, and then entering the symbols in order in the sorted - table. The sorted table is work[], with that space being provided by - the caller. - - The length counts are used for other purposes as well, i.e. finding - the minimum and maximum length codes, determining if there are any - codes at all, checking for a valid set of lengths, and looking ahead - at length counts to determine sub-table sizes when building the - decoding tables. - */ - - /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ - for (len = 0; len <= MAXBITS; len++) { - count[len] = 0; - } - for (sym = 0; sym < codes; sym++) { - count[lens[lens_index + sym]]++; - } - - /* bound code lengths, force root to be within code lengths */ - root = bits; - for (max = MAXBITS; max >= 1; max--) { - if (count[max] !== 0) { break; } - } - if (root > max) { - root = max; - } - if (max === 0) { /* no symbols to code at all */ - //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ - //table.bits[opts.table_index] = 1; //here.bits = (var char)1; - //table.val[opts.table_index++] = 0; //here.val = (var short)0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - - //table.op[opts.table_index] = 64; - //table.bits[opts.table_index] = 1; - //table.val[opts.table_index++] = 0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - opts.bits = 1; - return 0; /* no symbols, but wait for decoding to report error */ - } - for (min = 1; min < max; min++) { - if (count[min] !== 0) { break; } - } - if (root < min) { - root = min; - } - - /* check for an over-subscribed or incomplete set of lengths */ - left = 1; - for (len = 1; len <= MAXBITS; len++) { - left <<= 1; - left -= count[len]; - if (left < 0) { - return -1; - } /* over-subscribed */ - } - if (left > 0 && (type === CODES || max !== 1)) { - return -1; /* incomplete set */ - } - - /* generate offsets into symbol table for each length for sorting */ - offs[1] = 0; - for (len = 1; len < MAXBITS; len++) { - offs[len + 1] = offs[len] + count[len]; - } - - /* sort symbols by length, by symbol order within each length */ - for (sym = 0; sym < codes; sym++) { - if (lens[lens_index + sym] !== 0) { - work[offs[lens[lens_index + sym]]++] = sym; - } - } - - /* - Create and fill in decoding tables. In this loop, the table being - filled is at next and has curr index bits. The code being used is huff - with length len. That code is converted to an index by dropping drop - bits off of the bottom. For codes where len is less than drop + curr, - those top drop + curr - len bits are incremented through all values to - fill the table with replicated entries. - - root is the number of index bits for the root table. When len exceeds - root, sub-tables are created pointed to by the root entry with an index - of the low root bits of huff. This is saved in low to check for when a - new sub-table should be started. drop is zero when the root table is - being filled, and drop is root when sub-tables are being filled. - - When a new sub-table is needed, it is necessary to look ahead in the - code lengths to determine what size sub-table is needed. The length - counts are used for this, and so count[] is decremented as codes are - entered in the tables. - - used keeps track of how many table entries have been allocated from the - provided *table space. It is checked for LENS and DIST tables against - the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in - the initial root table size constants. See the comments in inftrees.h - for more information. - - sym increments through all symbols, and the loop terminates when - all codes of length max, i.e. all codes, have been processed. This - routine permits incomplete codes, so another loop after this one fills - in the rest of the decoding tables with invalid code markers. - */ - - /* set up for code type */ - // poor man optimization - use if-else instead of switch, - // to avoid deopts in old v8 - if (type === CODES) { - base = extra = work; /* dummy value--not used */ - end = 19; - } else if (type === LENS) { - base = lbase; - base_index -= 257; - extra = lext; - extra_index -= 257; - end = 256; - } else { /* DISTS */ - base = dbase; - extra = dext; - end = -1; - } - - /* initialize opts for loop */ - huff = 0; /* starting code */ - sym = 0; /* starting code symbol */ - len = min; /* starting code length */ - next = table_index; /* current table to fill in */ - curr = root; /* current table index bits */ - drop = 0; /* current bits to drop from code for index */ - low = -1; /* trigger new sub-table when len > root */ - used = 1 << root; /* use root table entries */ - mask = used - 1; /* mask for comparing low */ - - /* check available table space */ - if ((type === LENS && used > ENOUGH_LENS) || - (type === DISTS && used > ENOUGH_DISTS)) { - return 1; - } - - var i=0; - /* process all codes and make table entries */ - for (;;) { - i++; - /* create table entry */ - here_bits = len - drop; - if (work[sym] < end) { - here_op = 0; - here_val = work[sym]; - } - else if (work[sym] > end) { - here_op = extra[extra_index + work[sym]]; - here_val = base[base_index + work[sym]]; - } - else { - here_op = 32 + 64; /* end of block */ - here_val = 0; - } - - /* replicate for those indices with low len bits equal to huff */ - incr = 1 << (len - drop); - fill = 1 << curr; - min = fill; /* save offset to next table */ - do { - fill -= incr; - table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; - } while (fill !== 0); - - /* backwards increment the len-bit code huff */ - incr = 1 << (len - 1); - while (huff & incr) { - incr >>= 1; - } - if (incr !== 0) { - huff &= incr - 1; - huff += incr; - } else { - huff = 0; - } - - /* go to next symbol, update count, len */ - sym++; - if (--count[len] === 0) { - if (len === max) { break; } - len = lens[lens_index + work[sym]]; - } - - /* create new sub-table if needed */ - if (len > root && (huff & mask) !== low) { - /* if first time, transition to sub-tables */ - if (drop === 0) { - drop = root; - } - - /* increment past last table */ - next += min; /* here min is 1 << curr */ - - /* determine length of next table */ - curr = len - drop; - left = 1 << curr; - while (curr + drop < max) { - left -= count[curr + drop]; - if (left <= 0) { break; } - curr++; - left <<= 1; - } - - /* check for enough space */ - used += 1 << curr; - if ((type === LENS && used > ENOUGH_LENS) || - (type === DISTS && used > ENOUGH_DISTS)) { - return 1; - } - - /* point entry in root table to sub-table */ - low = huff & mask; - /*table.op[low] = curr; - table.bits[low] = root; - table.val[low] = next - opts.table_index;*/ - table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; - } - } - - /* fill in remaining table entry if code is incomplete (guaranteed to have - at most one remaining entry, since if the code is incomplete, the - maximum code length that was allowed to get this far is one bit) */ - if (huff !== 0) { - //table.op[next + huff] = 64; /* invalid code marker */ - //table.bits[next + huff] = len - drop; - //table.val[next + huff] = 0; - table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; - } - - /* set return parameters */ - //opts.table_index += used; - opts.bits = root; - return 0; -}; - -},{"../utils/common":27}],37:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = { - '2': 'need dictionary', /* Z_NEED_DICT 2 */ - '1': 'stream end', /* Z_STREAM_END 1 */ - '0': '', /* Z_OK 0 */ - '-1': 'file error', /* Z_ERRNO (-1) */ - '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ - '-3': 'data error', /* Z_DATA_ERROR (-3) */ - '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ - '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ - '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ -}; -},{}],38:[function(_dereq_,module,exports){ -'use strict'; - - -var utils = _dereq_('../utils/common'); - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - - -//var Z_FILTERED = 1; -//var Z_HUFFMAN_ONLY = 2; -//var Z_RLE = 3; -var Z_FIXED = 4; -//var Z_DEFAULT_STRATEGY = 0; - -/* Possible values of the data_type field (though see inflate()) */ -var Z_BINARY = 0; -var Z_TEXT = 1; -//var Z_ASCII = 1; // = Z_TEXT -var Z_UNKNOWN = 2; - -/*============================================================================*/ - - -function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } - -// From zutil.h - -var STORED_BLOCK = 0; -var STATIC_TREES = 1; -var DYN_TREES = 2; -/* The three kinds of block type */ - -var MIN_MATCH = 3; -var MAX_MATCH = 258; -/* The minimum and maximum match lengths */ - -// From deflate.h -/* =========================================================================== - * Internal compression state. - */ - -var LENGTH_CODES = 29; -/* number of length codes, not counting the special END_BLOCK code */ - -var LITERALS = 256; -/* number of literal bytes 0..255 */ - -var L_CODES = LITERALS + 1 + LENGTH_CODES; -/* number of Literal or Length codes, including the END_BLOCK code */ - -var D_CODES = 30; -/* number of distance codes */ - -var BL_CODES = 19; -/* number of codes used to transfer the bit lengths */ - -var HEAP_SIZE = 2*L_CODES + 1; -/* maximum heap size */ - -var MAX_BITS = 15; -/* All codes must not exceed MAX_BITS bits */ - -var Buf_size = 16; -/* size of bit buffer in bi_buf */ - - -/* =========================================================================== - * Constants - */ - -var MAX_BL_BITS = 7; -/* Bit length codes must not exceed MAX_BL_BITS bits */ - -var END_BLOCK = 256; -/* end of block literal code */ - -var REP_3_6 = 16; -/* repeat previous bit length 3-6 times (2 bits of repeat count) */ - -var REPZ_3_10 = 17; -/* repeat a zero length 3-10 times (3 bits of repeat count) */ - -var REPZ_11_138 = 18; -/* repeat a zero length 11-138 times (7 bits of repeat count) */ - -var extra_lbits = /* extra bits for each length code */ - [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]; - -var extra_dbits = /* extra bits for each distance code */ - [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]; - -var extra_blbits = /* extra bits for each bit length code */ - [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]; - -var bl_order = - [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]; -/* The lengths of the bit length codes are sent in order of decreasing - * probability, to avoid transmitting the lengths for unused bit length codes. - */ - -/* =========================================================================== - * Local data. These are initialized only once. - */ - -// We pre-fill arrays with 0 to avoid uninitialized gaps - -var DIST_CODE_LEN = 512; /* see definition of array dist_code below */ - -// !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1 -var static_ltree = new Array((L_CODES+2) * 2); -zero(static_ltree); -/* The static literal tree. Since the bit lengths are imposed, there is no - * need for the L_CODES extra codes used during heap construction. However - * The codes 286 and 287 are needed to build a canonical tree (see _tr_init - * below). - */ - -var static_dtree = new Array(D_CODES * 2); -zero(static_dtree); -/* The static distance tree. (Actually a trivial tree since all codes use - * 5 bits.) - */ - -var _dist_code = new Array(DIST_CODE_LEN); -zero(_dist_code); -/* Distance codes. The first 256 values correspond to the distances - * 3 .. 258, the last 256 values correspond to the top 8 bits of - * the 15 bit distances. - */ - -var _length_code = new Array(MAX_MATCH-MIN_MATCH+1); -zero(_length_code); -/* length code for each normalized match length (0 == MIN_MATCH) */ - -var base_length = new Array(LENGTH_CODES); -zero(base_length); -/* First normalized length for each code (0 = MIN_MATCH) */ - -var base_dist = new Array(D_CODES); -zero(base_dist); -/* First normalized distance for each code (0 = distance of 1) */ - - -var StaticTreeDesc = function (static_tree, extra_bits, extra_base, elems, max_length) { - - this.static_tree = static_tree; /* static tree or NULL */ - this.extra_bits = extra_bits; /* extra bits for each code or NULL */ - this.extra_base = extra_base; /* base index for extra_bits */ - this.elems = elems; /* max number of elements in the tree */ - this.max_length = max_length; /* max bit length for the codes */ - - // show if `static_tree` has data or dummy - needed for monomorphic objects - this.has_stree = static_tree && static_tree.length; -}; - - -var static_l_desc; -var static_d_desc; -var static_bl_desc; - - -var TreeDesc = function(dyn_tree, stat_desc) { - this.dyn_tree = dyn_tree; /* the dynamic tree */ - this.max_code = 0; /* largest code with non zero frequency */ - this.stat_desc = stat_desc; /* the corresponding static tree */ -}; - - - -function d_code(dist) { - return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; -} - - -/* =========================================================================== - * Output a short LSB first on the stream. - * IN assertion: there is enough room in pendingBuf. - */ -function put_short (s, w) { -// put_byte(s, (uch)((w) & 0xff)); -// put_byte(s, (uch)((ush)(w) >> 8)); - s.pending_buf[s.pending++] = (w) & 0xff; - s.pending_buf[s.pending++] = (w >>> 8) & 0xff; -} - - -/* =========================================================================== - * Send a value on a given number of bits. - * IN assertion: length <= 16 and value fits in length bits. - */ -function send_bits(s, value, length) { - if (s.bi_valid > (Buf_size - length)) { - s.bi_buf |= (value << s.bi_valid) & 0xffff; - put_short(s, s.bi_buf); - s.bi_buf = value >> (Buf_size - s.bi_valid); - s.bi_valid += length - Buf_size; - } else { - s.bi_buf |= (value << s.bi_valid) & 0xffff; - s.bi_valid += length; - } -} - - -function send_code(s, c, tree) { - send_bits(s, tree[c*2]/*.Code*/, tree[c*2 + 1]/*.Len*/); -} - - -/* =========================================================================== - * Reverse the first len bits of a code, using straightforward code (a faster - * method would use a table) - * IN assertion: 1 <= len <= 15 - */ -function bi_reverse(code, len) { - var res = 0; - do { - res |= code & 1; - code >>>= 1; - res <<= 1; - } while (--len > 0); - return res >>> 1; -} - - -/* =========================================================================== - * Flush the bit buffer, keeping at most 7 bits in it. - */ -function bi_flush(s) { - if (s.bi_valid === 16) { - put_short(s, s.bi_buf); - s.bi_buf = 0; - s.bi_valid = 0; - - } else if (s.bi_valid >= 8) { - s.pending_buf[s.pending++] = s.bi_buf & 0xff; - s.bi_buf >>= 8; - s.bi_valid -= 8; - } -} - - -/* =========================================================================== - * Compute the optimal bit lengths for a tree and update the total bit length - * for the current block. - * IN assertion: the fields freq and dad are set, heap[heap_max] and - * above are the tree nodes sorted by increasing frequency. - * OUT assertions: the field len is set to the optimal bit length, the - * array bl_count contains the frequencies for each bit length. - * The length opt_len is updated; static_len is also updated if stree is - * not null. - */ -function gen_bitlen(s, desc) -// deflate_state *s; -// tree_desc *desc; /* the tree descriptor */ -{ - var tree = desc.dyn_tree; - var max_code = desc.max_code; - var stree = desc.stat_desc.static_tree; - var has_stree = desc.stat_desc.has_stree; - var extra = desc.stat_desc.extra_bits; - var base = desc.stat_desc.extra_base; - var max_length = desc.stat_desc.max_length; - var h; /* heap index */ - var n, m; /* iterate over the tree elements */ - var bits; /* bit length */ - var xbits; /* extra bits */ - var f; /* frequency */ - var overflow = 0; /* number of elements with bit length too large */ - - for (bits = 0; bits <= MAX_BITS; bits++) { - s.bl_count[bits] = 0; - } - - /* In a first pass, compute the optimal bit lengths (which may - * overflow in the case of the bit length tree). - */ - tree[s.heap[s.heap_max]*2 + 1]/*.Len*/ = 0; /* root of the heap */ - - for (h = s.heap_max+1; h < HEAP_SIZE; h++) { - n = s.heap[h]; - bits = tree[tree[n*2 +1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; - if (bits > max_length) { - bits = max_length; - overflow++; - } - tree[n*2 + 1]/*.Len*/ = bits; - /* We overwrite tree[n].Dad which is no longer needed */ - - if (n > max_code) { continue; } /* not a leaf node */ - - s.bl_count[bits]++; - xbits = 0; - if (n >= base) { - xbits = extra[n-base]; - } - f = tree[n * 2]/*.Freq*/; - s.opt_len += f * (bits + xbits); - if (has_stree) { - s.static_len += f * (stree[n*2 + 1]/*.Len*/ + xbits); - } - } - if (overflow === 0) { return; } - - // Trace((stderr,"\nbit length overflow\n")); - /* This happens for example on obj2 and pic of the Calgary corpus */ - - /* Find the first bit length which could increase: */ - do { - bits = max_length-1; - while (s.bl_count[bits] === 0) { bits--; } - s.bl_count[bits]--; /* move one leaf down the tree */ - s.bl_count[bits+1] += 2; /* move one overflow item as its brother */ - s.bl_count[max_length]--; - /* The brother of the overflow item also moves one step up, - * but this does not affect bl_count[max_length] - */ - overflow -= 2; - } while (overflow > 0); - - /* Now recompute all bit lengths, scanning in increasing frequency. - * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all - * lengths instead of fixing only the wrong ones. This idea is taken - * from 'ar' written by Haruhiko Okumura.) - */ - for (bits = max_length; bits !== 0; bits--) { - n = s.bl_count[bits]; - while (n !== 0) { - m = s.heap[--h]; - if (m > max_code) { continue; } - if (tree[m*2 + 1]/*.Len*/ !== bits) { - // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); - s.opt_len += (bits - tree[m*2 + 1]/*.Len*/)*tree[m*2]/*.Freq*/; - tree[m*2 + 1]/*.Len*/ = bits; - } - n--; - } - } -} - - -/* =========================================================================== - * Generate the codes for a given tree and bit counts (which need not be - * optimal). - * IN assertion: the array bl_count contains the bit length statistics for - * the given tree and the field len is set for all tree elements. - * OUT assertion: the field code is set for all tree elements of non - * zero code length. - */ -function gen_codes(tree, max_code, bl_count) -// ct_data *tree; /* the tree to decorate */ -// int max_code; /* largest code with non zero frequency */ -// ushf *bl_count; /* number of codes at each bit length */ -{ - var next_code = new Array(MAX_BITS+1); /* next code value for each bit length */ - var code = 0; /* running code value */ - var bits; /* bit index */ - var n; /* code index */ - - /* The distribution counts are first used to generate the code values - * without bit reversal. - */ - for (bits = 1; bits <= MAX_BITS; bits++) { - next_code[bits] = code = (code + bl_count[bits-1]) << 1; - } - /* Check that the bit counts in bl_count are consistent. The last code - * must be all ones. - */ - //Assert (code + bl_count[MAX_BITS]-1 == (1< length code (0..28) */ - length = 0; - for (code = 0; code < LENGTH_CODES-1; code++) { - base_length[code] = length; - for (n = 0; n < (1< dist code (0..29) */ - dist = 0; - for (code = 0 ; code < 16; code++) { - base_dist[code] = dist; - for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ - for ( ; code < D_CODES; code++) { - base_dist[code] = dist << 7; - for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { - _dist_code[256 + dist++] = code; - } - } - //Assert (dist == 256, "tr_static_init: 256+dist != 512"); - - /* Construct the codes of the static literal tree */ - for (bits = 0; bits <= MAX_BITS; bits++) { - bl_count[bits] = 0; - } - - n = 0; - while (n <= 143) { - static_ltree[n*2 + 1]/*.Len*/ = 8; - n++; - bl_count[8]++; - } - while (n <= 255) { - static_ltree[n*2 + 1]/*.Len*/ = 9; - n++; - bl_count[9]++; - } - while (n <= 279) { - static_ltree[n*2 + 1]/*.Len*/ = 7; - n++; - bl_count[7]++; - } - while (n <= 287) { - static_ltree[n*2 + 1]/*.Len*/ = 8; - n++; - bl_count[8]++; - } - /* Codes 286 and 287 do not exist, but we must include them in the - * tree construction to get a canonical Huffman tree (longest code - * all ones) - */ - gen_codes(static_ltree, L_CODES+1, bl_count); - - /* The static distance tree is trivial: */ - for (n = 0; n < D_CODES; n++) { - static_dtree[n*2 + 1]/*.Len*/ = 5; - static_dtree[n*2]/*.Code*/ = bi_reverse(n, 5); - } - - // Now data ready and we can init static trees - static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS); - static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS); - static_bl_desc =new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS); - - //static_init_done = true; -} - - -/* =========================================================================== - * Initialize a new block. - */ -function init_block(s) { - var n; /* iterates over tree elements */ - - /* Initialize the trees. */ - for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n*2]/*.Freq*/ = 0; } - for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n*2]/*.Freq*/ = 0; } - for (n = 0; n < BL_CODES; n++) { s.bl_tree[n*2]/*.Freq*/ = 0; } - - s.dyn_ltree[END_BLOCK*2]/*.Freq*/ = 1; - s.opt_len = s.static_len = 0; - s.last_lit = s.matches = 0; -} - - -/* =========================================================================== - * Flush the bit buffer and align the output on a byte boundary - */ -function bi_windup(s) -{ - if (s.bi_valid > 8) { - put_short(s, s.bi_buf); - } else if (s.bi_valid > 0) { - //put_byte(s, (Byte)s->bi_buf); - s.pending_buf[s.pending++] = s.bi_buf; - } - s.bi_buf = 0; - s.bi_valid = 0; -} - -/* =========================================================================== - * Copy a stored block, storing first the length and its - * one's complement if requested. - */ -function copy_block(s, buf, len, header) -//DeflateState *s; -//charf *buf; /* the input data */ -//unsigned len; /* its length */ -//int header; /* true if block header must be written */ -{ - bi_windup(s); /* align on byte boundary */ - - if (header) { - put_short(s, len); - put_short(s, ~len); - } -// while (len--) { -// put_byte(s, *buf++); -// } - utils.arraySet(s.pending_buf, s.window, buf, len, s.pending); - s.pending += len; -} - -/* =========================================================================== - * Compares to subtrees, using the tree depth as tie breaker when - * the subtrees have equal frequency. This minimizes the worst case length. - */ -function smaller(tree, n, m, depth) { - var _n2 = n*2; - var _m2 = m*2; - return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || - (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); -} - -/* =========================================================================== - * Restore the heap property by moving down the tree starting at node k, - * exchanging a node with the smallest of its two sons if necessary, stopping - * when the heap property is re-established (each father smaller than its - * two sons). - */ -function pqdownheap(s, tree, k) -// deflate_state *s; -// ct_data *tree; /* the tree to restore */ -// int k; /* node to move down */ -{ - var v = s.heap[k]; - var j = k << 1; /* left son of k */ - while (j <= s.heap_len) { - /* Set j to the smallest of the two sons: */ - if (j < s.heap_len && - smaller(tree, s.heap[j+1], s.heap[j], s.depth)) { - j++; - } - /* Exit if v is smaller than both sons */ - if (smaller(tree, v, s.heap[j], s.depth)) { break; } - - /* Exchange v with the smallest son */ - s.heap[k] = s.heap[j]; - k = j; - - /* And continue down the tree, setting j to the left son of k */ - j <<= 1; - } - s.heap[k] = v; -} - - -// inlined manually -// var SMALLEST = 1; - -/* =========================================================================== - * Send the block data compressed using the given Huffman trees - */ -function compress_block(s, ltree, dtree) -// deflate_state *s; -// const ct_data *ltree; /* literal tree */ -// const ct_data *dtree; /* distance tree */ -{ - var dist; /* distance of matched string */ - var lc; /* match length or unmatched char (if dist == 0) */ - var lx = 0; /* running index in l_buf */ - var code; /* the code to send */ - var extra; /* number of extra bits to send */ - - if (s.last_lit !== 0) { - do { - dist = (s.pending_buf[s.d_buf + lx*2] << 8) | (s.pending_buf[s.d_buf + lx*2 + 1]); - lc = s.pending_buf[s.l_buf + lx]; - lx++; - - if (dist === 0) { - send_code(s, lc, ltree); /* send a literal byte */ - //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); - } else { - /* Here, lc is the match length - MIN_MATCH */ - code = _length_code[lc]; - send_code(s, code+LITERALS+1, ltree); /* send the length code */ - extra = extra_lbits[code]; - if (extra !== 0) { - lc -= base_length[code]; - send_bits(s, lc, extra); /* send the extra length bits */ - } - dist--; /* dist is now the match distance - 1 */ - code = d_code(dist); - //Assert (code < D_CODES, "bad d_code"); - - send_code(s, code, dtree); /* send the distance code */ - extra = extra_dbits[code]; - if (extra !== 0) { - dist -= base_dist[code]; - send_bits(s, dist, extra); /* send the extra distance bits */ - } - } /* literal or match pair ? */ - - /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ - //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, - // "pendingBuf overflow"); - - } while (lx < s.last_lit); - } - - send_code(s, END_BLOCK, ltree); -} - - -/* =========================================================================== - * Construct one Huffman tree and assigns the code bit strings and lengths. - * Update the total bit length for the current block. - * IN assertion: the field freq is set for all tree elements. - * OUT assertions: the fields len and code are set to the optimal bit length - * and corresponding code. The length opt_len is updated; static_len is - * also updated if stree is not null. The field max_code is set. - */ -function build_tree(s, desc) -// deflate_state *s; -// tree_desc *desc; /* the tree descriptor */ -{ - var tree = desc.dyn_tree; - var stree = desc.stat_desc.static_tree; - var has_stree = desc.stat_desc.has_stree; - var elems = desc.stat_desc.elems; - var n, m; /* iterate over heap elements */ - var max_code = -1; /* largest code with non zero frequency */ - var node; /* new node being created */ - - /* Construct the initial heap, with least frequent element in - * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. - * heap[0] is not used. - */ - s.heap_len = 0; - s.heap_max = HEAP_SIZE; - - for (n = 0; n < elems; n++) { - if (tree[n * 2]/*.Freq*/ !== 0) { - s.heap[++s.heap_len] = max_code = n; - s.depth[n] = 0; - - } else { - tree[n*2 + 1]/*.Len*/ = 0; - } - } - - /* The pkzip format requires that at least one distance code exists, - * and that at least one bit should be sent even if there is only one - * possible code. So to avoid special checks later on we force at least - * two codes of non zero frequency. - */ - while (s.heap_len < 2) { - node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); - tree[node * 2]/*.Freq*/ = 1; - s.depth[node] = 0; - s.opt_len--; - - if (has_stree) { - s.static_len -= stree[node*2 + 1]/*.Len*/; - } - /* node is 0 or 1 so it does not have extra bits */ - } - desc.max_code = max_code; - - /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, - * establish sub-heaps of increasing lengths: - */ - for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } - - /* Construct the Huffman tree by repeatedly combining the least two - * frequent nodes. - */ - node = elems; /* next internal node of the tree */ - do { - //pqremove(s, tree, n); /* n = node of least frequency */ - /*** pqremove ***/ - n = s.heap[1/*SMALLEST*/]; - s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; - pqdownheap(s, tree, 1/*SMALLEST*/); - /***/ - - m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ - - s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ - s.heap[--s.heap_max] = m; - - /* Create a new node father of n and m */ - tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; - s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; - tree[n*2 + 1]/*.Dad*/ = tree[m*2 + 1]/*.Dad*/ = node; - - /* and insert the new node in the heap */ - s.heap[1/*SMALLEST*/] = node++; - pqdownheap(s, tree, 1/*SMALLEST*/); - - } while (s.heap_len >= 2); - - s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; - - /* At this point, the fields freq and dad are set. We can now - * generate the bit lengths. - */ - gen_bitlen(s, desc); - - /* The field len is now set, we can generate the bit codes */ - gen_codes(tree, max_code, s.bl_count); -} - - -/* =========================================================================== - * Scan a literal or distance tree to determine the frequencies of the codes - * in the bit length tree. - */ -function scan_tree(s, tree, max_code) -// deflate_state *s; -// ct_data *tree; /* the tree to be scanned */ -// int max_code; /* and its largest code of non zero frequency */ -{ - var n; /* iterates over all tree elements */ - var prevlen = -1; /* last emitted length */ - var curlen; /* length of current code */ - - var nextlen = tree[0*2 + 1]/*.Len*/; /* length of next code */ - - var count = 0; /* repeat count of the current code */ - var max_count = 7; /* max repeat count */ - var min_count = 4; /* min repeat count */ - - if (nextlen === 0) { - max_count = 138; - min_count = 3; - } - tree[(max_code+1)*2 + 1]/*.Len*/ = 0xffff; /* guard */ - - for (n = 0; n <= max_code; n++) { - curlen = nextlen; - nextlen = tree[(n+1)*2 + 1]/*.Len*/; - - if (++count < max_count && curlen === nextlen) { - continue; - - } else if (count < min_count) { - s.bl_tree[curlen * 2]/*.Freq*/ += count; - - } else if (curlen !== 0) { - - if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } - s.bl_tree[REP_3_6*2]/*.Freq*/++; - - } else if (count <= 10) { - s.bl_tree[REPZ_3_10*2]/*.Freq*/++; - - } else { - s.bl_tree[REPZ_11_138*2]/*.Freq*/++; - } - - count = 0; - prevlen = curlen; - - if (nextlen === 0) { - max_count = 138; - min_count = 3; - - } else if (curlen === nextlen) { - max_count = 6; - min_count = 3; - - } else { - max_count = 7; - min_count = 4; - } - } -} - - -/* =========================================================================== - * Send a literal or distance tree in compressed form, using the codes in - * bl_tree. - */ -function send_tree(s, tree, max_code) -// deflate_state *s; -// ct_data *tree; /* the tree to be scanned */ -// int max_code; /* and its largest code of non zero frequency */ -{ - var n; /* iterates over all tree elements */ - var prevlen = -1; /* last emitted length */ - var curlen; /* length of current code */ - - var nextlen = tree[0*2 + 1]/*.Len*/; /* length of next code */ - - var count = 0; /* repeat count of the current code */ - var max_count = 7; /* max repeat count */ - var min_count = 4; /* min repeat count */ - - /* tree[max_code+1].Len = -1; */ /* guard already set */ - if (nextlen === 0) { - max_count = 138; - min_count = 3; - } - - for (n = 0; n <= max_code; n++) { - curlen = nextlen; - nextlen = tree[(n+1)*2 + 1]/*.Len*/; - - if (++count < max_count && curlen === nextlen) { - continue; - - } else if (count < min_count) { - do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); - - } else if (curlen !== 0) { - if (curlen !== prevlen) { - send_code(s, curlen, s.bl_tree); - count--; - } - //Assert(count >= 3 && count <= 6, " 3_6?"); - send_code(s, REP_3_6, s.bl_tree); - send_bits(s, count-3, 2); - - } else if (count <= 10) { - send_code(s, REPZ_3_10, s.bl_tree); - send_bits(s, count-3, 3); - - } else { - send_code(s, REPZ_11_138, s.bl_tree); - send_bits(s, count-11, 7); - } - - count = 0; - prevlen = curlen; - if (nextlen === 0) { - max_count = 138; - min_count = 3; - - } else if (curlen === nextlen) { - max_count = 6; - min_count = 3; - - } else { - max_count = 7; - min_count = 4; - } - } -} - - -/* =========================================================================== - * Construct the Huffman tree for the bit lengths and return the index in - * bl_order of the last bit length code to send. - */ -function build_bl_tree(s) { - var max_blindex; /* index of last bit length code of non zero freq */ - - /* Determine the bit length frequencies for literal and distance trees */ - scan_tree(s, s.dyn_ltree, s.l_desc.max_code); - scan_tree(s, s.dyn_dtree, s.d_desc.max_code); - - /* Build the bit length tree: */ - build_tree(s, s.bl_desc); - /* opt_len now includes the length of the tree representations, except - * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. - */ - - /* Determine the number of bit length codes to send. The pkzip format - * requires that at least 4 bit length codes be sent. (appnote.txt says - * 3 but the actual value used is 4.) - */ - for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { - if (s.bl_tree[bl_order[max_blindex]*2 + 1]/*.Len*/ !== 0) { - break; - } - } - /* Update opt_len to include the bit length tree and counts */ - s.opt_len += 3*(max_blindex+1) + 5+5+4; - //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", - // s->opt_len, s->static_len)); - - return max_blindex; -} - - -/* =========================================================================== - * Send the header for a block using dynamic Huffman trees: the counts, the - * lengths of the bit length codes, the literal tree and the distance tree. - * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. - */ -function send_all_trees(s, lcodes, dcodes, blcodes) -// deflate_state *s; -// int lcodes, dcodes, blcodes; /* number of codes for each tree */ -{ - var rank; /* index in bl_order */ - - //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); - //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, - // "too many codes"); - //Tracev((stderr, "\nbl counts: ")); - send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ - send_bits(s, dcodes-1, 5); - send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ - for (rank = 0; rank < blcodes; rank++) { - //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); - send_bits(s, s.bl_tree[bl_order[rank]*2 + 1]/*.Len*/, 3); - } - //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); - - send_tree(s, s.dyn_ltree, lcodes-1); /* literal tree */ - //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); - - send_tree(s, s.dyn_dtree, dcodes-1); /* distance tree */ - //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); -} - - -/* =========================================================================== - * Check if the data type is TEXT or BINARY, using the following algorithm: - * - TEXT if the two conditions below are satisfied: - * a) There are no non-portable control characters belonging to the - * "black list" (0..6, 14..25, 28..31). - * b) There is at least one printable character belonging to the - * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). - * - BINARY otherwise. - * - The following partially-portable control characters form a - * "gray list" that is ignored in this detection algorithm: - * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). - * IN assertion: the fields Freq of dyn_ltree are set. - */ -function detect_data_type(s) { - /* black_mask is the bit mask of black-listed bytes - * set bits 0..6, 14..25, and 28..31 - * 0xf3ffc07f = binary 11110011111111111100000001111111 - */ - var black_mask = 0xf3ffc07f; - var n; - - /* Check for non-textual ("black-listed") bytes. */ - for (n = 0; n <= 31; n++, black_mask >>>= 1) { - if ((black_mask & 1) && (s.dyn_ltree[n*2]/*.Freq*/ !== 0)) { - return Z_BINARY; - } - } - - /* Check for textual ("white-listed") bytes. */ - if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || - s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { - return Z_TEXT; - } - for (n = 32; n < LITERALS; n++) { - if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { - return Z_TEXT; - } - } - - /* There are no "black-listed" or "white-listed" bytes: - * this stream either is empty or has tolerated ("gray-listed") bytes only. - */ - return Z_BINARY; -} - - -var static_init_done = false; - -/* =========================================================================== - * Initialize the tree data structures for a new zlib stream. - */ -function _tr_init(s) -{ - - if (!static_init_done) { - tr_static_init(); - static_init_done = true; - } - - s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); - s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); - s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); - - s.bi_buf = 0; - s.bi_valid = 0; - - /* Initialize the first block of the first file: */ - init_block(s); -} - - -/* =========================================================================== - * Send a stored block - */ -function _tr_stored_block(s, buf, stored_len, last) -//DeflateState *s; -//charf *buf; /* input block */ -//ulg stored_len; /* length of input block */ -//int last; /* one if this is the last block for a file */ -{ - send_bits(s, (STORED_BLOCK<<1)+(last ? 1 : 0), 3); /* send block type */ - copy_block(s, buf, stored_len, true); /* with header */ -} - - -/* =========================================================================== - * Send one empty static block to give enough lookahead for inflate. - * This takes 10 bits, of which 7 may remain in the bit buffer. - */ -function _tr_align(s) { - send_bits(s, STATIC_TREES<<1, 3); - send_code(s, END_BLOCK, static_ltree); - bi_flush(s); -} - - -/* =========================================================================== - * Determine the best encoding for the current block: dynamic trees, static - * trees or store, and output the encoded block to the zip file. - */ -function _tr_flush_block(s, buf, stored_len, last) -//DeflateState *s; -//charf *buf; /* input block, or NULL if too old */ -//ulg stored_len; /* length of input block */ -//int last; /* one if this is the last block for a file */ -{ - var opt_lenb, static_lenb; /* opt_len and static_len in bytes */ - var max_blindex = 0; /* index of last bit length code of non zero freq */ - - /* Build the Huffman trees unless a stored block is forced */ - if (s.level > 0) { - - /* Check if the file is binary or text */ - if (s.strm.data_type === Z_UNKNOWN) { - s.strm.data_type = detect_data_type(s); - } - - /* Construct the literal and distance trees */ - build_tree(s, s.l_desc); - // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, - // s->static_len)); - - build_tree(s, s.d_desc); - // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, - // s->static_len)); - /* At this point, opt_len and static_len are the total bit lengths of - * the compressed block data, excluding the tree representations. - */ - - /* Build the bit length tree for the above two trees, and get the index - * in bl_order of the last bit length code to send. - */ - max_blindex = build_bl_tree(s); - - /* Determine the best encoding. Compute the block lengths in bytes. */ - opt_lenb = (s.opt_len+3+7) >>> 3; - static_lenb = (s.static_len+3+7) >>> 3; - - // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", - // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, - // s->last_lit)); - - if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } - - } else { - // Assert(buf != (char*)0, "lost buf"); - opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ - } - - if ((stored_len+4 <= opt_lenb) && (buf !== -1)) { - /* 4: two words for the lengths */ - - /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. - * Otherwise we can't have processed more than WSIZE input bytes since - * the last block flush, because compression would have been - * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to - * transform a block into a stored block. - */ - _tr_stored_block(s, buf, stored_len, last); - - } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) { - - send_bits(s, (STATIC_TREES<<1) + (last ? 1 : 0), 3); - compress_block(s, static_ltree, static_dtree); - - } else { - send_bits(s, (DYN_TREES<<1) + (last ? 1 : 0), 3); - send_all_trees(s, s.l_desc.max_code+1, s.d_desc.max_code+1, max_blindex+1); - compress_block(s, s.dyn_ltree, s.dyn_dtree); - } - // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); - /* The above check is made mod 2^32, for files larger than 512 MB - * and uLong implemented on 32 bits. - */ - init_block(s); - - if (last) { - bi_windup(s); - } - // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, - // s->compressed_len-7*last)); -} - -/* =========================================================================== - * Save the match info and tally the frequency counts. Return true if - * the current block must be flushed. - */ -function _tr_tally(s, dist, lc) -// deflate_state *s; -// unsigned dist; /* distance of matched string */ -// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ -{ - //var out_length, in_length, dcode; - - s.pending_buf[s.d_buf + s.last_lit * 2] = (dist >>> 8) & 0xff; - s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff; - - s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff; - s.last_lit++; - - if (dist === 0) { - /* lc is the unmatched char */ - s.dyn_ltree[lc*2]/*.Freq*/++; - } else { - s.matches++; - /* Here, lc is the match length - MIN_MATCH */ - dist--; /* dist = match distance - 1 */ - //Assert((ush)dist < (ush)MAX_DIST(s) && - // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && - // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); - - s.dyn_ltree[(_length_code[lc]+LITERALS+1) * 2]/*.Freq*/++; - s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; - } - -// (!) This block is disabled in zlib defailts, -// don't enable it for binary compatibility - -//#ifdef TRUNCATE_BLOCK -// /* Try to guess if it is profitable to stop the current block here */ -// if ((s.last_lit & 0x1fff) === 0 && s.level > 2) { -// /* Compute an upper bound for the compressed length */ -// out_length = s.last_lit*8; -// in_length = s.strstart - s.block_start; -// -// for (dcode = 0; dcode < D_CODES; dcode++) { -// out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]); -// } -// out_length >>>= 3; -// //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", -// // s->last_lit, in_length, out_length, -// // 100L - out_length*100L/in_length)); -// if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) { -// return true; -// } -// } -//#endif - - return (s.last_lit === s.lit_bufsize-1); - /* We avoid equality with lit_bufsize because of wraparound at 64K - * on 16 bit machines and because stored blocks are restricted to - * 64K-1 bytes. - */ -} - -exports._tr_init = _tr_init; -exports._tr_stored_block = _tr_stored_block; -exports._tr_flush_block = _tr_flush_block; -exports._tr_tally = _tr_tally; -exports._tr_align = _tr_align; -},{"../utils/common":27}],39:[function(_dereq_,module,exports){ -'use strict'; - - -function ZStream() { - /* next input byte */ - this.input = null; // JS specific, because we have no pointers - this.next_in = 0; - /* number of bytes available at input */ - this.avail_in = 0; - /* total number of input bytes read so far */ - this.total_in = 0; - /* next output byte should be put there */ - this.output = null; // JS specific, because we have no pointers - this.next_out = 0; - /* remaining free space at output */ - this.avail_out = 0; - /* total number of bytes output so far */ - this.total_out = 0; - /* last error message, NULL if no error */ - this.msg = ''/*Z_NULL*/; - /* not visible by applications */ - this.state = null; - /* best guess about the data type: binary or text */ - this.data_type = 2/*Z_UNKNOWN*/; - /* adler32 value of the uncompressed data */ - this.adler = 0; -} - -module.exports = ZStream; -},{}]},{},[9]) -(9) -}); +/*! + +JSZip - A Javascript class for generating and reading zip files + + +(c) 2009-2014 Stuart Knightley +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown. + +JSZip uses the library pako released under the MIT license : +https://github.com/nodeca/pako/blob/master/LICENSE +*/ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.JSZip=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } + else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); + + } + + return output; +}; + +// public method for decoding +exports.decode = function(input, utf8) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = _keyStr.indexOf(input.charAt(i++)); + enc2 = _keyStr.indexOf(input.charAt(i++)); + enc3 = _keyStr.indexOf(input.charAt(i++)); + enc4 = _keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + } + + return output; + +}; + +},{}],2:[function(_dereq_,module,exports){ +'use strict'; +function CompressedObject() { + this.compressedSize = 0; + this.uncompressedSize = 0; + this.crc32 = 0; + this.compressionMethod = null; + this.compressedContent = null; +} + +CompressedObject.prototype = { + /** + * Return the decompressed content in an unspecified format. + * The format will depend on the decompressor. + * @return {Object} the decompressed content. + */ + getContent: function() { + return null; // see implementation + }, + /** + * Return the compressed content in an unspecified format. + * The format will depend on the compressed conten source. + * @return {Object} the compressed content. + */ + getCompressedContent: function() { + return null; // see implementation + } +}; +module.exports = CompressedObject; + +},{}],3:[function(_dereq_,module,exports){ +'use strict'; +exports.STORE = { + magic: "\x00\x00", + compress: function(content, compressionOptions) { + return content; // no compression + }, + uncompress: function(content) { + return content; // no compression + }, + compressInputType: null, + uncompressInputType: null +}; +exports.DEFLATE = _dereq_('./flate'); + +},{"./flate":8}],4:[function(_dereq_,module,exports){ +'use strict'; + +var utils = _dereq_('./utils'); + +var table = [ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +]; + +/** + * + * Javascript crc32 + * http://www.webtoolkit.info/ + * + */ +module.exports = function crc32(input, crc) { + if (typeof input === "undefined" || !input.length) { + return 0; + } + + var isArray = utils.getTypeOf(input) !== "string"; + + if (typeof(crc) == "undefined") { + crc = 0; + } + var x = 0; + var y = 0; + var b = 0; + + crc = crc ^ (-1); + for (var i = 0, iTop = input.length; i < iTop; i++) { + b = isArray ? input[i] : input.charCodeAt(i); + y = (crc ^ b) & 0xFF; + x = table[y]; + crc = (crc >>> 8) ^ x; + } + + return crc ^ (-1); +}; +// vim: set shiftwidth=4 softtabstop=4: + +},{"./utils":21}],5:[function(_dereq_,module,exports){ +'use strict'; +var utils = _dereq_('./utils'); + +function DataReader(data) { + this.data = null; // type : see implementation + this.length = 0; + this.index = 0; +} +DataReader.prototype = { + /** + * Check that the offset will not go too far. + * @param {string} offset the additional offset to check. + * @throws {Error} an Error if the offset is out of bounds. + */ + checkOffset: function(offset) { + this.checkIndex(this.index + offset); + }, + /** + * Check that the specifed index will not be too far. + * @param {string} newIndex the index to check. + * @throws {Error} an Error if the index is out of bounds. + */ + checkIndex: function(newIndex) { + if (this.length < newIndex || newIndex < 0) { + throw new Error("End of data reached (data length = " + this.length + ", asked index = " + (newIndex) + "). Corrupted zip ?"); + } + }, + /** + * Change the index. + * @param {number} newIndex The new index. + * @throws {Error} if the new index is out of the data. + */ + setIndex: function(newIndex) { + this.checkIndex(newIndex); + this.index = newIndex; + }, + /** + * Skip the next n bytes. + * @param {number} n the number of bytes to skip. + * @throws {Error} if the new index is out of the data. + */ + skip: function(n) { + this.setIndex(this.index + n); + }, + /** + * Get the byte at the specified index. + * @param {number} i the index to use. + * @return {number} a byte. + */ + byteAt: function(i) { + // see implementations + }, + /** + * Get the next number with a given byte size. + * @param {number} size the number of bytes to read. + * @return {number} the corresponding number. + */ + readInt: function(size) { + var result = 0, + i; + this.checkOffset(size); + for (i = this.index + size - 1; i >= this.index; i--) { + result = (result << 8) + this.byteAt(i); + } + this.index += size; + return result; + }, + /** + * Get the next string with a given byte size. + * @param {number} size the number of bytes to read. + * @return {string} the corresponding string. + */ + readString: function(size) { + return utils.transformTo("string", this.readData(size)); + }, + /** + * Get raw data without conversion, bytes. + * @param {number} size the number of bytes to read. + * @return {Object} the raw data, implementation specific. + */ + readData: function(size) { + // see implementations + }, + /** + * Find the last occurence of a zip signature (4 bytes). + * @param {string} sig the signature to find. + * @return {number} the index of the last occurence, -1 if not found. + */ + lastIndexOfSignature: function(sig) { + // see implementations + }, + /** + * Get the next date. + * @return {Date} the date. + */ + readDate: function() { + var dostime = this.readInt(4); + return new Date( + ((dostime >> 25) & 0x7f) + 1980, // year + ((dostime >> 21) & 0x0f) - 1, // month + (dostime >> 16) & 0x1f, // day + (dostime >> 11) & 0x1f, // hour + (dostime >> 5) & 0x3f, // minute + (dostime & 0x1f) << 1); // second + } +}; +module.exports = DataReader; + +},{"./utils":21}],6:[function(_dereq_,module,exports){ +'use strict'; +exports.base64 = false; +exports.binary = false; +exports.dir = false; +exports.createFolders = false; +exports.date = null; +exports.compression = null; +exports.compressionOptions = null; +exports.comment = null; +exports.unixPermissions = null; +exports.dosPermissions = null; + +},{}],7:[function(_dereq_,module,exports){ +'use strict'; +var utils = _dereq_('./utils'); + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.string2binary = function(str) { + return utils.string2binary(str); +}; + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.string2Uint8Array = function(str) { + return utils.transformTo("uint8array", str); +}; + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.uint8Array2String = function(array) { + return utils.transformTo("string", array); +}; + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.string2Blob = function(str) { + var buffer = utils.transformTo("arraybuffer", str); + return utils.arrayBuffer2Blob(buffer); +}; + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.arrayBuffer2Blob = function(buffer) { + return utils.arrayBuffer2Blob(buffer); +}; + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.transformTo = function(outputType, input) { + return utils.transformTo(outputType, input); +}; + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.getTypeOf = function(input) { + return utils.getTypeOf(input); +}; + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.checkSupport = function(type) { + return utils.checkSupport(type); +}; + +/** + * @deprecated + * This value will be removed in a future version without replacement. + */ +exports.MAX_VALUE_16BITS = utils.MAX_VALUE_16BITS; + +/** + * @deprecated + * This value will be removed in a future version without replacement. + */ +exports.MAX_VALUE_32BITS = utils.MAX_VALUE_32BITS; + + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.pretty = function(str) { + return utils.pretty(str); +}; + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.findCompression = function(compressionMethod) { + return utils.findCompression(compressionMethod); +}; + +/** + * @deprecated + * This function will be removed in a future version without replacement. + */ +exports.isRegExp = function (object) { + return utils.isRegExp(object); +}; + + +},{"./utils":21}],8:[function(_dereq_,module,exports){ +'use strict'; +var USE_TYPEDARRAY = (typeof Uint8Array !== 'undefined') && (typeof Uint16Array !== 'undefined') && (typeof Uint32Array !== 'undefined'); + +var pako = _dereq_("pako"); +exports.uncompressInputType = USE_TYPEDARRAY ? "uint8array" : "array"; +exports.compressInputType = USE_TYPEDARRAY ? "uint8array" : "array"; + +exports.magic = "\x08\x00"; +exports.compress = function(input, compressionOptions) { + return pako.deflateRaw(input, { + level : compressionOptions.level || -1 // default compression + }); +}; +exports.uncompress = function(input) { + return pako.inflateRaw(input); +}; + +},{"pako":24}],9:[function(_dereq_,module,exports){ +'use strict'; + +var base64 = _dereq_('./base64'); + +/** +Usage: + zip = new JSZip(); + zip.file("hello.txt", "Hello, World!").file("tempfile", "nothing"); + zip.folder("images").file("smile.gif", base64Data, {base64: true}); + zip.file("Xmas.txt", "Ho ho ho !", {date : new Date("December 25, 2007 00:00:01")}); + zip.remove("tempfile"); + + base64zip = zip.generate(); + +**/ + +/** + * Representation a of zip file in js + * @constructor + * @param {String=|ArrayBuffer=|Uint8Array=} data the data to load, if any (optional). + * @param {Object=} options the options for creating this objects (optional). + */ +function JSZip(data, options) { + // if this constructor is used without `new`, it adds `new` before itself: + if(!(this instanceof JSZip)) return new JSZip(data, options); + + // object containing the files : + // { + // "folder/" : {...}, + // "folder/data.txt" : {...} + // } + this.files = {}; + + this.comment = null; + + // Where we are in the hierarchy + this.root = ""; + if (data) { + this.load(data, options); + } + this.clone = function() { + var newObj = new JSZip(); + for (var i in this) { + if (typeof this[i] !== "function") { + newObj[i] = this[i]; + } + } + return newObj; + }; +} +JSZip.prototype = _dereq_('./object'); +JSZip.prototype.load = _dereq_('./load'); +JSZip.support = _dereq_('./support'); +JSZip.defaults = _dereq_('./defaults'); + +/** + * @deprecated + * This namespace will be removed in a future version without replacement. + */ +JSZip.utils = _dereq_('./deprecatedPublicUtils'); + +JSZip.base64 = { + /** + * @deprecated + * This method will be removed in a future version without replacement. + */ + encode : function(input) { + return base64.encode(input); + }, + /** + * @deprecated + * This method will be removed in a future version without replacement. + */ + decode : function(input) { + return base64.decode(input); + } +}; +JSZip.compressions = _dereq_('./compressions'); +module.exports = JSZip; + +},{"./base64":1,"./compressions":3,"./defaults":6,"./deprecatedPublicUtils":7,"./load":10,"./object":13,"./support":17}],10:[function(_dereq_,module,exports){ +'use strict'; +var base64 = _dereq_('./base64'); +var ZipEntries = _dereq_('./zipEntries'); +module.exports = function(data, options) { + var files, zipEntries, i, input; + options = options || {}; + if (options.base64) { + data = base64.decode(data); + } + + zipEntries = new ZipEntries(data, options); + files = zipEntries.files; + for (i = 0; i < files.length; i++) { + input = files[i]; + this.file(input.fileName, input.decompressed, { + binary: true, + optimizedBinaryString: true, + date: input.date, + dir: input.dir, + comment : input.fileComment.length ? input.fileComment : null, + unixPermissions : input.unixPermissions, + dosPermissions : input.dosPermissions, + createFolders: options.createFolders + }); + } + if (zipEntries.zipComment.length) { + this.comment = zipEntries.zipComment; + } + + return this; +}; + +},{"./base64":1,"./zipEntries":22}],11:[function(_dereq_,module,exports){ +(function (Buffer){ +'use strict'; +module.exports = function(data, encoding){ + return new Buffer(data, encoding); +}; +module.exports.test = function(b){ + return Buffer.isBuffer(b); +}; + +}).call(this,(typeof Buffer !== "undefined" ? Buffer : undefined)) +},{}],12:[function(_dereq_,module,exports){ +'use strict'; +var Uint8ArrayReader = _dereq_('./uint8ArrayReader'); + +function NodeBufferReader(data) { + this.data = data; + this.length = this.data.length; + this.index = 0; +} +NodeBufferReader.prototype = new Uint8ArrayReader(); + +/** + * @see DataReader.readData + */ +NodeBufferReader.prototype.readData = function(size) { + this.checkOffset(size); + var result = this.data.slice(this.index, this.index + size); + this.index += size; + return result; +}; +module.exports = NodeBufferReader; + +},{"./uint8ArrayReader":18}],13:[function(_dereq_,module,exports){ +'use strict'; +var support = _dereq_('./support'); +var utils = _dereq_('./utils'); +var crc32 = _dereq_('./crc32'); +var signature = _dereq_('./signature'); +var defaults = _dereq_('./defaults'); +var base64 = _dereq_('./base64'); +var compressions = _dereq_('./compressions'); +var CompressedObject = _dereq_('./compressedObject'); +var nodeBuffer = _dereq_('./nodeBuffer'); +var utf8 = _dereq_('./utf8'); +var StringWriter = _dereq_('./stringWriter'); +var Uint8ArrayWriter = _dereq_('./uint8ArrayWriter'); + +/** + * Returns the raw data of a ZipObject, decompress the content if necessary. + * @param {ZipObject} file the file to use. + * @return {String|ArrayBuffer|Uint8Array|Buffer} the data. + */ +var getRawData = function(file) { + if (file._data instanceof CompressedObject) { + file._data = file._data.getContent(); + file.options.binary = true; + file.options.base64 = false; + + if (utils.getTypeOf(file._data) === "uint8array") { + var copy = file._data; + // when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array. + // if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file). + file._data = new Uint8Array(copy.length); + // with an empty Uint8Array, Opera fails with a "Offset larger than array size" + if (copy.length !== 0) { + file._data.set(copy, 0); + } + } + } + return file._data; +}; + +/** + * Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it. + * @param {ZipObject} file the file to use. + * @return {String|ArrayBuffer|Uint8Array|Buffer} the data. + */ +var getBinaryData = function(file) { + var result = getRawData(file), + type = utils.getTypeOf(result); + if (type === "string") { + if (!file.options.binary) { + // unicode text ! + // unicode string => binary string is a painful process, check if we can avoid it. + if (support.nodebuffer) { + return nodeBuffer(result, "utf-8"); + } + } + return file.asBinary(); + } + return result; +}; + +/** + * Transform this._data into a string. + * @param {function} filter a function String -> String, applied if not null on the result. + * @return {String} the string representing this._data. + */ +var dataToString = function(asUTF8) { + var result = getRawData(this); + if (result === null || typeof result === "undefined") { + return ""; + } + // if the data is a base64 string, we decode it before checking the encoding ! + if (this.options.base64) { + result = base64.decode(result); + } + if (asUTF8 && this.options.binary) { + // JSZip.prototype.utf8decode supports arrays as input + // skip to array => string step, utf8decode will do it. + result = out.utf8decode(result); + } + else { + // no utf8 transformation, do the array => string step. + result = utils.transformTo("string", result); + } + + if (!asUTF8 && !this.options.binary) { + result = utils.transformTo("string", out.utf8encode(result)); + } + return result; +}; +/** + * A simple object representing a file in the zip file. + * @constructor + * @param {string} name the name of the file + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data + * @param {Object} options the options of the file + */ +var ZipObject = function(name, data, options) { + this.name = name; + this.dir = options.dir; + this.date = options.date; + this.comment = options.comment; + this.unixPermissions = options.unixPermissions; + this.dosPermissions = options.dosPermissions; + + this._data = data; + this.options = options; + + /* + * This object contains initial values for dir and date. + * With them, we can check if the user changed the deprecated metadata in + * `ZipObject#options` or not. + */ + this._initialMetadata = { + dir : options.dir, + date : options.date + }; +}; + +ZipObject.prototype = { + /** + * Return the content as UTF8 string. + * @return {string} the UTF8 string. + */ + asText: function() { + return dataToString.call(this, true); + }, + /** + * Returns the binary content. + * @return {string} the content as binary. + */ + asBinary: function() { + return dataToString.call(this, false); + }, + /** + * Returns the content as a nodejs Buffer. + * @return {Buffer} the content as a Buffer. + */ + asNodeBuffer: function() { + var result = getBinaryData(this); + return utils.transformTo("nodebuffer", result); + }, + /** + * Returns the content as an Uint8Array. + * @return {Uint8Array} the content as an Uint8Array. + */ + asUint8Array: function() { + var result = getBinaryData(this); + return utils.transformTo("uint8array", result); + }, + /** + * Returns the content as an ArrayBuffer. + * @return {ArrayBuffer} the content as an ArrayBufer. + */ + asArrayBuffer: function() { + return this.asUint8Array().buffer; + } +}; + +/** + * Transform an integer into a string in hexadecimal. + * @private + * @param {number} dec the number to convert. + * @param {number} bytes the number of bytes to generate. + * @returns {string} the result. + */ +var decToHex = function(dec, bytes) { + var hex = "", + i; + for (i = 0; i < bytes; i++) { + hex += String.fromCharCode(dec & 0xff); + dec = dec >>> 8; + } + return hex; +}; + +/** + * Merge the objects passed as parameters into a new one. + * @private + * @param {...Object} var_args All objects to merge. + * @return {Object} a new object with the data of the others. + */ +var extend = function() { + var result = {}, i, attr; + for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers + for (attr in arguments[i]) { + if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") { + result[attr] = arguments[i][attr]; + } + } + } + return result; +}; + +/** + * Transforms the (incomplete) options from the user into the complete + * set of options to create a file. + * @private + * @param {Object} o the options from the user. + * @return {Object} the complete set of options. + */ +var prepareFileAttrs = function(o) { + o = o || {}; + if (o.base64 === true && (o.binary === null || o.binary === undefined)) { + o.binary = true; + } + o = extend(o, defaults); + o.date = o.date || new Date(); + if (o.compression !== null) o.compression = o.compression.toUpperCase(); + + return o; +}; + +/** + * Add a file in the current folder. + * @private + * @param {string} name the name of the file + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file + * @param {Object} o the options of the file + * @return {Object} the new file. + */ +var fileAdd = function(name, data, o) { + // be sure sub folders exist + var dataType = utils.getTypeOf(data), + parent; + + o = prepareFileAttrs(o); + + if (typeof o.unixPermissions === "string") { + o.unixPermissions = parseInt(o.unixPermissions, 8); + } + + // UNX_IFDIR 0040000 see zipinfo.c + if (o.unixPermissions && (o.unixPermissions & 0x4000)) { + o.dir = true; + } + // Bit 4 Directory + if (o.dosPermissions && (o.dosPermissions & 0x0010)) { + o.dir = true; + } + + if (o.dir) { + name = forceTrailingSlash(name); + } + + if (o.createFolders && (parent = parentFolder(name))) { + folderAdd.call(this, parent, true); + } + + if (o.dir || data === null || typeof data === "undefined") { + o.base64 = false; + o.binary = false; + data = null; + dataType = null; + } + else if (dataType === "string") { + if (o.binary && !o.base64) { + // optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask + if (o.optimizedBinaryString !== true) { + // this is a string, not in a base64 format. + // Be sure that this is a correct "binary string" + data = utils.string2binary(data); + } + } + } + else { // arraybuffer, uint8array, ... + o.base64 = false; + o.binary = true; + + if (!dataType && !(data instanceof CompressedObject)) { + throw new Error("The data of '" + name + "' is in an unsupported format !"); + } + + // special case : it's way easier to work with Uint8Array than with ArrayBuffer + if (dataType === "arraybuffer") { + data = utils.transformTo("uint8array", data); + } + } + + var object = new ZipObject(name, data, o); + this.files[name] = object; + return object; +}; + +/** + * Find the parent folder of the path. + * @private + * @param {string} path the path to use + * @return {string} the parent folder, or "" + */ +var parentFolder = function (path) { + if (path.slice(-1) == '/') { + path = path.substring(0, path.length - 1); + } + var lastSlash = path.lastIndexOf('/'); + return (lastSlash > 0) ? path.substring(0, lastSlash) : ""; +}; + + +/** + * Returns the path with a slash at the end. + * @private + * @param {String} path the path to check. + * @return {String} the path with a trailing slash. + */ +var forceTrailingSlash = function(path) { + // Check the name ends with a / + if (path.slice(-1) != "/") { + path += "/"; // IE doesn't like substr(-1) + } + return path; +}; +/** + * Add a (sub) folder in the current folder. + * @private + * @param {string} name the folder's name + * @param {boolean=} [createFolders] If true, automatically create sub + * folders. Defaults to false. + * @return {Object} the new folder. + */ +var folderAdd = function(name, createFolders) { + createFolders = (typeof createFolders !== 'undefined') ? createFolders : false; + + name = forceTrailingSlash(name); + + // Does this folder already exist? + if (!this.files[name]) { + fileAdd.call(this, name, null, { + dir: true, + createFolders: createFolders + }); + } + return this.files[name]; +}; + +/** + * Generate a JSZip.CompressedObject for a given zipOject. + * @param {ZipObject} file the object to read. + * @param {JSZip.compression} compression the compression to use. + * @param {Object} compressionOptions the options to use when compressing. + * @return {JSZip.CompressedObject} the compressed result. + */ +var generateCompressedObjectFrom = function(file, compression, compressionOptions) { + var result = new CompressedObject(), + content; + + // the data has not been decompressed, we might reuse things ! + if (file._data instanceof CompressedObject) { + result.uncompressedSize = file._data.uncompressedSize; + result.crc32 = file._data.crc32; + + if (result.uncompressedSize === 0 || file.dir) { + compression = compressions['STORE']; + result.compressedContent = ""; + result.crc32 = 0; + } + else if (file._data.compressionMethod === compression.magic) { + result.compressedContent = file._data.getCompressedContent(); + } + else { + content = file._data.getContent(); + // need to decompress / recompress + result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions); + } + } + else { + // have uncompressed data + content = getBinaryData(file); + if (!content || content.length === 0 || file.dir) { + compression = compressions['STORE']; + content = ""; + } + result.uncompressedSize = content.length; + result.crc32 = crc32(content); + result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions); + } + + result.compressedSize = result.compressedContent.length; + result.compressionMethod = compression.magic; + + return result; +}; + + + + +/** + * Generate the UNIX part of the external file attributes. + * @param {Object} unixPermissions the unix permissions or null. + * @param {Boolean} isDir true if the entry is a directory, false otherwise. + * @return {Number} a 32 bit integer. + * + * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute : + * + * TTTTsstrwxrwxrwx0000000000ADVSHR + * ^^^^____________________________ file type, see zipinfo.c (UNX_*) + * ^^^_________________________ setuid, setgid, sticky + * ^^^^^^^^^________________ permissions + * ^^^^^^^^^^______ not used ? + * ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only + */ +var generateUnixExternalFileAttr = function (unixPermissions, isDir) { + + var result = unixPermissions; + if (!unixPermissions) { + // I can't use octal values in strict mode, hence the hexa. + // 040775 => 0x41fd + // 0100664 => 0x81b4 + result = isDir ? 0x41fd : 0x81b4; + } + + return (result & 0xFFFF) << 16; +}; + +/** + * Generate the DOS part of the external file attributes. + * @param {Object} dosPermissions the dos permissions or null. + * @param {Boolean} isDir true if the entry is a directory, false otherwise. + * @return {Number} a 32 bit integer. + * + * Bit 0 Read-Only + * Bit 1 Hidden + * Bit 2 System + * Bit 3 Volume Label + * Bit 4 Directory + * Bit 5 Archive + */ +var generateDosExternalFileAttr = function (dosPermissions, isDir) { + + // the dir flag is already set for compatibility + + return (dosPermissions || 0) & 0x3F; +}; + +/** + * Generate the various parts used in the construction of the final zip file. + * @param {string} name the file name. + * @param {ZipObject} file the file content. + * @param {JSZip.CompressedObject} compressedObject the compressed object. + * @param {number} offset the current offset from the start of the zip file. + * @param {String} platform let's pretend we are this platform (change platform dependents fields) + * @return {object} the zip parts. + */ +var generateZipParts = function(name, file, compressedObject, offset, platform) { + var data = compressedObject.compressedContent, + utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)), + comment = file.comment || "", + utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)), + useUTF8ForFileName = utfEncodedFileName.length !== file.name.length, + useUTF8ForComment = utfEncodedComment.length !== comment.length, + o = file.options, + dosTime, + dosDate, + extraFields = "", + unicodePathExtraField = "", + unicodeCommentExtraField = "", + dir, date; + + + // handle the deprecated options.dir + if (file._initialMetadata.dir !== file.dir) { + dir = file.dir; + } else { + dir = o.dir; + } + + // handle the deprecated options.date + if(file._initialMetadata.date !== file.date) { + date = file.date; + } else { + date = o.date; + } + + var extFileAttr = 0; + var versionMadeBy = 0; + if (dir) { + // dos or unix, we set the dos dir flag + extFileAttr |= 0x00010; + } + if(platform === "UNIX") { + versionMadeBy = 0x031E; // UNIX, version 3.0 + extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir); + } else { // DOS or other, fallback to DOS + versionMadeBy = 0x0014; // DOS, version 2.0 + extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir); + } + + // date + // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html + // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html + // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html + + dosTime = date.getHours(); + dosTime = dosTime << 6; + dosTime = dosTime | date.getMinutes(); + dosTime = dosTime << 5; + dosTime = dosTime | date.getSeconds() / 2; + + dosDate = date.getFullYear() - 1980; + dosDate = dosDate << 4; + dosDate = dosDate | (date.getMonth() + 1); + dosDate = dosDate << 5; + dosDate = dosDate | date.getDate(); + + if (useUTF8ForFileName) { + // set the unicode path extra field. unzip needs at least one extra + // field to correctly handle unicode path, so using the path is as good + // as any other information. This could improve the situation with + // other archive managers too. + // This field is usually used without the utf8 flag, with a non + // unicode path in the header (winrar, winzip). This helps (a bit) + // with the messy Windows' default compressed folders feature but + // breaks on p7zip which doesn't seek the unicode path extra field. + // So for now, UTF-8 everywhere ! + unicodePathExtraField = + // Version + decToHex(1, 1) + + // NameCRC32 + decToHex(crc32(utfEncodedFileName), 4) + + // UnicodeName + utfEncodedFileName; + + extraFields += + // Info-ZIP Unicode Path Extra Field + "\x75\x70" + + // size + decToHex(unicodePathExtraField.length, 2) + + // content + unicodePathExtraField; + } + + if(useUTF8ForComment) { + + unicodeCommentExtraField = + // Version + decToHex(1, 1) + + // CommentCRC32 + decToHex(this.crc32(utfEncodedComment), 4) + + // UnicodeName + utfEncodedComment; + + extraFields += + // Info-ZIP Unicode Path Extra Field + "\x75\x63" + + // size + decToHex(unicodeCommentExtraField.length, 2) + + // content + unicodeCommentExtraField; + } + + var header = ""; + + // version needed to extract + header += "\x0A\x00"; + // general purpose bit flag + // set bit 11 if utf8 + header += (useUTF8ForFileName || useUTF8ForComment) ? "\x00\x08" : "\x00\x00"; + // compression method + header += compressedObject.compressionMethod; + // last mod file time + header += decToHex(dosTime, 2); + // last mod file date + header += decToHex(dosDate, 2); + // crc-32 + header += decToHex(compressedObject.crc32, 4); + // compressed size + header += decToHex(compressedObject.compressedSize, 4); + // uncompressed size + header += decToHex(compressedObject.uncompressedSize, 4); + // file name length + header += decToHex(utfEncodedFileName.length, 2); + // extra field length + header += decToHex(extraFields.length, 2); + + + var fileRecord = signature.LOCAL_FILE_HEADER + header + utfEncodedFileName + extraFields; + + var dirRecord = signature.CENTRAL_FILE_HEADER + + // version made by (00: DOS) + decToHex(versionMadeBy, 2) + + // file header (common to file and central directory) + header + + // file comment length + decToHex(utfEncodedComment.length, 2) + + // disk number start + "\x00\x00" + + // internal file attributes TODO + "\x00\x00" + + // external file attributes + decToHex(extFileAttr, 4) + + // relative offset of local header + decToHex(offset, 4) + + // file name + utfEncodedFileName + + // extra field + extraFields + + // file comment + utfEncodedComment; + + return { + fileRecord: fileRecord, + dirRecord: dirRecord, + compressedObject: compressedObject + }; +}; + + +// return the actual prototype of JSZip +var out = { + /** + * Read an existing zip and merge the data in the current JSZip object. + * The implementation is in jszip-load.js, don't forget to include it. + * @param {String|ArrayBuffer|Uint8Array|Buffer} stream The stream to load + * @param {Object} options Options for loading the stream. + * options.base64 : is the stream in base64 ? default : false + * @return {JSZip} the current JSZip object + */ + load: function(stream, options) { + throw new Error("Load method is not defined. Is the file jszip-load.js included ?"); + }, + + /** + * Filter nested files/folders with the specified function. + * @param {Function} search the predicate to use : + * function (relativePath, file) {...} + * It takes 2 arguments : the relative path and the file. + * @return {Array} An array of matching elements. + */ + filter: function(search) { + var result = [], + filename, relativePath, file, fileClone; + for (filename in this.files) { + if (!this.files.hasOwnProperty(filename)) { + continue; + } + file = this.files[filename]; + // return a new object, don't let the user mess with our internal objects :) + fileClone = new ZipObject(file.name, file._data, extend(file.options)); + relativePath = filename.slice(this.root.length, filename.length); + if (filename.slice(0, this.root.length) === this.root && // the file is in the current root + search(relativePath, fileClone)) { // and the file matches the function + result.push(fileClone); + } + } + return result; + }, + + /** + * Add a file to the zip file, or search a file. + * @param {string|RegExp} name The name of the file to add (if data is defined), + * the name of the file to find (if no data) or a regex to match files. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded + * @param {Object} o File options + * @return {JSZip|Object|Array} this JSZip object (when adding a file), + * a file (when searching by string) or an array of files (when searching by regex). + */ + file: function(name, data, o) { + if (arguments.length === 1) { + if (utils.isRegExp(name)) { + var regexp = name; + return this.filter(function(relativePath, file) { + return !file.dir && regexp.test(relativePath); + }); + } + else { // text + return this.filter(function(relativePath, file) { + return !file.dir && relativePath === name; + })[0] || null; + } + } + else { // more than one argument : we have data ! + name = this.root + name; + fileAdd.call(this, name, data, o); + } + return this; + }, + + /** + * Add a directory to the zip file, or search. + * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders. + * @return {JSZip} an object with the new directory as the root, or an array containing matching folders. + */ + folder: function(arg) { + if (!arg) { + return this; + } + + if (utils.isRegExp(arg)) { + return this.filter(function(relativePath, file) { + return file.dir && arg.test(relativePath); + }); + } + + // else, name is a new folder + var name = this.root + arg; + var newFolder = folderAdd.call(this, name); + + // Allow chaining by returning a new object with this folder as the root + var ret = this.clone(); + ret.root = newFolder.name; + return ret; + }, + + /** + * Delete a file, or a directory and all sub-files, from the zip + * @param {string} name the name of the file to delete + * @return {JSZip} this JSZip object + */ + remove: function(name) { + name = this.root + name; + var file = this.files[name]; + if (!file) { + // Look for any folders + if (name.slice(-1) != "/") { + name += "/"; + } + file = this.files[name]; + } + + if (file && !file.dir) { + // file + delete this.files[name]; + } else { + // maybe a folder, delete recursively + var kids = this.filter(function(relativePath, file) { + return file.name.slice(0, name.length) === name; + }); + for (var i = 0; i < kids.length; i++) { + delete this.files[kids[i].name]; + } + } + + return this; + }, + + /** + * Generate the complete zip file + * @param {Object} options the options to generate the zip file : + * - base64, (deprecated, use type instead) true to generate base64. + * - compression, "STORE" by default. + * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. + * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file + */ + generate: function(options) { + options = extend(options || {}, { + base64: true, + compression: "STORE", + compressionOptions : null, + type: "base64", + platform: "DOS", + comment: null, + mimeType: 'application/zip' + }); + + utils.checkSupport(options.type); + + // accept nodejs `process.platform` + if( + options.platform === 'darwin' || + options.platform === 'freebsd' || + options.platform === 'linux' || + options.platform === 'sunos' + ) { + options.platform = "UNIX"; + } + if (options.platform === 'win32') { + options.platform = "DOS"; + } + + var zipData = [], + localDirLength = 0, + centralDirLength = 0, + writer, i, + utfEncodedComment = utils.transformTo("string", this.utf8encode(options.comment || this.comment || "")); + + // first, generate all the zip parts. + for (var name in this.files) { + if (!this.files.hasOwnProperty(name)) { + continue; + } + var file = this.files[name]; + + var compressionName = file.options.compression || options.compression.toUpperCase(); + var compression = compressions[compressionName]; + if (!compression) { + throw new Error(compressionName + " is not a valid compression method !"); + } + var compressionOptions = file.options.compressionOptions || options.compressionOptions || {}; + + var compressedObject = generateCompressedObjectFrom.call(this, file, compression, compressionOptions); + + var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength, options.platform); + localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize; + centralDirLength += zipPart.dirRecord.length; + zipData.push(zipPart); + } + + var dirEnd = ""; + + // end of central dir signature + dirEnd = signature.CENTRAL_DIRECTORY_END + + // number of this disk + "\x00\x00" + + // number of the disk with the start of the central directory + "\x00\x00" + + // total number of entries in the central directory on this disk + decToHex(zipData.length, 2) + + // total number of entries in the central directory + decToHex(zipData.length, 2) + + // size of the central directory 4 bytes + decToHex(centralDirLength, 4) + + // offset of start of central directory with respect to the starting disk number + decToHex(localDirLength, 4) + + // .ZIP file comment length + decToHex(utfEncodedComment.length, 2) + + // .ZIP file comment + utfEncodedComment; + + + // we have all the parts (and the total length) + // time to create a writer ! + var typeName = options.type.toLowerCase(); + if(typeName==="uint8array"||typeName==="arraybuffer"||typeName==="blob"||typeName==="nodebuffer") { + writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length); + }else{ + writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length); + } + + for (i = 0; i < zipData.length; i++) { + writer.append(zipData[i].fileRecord); + writer.append(zipData[i].compressedObject.compressedContent); + } + for (i = 0; i < zipData.length; i++) { + writer.append(zipData[i].dirRecord); + } + + writer.append(dirEnd); + + var zip = writer.finalize(); + + + + switch(options.type.toLowerCase()) { + // case "zip is an Uint8Array" + case "uint8array" : + case "arraybuffer" : + case "nodebuffer" : + return utils.transformTo(options.type.toLowerCase(), zip); + case "blob" : + return utils.arrayBuffer2Blob(utils.transformTo("arraybuffer", zip), options.mimeType); + // case "zip is a string" + case "base64" : + return (options.base64) ? base64.encode(zip) : zip; + default : // case "string" : + return zip; + } + + }, + + /** + * @deprecated + * This method will be removed in a future version without replacement. + */ + crc32: function (input, crc) { + return crc32(input, crc); + }, + + /** + * @deprecated + * This method will be removed in a future version without replacement. + */ + utf8encode: function (string) { + return utils.transformTo("string", utf8.utf8encode(string)); + }, + + /** + * @deprecated + * This method will be removed in a future version without replacement. + */ + utf8decode: function (input) { + return utf8.utf8decode(input); + } +}; +module.exports = out; + +},{"./base64":1,"./compressedObject":2,"./compressions":3,"./crc32":4,"./defaults":6,"./nodeBuffer":11,"./signature":14,"./stringWriter":16,"./support":17,"./uint8ArrayWriter":19,"./utf8":20,"./utils":21}],14:[function(_dereq_,module,exports){ +'use strict'; +exports.LOCAL_FILE_HEADER = "PK\x03\x04"; +exports.CENTRAL_FILE_HEADER = "PK\x01\x02"; +exports.CENTRAL_DIRECTORY_END = "PK\x05\x06"; +exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x06\x07"; +exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\x06\x06"; +exports.DATA_DESCRIPTOR = "PK\x07\x08"; + +},{}],15:[function(_dereq_,module,exports){ +'use strict'; +var DataReader = _dereq_('./dataReader'); +var utils = _dereq_('./utils'); + +function StringReader(data, optimizedBinaryString) { + this.data = data; + if (!optimizedBinaryString) { + this.data = utils.string2binary(this.data); + } + this.length = this.data.length; + this.index = 0; +} +StringReader.prototype = new DataReader(); +/** + * @see DataReader.byteAt + */ +StringReader.prototype.byteAt = function(i) { + return this.data.charCodeAt(i); +}; +/** + * @see DataReader.lastIndexOfSignature + */ +StringReader.prototype.lastIndexOfSignature = function(sig) { + return this.data.lastIndexOf(sig); +}; +/** + * @see DataReader.readData + */ +StringReader.prototype.readData = function(size) { + this.checkOffset(size); + // this will work because the constructor applied the "& 0xff" mask. + var result = this.data.slice(this.index, this.index + size); + this.index += size; + return result; +}; +module.exports = StringReader; + +},{"./dataReader":5,"./utils":21}],16:[function(_dereq_,module,exports){ +'use strict'; + +var utils = _dereq_('./utils'); + +/** + * An object to write any content to a string. + * @constructor + */ +var StringWriter = function() { + this.data = []; +}; +StringWriter.prototype = { + /** + * Append any content to the current string. + * @param {Object} input the content to add. + */ + append: function(input) { + input = utils.transformTo("string", input); + this.data.push(input); + }, + /** + * Finalize the construction an return the result. + * @return {string} the generated string. + */ + finalize: function() { + return this.data.join(""); + } +}; + +module.exports = StringWriter; + +},{"./utils":21}],17:[function(_dereq_,module,exports){ +(function (Buffer){ +'use strict'; +exports.base64 = true; +exports.array = true; +exports.string = true; +exports.arraybuffer = typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined"; +// contains true if JSZip can read/generate nodejs Buffer, false otherwise. +// Browserify will provide a Buffer implementation for browsers, which is +// an augmented Uint8Array (i.e., can be used as either Buffer or U8). +exports.nodebuffer = typeof Buffer !== "undefined"; +// contains true if JSZip can read/generate Uint8Array, false otherwise. +exports.uint8array = typeof Uint8Array !== "undefined"; + +if (typeof ArrayBuffer === "undefined") { + exports.blob = false; +} +else { + var buffer = new ArrayBuffer(0); + try { + exports.blob = new Blob([buffer], { + type: "application/zip" + }).size === 0; + } + catch (e) { + try { + var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; + var builder = new Builder(); + builder.append(buffer); + exports.blob = builder.getBlob('application/zip').size === 0; + } + catch (e) { + exports.blob = false; + } + } +} + +}).call(this,(typeof Buffer !== "undefined" ? Buffer : undefined)) +},{}],18:[function(_dereq_,module,exports){ +'use strict'; +var DataReader = _dereq_('./dataReader'); + +function Uint8ArrayReader(data) { + if (data) { + this.data = data; + this.length = this.data.length; + this.index = 0; + } +} +Uint8ArrayReader.prototype = new DataReader(); +/** + * @see DataReader.byteAt + */ +Uint8ArrayReader.prototype.byteAt = function(i) { + return this.data[i]; +}; +/** + * @see DataReader.lastIndexOfSignature + */ +Uint8ArrayReader.prototype.lastIndexOfSignature = function(sig) { + var sig0 = sig.charCodeAt(0), + sig1 = sig.charCodeAt(1), + sig2 = sig.charCodeAt(2), + sig3 = sig.charCodeAt(3); + for (var i = this.length - 4; i >= 0; --i) { + if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) { + return i; + } + } + + return -1; +}; +/** + * @see DataReader.readData + */ +Uint8ArrayReader.prototype.readData = function(size) { + this.checkOffset(size); + if(size === 0) { + // in IE10, when using subarray(idx, idx), we get the array [0x00] instead of []. + return new Uint8Array(0); + } + var result = this.data.subarray(this.index, this.index + size); + this.index += size; + return result; +}; +module.exports = Uint8ArrayReader; + +},{"./dataReader":5}],19:[function(_dereq_,module,exports){ +'use strict'; + +var utils = _dereq_('./utils'); + +/** + * An object to write any content to an Uint8Array. + * @constructor + * @param {number} length The length of the array. + */ +var Uint8ArrayWriter = function(length) { + this.data = new Uint8Array(length); + this.index = 0; +}; +Uint8ArrayWriter.prototype = { + /** + * Append any content to the current array. + * @param {Object} input the content to add. + */ + append: function(input) { + if (input.length !== 0) { + // with an empty Uint8Array, Opera fails with a "Offset larger than array size" + input = utils.transformTo("uint8array", input); + this.data.set(input, this.index); + this.index += input.length; + } + }, + /** + * Finalize the construction an return the result. + * @return {Uint8Array} the generated array. + */ + finalize: function() { + return this.data; + } +}; + +module.exports = Uint8ArrayWriter; + +},{"./utils":21}],20:[function(_dereq_,module,exports){ +'use strict'; + +var utils = _dereq_('./utils'); +var support = _dereq_('./support'); +var nodeBuffer = _dereq_('./nodeBuffer'); + +/** + * The following functions come from pako, from pako/lib/utils/strings + * released under the MIT license, see pako https://github.com/nodeca/pako/ + */ + +// Table with utf8 lengths (calculated by first byte of sequence) +// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, +// because max possible codepoint is 0x10ffff +var _utf8len = new Array(256); +for (var i=0; i<256; i++) { + _utf8len[i] = (i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1); +} +_utf8len[254]=_utf8len[254]=1; // Invalid sequence start + +// convert string to array (typed, when possible) +var string2buf = function (str) { + var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; + + // count binary size + for (m_pos = 0; m_pos < str_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { + c2 = str.charCodeAt(m_pos+1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } + + // allocate buffer + if (support.uint8array) { + buf = new Uint8Array(buf_len); + } else { + buf = new Array(buf_len); + } + + // convert + for (i=0, m_pos = 0; i < buf_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { + c2 = str.charCodeAt(m_pos+1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + if (c < 0x80) { + /* one byte */ + buf[i++] = c; + } else if (c < 0x800) { + /* two bytes */ + buf[i++] = 0xC0 | (c >>> 6); + buf[i++] = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + /* three bytes */ + buf[i++] = 0xE0 | (c >>> 12); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } else { + /* four bytes */ + buf[i++] = 0xf0 | (c >>> 18); + buf[i++] = 0x80 | (c >>> 12 & 0x3f); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } + } + + return buf; +}; + +// Calculate max possible position in utf8 buffer, +// that will not break sequence. If that's not possible +// - (very small limits) return max size as is. +// +// buf[] - utf8 bytes array +// max - length limit (mandatory); +var utf8border = function(buf, max) { + var pos; + + max = max || buf.length; + if (max > buf.length) { max = buf.length; } + + // go back from last position, until start of sequence found + pos = max-1; + while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } + + // Fuckup - very small and broken sequence, + // return max, because we should return something anyway. + if (pos < 0) { return max; } + + // If we came to start of buffer - that means vuffer is too small, + // return max too. + if (pos === 0) { return max; } + + return (pos + _utf8len[buf[pos]] > max) ? pos : max; +}; + +// convert array to string +var buf2string = function (buf) { + var str, i, out, c, c_len; + var len = buf.length; + + // Reserve max possible length (2 words per char) + // NB: by unknown reasons, Array is significantly faster for + // String.fromCharCode.apply than Uint16Array. + var utf16buf = new Array(len*2); + + for (out=0, i=0; i 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; } + + // apply mask on first byte + c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; + // join the rest + while (c_len > 1 && i < len) { + c = (c << 6) | (buf[i++] & 0x3f); + c_len--; + } + + // terminated by end of string? + if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } + + if (c < 0x10000) { + utf16buf[out++] = c; + } else { + c -= 0x10000; + utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); + utf16buf[out++] = 0xdc00 | (c & 0x3ff); + } + } + + // shrinkBuf(utf16buf, out) + if (utf16buf.length !== out) { + if(utf16buf.subarray) { + utf16buf = utf16buf.subarray(0, out); + } else { + utf16buf.length = out; + } + } + + // return String.fromCharCode.apply(null, utf16buf); + return utils.applyFromCharCode(utf16buf); +}; + + +// That's all for the pako functions. + + +/** + * Transform a javascript string into an array (typed if possible) of bytes, + * UTF-8 encoded. + * @param {String} str the string to encode + * @return {Array|Uint8Array|Buffer} the UTF-8 encoded string. + */ +exports.utf8encode = function utf8encode(str) { + if (support.nodebuffer) { + return nodeBuffer(str, "utf-8"); + } + + return string2buf(str); +}; + + +/** + * Transform a bytes array (or a representation) representing an UTF-8 encoded + * string into a javascript string. + * @param {Array|Uint8Array|Buffer} buf the data de decode + * @return {String} the decoded string. + */ +exports.utf8decode = function utf8decode(buf) { + if (support.nodebuffer) { + return utils.transformTo("nodebuffer", buf).toString("utf-8"); + } + + buf = utils.transformTo(support.uint8array ? "uint8array" : "array", buf); + + // return buf2string(buf); + // Chrome prefers to work with "small" chunks of data + // for the method buf2string. + // Firefox and Chrome has their own shortcut, IE doesn't seem to really care. + var result = [], k = 0, len = buf.length, chunk = 65536; + while (k < len) { + var nextBoundary = utf8border(buf, Math.min(k + chunk, len)); + if (support.uint8array) { + result.push(buf2string(buf.subarray(k, nextBoundary))); + } else { + result.push(buf2string(buf.slice(k, nextBoundary))); + } + k = nextBoundary; + } + return result.join(""); + +}; +// vim: set shiftwidth=4 softtabstop=4: + +},{"./nodeBuffer":11,"./support":17,"./utils":21}],21:[function(_dereq_,module,exports){ +'use strict'; +var support = _dereq_('./support'); +var compressions = _dereq_('./compressions'); +var nodeBuffer = _dereq_('./nodeBuffer'); +/** + * Convert a string to a "binary string" : a string containing only char codes between 0 and 255. + * @param {string} str the string to transform. + * @return {String} the binary string. + */ +exports.string2binary = function(str) { + var result = ""; + for (var i = 0; i < str.length; i++) { + result += String.fromCharCode(str.charCodeAt(i) & 0xff); + } + return result; +}; +exports.arrayBuffer2Blob = function(buffer, mimeType) { + exports.checkSupport("blob"); + mimeType = mimeType || 'application/zip'; + + try { + // Blob constructor + return new Blob([buffer], { + type: mimeType + }); + } + catch (e) { + + try { + // deprecated, browser only, old way + var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; + var builder = new Builder(); + builder.append(buffer); + return builder.getBlob(mimeType); + } + catch (e) { + + // well, fuck ?! + throw new Error("Bug : can't construct the Blob."); + } + } + + +}; +/** + * The identity function. + * @param {Object} input the input. + * @return {Object} the same input. + */ +function identity(input) { + return input; +} + +/** + * Fill in an array with a string. + * @param {String} str the string to use. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated). + * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array. + */ +function stringToArrayLike(str, array) { + for (var i = 0; i < str.length; ++i) { + array[i] = str.charCodeAt(i) & 0xFF; + } + return array; +} + +/** + * Transform an array-like object to a string. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. + * @return {String} the result. + */ +function arrayLikeToString(array) { + // Performances notes : + // -------------------- + // String.fromCharCode.apply(null, array) is the fastest, see + // see http://jsperf.com/converting-a-uint8array-to-a-string/2 + // but the stack is limited (and we can get huge arrays !). + // + // result += String.fromCharCode(array[i]); generate too many strings ! + // + // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2 + var chunk = 65536; + var result = [], + len = array.length, + type = exports.getTypeOf(array), + k = 0, + canUseApply = true; + try { + switch(type) { + case "uint8array": + String.fromCharCode.apply(null, new Uint8Array(0)); + break; + case "nodebuffer": + String.fromCharCode.apply(null, nodeBuffer(0)); + break; + } + } catch(e) { + canUseApply = false; + } + + // no apply : slow and painful algorithm + // default browser on android 4.* + if (!canUseApply) { + var resultStr = ""; + for(var i = 0; i < array.length;i++) { + resultStr += String.fromCharCode(array[i]); + } + return resultStr; + } + while (k < len && chunk > 1) { + try { + if (type === "array" || type === "nodebuffer") { + result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len)))); + } + else { + result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len)))); + } + k += chunk; + } + catch (e) { + chunk = Math.floor(chunk / 2); + } + } + return result.join(""); +} + +exports.applyFromCharCode = arrayLikeToString; + + +/** + * Copy the data from an array-like to an other array-like. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated. + * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array. + */ +function arrayLikeToArrayLike(arrayFrom, arrayTo) { + for (var i = 0; i < arrayFrom.length; i++) { + arrayTo[i] = arrayFrom[i]; + } + return arrayTo; +} + +// a matrix containing functions to transform everything into everything. +var transform = {}; + +// string to ? +transform["string"] = { + "string": identity, + "array": function(input) { + return stringToArrayLike(input, new Array(input.length)); + }, + "arraybuffer": function(input) { + return transform["string"]["uint8array"](input).buffer; + }, + "uint8array": function(input) { + return stringToArrayLike(input, new Uint8Array(input.length)); + }, + "nodebuffer": function(input) { + return stringToArrayLike(input, nodeBuffer(input.length)); + } +}; + +// array to ? +transform["array"] = { + "string": arrayLikeToString, + "array": identity, + "arraybuffer": function(input) { + return (new Uint8Array(input)).buffer; + }, + "uint8array": function(input) { + return new Uint8Array(input); + }, + "nodebuffer": function(input) { + return nodeBuffer(input); + } +}; + +// arraybuffer to ? +transform["arraybuffer"] = { + "string": function(input) { + return arrayLikeToString(new Uint8Array(input)); + }, + "array": function(input) { + return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength)); + }, + "arraybuffer": identity, + "uint8array": function(input) { + return new Uint8Array(input); + }, + "nodebuffer": function(input) { + return nodeBuffer(new Uint8Array(input)); + } +}; + +// uint8array to ? +transform["uint8array"] = { + "string": arrayLikeToString, + "array": function(input) { + return arrayLikeToArrayLike(input, new Array(input.length)); + }, + "arraybuffer": function(input) { + return input.buffer; + }, + "uint8array": identity, + "nodebuffer": function(input) { + return nodeBuffer(input); + } +}; + +// nodebuffer to ? +transform["nodebuffer"] = { + "string": arrayLikeToString, + "array": function(input) { + return arrayLikeToArrayLike(input, new Array(input.length)); + }, + "arraybuffer": function(input) { + return transform["nodebuffer"]["uint8array"](input).buffer; + }, + "uint8array": function(input) { + return arrayLikeToArrayLike(input, new Uint8Array(input.length)); + }, + "nodebuffer": identity +}; + +/** + * Transform an input into any type. + * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer. + * If no output type is specified, the unmodified input will be returned. + * @param {String} outputType the output type. + * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert. + * @throws {Error} an Error if the browser doesn't support the requested output type. + */ +exports.transformTo = function(outputType, input) { + if (!input) { + // undefined, null, etc + // an empty string won't harm. + input = ""; + } + if (!outputType) { + return input; + } + exports.checkSupport(outputType); + var inputType = exports.getTypeOf(input); + var result = transform[inputType][outputType](input); + return result; +}; + +/** + * Return the type of the input. + * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer. + * @param {Object} input the input to identify. + * @return {String} the (lowercase) type of the input. + */ +exports.getTypeOf = function(input) { + if (typeof input === "string") { + return "string"; + } + if (Object.prototype.toString.call(input) === "[object Array]") { + return "array"; + } + if (support.nodebuffer && nodeBuffer.test(input)) { + return "nodebuffer"; + } + if (support.uint8array && input instanceof Uint8Array) { + return "uint8array"; + } + if (support.arraybuffer && input instanceof ArrayBuffer) { + return "arraybuffer"; + } +}; + +/** + * Throw an exception if the type is not supported. + * @param {String} type the type to check. + * @throws {Error} an Error if the browser doesn't support the requested type. + */ +exports.checkSupport = function(type) { + var supported = support[type.toLowerCase()]; + if (!supported) { + throw new Error(type + " is not supported by this browser"); + } +}; +exports.MAX_VALUE_16BITS = 65535; +exports.MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1 + +/** + * Prettify a string read as binary. + * @param {string} str the string to prettify. + * @return {string} a pretty string. + */ +exports.pretty = function(str) { + var res = '', + code, i; + for (i = 0; i < (str || "").length; i++) { + code = str.charCodeAt(i); + res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase(); + } + return res; +}; + +/** + * Find a compression registered in JSZip. + * @param {string} compressionMethod the method magic to find. + * @return {Object|null} the JSZip compression object, null if none found. + */ +exports.findCompression = function(compressionMethod) { + for (var method in compressions) { + if (!compressions.hasOwnProperty(method)) { + continue; + } + if (compressions[method].magic === compressionMethod) { + return compressions[method]; + } + } + return null; +}; +/** +* Cross-window, cross-Node-context regular expression detection +* @param {Object} object Anything +* @return {Boolean} true if the object is a regular expression, +* false otherwise +*/ +exports.isRegExp = function (object) { + return Object.prototype.toString.call(object) === "[object RegExp]"; +}; + + +},{"./compressions":3,"./nodeBuffer":11,"./support":17}],22:[function(_dereq_,module,exports){ +'use strict'; +var StringReader = _dereq_('./stringReader'); +var NodeBufferReader = _dereq_('./nodeBufferReader'); +var Uint8ArrayReader = _dereq_('./uint8ArrayReader'); +var utils = _dereq_('./utils'); +var sig = _dereq_('./signature'); +var ZipEntry = _dereq_('./zipEntry'); +var support = _dereq_('./support'); +var jszipProto = _dereq_('./object'); +// class ZipEntries {{{ +/** + * All the entries in the zip file. + * @constructor + * @param {String|ArrayBuffer|Uint8Array} data the binary stream to load. + * @param {Object} loadOptions Options for loading the stream. + */ +function ZipEntries(data, loadOptions) { + this.files = []; + this.loadOptions = loadOptions; + if (data) { + this.load(data); + } +} +ZipEntries.prototype = { + /** + * Check that the reader is on the speficied signature. + * @param {string} expectedSignature the expected signature. + * @throws {Error} if it is an other signature. + */ + checkSignature: function(expectedSignature) { + var signature = this.reader.readString(4); + if (signature !== expectedSignature) { + throw new Error("Corrupted zip or bug : unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")"); + } + }, + /** + * Read the end of the central directory. + */ + readBlockEndOfCentral: function() { + this.diskNumber = this.reader.readInt(2); + this.diskWithCentralDirStart = this.reader.readInt(2); + this.centralDirRecordsOnThisDisk = this.reader.readInt(2); + this.centralDirRecords = this.reader.readInt(2); + this.centralDirSize = this.reader.readInt(4); + this.centralDirOffset = this.reader.readInt(4); + + this.zipCommentLength = this.reader.readInt(2); + // warning : the encoding depends of the system locale + // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded. + // On a windows machine, this field is encoded with the localized windows code page. + this.zipComment = this.reader.readString(this.zipCommentLength); + // To get consistent behavior with the generation part, we will assume that + // this is utf8 encoded. + this.zipComment = jszipProto.utf8decode(this.zipComment); + }, + /** + * Read the end of the Zip 64 central directory. + * Not merged with the method readEndOfCentral : + * The end of central can coexist with its Zip64 brother, + * I don't want to read the wrong number of bytes ! + */ + readBlockZip64EndOfCentral: function() { + this.zip64EndOfCentralSize = this.reader.readInt(8); + this.versionMadeBy = this.reader.readString(2); + this.versionNeeded = this.reader.readInt(2); + this.diskNumber = this.reader.readInt(4); + this.diskWithCentralDirStart = this.reader.readInt(4); + this.centralDirRecordsOnThisDisk = this.reader.readInt(8); + this.centralDirRecords = this.reader.readInt(8); + this.centralDirSize = this.reader.readInt(8); + this.centralDirOffset = this.reader.readInt(8); + + this.zip64ExtensibleData = {}; + var extraDataSize = this.zip64EndOfCentralSize - 44, + index = 0, + extraFieldId, + extraFieldLength, + extraFieldValue; + while (index < extraDataSize) { + extraFieldId = this.reader.readInt(2); + extraFieldLength = this.reader.readInt(4); + extraFieldValue = this.reader.readString(extraFieldLength); + this.zip64ExtensibleData[extraFieldId] = { + id: extraFieldId, + length: extraFieldLength, + value: extraFieldValue + }; + } + }, + /** + * Read the end of the Zip 64 central directory locator. + */ + readBlockZip64EndOfCentralLocator: function() { + this.diskWithZip64CentralDirStart = this.reader.readInt(4); + this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8); + this.disksCount = this.reader.readInt(4); + if (this.disksCount > 1) { + throw new Error("Multi-volumes zip are not supported"); + } + }, + /** + * Read the local files, based on the offset read in the central part. + */ + readLocalFiles: function() { + var i, file; + for (i = 0; i < this.files.length; i++) { + file = this.files[i]; + this.reader.setIndex(file.localHeaderOffset); + this.checkSignature(sig.LOCAL_FILE_HEADER); + file.readLocalPart(this.reader); + file.handleUTF8(); + file.processAttributes(); + } + }, + /** + * Read the central directory. + */ + readCentralDir: function() { + var file; + + this.reader.setIndex(this.centralDirOffset); + while (this.reader.readString(4) === sig.CENTRAL_FILE_HEADER) { + file = new ZipEntry({ + zip64: this.zip64 + }, this.loadOptions); + file.readCentralPart(this.reader); + this.files.push(file); + } + }, + /** + * Read the end of central directory. + */ + readEndOfCentral: function() { + var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END); + if (offset === -1) { + // Check if the content is a truncated zip or complete garbage. + // A "LOCAL_FILE_HEADER" is not required at the beginning (auto + // extractible zip for example) but it can give a good hint. + // If an ajax request was used without responseType, we will also + // get unreadable data. + var isGarbage = true; + try { + this.reader.setIndex(0); + this.checkSignature(sig.LOCAL_FILE_HEADER); + isGarbage = false; + } catch (e) {} + + if (isGarbage) { + throw new Error("Can't find end of central directory : is this a zip file ? " + + "If it is, see http://stuk.github.io/jszip/documentation/howto/read_zip.html"); + } else { + throw new Error("Corrupted zip : can't find end of central directory"); + } + } + this.reader.setIndex(offset); + this.checkSignature(sig.CENTRAL_DIRECTORY_END); + this.readBlockEndOfCentral(); + + + /* extract from the zip spec : + 4) If one of the fields in the end of central directory + record is too small to hold required data, the field + should be set to -1 (0xFFFF or 0xFFFFFFFF) and the + ZIP64 format record should be created. + 5) The end of central directory record and the + Zip64 end of central directory locator record must + reside on the same disk when splitting or spanning + an archive. + */ + if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) { + this.zip64 = true; + + /* + Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from + the zip file can fit into a 32bits integer. This cannot be solved : Javascript represents + all numbers as 64-bit double precision IEEE 754 floating point numbers. + So, we have 53bits for integers and bitwise operations treat everything as 32bits. + see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators + and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5 + */ + + // should look for a zip64 EOCD locator + offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); + if (offset === -1) { + throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator"); + } + this.reader.setIndex(offset); + this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); + this.readBlockZip64EndOfCentralLocator(); + + // now the zip64 EOCD record + this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir); + this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); + this.readBlockZip64EndOfCentral(); + } + }, + prepareReader: function(data) { + var type = utils.getTypeOf(data); + if (type === "string" && !support.uint8array) { + this.reader = new StringReader(data, this.loadOptions.optimizedBinaryString); + } + else if (type === "nodebuffer") { + this.reader = new NodeBufferReader(data); + } + else { + this.reader = new Uint8ArrayReader(utils.transformTo("uint8array", data)); + } + }, + /** + * Read a zip file and create ZipEntries. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file. + */ + load: function(data) { + this.prepareReader(data); + this.readEndOfCentral(); + this.readCentralDir(); + this.readLocalFiles(); + } +}; +// }}} end of ZipEntries +module.exports = ZipEntries; + +},{"./nodeBufferReader":12,"./object":13,"./signature":14,"./stringReader":15,"./support":17,"./uint8ArrayReader":18,"./utils":21,"./zipEntry":23}],23:[function(_dereq_,module,exports){ +'use strict'; +var StringReader = _dereq_('./stringReader'); +var utils = _dereq_('./utils'); +var CompressedObject = _dereq_('./compressedObject'); +var jszipProto = _dereq_('./object'); + +var MADE_BY_DOS = 0x00; +var MADE_BY_UNIX = 0x03; + +// class ZipEntry {{{ +/** + * An entry in the zip file. + * @constructor + * @param {Object} options Options of the current file. + * @param {Object} loadOptions Options for loading the stream. + */ +function ZipEntry(options, loadOptions) { + this.options = options; + this.loadOptions = loadOptions; +} +ZipEntry.prototype = { + /** + * say if the file is encrypted. + * @return {boolean} true if the file is encrypted, false otherwise. + */ + isEncrypted: function() { + // bit 1 is set + return (this.bitFlag & 0x0001) === 0x0001; + }, + /** + * say if the file has utf-8 filename/comment. + * @return {boolean} true if the filename/comment is in utf-8, false otherwise. + */ + useUTF8: function() { + // bit 11 is set + return (this.bitFlag & 0x0800) === 0x0800; + }, + /** + * Prepare the function used to generate the compressed content from this ZipFile. + * @param {DataReader} reader the reader to use. + * @param {number} from the offset from where we should read the data. + * @param {number} length the length of the data to read. + * @return {Function} the callback to get the compressed content (the type depends of the DataReader class). + */ + prepareCompressedContent: function(reader, from, length) { + return function() { + var previousIndex = reader.index; + reader.setIndex(from); + var compressedFileData = reader.readData(length); + reader.setIndex(previousIndex); + + return compressedFileData; + }; + }, + /** + * Prepare the function used to generate the uncompressed content from this ZipFile. + * @param {DataReader} reader the reader to use. + * @param {number} from the offset from where we should read the data. + * @param {number} length the length of the data to read. + * @param {JSZip.compression} compression the compression used on this file. + * @param {number} uncompressedSize the uncompressed size to expect. + * @return {Function} the callback to get the uncompressed content (the type depends of the DataReader class). + */ + prepareContent: function(reader, from, length, compression, uncompressedSize) { + return function() { + + var compressedFileData = utils.transformTo(compression.uncompressInputType, this.getCompressedContent()); + var uncompressedFileData = compression.uncompress(compressedFileData); + + if (uncompressedFileData.length !== uncompressedSize) { + throw new Error("Bug : uncompressed data size mismatch"); + } + + return uncompressedFileData; + }; + }, + /** + * Read the local part of a zip file and add the info in this object. + * @param {DataReader} reader the reader to use. + */ + readLocalPart: function(reader) { + var compression, localExtraFieldsLength; + + // we already know everything from the central dir ! + // If the central dir data are false, we are doomed. + // On the bright side, the local part is scary : zip64, data descriptors, both, etc. + // The less data we get here, the more reliable this should be. + // Let's skip the whole header and dash to the data ! + reader.skip(22); + // in some zip created on windows, the filename stored in the central dir contains \ instead of /. + // Strangely, the filename here is OK. + // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes + // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators... + // Search "unzip mismatching "local" filename continuing with "central" filename version" on + // the internet. + // + // I think I see the logic here : the central directory is used to display + // content and the local directory is used to extract the files. Mixing / and \ + // may be used to display \ to windows users and use / when extracting the files. + // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394 + this.fileNameLength = reader.readInt(2); + localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir + this.fileName = reader.readString(this.fileNameLength); + reader.skip(localExtraFieldsLength); + + if (this.compressedSize == -1 || this.uncompressedSize == -1) { + throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory " + "(compressedSize == -1 || uncompressedSize == -1)"); + } + + compression = utils.findCompression(this.compressionMethod); + if (compression === null) { // no compression found + throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + this.fileName + ")"); + } + this.decompressed = new CompressedObject(); + this.decompressed.compressedSize = this.compressedSize; + this.decompressed.uncompressedSize = this.uncompressedSize; + this.decompressed.crc32 = this.crc32; + this.decompressed.compressionMethod = this.compressionMethod; + this.decompressed.getCompressedContent = this.prepareCompressedContent(reader, reader.index, this.compressedSize, compression); + this.decompressed.getContent = this.prepareContent(reader, reader.index, this.compressedSize, compression, this.uncompressedSize); + + // we need to compute the crc32... + if (this.loadOptions.checkCRC32) { + this.decompressed = utils.transformTo("string", this.decompressed.getContent()); + if (jszipProto.crc32(this.decompressed) !== this.crc32) { + throw new Error("Corrupted zip : CRC32 mismatch"); + } + } + }, + + /** + * Read the central part of a zip file and add the info in this object. + * @param {DataReader} reader the reader to use. + */ + readCentralPart: function(reader) { + this.versionMadeBy = reader.readInt(2); + this.versionNeeded = reader.readInt(2); + this.bitFlag = reader.readInt(2); + this.compressionMethod = reader.readString(2); + this.date = reader.readDate(); + this.crc32 = reader.readInt(4); + this.compressedSize = reader.readInt(4); + this.uncompressedSize = reader.readInt(4); + this.fileNameLength = reader.readInt(2); + this.extraFieldsLength = reader.readInt(2); + this.fileCommentLength = reader.readInt(2); + this.diskNumberStart = reader.readInt(2); + this.internalFileAttributes = reader.readInt(2); + this.externalFileAttributes = reader.readInt(4); + this.localHeaderOffset = reader.readInt(4); + + if (this.isEncrypted()) { + throw new Error("Encrypted zip are not supported"); + } + + this.fileName = reader.readString(this.fileNameLength); + this.readExtraFields(reader); + this.parseZIP64ExtraField(reader); + this.fileComment = reader.readString(this.fileCommentLength); + }, + + /** + * Parse the external file attributes and get the unix/dos permissions. + */ + processAttributes: function () { + this.unixPermissions = null; + this.dosPermissions = null; + var madeBy = this.versionMadeBy >> 8; + + // Check if we have the DOS directory flag set. + // We look for it in the DOS and UNIX permissions + // but some unknown platform could set it as a compatibility flag. + this.dir = this.externalFileAttributes & 0x0010 ? true : false; + + if(madeBy === MADE_BY_DOS) { + // first 6 bits (0 to 5) + this.dosPermissions = this.externalFileAttributes & 0x3F; + } + + if(madeBy === MADE_BY_UNIX) { + this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF; + // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8); + } + + // fail safe : if the name ends with a / it probably means a folder + if (!this.dir && this.fileName.slice(-1) === '/') { + this.dir = true; + } + }, + + /** + * Parse the ZIP64 extra field and merge the info in the current ZipEntry. + * @param {DataReader} reader the reader to use. + */ + parseZIP64ExtraField: function(reader) { + + if (!this.extraFields[0x0001]) { + return; + } + + // should be something, preparing the extra reader + var extraReader = new StringReader(this.extraFields[0x0001].value); + + // I really hope that these 64bits integer can fit in 32 bits integer, because js + // won't let us have more. + if (this.uncompressedSize === utils.MAX_VALUE_32BITS) { + this.uncompressedSize = extraReader.readInt(8); + } + if (this.compressedSize === utils.MAX_VALUE_32BITS) { + this.compressedSize = extraReader.readInt(8); + } + if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) { + this.localHeaderOffset = extraReader.readInt(8); + } + if (this.diskNumberStart === utils.MAX_VALUE_32BITS) { + this.diskNumberStart = extraReader.readInt(4); + } + }, + /** + * Read the central part of a zip file and add the info in this object. + * @param {DataReader} reader the reader to use. + */ + readExtraFields: function(reader) { + var start = reader.index, + extraFieldId, + extraFieldLength, + extraFieldValue; + + this.extraFields = this.extraFields || {}; + + while (reader.index < start + this.extraFieldsLength) { + extraFieldId = reader.readInt(2); + extraFieldLength = reader.readInt(2); + extraFieldValue = reader.readString(extraFieldLength); + + this.extraFields[extraFieldId] = { + id: extraFieldId, + length: extraFieldLength, + value: extraFieldValue + }; + } + }, + /** + * Apply an UTF8 transformation if needed. + */ + handleUTF8: function() { + if (this.useUTF8()) { + this.fileName = jszipProto.utf8decode(this.fileName); + this.fileComment = jszipProto.utf8decode(this.fileComment); + } else { + var upath = this.findExtraFieldUnicodePath(); + if (upath !== null) { + this.fileName = upath; + } + var ucomment = this.findExtraFieldUnicodeComment(); + if (ucomment !== null) { + this.fileComment = ucomment; + } + } + }, + + /** + * Find the unicode path declared in the extra field, if any. + * @return {String} the unicode path, null otherwise. + */ + findExtraFieldUnicodePath: function() { + var upathField = this.extraFields[0x7075]; + if (upathField) { + var extraReader = new StringReader(upathField.value); + + // wrong version + if (extraReader.readInt(1) !== 1) { + return null; + } + + // the crc of the filename changed, this field is out of date. + if (jszipProto.crc32(this.fileName) !== extraReader.readInt(4)) { + return null; + } + + return jszipProto.utf8decode(extraReader.readString(upathField.length - 5)); + } + return null; + }, + + /** + * Find the unicode comment declared in the extra field, if any. + * @return {String} the unicode comment, null otherwise. + */ + findExtraFieldUnicodeComment: function() { + var ucommentField = this.extraFields[0x6375]; + if (ucommentField) { + var extraReader = new StringReader(ucommentField.value); + + // wrong version + if (extraReader.readInt(1) !== 1) { + return null; + } + + // the crc of the comment changed, this field is out of date. + if (jszipProto.crc32(this.fileComment) !== extraReader.readInt(4)) { + return null; + } + + return jszipProto.utf8decode(extraReader.readString(ucommentField.length - 5)); + } + return null; + } +}; +module.exports = ZipEntry; + +},{"./compressedObject":2,"./object":13,"./stringReader":15,"./utils":21}],24:[function(_dereq_,module,exports){ +// Top level file is just a mixin of submodules & constants +'use strict'; + +var assign = _dereq_('./lib/utils/common').assign; + +var deflate = _dereq_('./lib/deflate'); +var inflate = _dereq_('./lib/inflate'); +var constants = _dereq_('./lib/zlib/constants'); + +var pako = {}; + +assign(pako, deflate, inflate, constants); + +module.exports = pako; +},{"./lib/deflate":25,"./lib/inflate":26,"./lib/utils/common":27,"./lib/zlib/constants":30}],25:[function(_dereq_,module,exports){ +'use strict'; + + +var zlib_deflate = _dereq_('./zlib/deflate.js'); +var utils = _dereq_('./utils/common'); +var strings = _dereq_('./utils/strings'); +var msg = _dereq_('./zlib/messages'); +var zstream = _dereq_('./zlib/zstream'); + + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +var Z_NO_FLUSH = 0; +var Z_FINISH = 4; + +var Z_OK = 0; +var Z_STREAM_END = 1; + +var Z_DEFAULT_COMPRESSION = -1; + +var Z_DEFAULT_STRATEGY = 0; + +var Z_DEFLATED = 8; + +/* ===========================================================================*/ + + +/** + * class Deflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[deflate]], + * [[deflateRaw]] and [[gzip]]. + **/ + +/* internal + * Deflate.chunks -> Array + * + * Chunks of output data, if [[Deflate#onData]] not overriden. + **/ + +/** + * Deflate.result -> Uint8Array|Array + * + * Compressed result, generated by default [[Deflate#onData]] + * and [[Deflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Deflate#push]] with `Z_FINISH` / `true` param). + **/ + +/** + * Deflate.err -> Number + * + * Error code after deflate finished. 0 (Z_OK) on success. + * You will not need it in real life, because deflate errors + * are possible only on wrong options or bad `onData` / `onEnd` + * custom handlers. + **/ + +/** + * Deflate.msg -> String + * + * Error message, if [[Deflate.err]] != 0 + **/ + + +/** + * new Deflate(options) + * - options (Object): zlib deflate options. + * + * Creates new deflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `level` + * - `windowBits` + * - `memLevel` + * - `strategy` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw deflate + * - `gzip` (Boolean) - create gzip wrapper + * - `to` (String) - if equal to 'string', then result will be "binary string" + * (each char code [0..255]) + * - `header` (Object) - custom header for gzip + * - `text` (Boolean) - true if compressed data believed to be text + * - `time` (Number) - modification time, unix timestamp + * - `os` (Number) - operation system code + * - `extra` (Array) - array of bytes with extra data (max 65536) + * - `name` (String) - file name (binary string) + * - `comment` (String) - comment (binary string) + * - `hcrc` (Boolean) - true if header crc should be added + * + * ##### Example: + * + * ```javascript + * var pako = require('pako') + * , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9]) + * , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * var deflate = new pako.Deflate({ level: 3}); + * + * deflate.push(chunk1, false); + * deflate.push(chunk2, true); // true -> last chunk + * + * if (deflate.err) { throw new Error(deflate.err); } + * + * console.log(deflate.result); + * ``` + **/ +var Deflate = function(options) { + + this.options = utils.assign({ + level: Z_DEFAULT_COMPRESSION, + method: Z_DEFLATED, + chunkSize: 16384, + windowBits: 15, + memLevel: 8, + strategy: Z_DEFAULT_STRATEGY, + to: '' + }, options || {}); + + var opt = this.options; + + if (opt.raw && (opt.windowBits > 0)) { + opt.windowBits = -opt.windowBits; + } + + else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) { + opt.windowBits += 16; + } + + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ''; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data + + this.strm = new zstream(); + this.strm.avail_out = 0; + + var status = zlib_deflate.deflateInit2( + this.strm, + opt.level, + opt.method, + opt.windowBits, + opt.memLevel, + opt.strategy + ); + + if (status !== Z_OK) { + throw new Error(msg[status]); + } + + if (opt.header) { + zlib_deflate.deflateSetHeader(this.strm, opt.header); + } +}; + +/** + * Deflate#push(data[, mode]) -> Boolean + * - data (Uint8Array|Array|String): input data. Strings will be converted to + * utf8 byte sequence. + * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. + * See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH. + * + * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with + * new compressed chunks. Returns `true` on success. The last data block must have + * mode Z_FINISH (or `true`). That flush internal pending buffers and call + * [[Deflate#onEnd]]. + * + * On fail call [[Deflate#onEnd]] with error code and return false. + * + * We strongly recommend to use `Uint8Array` on input for best speed (output + * array format is detected automatically). Also, don't skip last param and always + * use the same type in your code (boolean or number). That will improve JS speed. + * + * For regular `Array`-s make sure all elements are [0..255]. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + **/ +Deflate.prototype.push = function(data, mode) { + var strm = this.strm; + var chunkSize = this.options.chunkSize; + var status, _mode; + + if (this.ended) { return false; } + + _mode = (mode === ~~mode) ? mode : ((mode === true) ? Z_FINISH : Z_NO_FLUSH); + + // Convert data if needed + if (typeof data === 'string') { + // If we need to compress text, change encoding to utf8. + strm.input = strings.string2buf(data); + } else { + strm.input = data; + } + + strm.next_in = 0; + strm.avail_in = strm.input.length; + + do { + if (strm.avail_out === 0) { + strm.output = new utils.Buf8(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } + status = zlib_deflate.deflate(strm, _mode); /* no bad return value */ + + if (status !== Z_STREAM_END && status !== Z_OK) { + this.onEnd(status); + this.ended = true; + return false; + } + if (strm.avail_out === 0 || (strm.avail_in === 0 && _mode === Z_FINISH)) { + if (this.options.to === 'string') { + this.onData(strings.buf2binstring(utils.shrinkBuf(strm.output, strm.next_out))); + } else { + this.onData(utils.shrinkBuf(strm.output, strm.next_out)); + } + } + } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== Z_STREAM_END); + + // Finalize on the last chunk. + if (_mode === Z_FINISH) { + status = zlib_deflate.deflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return status === Z_OK; + } + + return true; +}; + + +/** + * Deflate#onData(chunk) -> Void + * - chunk (Uint8Array|Array|String): ouput data. Type of array depends + * on js engine support. When string output requested, each chunk + * will be string. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + **/ +Deflate.prototype.onData = function(chunk) { + this.chunks.push(chunk); +}; + + +/** + * Deflate#onEnd(status) -> Void + * - status (Number): deflate status. 0 (Z_OK) on success, + * other if not. + * + * Called once after you tell deflate that input stream complete + * or error happenned. By default - join collected chunks, + * free memory and fill `results` / `err` properties. + **/ +Deflate.prototype.onEnd = function(status) { + // On success - join + if (status === Z_OK) { + if (this.options.to === 'string') { + this.result = this.chunks.join(''); + } else { + this.result = utils.flattenChunks(this.chunks); + } + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; +}; + + +/** + * deflate(data[, options]) -> Uint8Array|Array|String + * - data (Uint8Array|Array|String): input data to compress. + * - options (Object): zlib deflate options. + * + * Compress `data` with deflate alrorythm and `options`. + * + * Supported options are: + * + * - level + * - windowBits + * - memLevel + * - strategy + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Sugar (options): + * + * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify + * negative windowBits implicitly. + * - `to` (String) - if equal to 'string', then result will be "binary string" + * (each char code [0..255]) + * + * ##### Example: + * + * ```javascript + * var pako = require('pako') + * , data = Uint8Array([1,2,3,4,5,6,7,8,9]); + * + * console.log(pako.deflate(data)); + * ``` + **/ +function deflate(input, options) { + var deflator = new Deflate(options); + + deflator.push(input, true); + + // That will never happens, if you don't cheat with options :) + if (deflator.err) { throw deflator.msg; } + + return deflator.result; +} + + +/** + * deflateRaw(data[, options]) -> Uint8Array|Array|String + * - data (Uint8Array|Array|String): input data to compress. + * - options (Object): zlib deflate options. + * + * The same as [[deflate]], but creates raw data, without wrapper + * (header and adler32 crc). + **/ +function deflateRaw(input, options) { + options = options || {}; + options.raw = true; + return deflate(input, options); +} + + +/** + * gzip(data[, options]) -> Uint8Array|Array|String + * - data (Uint8Array|Array|String): input data to compress. + * - options (Object): zlib deflate options. + * + * The same as [[deflate]], but create gzip wrapper instead of + * deflate one. + **/ +function gzip(input, options) { + options = options || {}; + options.gzip = true; + return deflate(input, options); +} + + +exports.Deflate = Deflate; +exports.deflate = deflate; +exports.deflateRaw = deflateRaw; +exports.gzip = gzip; +},{"./utils/common":27,"./utils/strings":28,"./zlib/deflate.js":32,"./zlib/messages":37,"./zlib/zstream":39}],26:[function(_dereq_,module,exports){ +'use strict'; + + +var zlib_inflate = _dereq_('./zlib/inflate.js'); +var utils = _dereq_('./utils/common'); +var strings = _dereq_('./utils/strings'); +var c = _dereq_('./zlib/constants'); +var msg = _dereq_('./zlib/messages'); +var zstream = _dereq_('./zlib/zstream'); +var gzheader = _dereq_('./zlib/gzheader'); + + +/** + * class Inflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[inflate]] + * and [[inflateRaw]]. + **/ + +/* internal + * inflate.chunks -> Array + * + * Chunks of output data, if [[Inflate#onData]] not overriden. + **/ + +/** + * Inflate.result -> Uint8Array|Array|String + * + * Uncompressed result, generated by default [[Inflate#onData]] + * and [[Inflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Inflate#push]] with `Z_FINISH` / `true` param). + **/ + +/** + * Inflate.err -> Number + * + * Error code after inflate finished. 0 (Z_OK) on success. + * Should be checked if broken data possible. + **/ + +/** + * Inflate.msg -> String + * + * Error message, if [[Inflate.err]] != 0 + **/ + + +/** + * new Inflate(options) + * - options (Object): zlib inflate options. + * + * Creates new inflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `windowBits` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw inflate + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * By default, when no options set, autodetect deflate/gzip data format via + * wrapper header. + * + * ##### Example: + * + * ```javascript + * var pako = require('pako') + * , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9]) + * , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * var inflate = new pako.Inflate({ level: 3}); + * + * inflate.push(chunk1, false); + * inflate.push(chunk2, true); // true -> last chunk + * + * if (inflate.err) { throw new Error(inflate.err); } + * + * console.log(inflate.result); + * ``` + **/ +var Inflate = function(options) { + + this.options = utils.assign({ + chunkSize: 16384, + windowBits: 0, + to: '' + }, options || {}); + + var opt = this.options; + + // Force window size for `raw` data, if not set directly, + // because we have no header for autodetect. + if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { + opt.windowBits = -opt.windowBits; + if (opt.windowBits === 0) { opt.windowBits = -15; } + } + + // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate + if ((opt.windowBits >= 0) && (opt.windowBits < 16) && + !(options && options.windowBits)) { + opt.windowBits += 32; + } + + // Gzip header has no info about windows size, we can do autodetect only + // for deflate. So, if window size not set, force it to max when gzip possible + if ((opt.windowBits > 15) && (opt.windowBits < 48)) { + // bit 3 (16) -> gzipped data + // bit 4 (32) -> autodetect gzip/deflate + if ((opt.windowBits & 15) === 0) { + opt.windowBits |= 15; + } + } + + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ''; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data + + this.strm = new zstream(); + this.strm.avail_out = 0; + + var status = zlib_inflate.inflateInit2( + this.strm, + opt.windowBits + ); + + if (status !== c.Z_OK) { + throw new Error(msg[status]); + } + + this.header = new gzheader(); + + zlib_inflate.inflateGetHeader(this.strm, this.header); +}; + +/** + * Inflate#push(data[, mode]) -> Boolean + * - data (Uint8Array|Array|String): input data + * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. + * See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH. + * + * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with + * new output chunks. Returns `true` on success. The last data block must have + * mode Z_FINISH (or `true`). That flush internal pending buffers and call + * [[Inflate#onEnd]]. + * + * On fail call [[Inflate#onEnd]] with error code and return false. + * + * We strongly recommend to use `Uint8Array` on input for best speed (output + * format is detected automatically). Also, don't skip last param and always + * use the same type in your code (boolean or number). That will improve JS speed. + * + * For regular `Array`-s make sure all elements are [0..255]. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + **/ +Inflate.prototype.push = function(data, mode) { + var strm = this.strm; + var chunkSize = this.options.chunkSize; + var status, _mode; + var next_out_utf8, tail, utf8str; + + if (this.ended) { return false; } + _mode = (mode === ~~mode) ? mode : ((mode === true) ? c.Z_FINISH : c.Z_NO_FLUSH); + + // Convert data if needed + if (typeof data === 'string') { + // Only binary strings can be decompressed on practice + strm.input = strings.binstring2buf(data); + } else { + strm.input = data; + } + + strm.next_in = 0; + strm.avail_in = strm.input.length; + + do { + if (strm.avail_out === 0) { + strm.output = new utils.Buf8(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } + + status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH); /* no bad return value */ + + if (status !== c.Z_STREAM_END && status !== c.Z_OK) { + this.onEnd(status); + this.ended = true; + return false; + } + + if (strm.next_out) { + if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && _mode === c.Z_FINISH)) { + + if (this.options.to === 'string') { + + next_out_utf8 = strings.utf8border(strm.output, strm.next_out); + + tail = strm.next_out - next_out_utf8; + utf8str = strings.buf2string(strm.output, next_out_utf8); + + // move tail + strm.next_out = tail; + strm.avail_out = chunkSize - tail; + if (tail) { utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0); } + + this.onData(utf8str); + + } else { + this.onData(utils.shrinkBuf(strm.output, strm.next_out)); + } + } + } + } while ((strm.avail_in > 0) && status !== c.Z_STREAM_END); + + if (status === c.Z_STREAM_END) { + _mode = c.Z_FINISH; + } + // Finalize on the last chunk. + if (_mode === c.Z_FINISH) { + status = zlib_inflate.inflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return status === c.Z_OK; + } + + return true; +}; + + +/** + * Inflate#onData(chunk) -> Void + * - chunk (Uint8Array|Array|String): ouput data. Type of array depends + * on js engine support. When string output requested, each chunk + * will be string. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + **/ +Inflate.prototype.onData = function(chunk) { + this.chunks.push(chunk); +}; + + +/** + * Inflate#onEnd(status) -> Void + * - status (Number): inflate status. 0 (Z_OK) on success, + * other if not. + * + * Called once after you tell inflate that input stream complete + * or error happenned. By default - join collected chunks, + * free memory and fill `results` / `err` properties. + **/ +Inflate.prototype.onEnd = function(status) { + // On success - join + if (status === c.Z_OK) { + if (this.options.to === 'string') { + // Glue & convert here, until we teach pako to send + // utf8 alligned strings to onData + this.result = this.chunks.join(''); + } else { + this.result = utils.flattenChunks(this.chunks); + } + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; +}; + + +/** + * inflate(data[, options]) -> Uint8Array|Array|String + * - data (Uint8Array|Array|String): input data to decompress. + * - options (Object): zlib inflate options. + * + * Decompress `data` with inflate/ungzip and `options`. Autodetect + * format via wrapper header by default. That's why we don't provide + * separate `ungzip` method. + * + * Supported options are: + * + * - windowBits + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information. + * + * Sugar (options): + * + * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify + * negative windowBits implicitly. + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * + * ##### Example: + * + * ```javascript + * var pako = require('pako') + * , input = pako.deflate([1,2,3,4,5,6,7,8,9]) + * , output; + * + * try { + * output = pako.inflate(input); + * } catch (err) + * console.log(err); + * } + * ``` + **/ +function inflate(input, options) { + var inflator = new Inflate(options); + + inflator.push(input, true); + + // That will never happens, if you don't cheat with options :) + if (inflator.err) { throw inflator.msg; } + + return inflator.result; +} + + +/** + * inflateRaw(data[, options]) -> Uint8Array|Array|String + * - data (Uint8Array|Array|String): input data to decompress. + * - options (Object): zlib inflate options. + * + * The same as [[inflate]], but creates raw data, without wrapper + * (header and adler32 crc). + **/ +function inflateRaw(input, options) { + options = options || {}; + options.raw = true; + return inflate(input, options); +} + + +/** + * ungzip(data[, options]) -> Uint8Array|Array|String + * - data (Uint8Array|Array|String): input data to decompress. + * - options (Object): zlib inflate options. + * + * Just shortcut to [[inflate]], because it autodetects format + * by header.content. Done for convenience. + **/ + + +exports.Inflate = Inflate; +exports.inflate = inflate; +exports.inflateRaw = inflateRaw; +exports.ungzip = inflate; + +},{"./utils/common":27,"./utils/strings":28,"./zlib/constants":30,"./zlib/gzheader":33,"./zlib/inflate.js":35,"./zlib/messages":37,"./zlib/zstream":39}],27:[function(_dereq_,module,exports){ +'use strict'; + + +var TYPED_OK = (typeof Uint8Array !== 'undefined') && + (typeof Uint16Array !== 'undefined') && + (typeof Int32Array !== 'undefined'); + + +exports.assign = function (obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + while (sources.length) { + var source = sources.shift(); + if (!source) { continue; } + + if (typeof(source) !== 'object') { + throw new TypeError(source + 'must be non-object'); + } + + for (var p in source) { + if (source.hasOwnProperty(p)) { + obj[p] = source[p]; + } + } + } + + return obj; +}; + + +// reduce buffer size, avoiding mem copy +exports.shrinkBuf = function (buf, size) { + if (buf.length === size) { return buf; } + if (buf.subarray) { return buf.subarray(0, size); } + buf.length = size; + return buf; +}; + + +var fnTyped = { + arraySet: function (dest, src, src_offs, len, dest_offs) { + if (src.subarray && dest.subarray) { + dest.set(src.subarray(src_offs, src_offs+len), dest_offs); + return; + } + // Fallback to ordinary array + for(var i=0; i= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1); +} +_utf8len[254]=_utf8len[254]=1; // Invalid sequence start + + +// convert string to array (typed, when possible) +exports.string2buf = function (str) { + var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; + + // count binary size + for (m_pos = 0; m_pos < str_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { + c2 = str.charCodeAt(m_pos+1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } + + // allocate buffer + buf = new utils.Buf8(buf_len); + + // convert + for (i=0, m_pos = 0; i < buf_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { + c2 = str.charCodeAt(m_pos+1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + if (c < 0x80) { + /* one byte */ + buf[i++] = c; + } else if (c < 0x800) { + /* two bytes */ + buf[i++] = 0xC0 | (c >>> 6); + buf[i++] = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + /* three bytes */ + buf[i++] = 0xE0 | (c >>> 12); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } else { + /* four bytes */ + buf[i++] = 0xf0 | (c >>> 18); + buf[i++] = 0x80 | (c >>> 12 & 0x3f); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } + } + + return buf; +}; + +// Helper (used in 2 places) +function buf2binstring(buf, len) { + // use fallback for big arrays to avoid stack overflow + if (len < 65537) { + if ((buf.subarray && STR_APPLY_UIA_OK) || (!buf.subarray && STR_APPLY_OK)) { + return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len)); + } + } + + var result = ''; + for(var i=0; i < len; i++) { + result += String.fromCharCode(buf[i]); + } + return result; +} + + +// Convert byte array to binary string +exports.buf2binstring = function(buf) { + return buf2binstring(buf, buf.length); +}; + + +// Convert binary string (typed, when possible) +exports.binstring2buf = function(str) { + var buf = new utils.Buf8(str.length); + for(var i=0, len=buf.length; i < len; i++) { + buf[i] = str.charCodeAt(i); + } + return buf; +}; + + +// convert array to string +exports.buf2string = function (buf, max) { + var i, out, c, c_len; + var len = max || buf.length; + + // Reserve max possible length (2 words per char) + // NB: by unknown reasons, Array is significantly faster for + // String.fromCharCode.apply than Uint16Array. + var utf16buf = new Array(len*2); + + for (out=0, i=0; i 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; } + + // apply mask on first byte + c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; + // join the rest + while (c_len > 1 && i < len) { + c = (c << 6) | (buf[i++] & 0x3f); + c_len--; + } + + // terminated by end of string? + if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } + + if (c < 0x10000) { + utf16buf[out++] = c; + } else { + c -= 0x10000; + utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); + utf16buf[out++] = 0xdc00 | (c & 0x3ff); + } + } + + return buf2binstring(utf16buf, out); +}; + + +// Calculate max possible position in utf8 buffer, +// that will not break sequence. If that's not possible +// - (very small limits) return max size as is. +// +// buf[] - utf8 bytes array +// max - length limit (mandatory); +exports.utf8border = function(buf, max) { + var pos; + + max = max || buf.length; + if (max > buf.length) { max = buf.length; } + + // go back from last position, until start of sequence found + pos = max-1; + while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } + + // Fuckup - very small and broken sequence, + // return max, because we should return something anyway. + if (pos < 0) { return max; } + + // If we came to start of buffer - that means vuffer is too small, + // return max too. + if (pos === 0) { return max; } + + return (pos + _utf8len[buf[pos]] > max) ? pos : max; +}; + +},{"./common":27}],29:[function(_dereq_,module,exports){ +'use strict'; + +// Note: adler32 takes 12% for level 0 and 2% for level 6. +// It doesn't worth to make additional optimizationa as in original. +// Small size is preferable. + +function adler32(adler, buf, len, pos) { + var s1 = (adler & 0xffff) |0 + , s2 = ((adler >>> 16) & 0xffff) |0 + , n = 0; + + while (len !== 0) { + // Set limit ~ twice less than 5552, to keep + // s2 in 31-bits, because we force signed ints. + // in other case %= will fail. + n = len > 2000 ? 2000 : len; + len -= n; + + do { + s1 = (s1 + buf[pos++]) |0; + s2 = (s2 + s1) |0; + } while (--n); + + s1 %= 65521; + s2 %= 65521; + } + + return (s1 | (s2 << 16)) |0; +} + + +module.exports = adler32; +},{}],30:[function(_dereq_,module,exports){ +module.exports = { + + /* Allowed flush values; see deflate() and inflate() below for details */ + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + + /* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + //Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + //Z_VERSION_ERROR: -6, + + /* compression levels */ + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + + + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + + /* Possible values of the data_type field (though see inflate()) */ + Z_BINARY: 0, + Z_TEXT: 1, + //Z_ASCII: 1, // = Z_TEXT (deprecated) + Z_UNKNOWN: 2, + + /* The deflate compression method */ + Z_DEFLATED: 8 + //Z_NULL: null // Use -1 or null inline, depending on var type +}; +},{}],31:[function(_dereq_,module,exports){ +'use strict'; + +// Note: we can't get significant speed boost here. +// So write code to minimize size - no pregenerated tables +// and array tools dependencies. + + +// Use ordinary array, since untyped makes no boost here +function makeTable() { + var c, table = []; + + for(var n =0; n < 256; n++){ + c = n; + for(var k =0; k < 8; k++){ + c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } + + return table; +} + +// Create table on load. Just 255 signed longs. Not a problem. +var crcTable = makeTable(); + + +function crc32(crc, buf, len, pos) { + var t = crcTable + , end = pos + len; + + crc = crc ^ (-1); + + for (var i = pos; i < end; i++ ) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +} + + +module.exports = crc32; +},{}],32:[function(_dereq_,module,exports){ +'use strict'; + +var utils = _dereq_('../utils/common'); +var trees = _dereq_('./trees'); +var adler32 = _dereq_('./adler32'); +var crc32 = _dereq_('./crc32'); +var msg = _dereq_('./messages'); + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +/* Allowed flush values; see deflate() and inflate() below for details */ +var Z_NO_FLUSH = 0; +var Z_PARTIAL_FLUSH = 1; +//var Z_SYNC_FLUSH = 2; +var Z_FULL_FLUSH = 3; +var Z_FINISH = 4; +var Z_BLOCK = 5; +//var Z_TREES = 6; + + +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ +var Z_OK = 0; +var Z_STREAM_END = 1; +//var Z_NEED_DICT = 2; +//var Z_ERRNO = -1; +var Z_STREAM_ERROR = -2; +var Z_DATA_ERROR = -3; +//var Z_MEM_ERROR = -4; +var Z_BUF_ERROR = -5; +//var Z_VERSION_ERROR = -6; + + +/* compression levels */ +//var Z_NO_COMPRESSION = 0; +//var Z_BEST_SPEED = 1; +//var Z_BEST_COMPRESSION = 9; +var Z_DEFAULT_COMPRESSION = -1; + + +var Z_FILTERED = 1; +var Z_HUFFMAN_ONLY = 2; +var Z_RLE = 3; +var Z_FIXED = 4; +var Z_DEFAULT_STRATEGY = 0; + +/* Possible values of the data_type field (though see inflate()) */ +//var Z_BINARY = 0; +//var Z_TEXT = 1; +//var Z_ASCII = 1; // = Z_TEXT +var Z_UNKNOWN = 2; + + +/* The deflate compression method */ +var Z_DEFLATED = 8; + +/*============================================================================*/ + + +var MAX_MEM_LEVEL = 9; +/* Maximum value for memLevel in deflateInit2 */ +var MAX_WBITS = 15; +/* 32K LZ77 window */ +var DEF_MEM_LEVEL = 8; + + +var LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ +var LITERALS = 256; +/* number of literal bytes 0..255 */ +var L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ +var D_CODES = 30; +/* number of distance codes */ +var BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ +var HEAP_SIZE = 2*L_CODES + 1; +/* maximum heap size */ +var MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +var MIN_MATCH = 3; +var MAX_MATCH = 258; +var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); + +var PRESET_DICT = 0x20; + +var INIT_STATE = 42; +var EXTRA_STATE = 69; +var NAME_STATE = 73; +var COMMENT_STATE = 91; +var HCRC_STATE = 103; +var BUSY_STATE = 113; +var FINISH_STATE = 666; + +var BS_NEED_MORE = 1; /* block not completed, need more input or more output */ +var BS_BLOCK_DONE = 2; /* block flush performed */ +var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ +var BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ + +var OS_CODE = 0x03; // Unix :) . Don't detect, use this default. + +function err(strm, errorCode) { + strm.msg = msg[errorCode]; + return errorCode; +} + +function rank(f) { + return ((f) << 1) - ((f) > 4 ? 9 : 0); +} + +function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } + + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->output buffer and copying into it. + * (See also read_buf()). + */ +function flush_pending(strm) { + var s = strm.state; + + //_tr_flush_bits(s); + var len = s.pending; + if (len > strm.avail_out) { + len = strm.avail_out; + } + if (len === 0) { return; } + + utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out); + strm.next_out += len; + s.pending_out += len; + strm.total_out += len; + strm.avail_out -= len; + s.pending -= len; + if (s.pending === 0) { + s.pending_out = 0; + } +} + + +function flush_block_only (s, last) { + trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); + s.block_start = s.strstart; + flush_pending(s.strm); +} + + +function put_byte(s, b) { + s.pending_buf[s.pending++] = b; +} + + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +function putShortMSB(s, b) { +// put_byte(s, (Byte)(b >> 8)); +// put_byte(s, (Byte)(b & 0xff)); + s.pending_buf[s.pending++] = (b >>> 8) & 0xff; + s.pending_buf[s.pending++] = b & 0xff; +} + + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->input buffer and copying from it. + * (See also flush_pending()). + */ +function read_buf(strm, buf, start, size) { + var len = strm.avail_in; + + if (len > size) { len = size; } + if (len === 0) { return 0; } + + strm.avail_in -= len; + + utils.arraySet(buf, strm.input, strm.next_in, len, start); + if (strm.state.wrap === 1) { + strm.adler = adler32(strm.adler, buf, len, start); + } + + else if (strm.state.wrap === 2) { + strm.adler = crc32(strm.adler, buf, len, start); + } + + strm.next_in += len; + strm.total_in += len; + + return len; +} + + +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +function longest_match(s, cur_match) { + var chain_length = s.max_chain_length; /* max hash chain length */ + var scan = s.strstart; /* current string */ + var match; /* matched string */ + var len; /* length of current match */ + var best_len = s.prev_length; /* best match length so far */ + var nice_match = s.nice_match; /* stop if match long enough */ + var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? + s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; + + var _win = s.window; // shortcut + + var wmask = s.w_mask; + var prev = s.prev; + + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + + var strend = s.strstart + MAX_MATCH; + var scan_end1 = _win[scan + best_len - 1]; + var scan_end = _win[scan + best_len]; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s.prev_length >= s.good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if (nice_match > s.lookahead) { nice_match = s.lookahead; } + + // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + // Assert(cur_match < s->strstart, "no future"); + match = cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ + + if (_win[match + best_len] !== scan_end || + _win[match + best_len - 1] !== scan_end1 || + _win[match] !== _win[scan] || + _win[++match] !== _win[scan + 1]) { + continue; + } + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2; + match++; + // Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + /*jshint noempty:false*/ + } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + scan < strend); + + // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) { + s.match_start = cur_match; + best_len = len; + if (len >= nice_match) { + break; + } + scan_end1 = _win[scan + best_len - 1]; + scan_end = _win[scan + best_len]; + } + } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); + + if (best_len <= s.lookahead) { + return best_len; + } + return s.lookahead; +} + + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +function fill_window(s) { + var _w_size = s.w_size; + var p, n, m, more, str; + + //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = s.window_size - s.lookahead - s.strstart; + + // JS ints have 32 bit, block below not needed + /* Deal with !@#$% 64K limit: */ + //if (sizeof(int) <= 2) { + // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + // more = wsize; + // + // } else if (more == (unsigned)(-1)) { + // /* Very unlikely, but possible on 16 bit machine if + // * strstart == 0 && lookahead == 1 (input done a byte at time) + // */ + // more--; + // } + //} + + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { + + utils.arraySet(s.window, s.window, _w_size, _w_size, 0); + s.match_start -= _w_size; + s.strstart -= _w_size; + /* we now have strstart >= MAX_DIST */ + s.block_start -= _w_size; + + /* Slide the hash table (could be avoided with 32 bit values + at the expense of memory usage). We slide even when level == 0 + to keep the hash table consistent if we switch back to level > 0 + later. (Using level 0 permanently is not an optimal usage of + zlib, so we don't care about this pathological case.) + */ + + n = s.hash_size; + p = n; + do { + m = s.head[--p]; + s.head[p] = (m >= _w_size ? m - _w_size : 0); + } while (--n); + + n = _w_size; + p = n; + do { + m = s.prev[--p]; + s.prev[p] = (m >= _w_size ? m - _w_size : 0); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); + + more += _w_size; + } + if (s.strm.avail_in === 0) { + break; + } + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + //Assert(more >= 2, "more < 2"); + n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); + s.lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s.lookahead + s.insert >= MIN_MATCH) { + str = s.strstart - s.insert; + s.ins_h = s.window[str]; + + /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask; +//#if MIN_MATCH != 3 +// Call update_hash() MIN_MATCH-3 more times +//#endif + while (s.insert) { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH-1]) & s.hash_mask; + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = str; + str++; + s.insert--; + if (s.lookahead + s.insert < MIN_MATCH) { + break; + } + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ +// if (s.high_water < s.window_size) { +// var curr = s.strstart + s.lookahead; +// var init = 0; +// +// if (s.high_water < curr) { +// /* Previous high water mark below current data -- zero WIN_INIT +// * bytes or up to end of window, whichever is less. +// */ +// init = s.window_size - curr; +// if (init > WIN_INIT) +// init = WIN_INIT; +// zmemzero(s->window + curr, (unsigned)init); +// s->high_water = curr + init; +// } +// else if (s->high_water < (ulg)curr + WIN_INIT) { +// /* High water mark at or above current data, but below current data +// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up +// * to end of window, whichever is less. +// */ +// init = (ulg)curr + WIN_INIT - s->high_water; +// if (init > s->window_size - s->high_water) +// init = s->window_size - s->high_water; +// zmemzero(s->window + s->high_water, (unsigned)init); +// s->high_water += init; +// } +// } +// +// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, +// "not enough room for search"); +} + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * This function does not insert new strings in the dictionary since + * uncompressible data is probably not useful. This function is used + * only for the level=0 compression option. + * NOTE: this function should be optimized to avoid extra copying from + * window to pending_buf. + */ +function deflate_stored(s, flush) { + /* Stored blocks are limited to 0xffff bytes, pending_buf is limited + * to pending_buf_size, and each stored block has a 5 byte header: + */ + var max_block_size = 0xffff; + + if (max_block_size > s.pending_buf_size - 5) { + max_block_size = s.pending_buf_size - 5; + } + + /* Copy as much as possible from input to output: */ + for (;;) { + /* Fill the window as much as possible: */ + if (s.lookahead <= 1) { + + //Assert(s->strstart < s->w_size+MAX_DIST(s) || + // s->block_start >= (long)s->w_size, "slide too late"); +// if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) || +// s.block_start >= s.w_size)) { +// throw new Error("slide too late"); +// } + + fill_window(s); + if (s.lookahead === 0 && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + + if (s.lookahead === 0) { + break; + } + /* flush the current block */ + } + //Assert(s->block_start >= 0L, "block gone"); +// if (s.block_start < 0) throw new Error("block gone"); + + s.strstart += s.lookahead; + s.lookahead = 0; + + /* Emit a stored block if pending_buf will be full: */ + var max_start = s.block_start + max_block_size; + + if (s.strstart === 0 || s.strstart >= max_start) { + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s.lookahead = s.strstart - max_start; + s.strstart = max_start; + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + + + } + /* Flush if we may have to slide, otherwise block_start may become + * negative and the data will be gone: + */ + if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + + s.insert = 0; + + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + + if (s.strstart > s.block_start) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_NEED_MORE; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +function deflate_fast(s, flush) { + var hash_head; /* head of the hash chain */ + var bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { + break; /* flush the current block */ + } + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + } + if (s.match_length >= MIN_MATCH) { + // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only + + /*** _tr_tally_dist(s, s.strstart - s.match_start, + s.match_length - MIN_MATCH, bflush); ***/ + bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ + if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { + s.match_length--; /* string at strstart already in table */ + do { + s.strstart++; + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s.match_length !== 0); + s.strstart++; + } else + { + s.strstart += s.match_length; + s.match_length = 0; + s.ins_h = s.window[s.strstart]; + /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask; + +//#if MIN_MATCH != 3 +// Call UPDATE_HASH() MIN_MATCH-3 more times +//#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s.window[s.strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = ((s.strstart < (MIN_MATCH-1)) ? s.strstart : MIN_MATCH-1); + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +} + +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +function deflate_slow(s, flush) { + var hash_head; /* head of hash chain */ + var bflush; /* set if current block must be flushed */ + + var max_insert; + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + */ + s.prev_length = s.match_length; + s.prev_match = s.match_start; + s.match_length = MIN_MATCH-1; + + if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && + s.strstart - hash_head <= (s.w_size-MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + + if (s.match_length <= 5 && + (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s.match_length = MIN_MATCH-1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { + max_insert = s.strstart + s.lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + //check_match(s, s.strstart-1, s.prev_match, s.prev_length); + + /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, + s.prev_length - MIN_MATCH, bflush);***/ + bflush = trees._tr_tally(s, s.strstart - 1- s.prev_match, s.prev_length - MIN_MATCH); + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s.lookahead -= s.prev_length-1; + s.prev_length -= 2; + do { + if (++s.strstart <= max_insert) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + } while (--s.prev_length !== 0); + s.match_available = 0; + s.match_length = MIN_MATCH-1; + s.strstart++; + + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + } else if (s.match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart-1]); + + if (bflush) { + /*** FLUSH_BLOCK_ONLY(s, 0) ***/ + flush_block_only(s, false); + /***/ + } + s.strstart++; + s.lookahead--; + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s.match_available = 1; + s.strstart++; + s.lookahead--; + } + } + //Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s.match_available) { + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart-1]); + + s.match_available = 0; + } + s.insert = s.strstart < MIN_MATCH-1 ? s.strstart : MIN_MATCH-1; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_BLOCK_DONE; +} + + +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +function deflate_rle(s, flush) { + var bflush; /* set if current block must be flushed */ + var prev; /* byte at distance one to match */ + var scan, strend; /* scan goes up to strend for length of run */ + + var _win = s.window; + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest run, plus one for the unrolled loop. + */ + if (s.lookahead <= MAX_MATCH) { + fill_window(s); + if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + s.match_length = 0; + if (s.lookahead >= MIN_MATCH && s.strstart > 0) { + scan = s.strstart - 1; + prev = _win[scan]; + if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { + strend = s.strstart + MAX_MATCH; + do { + /*jshint noempty:false*/ + } while (prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + scan < strend); + s.match_length = MAX_MATCH - (strend - scan); + if (s.match_length > s.lookahead) { + s.match_length = s.lookahead; + } + } + //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (s.match_length >= MIN_MATCH) { + //check_match(s, s.strstart, s.strstart - 1, s.match_length); + + /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ + bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + s.strstart += s.match_length; + s.match_length = 0; + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +} + +/* =========================================================================== + * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + * (It will be regenerated if this run of deflate switches away from Huffman.) + */ +function deflate_huff(s, flush) { + var bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we have a literal to write. */ + if (s.lookahead === 0) { + fill_window(s); + if (s.lookahead === 0) { + if (flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + break; /* flush the current block */ + } + } + + /* Output a literal byte */ + s.match_length = 0; + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart]); + s.lookahead--; + s.strstart++; + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +} + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +var Config = function (good_length, max_lazy, nice_length, max_chain, func) { + this.good_length = good_length; + this.max_lazy = max_lazy; + this.nice_length = nice_length; + this.max_chain = max_chain; + this.func = func; +}; + +var configuration_table; + +configuration_table = [ + /* good lazy nice chain */ + new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ + new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ + new Config(4, 5, 16, 8, deflate_fast), /* 2 */ + new Config(4, 6, 32, 32, deflate_fast), /* 3 */ + + new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ + new Config(8, 16, 32, 32, deflate_slow), /* 5 */ + new Config(8, 16, 128, 128, deflate_slow), /* 6 */ + new Config(8, 32, 128, 256, deflate_slow), /* 7 */ + new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ + new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ +]; + + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +function lm_init(s) { + s.window_size = 2 * s.w_size; + + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + + /* Set the default configuration parameters: + */ + s.max_lazy_match = configuration_table[s.level].max_lazy; + s.good_match = configuration_table[s.level].good_length; + s.nice_match = configuration_table[s.level].nice_length; + s.max_chain_length = configuration_table[s.level].max_chain; + + s.strstart = 0; + s.block_start = 0; + s.lookahead = 0; + s.insert = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + s.ins_h = 0; +} + + +function DeflateState() { + this.strm = null; /* pointer back to this zlib stream */ + this.status = 0; /* as the name implies */ + this.pending_buf = null; /* output still pending */ + this.pending_buf_size = 0; /* size of pending_buf */ + this.pending_out = 0; /* next pending byte to output to the stream */ + this.pending = 0; /* nb of bytes in the pending buffer */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.gzhead = null; /* gzip header information to write */ + this.gzindex = 0; /* where in extra, name, or comment */ + this.method = Z_DEFLATED; /* can only be DEFLATED */ + this.last_flush = -1; /* value of flush param for previous deflate call */ + + this.w_size = 0; /* LZ77 window size (32K by default) */ + this.w_bits = 0; /* log2(w_size) (8..16) */ + this.w_mask = 0; /* w_size - 1 */ + + this.window = null; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. + */ + + this.window_size = 0; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + this.prev = null; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + this.head = null; /* Heads of the hash chains or NIL. */ + + this.ins_h = 0; /* hash index of string to be inserted */ + this.hash_size = 0; /* number of elements in hash table */ + this.hash_bits = 0; /* log2(hash_size) */ + this.hash_mask = 0; /* hash_size-1 */ + + this.hash_shift = 0; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + this.block_start = 0; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + this.match_length = 0; /* length of best match */ + this.prev_match = 0; /* previous match */ + this.match_available = 0; /* set if previous match exists */ + this.strstart = 0; /* start of string to insert */ + this.match_start = 0; /* start of matching string */ + this.lookahead = 0; /* number of valid bytes ahead in window */ + + this.prev_length = 0; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + this.max_chain_length = 0; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + this.max_lazy_match = 0; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ + // That's alias to max_lazy_match, don't use directly + //this.max_insert_length = 0; + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + this.level = 0; /* compression level (1..9) */ + this.strategy = 0; /* favor or force Huffman coding*/ + + this.good_match = 0; + /* Use a faster search when the previous match is longer than this */ + + this.nice_match = 0; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + + /* Didn't use ct_data typedef below to suppress compiler warning */ + + // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + // Use flat array of DOUBLE size, with interleaved fata, + // because JS does not support effective + this.dyn_ltree = new utils.Buf16(HEAP_SIZE * 2); + this.dyn_dtree = new utils.Buf16((2*D_CODES+1) * 2); + this.bl_tree = new utils.Buf16((2*BL_CODES+1) * 2); + zero(this.dyn_ltree); + zero(this.dyn_dtree); + zero(this.bl_tree); + + this.l_desc = null; /* desc. for literal tree */ + this.d_desc = null; /* desc. for distance tree */ + this.bl_desc = null; /* desc. for bit length tree */ + + //ush bl_count[MAX_BITS+1]; + this.bl_count = new utils.Buf16(MAX_BITS+1); + /* number of codes at each bit length for an optimal tree */ + + //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + this.heap = new utils.Buf16(2*L_CODES+1); /* heap used to build the Huffman trees */ + zero(this.heap); + + this.heap_len = 0; /* number of elements in the heap */ + this.heap_max = 0; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + this.depth = new utils.Buf16(2*L_CODES+1); //uch depth[2*L_CODES+1]; + zero(this.depth); + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + this.l_buf = 0; /* buffer index for literals or lengths */ + + this.lit_bufsize = 0; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + this.last_lit = 0; /* running index in l_buf */ + + this.d_buf = 0; + /* Buffer index for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + this.opt_len = 0; /* bit length of current block with optimal trees */ + this.static_len = 0; /* bit length of current block with static trees */ + this.matches = 0; /* number of string matches in current block */ + this.insert = 0; /* bytes at end of window left to insert */ + + + this.bi_buf = 0; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + this.bi_valid = 0; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + + // Used for window memory init. We safely ignore it for JS. That makes + // sense only for pointers and memory check tools. + //this.high_water = 0; + /* High water mark offset in window for initialized bytes -- bytes above + * this are set to zero in order to avoid memory check warnings when + * longest match routines access bytes past the input. This is then + * updated to the new high water mark. + */ +} + + +function deflateResetKeep(strm) { + var s; + + if (!strm || !strm.state) { + return err(strm, Z_STREAM_ERROR); + } + + strm.total_in = strm.total_out = 0; + strm.data_type = Z_UNKNOWN; + + s = strm.state; + s.pending = 0; + s.pending_out = 0; + + if (s.wrap < 0) { + s.wrap = -s.wrap; + /* was made negative by deflate(..., Z_FINISH); */ + } + s.status = (s.wrap ? INIT_STATE : BUSY_STATE); + strm.adler = (s.wrap === 2) ? + 0 // crc32(0, Z_NULL, 0) + : + 1; // adler32(0, Z_NULL, 0) + s.last_flush = Z_NO_FLUSH; + trees._tr_init(s); + return Z_OK; +} + + +function deflateReset(strm) { + var ret = deflateResetKeep(strm); + if (ret === Z_OK) { + lm_init(strm.state); + } + return ret; +} + + +function deflateSetHeader(strm, head) { + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; } + strm.state.gzhead = head; + return Z_OK; +} + + +function deflateInit2(strm, level, method, windowBits, memLevel, strategy) { + if (!strm) { // === Z_NULL + return Z_STREAM_ERROR; + } + var wrap = 1; + + if (level === Z_DEFAULT_COMPRESSION) { + level = 6; + } + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } + + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } + + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED) { + return err(strm, Z_STREAM_ERROR); + } + + + if (windowBits === 8) { + windowBits = 9; + } + /* until 256-byte window bug fixed */ + + var s = new DeflateState(); + + strm.state = s; + s.strm = strm; + + s.wrap = wrap; + s.gzhead = null; + s.w_bits = windowBits; + s.w_size = 1 << s.w_bits; + s.w_mask = s.w_size - 1; + + s.hash_bits = memLevel + 7; + s.hash_size = 1 << s.hash_bits; + s.hash_mask = s.hash_size - 1; + s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); + + s.window = new utils.Buf8(s.w_size * 2); + s.head = new utils.Buf16(s.hash_size); + s.prev = new utils.Buf16(s.w_size); + + // Don't need mem init magic for JS. + //s.high_water = 0; /* nothing written to s->window yet */ + + s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + s.pending_buf_size = s.lit_bufsize * 4; + s.pending_buf = new utils.Buf8(s.pending_buf_size); + + s.d_buf = s.lit_bufsize >> 1; + s.l_buf = (1 + 2) * s.lit_bufsize; + + s.level = level; + s.strategy = strategy; + s.method = method; + + return deflateReset(strm); +} + +function deflateInit(strm, level) { + return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); +} + + +function deflate(strm, flush) { + var old_flush, s; + var beg, val; // for gzip header write only + + if (!strm || !strm.state || + flush > Z_BLOCK || flush < 0) { + return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR; + } + + s = strm.state; + + if (!strm.output || + (!strm.input && strm.avail_in !== 0) || + (s.status === FINISH_STATE && flush !== Z_FINISH)) { + return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR); + } + + s.strm = strm; /* just in case */ + old_flush = s.last_flush; + s.last_flush = flush; + + /* Write the header */ + if (s.status === INIT_STATE) { + + if (s.wrap === 2) { // GZIP header + strm.adler = 0; //crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (!s.gzhead) { // s->gzhead == Z_NULL + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s.status = BUSY_STATE; + } + else { + put_byte(s, (s.gzhead.text ? 1 : 0) + + (s.gzhead.hcrc ? 2 : 0) + + (!s.gzhead.extra ? 0 : 4) + + (!s.gzhead.name ? 0 : 8) + + (!s.gzhead.comment ? 0 : 16) + ); + put_byte(s, s.gzhead.time & 0xff); + put_byte(s, (s.gzhead.time >> 8) & 0xff); + put_byte(s, (s.gzhead.time >> 16) & 0xff); + put_byte(s, (s.gzhead.time >> 24) & 0xff); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, s.gzhead.os & 0xff); + if (s.gzhead.extra && s.gzhead.extra.length) { + put_byte(s, s.gzhead.extra.length & 0xff); + put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); + } + if (s.gzhead.hcrc) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0); + } + s.gzindex = 0; + s.status = EXTRA_STATE; + } + } + else // DEFLATE header + { + var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8; + var level_flags = -1; + + if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { + level_flags = 0; + } else if (s.level < 6) { + level_flags = 1; + } else if (s.level === 6) { + level_flags = 2; + } else { + level_flags = 3; + } + header |= (level_flags << 6); + if (s.strstart !== 0) { header |= PRESET_DICT; } + header += 31 - (header % 31); + + s.status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s.strstart !== 0) { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + strm.adler = 1; // adler32(0L, Z_NULL, 0); + } + } + +//#ifdef GZIP + if (s.status === EXTRA_STATE) { + if (s.gzhead.extra/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + + while (s.gzindex < (s.gzhead.extra.length & 0xffff)) { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + break; + } + } + put_byte(s, s.gzhead.extra[s.gzindex] & 0xff); + s.gzindex++; + } + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (s.gzindex === s.gzhead.extra.length) { + s.gzindex = 0; + s.status = NAME_STATE; + } + } + else { + s.status = NAME_STATE; + } + } + if (s.status === NAME_STATE) { + if (s.gzhead.name/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.name.length) { + val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg){ + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.gzindex = 0; + s.status = COMMENT_STATE; + } + } + else { + s.status = COMMENT_STATE; + } + } + if (s.status === COMMENT_STATE) { + if (s.gzhead.comment/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.comment.length) { + val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.status = HCRC_STATE; + } + } + else { + s.status = HCRC_STATE; + } + } + if (s.status === HCRC_STATE) { + if (s.gzhead.hcrc) { + if (s.pending + 2 > s.pending_buf_size) { + flush_pending(strm); + } + if (s.pending + 2 <= s.pending_buf_size) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + strm.adler = 0; //crc32(0L, Z_NULL, 0); + s.status = BUSY_STATE; + } + } + else { + s.status = BUSY_STATE; + } + } +//#endif + + /* Flush as much pending output as possible */ + if (s.pending !== 0) { + flush_pending(strm); + if (strm.avail_out === 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s.last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && + flush !== Z_FINISH) { + return err(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s.status === FINISH_STATE && strm.avail_in !== 0) { + return err(strm, Z_BUF_ERROR); + } + + /* Start a new block or continue the current one. + */ + if (strm.avail_in !== 0 || s.lookahead !== 0 || + (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) { + var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) : + (s.strategy === Z_RLE ? deflate_rle(s, flush) : + configuration_table[s.level].func(s, flush)); + + if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { + s.status = FINISH_STATE; + } + if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { + if (strm.avail_out === 0) { + s.last_flush = -1; + /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate === BS_BLOCK_DONE) { + if (flush === Z_PARTIAL_FLUSH) { + trees._tr_align(s); + } + else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */ + + trees._tr_stored_block(s, 0, 0, false); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush === Z_FULL_FLUSH) { + /*** CLEAR_HASH(s); ***/ /* forget history */ + zero(s.head); // Fill with NIL (= 0); + + if (s.lookahead === 0) { + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + } + } + flush_pending(strm); + if (strm.avail_out === 0) { + s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + //Assert(strm->avail_out > 0, "bug2"); + //if (strm.avail_out <= 0) { throw new Error("bug2");} + + if (flush !== Z_FINISH) { return Z_OK; } + if (s.wrap <= 0) { return Z_STREAM_END; } + + /* Write the trailer */ + if (s.wrap === 2) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + put_byte(s, (strm.adler >> 16) & 0xff); + put_byte(s, (strm.adler >> 24) & 0xff); + put_byte(s, strm.total_in & 0xff); + put_byte(s, (strm.total_in >> 8) & 0xff); + put_byte(s, (strm.total_in >> 16) & 0xff); + put_byte(s, (strm.total_in >> 24) & 0xff); + } + else + { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s.wrap > 0) { s.wrap = -s.wrap; } + /* write the trailer only once! */ + return s.pending !== 0 ? Z_OK : Z_STREAM_END; +} + +function deflateEnd(strm) { + var status; + + if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) { + return Z_STREAM_ERROR; + } + + status = strm.state.status; + if (status !== INIT_STATE && + status !== EXTRA_STATE && + status !== NAME_STATE && + status !== COMMENT_STATE && + status !== HCRC_STATE && + status !== BUSY_STATE && + status !== FINISH_STATE + ) { + return err(strm, Z_STREAM_ERROR); + } + + strm.state = null; + + return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK; +} + +/* ========================================================================= + * Copy the source state to the destination state + */ +//function deflateCopy(dest, source) { +// +//} + +exports.deflateInit = deflateInit; +exports.deflateInit2 = deflateInit2; +exports.deflateReset = deflateReset; +exports.deflateResetKeep = deflateResetKeep; +exports.deflateSetHeader = deflateSetHeader; +exports.deflate = deflate; +exports.deflateEnd = deflateEnd; +exports.deflateInfo = 'pako deflate (from Nodeca project)'; + +/* Not implemented +exports.deflateBound = deflateBound; +exports.deflateCopy = deflateCopy; +exports.deflateSetDictionary = deflateSetDictionary; +exports.deflateParams = deflateParams; +exports.deflatePending = deflatePending; +exports.deflatePrime = deflatePrime; +exports.deflateTune = deflateTune; +*/ +},{"../utils/common":27,"./adler32":29,"./crc32":31,"./messages":37,"./trees":38}],33:[function(_dereq_,module,exports){ +'use strict'; + + +function GZheader() { + /* true if compressed data believed to be text */ + this.text = 0; + /* modification time */ + this.time = 0; + /* extra flags (not used when writing a gzip file) */ + this.xflags = 0; + /* operating system */ + this.os = 0; + /* pointer to extra field or Z_NULL if none */ + this.extra = null; + /* extra field length (valid if extra != Z_NULL) */ + this.extra_len = 0; // Actually, we don't need it in JS, + // but leave for few code modifications + + // + // Setup limits is not necessary because in js we should not preallocate memory + // for inflate use constant limit in 65536 bytes + // + + /* space at extra (only when reading header) */ + // this.extra_max = 0; + /* pointer to zero-terminated file name or Z_NULL */ + this.name = ''; + /* space at name (only when reading header) */ + // this.name_max = 0; + /* pointer to zero-terminated comment or Z_NULL */ + this.comment = ''; + /* space at comment (only when reading header) */ + // this.comm_max = 0; + /* true if there was or will be a header crc */ + this.hcrc = 0; + /* true when done reading gzip header (not used when writing a gzip file) */ + this.done = false; +} + +module.exports = GZheader; +},{}],34:[function(_dereq_,module,exports){ +'use strict'; + +// See state defs from inflate.js +var BAD = 30; /* got a data error -- remain here until reset */ +var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state.mode === LEN + strm.avail_in >= 6 + strm.avail_out >= 258 + start >= strm.avail_out + state.bits < 8 + + On return, state.mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm.avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm.avail_out >= 258 for each loop to avoid checking for + output space. + */ +module.exports = function inflate_fast(strm, start) { + var state; + var _in; /* local strm.input */ + var last; /* have enough input while in < last */ + var _out; /* local strm.output */ + var beg; /* inflate()'s initial strm.output */ + var end; /* while out < end, enough space available */ +//#ifdef INFLATE_STRICT + var dmax; /* maximum distance from zlib header */ +//#endif + var wsize; /* window size or zero if not using window */ + var whave; /* valid bytes in the window */ + var wnext; /* window write index */ + var window; /* allocated sliding window, if wsize != 0 */ + var hold; /* local strm.hold */ + var bits; /* local strm.bits */ + var lcode; /* local strm.lencode */ + var dcode; /* local strm.distcode */ + var lmask; /* mask for first level of length codes */ + var dmask; /* mask for first level of distance codes */ + var here; /* retrieved table entry */ + var op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + var len; /* match length, unused bytes */ + var dist; /* match distance */ + var from; /* where to copy match from */ + var from_source; + + + var input, output; // JS specific, because we have no pointers + + /* copy state to local variables */ + state = strm.state; + //here = state.here; + _in = strm.next_in; + input = strm.input; + last = _in + (strm.avail_in - 5); + _out = strm.next_out; + output = strm.output; + beg = _out - (start - strm.avail_out); + end = _out + (strm.avail_out - 257); +//#ifdef INFLATE_STRICT + dmax = state.dmax; +//#endif + wsize = state.wsize; + whave = state.whave; + wnext = state.wnext; + window = state.window; + hold = state.hold; + bits = state.bits; + lcode = state.lencode; + dcode = state.distcode; + lmask = (1 << state.lenbits) - 1; + dmask = (1 << state.distbits) - 1; + + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + top: + do { + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + + here = lcode[hold & lmask]; + + dolen: + for (;;) { // Goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + if (op === 0) { /* literal */ + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + output[_out++] = here & 0xffff/*here.val*/; + } + else if (op & 16) { /* length base */ + len = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + len += hold & ((1 << op) - 1); + hold >>>= op; + bits -= op; + } + //Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + here = dcode[hold & dmask]; + + dodist: + for (;;) { // goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + + if (op & 16) { /* distance base */ + dist = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + } + dist += hold & ((1 << op) - 1); +//#ifdef INFLATE_STRICT + if (dist > dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } +//#endif + hold >>>= op; + bits -= op; + //Tracevv((stderr, "inflate: distance %u\n", dist)); + op = _out - beg; /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } + +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// if (len <= op - whave) { +// do { +// output[_out++] = 0; +// } while (--len); +// continue top; +// } +// len -= op - whave; +// do { +// output[_out++] = 0; +// } while (--op > whave); +// if (op === 0) { +// from = _out - dist; +// do { +// output[_out++] = output[from++]; +// } while (--len); +// continue top; +// } +//#endif + } + from = 0; // window index + from_source = window; + if (wnext === 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + output[_out++] = window[from++]; + } while (--op); + from = 0; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + while (len > 2) { + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + len -= 3; + } + if (len) { + output[_out++] = from_source[from++]; + if (len > 1) { + output[_out++] = from_source[from++]; + } + } + } + else { + from = _out - dist; /* copy direct from output */ + do { /* minimum length is three */ + output[_out++] = output[from++]; + output[_out++] = output[from++]; + output[_out++] = output[from++]; + len -= 3; + } while (len > 2); + if (len) { + output[_out++] = output[from++]; + if (len > 1) { + output[_out++] = output[from++]; + } + } + } + } + else if ((op & 64) === 0) { /* 2nd level distance code */ + here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dodist; + } + else { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } + else if ((op & 64) === 0) { /* 2nd level length code */ + here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dolen; + } + else if (op & 32) { /* end-of-block */ + //Tracevv((stderr, "inflate: end of block\n")); + state.mode = TYPE; + break top; + } + else { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } while (_in < last && _out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + _in -= len; + bits -= len << 3; + hold &= (1 << bits) - 1; + + /* update state and return */ + strm.next_in = _in; + strm.next_out = _out; + strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); + strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); + state.hold = hold; + state.bits = bits; + return; +}; + +},{}],35:[function(_dereq_,module,exports){ +'use strict'; + + +var utils = _dereq_('../utils/common'); +var adler32 = _dereq_('./adler32'); +var crc32 = _dereq_('./crc32'); +var inflate_fast = _dereq_('./inffast'); +var inflate_table = _dereq_('./inftrees'); + +var CODES = 0; +var LENS = 1; +var DISTS = 2; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +/* Allowed flush values; see deflate() and inflate() below for details */ +//var Z_NO_FLUSH = 0; +//var Z_PARTIAL_FLUSH = 1; +//var Z_SYNC_FLUSH = 2; +//var Z_FULL_FLUSH = 3; +var Z_FINISH = 4; +var Z_BLOCK = 5; +var Z_TREES = 6; + + +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ +var Z_OK = 0; +var Z_STREAM_END = 1; +var Z_NEED_DICT = 2; +//var Z_ERRNO = -1; +var Z_STREAM_ERROR = -2; +var Z_DATA_ERROR = -3; +var Z_MEM_ERROR = -4; +var Z_BUF_ERROR = -5; +//var Z_VERSION_ERROR = -6; + +/* The deflate compression method */ +var Z_DEFLATED = 8; + + +/* STATES ====================================================================*/ +/* ===========================================================================*/ + + +var HEAD = 1; /* i: waiting for magic header */ +var FLAGS = 2; /* i: waiting for method and flags (gzip) */ +var TIME = 3; /* i: waiting for modification time (gzip) */ +var OS = 4; /* i: waiting for extra flags and operating system (gzip) */ +var EXLEN = 5; /* i: waiting for extra length (gzip) */ +var EXTRA = 6; /* i: waiting for extra bytes (gzip) */ +var NAME = 7; /* i: waiting for end of file name (gzip) */ +var COMMENT = 8; /* i: waiting for end of comment (gzip) */ +var HCRC = 9; /* i: waiting for header crc (gzip) */ +var DICTID = 10; /* i: waiting for dictionary check value */ +var DICT = 11; /* waiting for inflateSetDictionary() call */ +var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ +var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */ +var STORED = 14; /* i: waiting for stored size (length and complement) */ +var COPY_ = 15; /* i/o: same as COPY below, but only first time in */ +var COPY = 16; /* i/o: waiting for input or output to copy stored block */ +var TABLE = 17; /* i: waiting for dynamic block table lengths */ +var LENLENS = 18; /* i: waiting for code length code lengths */ +var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */ +var LEN_ = 20; /* i: same as LEN below, but only first time in */ +var LEN = 21; /* i: waiting for length/lit/eob code */ +var LENEXT = 22; /* i: waiting for length extra bits */ +var DIST = 23; /* i: waiting for distance code */ +var DISTEXT = 24; /* i: waiting for distance extra bits */ +var MATCH = 25; /* o: waiting for output space to copy string */ +var LIT = 26; /* o: waiting for output space to write literal */ +var CHECK = 27; /* i: waiting for 32-bit check value */ +var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */ +var DONE = 29; /* finished check, done -- remain here until reset */ +var BAD = 30; /* got a data error -- remain here until reset */ +var MEM = 31; /* got an inflate() memory error -- remain here until reset */ +var SYNC = 32; /* looking for synchronization bytes to restart inflate() */ + +/* ===========================================================================*/ + + + +var ENOUGH_LENS = 852; +var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +var MAX_WBITS = 15; +/* 32K LZ77 window */ +var DEF_WBITS = MAX_WBITS; + + +function ZSWAP32(q) { + return (((q >>> 24) & 0xff) + + ((q >>> 8) & 0xff00) + + ((q & 0xff00) << 8) + + ((q & 0xff) << 24)); +} + + +function InflateState() { + this.mode = 0; /* current inflate mode */ + this.last = false; /* true if processing last block */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.havedict = false; /* true if dictionary provided */ + this.flags = 0; /* gzip header method and flags (0 if zlib) */ + this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ + this.check = 0; /* protected copy of check value */ + this.total = 0; /* protected copy of output count */ + // TODO: may be {} + this.head = null; /* where to save gzip header information */ + + /* sliding window */ + this.wbits = 0; /* log base 2 of requested window size */ + this.wsize = 0; /* window size or zero if not using window */ + this.whave = 0; /* valid bytes in the window */ + this.wnext = 0; /* window write index */ + this.window = null; /* allocated sliding window, if needed */ + + /* bit accumulator */ + this.hold = 0; /* input bit accumulator */ + this.bits = 0; /* number of bits in "in" */ + + /* for string and stored block copying */ + this.length = 0; /* literal or length of data to copy */ + this.offset = 0; /* distance back to copy string from */ + + /* for table and code decoding */ + this.extra = 0; /* extra bits needed */ + + /* fixed and dynamic code tables */ + this.lencode = null; /* starting table for length/literal codes */ + this.distcode = null; /* starting table for distance codes */ + this.lenbits = 0; /* index bits for lencode */ + this.distbits = 0; /* index bits for distcode */ + + /* dynamic table building */ + this.ncode = 0; /* number of code length code lengths */ + this.nlen = 0; /* number of length code lengths */ + this.ndist = 0; /* number of distance code lengths */ + this.have = 0; /* number of code lengths in lens[] */ + this.next = null; /* next available space in codes[] */ + + this.lens = new utils.Buf16(320); /* temporary storage for code lengths */ + this.work = new utils.Buf16(288); /* work area for code table building */ + + /* + because we don't have pointers in js, we use lencode and distcode directly + as buffers so we don't need codes + */ + //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */ + this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ + this.distdyn = null; /* dynamic table for distance codes (JS specific) */ + this.sane = 0; /* if false, allow invalid distance too far */ + this.back = 0; /* bits back of last unprocessed length/lit */ + this.was = 0; /* initial length of match */ +} + +function inflateResetKeep(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + strm.total_in = strm.total_out = state.total = 0; + strm.msg = ''; /*Z_NULL*/ + if (state.wrap) { /* to support ill-conceived Java test suite */ + strm.adler = state.wrap & 1; + } + state.mode = HEAD; + state.last = 0; + state.havedict = 0; + state.dmax = 32768; + state.head = null/*Z_NULL*/; + state.hold = 0; + state.bits = 0; + //state.lencode = state.distcode = state.next = state.codes; + state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS); + state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS); + + state.sane = 1; + state.back = -1; + //Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +function inflateReset(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + state.wsize = 0; + state.whave = 0; + state.wnext = 0; + return inflateResetKeep(strm); + +} + +function inflateReset2(strm, windowBits) { + var wrap; + var state; + + /* get the state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 1; + if (windowBits < 48) { + windowBits &= 15; + } + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) { + return Z_STREAM_ERROR; + } + if (state.window !== null && state.wbits !== windowBits) { + state.window = null; + } + + /* update state and reset the rest of it */ + state.wrap = wrap; + state.wbits = windowBits; + return inflateReset(strm); +} + +function inflateInit2(strm, windowBits) { + var ret; + var state; + + if (!strm) { return Z_STREAM_ERROR; } + //strm.msg = Z_NULL; /* in case we return an error */ + + state = new InflateState(); + + //if (state === Z_NULL) return Z_MEM_ERROR; + //Tracev((stderr, "inflate: allocated\n")); + strm.state = state; + state.window = null/*Z_NULL*/; + ret = inflateReset2(strm, windowBits); + if (ret !== Z_OK) { + strm.state = null/*Z_NULL*/; + } + return ret; +} + +function inflateInit(strm) { + return inflateInit2(strm, DEF_WBITS); +} + + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +var virgin = true; + +var lenfix, distfix; // We have no pointers in JS, so keep tables separate + +function fixedtables(state) { + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + var sym; + + lenfix = new utils.Buf32(512); + distfix = new utils.Buf32(32); + + /* literal/length table */ + sym = 0; + while (sym < 144) { state.lens[sym++] = 8; } + while (sym < 256) { state.lens[sym++] = 9; } + while (sym < 280) { state.lens[sym++] = 7; } + while (sym < 288) { state.lens[sym++] = 8; } + + inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, {bits: 9}); + + /* distance table */ + sym = 0; + while (sym < 32) { state.lens[sym++] = 5; } + + inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, {bits: 5}); + + /* do this just once */ + virgin = false; + } + + state.lencode = lenfix; + state.lenbits = 9; + state.distcode = distfix; + state.distbits = 5; +} + + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +function updatewindow(strm, src, end, copy) { + var dist; + var state = strm.state; + + /* if it hasn't been done already, allocate space for the window */ + if (state.window === null) { + state.wsize = 1 << state.wbits; + state.wnext = 0; + state.whave = 0; + + state.window = new utils.Buf8(state.wsize); + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state.wsize) { + utils.arraySet(state.window,src, end - state.wsize, state.wsize, 0); + state.wnext = 0; + state.whave = state.wsize; + } + else { + dist = state.wsize - state.wnext; + if (dist > copy) { + dist = copy; + } + //zmemcpy(state->window + state->wnext, end - copy, dist); + utils.arraySet(state.window,src, end - copy, dist, state.wnext); + copy -= dist; + if (copy) { + //zmemcpy(state->window, end - copy, copy); + utils.arraySet(state.window,src, end - copy, copy, 0); + state.wnext = copy; + state.whave = state.wsize; + } + else { + state.wnext += dist; + if (state.wnext === state.wsize) { state.wnext = 0; } + if (state.whave < state.wsize) { state.whave += dist; } + } + } + return 0; +} + +function inflate(strm, flush) { + var state; + var input, output; // input/output buffers + var next; /* next input INDEX */ + var put; /* next output INDEX */ + var have, left; /* available input and output */ + var hold; /* bit buffer */ + var bits; /* bits in bit buffer */ + var _in, _out; /* save starting available input and output */ + var copy; /* number of stored or match bytes to copy */ + var from; /* where to copy match bytes from */ + var from_source; + var here = 0; /* current decoding table entry */ + var here_bits, here_op, here_val; // paked "here" denormalized (JS specific) + //var last; /* parent table entry */ + var last_bits, last_op, last_val; // paked "last" denormalized (JS specific) + var len; /* length to copy for repeats, bits to drop */ + var ret; /* return code */ + var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */ + var opts; + + var n; // temporary var for NEED_BITS + + var order = /* permutation of code lengths */ + [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + + + if (!strm || !strm.state || !strm.output || + (!strm.input && strm.avail_in !== 0)) { + return Z_STREAM_ERROR; + } + + state = strm.state; + if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ + + + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + _in = have; + _out = left; + ret = Z_OK; + + inf_leave: // goto emulation + for (;;) { + switch (state.mode) { + case HEAD: + if (state.wrap === 0) { + state.mode = TYPEDO; + break; + } + //=== NEEDBITS(16); + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ + state.check = 0/*crc32(0L, Z_NULL, 0)*/; + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = FLAGS; + break; + } + state.flags = 0; /* expect zlib header */ + if (state.head) { + state.head.done = false; + } + if (!(state.wrap & 1) || /* check if zlib header allowed */ + (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { + strm.msg = 'incorrect header check'; + state.mode = BAD; + break; + } + if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + len = (hold & 0x0f)/*BITS(4)*/ + 8; + if (state.wbits === 0) { + state.wbits = len; + } + else if (len > state.wbits) { + strm.msg = 'invalid window size'; + state.mode = BAD; + break; + } + state.dmax = 1 << len; + //Tracev((stderr, "inflate: zlib header ok\n")); + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = hold & 0x200 ? DICTID : TYPE; + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + break; + case FLAGS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.flags = hold; + if ((state.flags & 0xff) !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + if (state.flags & 0xe000) { + strm.msg = 'unknown header flags set'; + state.mode = BAD; + break; + } + if (state.head) { + state.head.text = ((hold >> 8) & 1); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = TIME; + /* falls through */ + case TIME: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.time = hold; + } + if (state.flags & 0x0200) { + //=== CRC4(state.check, hold) + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + hbuf[2] = (hold >>> 16) & 0xff; + hbuf[3] = (hold >>> 24) & 0xff; + state.check = crc32(state.check, hbuf, 4, 0); + //=== + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = OS; + /* falls through */ + case OS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.xflags = (hold & 0xff); + state.head.os = (hold >> 8); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = EXLEN; + /* falls through */ + case EXLEN: + if (state.flags & 0x0400) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length = hold; + if (state.head) { + state.head.extra_len = hold; + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + else if (state.head) { + state.head.extra = null/*Z_NULL*/; + } + state.mode = EXTRA; + /* falls through */ + case EXTRA: + if (state.flags & 0x0400) { + copy = state.length; + if (copy > have) { copy = have; } + if (copy) { + if (state.head) { + len = state.head.extra_len - state.length; + if (!state.head.extra) { + // Use untyped array for more conveniend processing later + state.head.extra = new Array(state.head.extra_len); + } + utils.arraySet( + state.head.extra, + input, + next, + // extra field is limited to 65536 bytes + // - no need for additional size check + copy, + /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ + len + ); + //zmemcpy(state.head.extra + len, next, + // len + copy > state.head.extra_max ? + // state.head.extra_max - len : copy); + } + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + state.length -= copy; + } + if (state.length) { break inf_leave; } + } + state.length = 0; + state.mode = NAME; + /* falls through */ + case NAME: + if (state.flags & 0x0800) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + // TODO: 2 or 1 bytes? + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.name_max*/)) { + state.head.name += String.fromCharCode(len); + } + } while (len && copy < have); + + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.name = null; + } + state.length = 0; + state.mode = COMMENT; + /* falls through */ + case COMMENT: + if (state.flags & 0x1000) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.comm_max*/)) { + state.head.comment += String.fromCharCode(len); + } + } while (len && copy < have); + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.comment = null; + } + state.mode = HCRC; + /* falls through */ + case HCRC: + if (state.flags & 0x0200) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.check & 0xffff)) { + strm.msg = 'header crc mismatch'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + if (state.head) { + state.head.hcrc = ((state.flags >> 9) & 1); + state.head.done = true; + } + strm.adler = state.check = 0 /*crc32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + break; + case DICTID: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + strm.adler = state.check = ZSWAP32(hold); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = DICT; + /* falls through */ + case DICT: + if (state.havedict === 0) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + return Z_NEED_DICT; + } + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + /* falls through */ + case TYPE: + if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } + /* falls through */ + case TYPEDO: + if (state.last) { + //--- BYTEBITS() ---// + hold >>>= bits & 7; + bits -= bits & 7; + //---// + state.mode = CHECK; + break; + } + //=== NEEDBITS(3); */ + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.last = (hold & 0x01)/*BITS(1)*/; + //--- DROPBITS(1) ---// + hold >>>= 1; + bits -= 1; + //---// + + switch ((hold & 0x03)/*BITS(2)*/) { + case 0: /* stored block */ + //Tracev((stderr, "inflate: stored block%s\n", + // state.last ? " (last)" : "")); + state.mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + //Tracev((stderr, "inflate: fixed codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = LEN_; /* decode codes */ + if (flush === Z_TREES) { + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break inf_leave; + } + break; + case 2: /* dynamic block */ + //Tracev((stderr, "inflate: dynamic codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = TABLE; + break; + case 3: + strm.msg = 'invalid block type'; + state.mode = BAD; + } + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break; + case STORED: + //--- BYTEBITS() ---// /* go to byte boundary */ + hold >>>= bits & 7; + bits -= bits & 7; + //---// + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { + strm.msg = 'invalid stored block lengths'; + state.mode = BAD; + break; + } + state.length = hold & 0xffff; + //Tracev((stderr, "inflate: stored length %u\n", + // state.length)); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = COPY_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case COPY_: + state.mode = COPY; + /* falls through */ + case COPY: + copy = state.length; + if (copy) { + if (copy > have) { copy = have; } + if (copy > left) { copy = left; } + if (copy === 0) { break inf_leave; } + //--- zmemcpy(put, next, copy); --- + utils.arraySet(output, input, next, copy, put); + //---// + have -= copy; + next += copy; + left -= copy; + put += copy; + state.length -= copy; + break; + } + //Tracev((stderr, "inflate: stored end\n")); + state.mode = TYPE; + break; + case TABLE: + //=== NEEDBITS(14); */ + while (bits < 14) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// +//#ifndef PKZIP_BUG_WORKAROUND + if (state.nlen > 286 || state.ndist > 30) { + strm.msg = 'too many length or distance symbols'; + state.mode = BAD; + break; + } +//#endif + //Tracev((stderr, "inflate: table sizes ok\n")); + state.have = 0; + state.mode = LENLENS; + /* falls through */ + case LENLENS: + while (state.have < state.ncode) { + //=== NEEDBITS(3); + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + while (state.have < 19) { + state.lens[order[state.have++]] = 0; + } + // We have separate tables & no pointers. 2 commented lines below not needed. + //state.next = state.codes; + //state.lencode = state.next; + // Switch to use dynamic table + state.lencode = state.lendyn; + state.lenbits = 7; + + opts = {bits: state.lenbits}; + ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); + state.lenbits = opts.bits; + + if (ret) { + strm.msg = 'invalid code lengths set'; + state.mode = BAD; + break; + } + //Tracev((stderr, "inflate: code lengths ok\n")); + state.have = 0; + state.mode = CODELENS; + /* falls through */ + case CODELENS: + while (state.have < state.nlen + state.ndist) { + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_val < 16) { + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.lens[state.have++] = here_val; + } + else { + if (here_val === 16) { + //=== NEEDBITS(here.bits + 2); + n = here_bits + 2; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + if (state.have === 0) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + len = state.lens[state.have - 1]; + copy = 3 + (hold & 0x03);//BITS(2); + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + } + else if (here_val === 17) { + //=== NEEDBITS(here.bits + 3); + n = here_bits + 3; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 3 + (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + else { + //=== NEEDBITS(here.bits + 7); + n = here_bits + 7; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 11 + (hold & 0x7f);//BITS(7); + //--- DROPBITS(7) ---// + hold >>>= 7; + bits -= 7; + //---// + } + if (state.have + copy > state.nlen + state.ndist) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + while (copy--) { + state.lens[state.have++] = len; + } + } + } + + /* handle error breaks in while */ + if (state.mode === BAD) { break; } + + /* check for end-of-block code (better have one) */ + if (state.lens[256] === 0) { + strm.msg = 'invalid code -- missing end-of-block'; + state.mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state.lenbits = 9; + + opts = {bits: state.lenbits}; + ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.lenbits = opts.bits; + // state.lencode = state.next; + + if (ret) { + strm.msg = 'invalid literal/lengths set'; + state.mode = BAD; + break; + } + + state.distbits = 6; + //state.distcode.copy(state.codes); + // Switch to use dynamic table + state.distcode = state.distdyn; + opts = {bits: state.distbits}; + ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.distbits = opts.bits; + // state.distcode = state.next; + + if (ret) { + strm.msg = 'invalid distances set'; + state.mode = BAD; + break; + } + //Tracev((stderr, 'inflate: codes ok\n')); + state.mode = LEN_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case LEN_: + state.mode = LEN; + /* falls through */ + case LEN: + if (have >= 6 && left >= 258) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + inflate_fast(strm, _out); + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + if (state.mode === TYPE) { + state.back = -1; + } + break; + } + state.back = 0; + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) -1)]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if (here_bits <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_op && (here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.lencode[last_val + + ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + state.length = here_val; + if (here_op === 0) { + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + state.mode = LIT; + break; + } + if (here_op & 32) { + //Tracevv((stderr, "inflate: end of block\n")); + state.back = -1; + state.mode = TYPE; + break; + } + if (here_op & 64) { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break; + } + state.extra = here_op & 15; + state.mode = LENEXT; + /* falls through */ + case LENEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //Tracevv((stderr, "inflate: length %u\n", state.length)); + state.was = state.length; + state.mode = DIST; + /* falls through */ + case DIST: + for (;;) { + here = state.distcode[hold & ((1 << state.distbits) -1)];/*BITS(state.distbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if ((here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.distcode[last_val + + ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + if (here_op & 64) { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break; + } + state.offset = here_val; + state.extra = (here_op) & 15; + state.mode = DISTEXT; + /* falls through */ + case DISTEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.offset += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } +//#ifdef INFLATE_STRICT + if (state.offset > state.dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +//#endif + //Tracevv((stderr, "inflate: distance %u\n", state.offset)); + state.mode = MATCH; + /* falls through */ + case MATCH: + if (left === 0) { break inf_leave; } + copy = _out - left; + if (state.offset > copy) { /* copy from window */ + copy = state.offset - copy; + if (copy > state.whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// Trace((stderr, "inflate.c too far\n")); +// copy -= state.whave; +// if (copy > state.length) { copy = state.length; } +// if (copy > left) { copy = left; } +// left -= copy; +// state.length -= copy; +// do { +// output[put++] = 0; +// } while (--copy); +// if (state.length === 0) { state.mode = LEN; } +// break; +//#endif + } + if (copy > state.wnext) { + copy -= state.wnext; + from = state.wsize - copy; + } + else { + from = state.wnext - copy; + } + if (copy > state.length) { copy = state.length; } + from_source = state.window; + } + else { /* copy from output */ + from_source = output; + from = put - state.offset; + copy = state.length; + } + if (copy > left) { copy = left; } + left -= copy; + state.length -= copy; + do { + output[put++] = from_source[from++]; + } while (--copy); + if (state.length === 0) { state.mode = LEN; } + break; + case LIT: + if (left === 0) { break inf_leave; } + output[put++] = state.length; + left--; + state.mode = LEN; + break; + case CHECK: + if (state.wrap) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + // Use '|' insdead of '+' to make sure that result is signed + hold |= input[next++] << bits; + bits += 8; + } + //===// + _out -= left; + strm.total_out += _out; + state.total += _out; + if (_out) { + strm.adler = state.check = + /*UPDATE(state.check, put - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out)); + + } + _out = left; + // NB: crc32 stored as signed 32-bit int, ZSWAP32 returns signed too + if ((state.flags ? hold : ZSWAP32(hold)) !== state.check) { + strm.msg = 'incorrect data check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: check matches trailer\n")); + } + state.mode = LENGTH; + /* falls through */ + case LENGTH: + if (state.wrap && state.flags) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.total & 0xffffffff)) { + strm.msg = 'incorrect length check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: length matches trailer\n")); + } + state.mode = DONE; + /* falls through */ + case DONE: + ret = Z_STREAM_END; + break inf_leave; + case BAD: + ret = Z_DATA_ERROR; + break inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + /* falls through */ + default: + return Z_STREAM_ERROR; + } + } + + // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + + if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && + (state.mode < CHECK || flush !== Z_FINISH))) { + if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) { + state.mode = MEM; + return Z_MEM_ERROR; + } + } + _in -= strm.avail_in; + _out -= strm.avail_out; + strm.total_in += _in; + strm.total_out += _out; + state.total += _out; + if (state.wrap && _out) { + strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out)); + } + strm.data_type = state.bits + (state.last ? 64 : 0) + + (state.mode === TYPE ? 128 : 0) + + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); + if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) { + ret = Z_BUF_ERROR; + } + return ret; +} + +function inflateEnd(strm) { + + if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) { + return Z_STREAM_ERROR; + } + + var state = strm.state; + if (state.window) { + state.window = null; + } + strm.state = null; + return Z_OK; +} + +function inflateGetHeader(strm, head) { + var state; + + /* check state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; } + + /* save header structure */ + state.head = head; + head.done = false; + return Z_OK; +} + + +exports.inflateReset = inflateReset; +exports.inflateReset2 = inflateReset2; +exports.inflateResetKeep = inflateResetKeep; +exports.inflateInit = inflateInit; +exports.inflateInit2 = inflateInit2; +exports.inflate = inflate; +exports.inflateEnd = inflateEnd; +exports.inflateGetHeader = inflateGetHeader; +exports.inflateInfo = 'pako inflate (from Nodeca project)'; + +/* Not implemented +exports.inflateCopy = inflateCopy; +exports.inflateGetDictionary = inflateGetDictionary; +exports.inflateMark = inflateMark; +exports.inflatePrime = inflatePrime; +exports.inflateSetDictionary = inflateSetDictionary; +exports.inflateSync = inflateSync; +exports.inflateSyncPoint = inflateSyncPoint; +exports.inflateUndermine = inflateUndermine; +*/ +},{"../utils/common":27,"./adler32":29,"./crc32":31,"./inffast":34,"./inftrees":36}],36:[function(_dereq_,module,exports){ +'use strict'; + + +var utils = _dereq_('../utils/common'); + +var MAXBITS = 15; +var ENOUGH_LENS = 852; +var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +var CODES = 0; +var LENS = 1; +var DISTS = 2; + +var lbase = [ /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +]; + +var lext = [ /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 +]; + +var dbase = [ /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0 +]; + +var dext = [ /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64 +]; + +module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) +{ + var bits = opts.bits; + //here = opts.here; /* table entry for duplication */ + + var len = 0; /* a code's length in bits */ + var sym = 0; /* index of code symbols */ + var min = 0, max = 0; /* minimum and maximum code lengths */ + var root = 0; /* number of index bits for root table */ + var curr = 0; /* number of index bits for current table */ + var drop = 0; /* code bits to drop for sub-table */ + var left = 0; /* number of prefix codes available */ + var used = 0; /* code entries in table used */ + var huff = 0; /* Huffman code */ + var incr; /* for incrementing code, index */ + var fill; /* index for replicating entries */ + var low; /* low bits for current root entry */ + var mask; /* mask for low root bits */ + var next; /* next available space in table */ + var base = null; /* base value table to use */ + var base_index = 0; +// var shoextra; /* extra bits table to use */ + var end; /* use base and extra for symbol > end */ + var count = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* number of codes of each length */ + var offs = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* offsets in table for each length */ + var extra = null; + var extra_index = 0; + + var here_bits, here_op, here_val; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) { + count[len] = 0; + } + for (sym = 0; sym < codes; sym++) { + count[lens[lens_index + sym]]++; + } + + /* bound code lengths, force root to be within code lengths */ + root = bits; + for (max = MAXBITS; max >= 1; max--) { + if (count[max] !== 0) { break; } + } + if (root > max) { + root = max; + } + if (max === 0) { /* no symbols to code at all */ + //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ + //table.bits[opts.table_index] = 1; //here.bits = (var char)1; + //table.val[opts.table_index++] = 0; //here.val = (var short)0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + + //table.op[opts.table_index] = 64; + //table.bits[opts.table_index] = 1; + //table.val[opts.table_index++] = 0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + opts.bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) { + if (count[min] !== 0) { break; } + } + if (root < min) { + root = min; + } + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) { + return -1; + } /* over-subscribed */ + } + if (left > 0 && (type === CODES || max !== 1)) { + return -1; /* incomplete set */ + } + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) { + offs[len + 1] = offs[len] + count[len]; + } + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) { + if (lens[lens_index + sym] !== 0) { + work[offs[lens[lens_index + sym]]++] = sym; + } + } + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + // poor man optimization - use if-else instead of switch, + // to avoid deopts in old v8 + if (type === CODES) { + base = extra = work; /* dummy value--not used */ + end = 19; + } else if (type === LENS) { + base = lbase; + base_index -= 257; + extra = lext; + extra_index -= 257; + end = 256; + } else { /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize opts for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = table_index; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = -1; /* trigger new sub-table when len > root */ + used = 1 << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + var i=0; + /* process all codes and make table entries */ + for (;;) { + i++; + /* create table entry */ + here_bits = len - drop; + if (work[sym] < end) { + here_op = 0; + here_val = work[sym]; + } + else if (work[sym] > end) { + here_op = extra[extra_index + work[sym]]; + here_val = base[base_index + work[sym]]; + } + else { + here_op = 32 + 64; /* end of block */ + here_val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1 << (len - drop); + fill = 1 << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; + } while (fill !== 0); + + /* backwards increment the len-bit code huff */ + incr = 1 << (len - 1); + while (huff & incr) { + incr >>= 1; + } + if (incr !== 0) { + huff &= incr - 1; + huff += incr; + } else { + huff = 0; + } + + /* go to next symbol, update count, len */ + sym++; + if (--count[len] === 0) { + if (len === max) { break; } + len = lens[lens_index + work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) !== low) { + /* if first time, transition to sub-tables */ + if (drop === 0) { + drop = root; + } + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = 1 << curr; + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) { break; } + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1 << curr; + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* point entry in root table to sub-table */ + low = huff & mask; + /*table.op[low] = curr; + table.bits[low] = root; + table.val[low] = next - opts.table_index;*/ + table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff !== 0) { + //table.op[next + huff] = 64; /* invalid code marker */ + //table.bits[next + huff] = len - drop; + //table.val[next + huff] = 0; + table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; + } + + /* set return parameters */ + //opts.table_index += used; + opts.bits = root; + return 0; +}; + +},{"../utils/common":27}],37:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = { + '2': 'need dictionary', /* Z_NEED_DICT 2 */ + '1': 'stream end', /* Z_STREAM_END 1 */ + '0': '', /* Z_OK 0 */ + '-1': 'file error', /* Z_ERRNO (-1) */ + '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ + '-3': 'data error', /* Z_DATA_ERROR (-3) */ + '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ + '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ + '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ +}; +},{}],38:[function(_dereq_,module,exports){ +'use strict'; + + +var utils = _dereq_('../utils/common'); + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +//var Z_FILTERED = 1; +//var Z_HUFFMAN_ONLY = 2; +//var Z_RLE = 3; +var Z_FIXED = 4; +//var Z_DEFAULT_STRATEGY = 0; + +/* Possible values of the data_type field (though see inflate()) */ +var Z_BINARY = 0; +var Z_TEXT = 1; +//var Z_ASCII = 1; // = Z_TEXT +var Z_UNKNOWN = 2; + +/*============================================================================*/ + + +function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } + +// From zutil.h + +var STORED_BLOCK = 0; +var STATIC_TREES = 1; +var DYN_TREES = 2; +/* The three kinds of block type */ + +var MIN_MATCH = 3; +var MAX_MATCH = 258; +/* The minimum and maximum match lengths */ + +// From deflate.h +/* =========================================================================== + * Internal compression state. + */ + +var LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ + +var LITERALS = 256; +/* number of literal bytes 0..255 */ + +var L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ + +var D_CODES = 30; +/* number of distance codes */ + +var BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ + +var HEAP_SIZE = 2*L_CODES + 1; +/* maximum heap size */ + +var MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +var Buf_size = 16; +/* size of bit buffer in bi_buf */ + + +/* =========================================================================== + * Constants + */ + +var MAX_BL_BITS = 7; +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +var END_BLOCK = 256; +/* end of block literal code */ + +var REP_3_6 = 16; +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +var REPZ_3_10 = 17; +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +var REPZ_11_138 = 18; +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +var extra_lbits = /* extra bits for each length code */ + [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]; + +var extra_dbits = /* extra bits for each distance code */ + [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]; + +var extra_blbits = /* extra bits for each bit length code */ + [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]; + +var bl_order = + [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]; +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +// We pre-fill arrays with 0 to avoid uninitialized gaps + +var DIST_CODE_LEN = 512; /* see definition of array dist_code below */ + +// !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1 +var static_ltree = new Array((L_CODES+2) * 2); +zero(static_ltree); +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +var static_dtree = new Array(D_CODES * 2); +zero(static_dtree); +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +var _dist_code = new Array(DIST_CODE_LEN); +zero(_dist_code); +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +var _length_code = new Array(MAX_MATCH-MIN_MATCH+1); +zero(_length_code); +/* length code for each normalized match length (0 == MIN_MATCH) */ + +var base_length = new Array(LENGTH_CODES); +zero(base_length); +/* First normalized length for each code (0 = MIN_MATCH) */ + +var base_dist = new Array(D_CODES); +zero(base_dist); +/* First normalized distance for each code (0 = distance of 1) */ + + +var StaticTreeDesc = function (static_tree, extra_bits, extra_base, elems, max_length) { + + this.static_tree = static_tree; /* static tree or NULL */ + this.extra_bits = extra_bits; /* extra bits for each code or NULL */ + this.extra_base = extra_base; /* base index for extra_bits */ + this.elems = elems; /* max number of elements in the tree */ + this.max_length = max_length; /* max bit length for the codes */ + + // show if `static_tree` has data or dummy - needed for monomorphic objects + this.has_stree = static_tree && static_tree.length; +}; + + +var static_l_desc; +var static_d_desc; +var static_bl_desc; + + +var TreeDesc = function(dyn_tree, stat_desc) { + this.dyn_tree = dyn_tree; /* the dynamic tree */ + this.max_code = 0; /* largest code with non zero frequency */ + this.stat_desc = stat_desc; /* the corresponding static tree */ +}; + + + +function d_code(dist) { + return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; +} + + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +function put_short (s, w) { +// put_byte(s, (uch)((w) & 0xff)); +// put_byte(s, (uch)((ush)(w) >> 8)); + s.pending_buf[s.pending++] = (w) & 0xff; + s.pending_buf[s.pending++] = (w >>> 8) & 0xff; +} + + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +function send_bits(s, value, length) { + if (s.bi_valid > (Buf_size - length)) { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + put_short(s, s.bi_buf); + s.bi_buf = value >> (Buf_size - s.bi_valid); + s.bi_valid += length - Buf_size; + } else { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + s.bi_valid += length; + } +} + + +function send_code(s, c, tree) { + send_bits(s, tree[c*2]/*.Code*/, tree[c*2 + 1]/*.Len*/); +} + + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +function bi_reverse(code, len) { + var res = 0; + do { + res |= code & 1; + code >>>= 1; + res <<= 1; + } while (--len > 0); + return res >>> 1; +} + + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +function bi_flush(s) { + if (s.bi_valid === 16) { + put_short(s, s.bi_buf); + s.bi_buf = 0; + s.bi_valid = 0; + + } else if (s.bi_valid >= 8) { + s.pending_buf[s.pending++] = s.bi_buf & 0xff; + s.bi_buf >>= 8; + s.bi_valid -= 8; + } +} + + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +function gen_bitlen(s, desc) +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ +{ + var tree = desc.dyn_tree; + var max_code = desc.max_code; + var stree = desc.stat_desc.static_tree; + var has_stree = desc.stat_desc.has_stree; + var extra = desc.stat_desc.extra_bits; + var base = desc.stat_desc.extra_base; + var max_length = desc.stat_desc.max_length; + var h; /* heap index */ + var n, m; /* iterate over the tree elements */ + var bits; /* bit length */ + var xbits; /* extra bits */ + var f; /* frequency */ + var overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) { + s.bl_count[bits] = 0; + } + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s.heap[s.heap_max]*2 + 1]/*.Len*/ = 0; /* root of the heap */ + + for (h = s.heap_max+1; h < HEAP_SIZE; h++) { + n = s.heap[h]; + bits = tree[tree[n*2 +1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; + if (bits > max_length) { + bits = max_length; + overflow++; + } + tree[n*2 + 1]/*.Len*/ = bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) { continue; } /* not a leaf node */ + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) { + xbits = extra[n-base]; + } + f = tree[n * 2]/*.Freq*/; + s.opt_len += f * (bits + xbits); + if (has_stree) { + s.static_len += f * (stree[n*2 + 1]/*.Len*/ + xbits); + } + } + if (overflow === 0) { return; } + + // Trace((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length-1; + while (s.bl_count[bits] === 0) { bits--; } + s.bl_count[bits]--; /* move one leaf down the tree */ + s.bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s.bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits !== 0; bits--) { + n = s.bl_count[bits]; + while (n !== 0) { + m = s.heap[--h]; + if (m > max_code) { continue; } + if (tree[m*2 + 1]/*.Len*/ !== bits) { + // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s.opt_len += (bits - tree[m*2 + 1]/*.Len*/)*tree[m*2]/*.Freq*/; + tree[m*2 + 1]/*.Len*/ = bits; + } + n--; + } + } +} + + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +function gen_codes(tree, max_code, bl_count) +// ct_data *tree; /* the tree to decorate */ +// int max_code; /* largest code with non zero frequency */ +// ushf *bl_count; /* number of codes at each bit length */ +{ + var next_code = new Array(MAX_BITS+1); /* next code value for each bit length */ + var code = 0; /* running code value */ + var bits; /* bit index */ + var n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits-1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + //Assert (code + bl_count[MAX_BITS]-1 == (1< length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES-1; code++) { + base_length[code] = length; + for (n = 0; n < (1< dist code (0..29) */ + dist = 0; + for (code = 0 ; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ + for ( ; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + _dist_code[256 + dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) { + bl_count[bits] = 0; + } + + n = 0; + while (n <= 143) { + static_ltree[n*2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + while (n <= 255) { + static_ltree[n*2 + 1]/*.Len*/ = 9; + n++; + bl_count[9]++; + } + while (n <= 279) { + static_ltree[n*2 + 1]/*.Len*/ = 7; + n++; + bl_count[7]++; + } + while (n <= 287) { + static_ltree[n*2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes(static_ltree, L_CODES+1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n*2 + 1]/*.Len*/ = 5; + static_dtree[n*2]/*.Code*/ = bi_reverse(n, 5); + } + + // Now data ready and we can init static trees + static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS); + static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS); + static_bl_desc =new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS); + + //static_init_done = true; +} + + +/* =========================================================================== + * Initialize a new block. + */ +function init_block(s) { + var n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n*2]/*.Freq*/ = 0; } + for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n*2]/*.Freq*/ = 0; } + for (n = 0; n < BL_CODES; n++) { s.bl_tree[n*2]/*.Freq*/ = 0; } + + s.dyn_ltree[END_BLOCK*2]/*.Freq*/ = 1; + s.opt_len = s.static_len = 0; + s.last_lit = s.matches = 0; +} + + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +function bi_windup(s) +{ + if (s.bi_valid > 8) { + put_short(s, s.bi_buf); + } else if (s.bi_valid > 0) { + //put_byte(s, (Byte)s->bi_buf); + s.pending_buf[s.pending++] = s.bi_buf; + } + s.bi_buf = 0; + s.bi_valid = 0; +} + +/* =========================================================================== + * Copy a stored block, storing first the length and its + * one's complement if requested. + */ +function copy_block(s, buf, len, header) +//DeflateState *s; +//charf *buf; /* the input data */ +//unsigned len; /* its length */ +//int header; /* true if block header must be written */ +{ + bi_windup(s); /* align on byte boundary */ + + if (header) { + put_short(s, len); + put_short(s, ~len); + } +// while (len--) { +// put_byte(s, *buf++); +// } + utils.arraySet(s.pending_buf, s.window, buf, len, s.pending); + s.pending += len; +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +function smaller(tree, n, m, depth) { + var _n2 = n*2; + var _m2 = m*2; + return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || + (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); +} + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +function pqdownheap(s, tree, k) +// deflate_state *s; +// ct_data *tree; /* the tree to restore */ +// int k; /* node to move down */ +{ + var v = s.heap[k]; + var j = k << 1; /* left son of k */ + while (j <= s.heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s.heap_len && + smaller(tree, s.heap[j+1], s.heap[j], s.depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s.heap[j], s.depth)) { break; } + + /* Exchange v with the smallest son */ + s.heap[k] = s.heap[j]; + k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s.heap[k] = v; +} + + +// inlined manually +// var SMALLEST = 1; + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +function compress_block(s, ltree, dtree) +// deflate_state *s; +// const ct_data *ltree; /* literal tree */ +// const ct_data *dtree; /* distance tree */ +{ + var dist; /* distance of matched string */ + var lc; /* match length or unmatched char (if dist == 0) */ + var lx = 0; /* running index in l_buf */ + var code; /* the code to send */ + var extra; /* number of extra bits to send */ + + if (s.last_lit !== 0) { + do { + dist = (s.pending_buf[s.d_buf + lx*2] << 8) | (s.pending_buf[s.d_buf + lx*2 + 1]); + lc = s.pending_buf[s.l_buf + lx]; + lx++; + + if (dist === 0) { + send_code(s, lc, ltree); /* send a literal byte */ + //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code+LITERALS+1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra !== 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + //Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra !== 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + // "pendingBuf overflow"); + + } while (lx < s.last_lit); + } + + send_code(s, END_BLOCK, ltree); +} + + +/* =========================================================================== + * Construct one Huffman tree and assigns the code bit strings and lengths. + * Update the total bit length for the current block. + * IN assertion: the field freq is set for all tree elements. + * OUT assertions: the fields len and code are set to the optimal bit length + * and corresponding code. The length opt_len is updated; static_len is + * also updated if stree is not null. The field max_code is set. + */ +function build_tree(s, desc) +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ +{ + var tree = desc.dyn_tree; + var stree = desc.stat_desc.static_tree; + var has_stree = desc.stat_desc.has_stree; + var elems = desc.stat_desc.elems; + var n, m; /* iterate over heap elements */ + var max_code = -1; /* largest code with non zero frequency */ + var node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n * 2]/*.Freq*/ !== 0) { + s.heap[++s.heap_len] = max_code = n; + s.depth[n] = 0; + + } else { + tree[n*2 + 1]/*.Len*/ = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s.heap_len < 2) { + node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); + tree[node * 2]/*.Freq*/ = 1; + s.depth[node] = 0; + s.opt_len--; + + if (has_stree) { + s.static_len -= stree[node*2 + 1]/*.Len*/; + } + /* node is 0 or 1 so it does not have extra bits */ + } + desc.max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + //pqremove(s, tree, n); /* n = node of least frequency */ + /*** pqremove ***/ + n = s.heap[1/*SMALLEST*/]; + s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; + pqdownheap(s, tree, 1/*SMALLEST*/); + /***/ + + m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ + + s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ + s.heap[--s.heap_max] = m; + + /* Create a new node father of n and m */ + tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; + s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; + tree[n*2 + 1]/*.Dad*/ = tree[m*2 + 1]/*.Dad*/ = node; + + /* and insert the new node in the heap */ + s.heap[1/*SMALLEST*/] = node++; + pqdownheap(s, tree, 1/*SMALLEST*/); + + } while (s.heap_len >= 2); + + s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes(tree, max_code, s.bl_count); +} + + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +function scan_tree(s, tree, max_code) +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ +{ + var n; /* iterates over all tree elements */ + var prevlen = -1; /* last emitted length */ + var curlen; /* length of current code */ + + var nextlen = tree[0*2 + 1]/*.Len*/; /* length of next code */ + + var count = 0; /* repeat count of the current code */ + var max_count = 7; /* max repeat count */ + var min_count = 4; /* min repeat count */ + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + tree[(max_code+1)*2 + 1]/*.Len*/ = 0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n+1)*2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + s.bl_tree[curlen * 2]/*.Freq*/ += count; + + } else if (curlen !== 0) { + + if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } + s.bl_tree[REP_3_6*2]/*.Freq*/++; + + } else if (count <= 10) { + s.bl_tree[REPZ_3_10*2]/*.Freq*/++; + + } else { + s.bl_tree[REPZ_11_138*2]/*.Freq*/++; + } + + count = 0; + prevlen = curlen; + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +} + + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +function send_tree(s, tree, max_code) +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ +{ + var n; /* iterates over all tree elements */ + var prevlen = -1; /* last emitted length */ + var curlen; /* length of current code */ + + var nextlen = tree[0*2 + 1]/*.Len*/; /* length of next code */ + + var count = 0; /* repeat count of the current code */ + var max_count = 7; /* max repeat count */ + var min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n+1)*2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); + + } else if (curlen !== 0) { + if (curlen !== prevlen) { + send_code(s, curlen, s.bl_tree); + count--; + } + //Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s.bl_tree); + send_bits(s, count-3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s.bl_tree); + send_bits(s, count-3, 3); + + } else { + send_code(s, REPZ_11_138, s.bl_tree); + send_bits(s, count-11, 7); + } + + count = 0; + prevlen = curlen; + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +} + + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +function build_bl_tree(s) { + var max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, s.dyn_ltree, s.l_desc.max_code); + scan_tree(s, s.dyn_dtree, s.d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, s.bl_desc); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { + if (s.bl_tree[bl_order[max_blindex]*2 + 1]/*.Len*/ !== 0) { + break; + } + } + /* Update opt_len to include the bit length tree and counts */ + s.opt_len += 3*(max_blindex+1) + 5+5+4; + //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + // s->opt_len, s->static_len)); + + return max_blindex; +} + + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +function send_all_trees(s, lcodes, dcodes, blcodes) +// deflate_state *s; +// int lcodes, dcodes, blcodes; /* number of codes for each tree */ +{ + var rank; /* index in bl_order */ + + //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + // "too many codes"); + //Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes-1, 5); + send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s.bl_tree[bl_order[rank]*2 + 1]/*.Len*/, 3); + } + //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_ltree, lcodes-1); /* literal tree */ + //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_dtree, dcodes-1); /* distance tree */ + //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +} + + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "black list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +function detect_data_type(s) { + /* black_mask is the bit mask of black-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + var black_mask = 0xf3ffc07f; + var n; + + /* Check for non-textual ("black-listed") bytes. */ + for (n = 0; n <= 31; n++, black_mask >>>= 1) { + if ((black_mask & 1) && (s.dyn_ltree[n*2]/*.Freq*/ !== 0)) { + return Z_BINARY; + } + } + + /* Check for textual ("white-listed") bytes. */ + if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || + s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + for (n = 32; n < LITERALS; n++) { + if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + } + + /* There are no "black-listed" or "white-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +} + + +var static_init_done = false; + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +function _tr_init(s) +{ + + if (!static_init_done) { + tr_static_init(); + static_init_done = true; + } + + s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); + s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); + s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); + + s.bi_buf = 0; + s.bi_valid = 0; + + /* Initialize the first block of the first file: */ + init_block(s); +} + + +/* =========================================================================== + * Send a stored block + */ +function _tr_stored_block(s, buf, stored_len, last) +//DeflateState *s; +//charf *buf; /* input block */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ +{ + send_bits(s, (STORED_BLOCK<<1)+(last ? 1 : 0), 3); /* send block type */ + copy_block(s, buf, stored_len, true); /* with header */ +} + + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + */ +function _tr_align(s) { + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); + bi_flush(s); +} + + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. + */ +function _tr_flush_block(s, buf, stored_len, last) +//DeflateState *s; +//charf *buf; /* input block, or NULL if too old */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ +{ + var opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + var max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s.level > 0) { + + /* Check if the file is binary or text */ + if (s.strm.data_type === Z_UNKNOWN) { + s.strm.data_type = detect_data_type(s); + } + + /* Construct the literal and distance trees */ + build_tree(s, s.l_desc); + // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + + build_tree(s, s.d_desc); + // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s.opt_len+3+7) >>> 3; + static_lenb = (s.static_len+3+7) >>> 3; + + // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + // s->last_lit)); + + if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } + + } else { + // Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + + if ((stored_len+4 <= opt_lenb) && (buf !== -1)) { + /* 4: two words for the lengths */ + + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, last); + + } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) { + + send_bits(s, (STATIC_TREES<<1) + (last ? 1 : 0), 3); + compress_block(s, static_ltree, static_dtree); + + } else { + send_bits(s, (DYN_TREES<<1) + (last ? 1 : 0), 3); + send_all_trees(s, s.l_desc.max_code+1, s.d_desc.max_code+1, max_blindex+1); + compress_block(s, s.dyn_ltree, s.dyn_dtree); + } + // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (last) { + bi_windup(s); + } + // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + // s->compressed_len-7*last)); +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +function _tr_tally(s, dist, lc) +// deflate_state *s; +// unsigned dist; /* distance of matched string */ +// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + //var out_length, in_length, dcode; + + s.pending_buf[s.d_buf + s.last_lit * 2] = (dist >>> 8) & 0xff; + s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff; + + s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff; + s.last_lit++; + + if (dist === 0) { + /* lc is the unmatched char */ + s.dyn_ltree[lc*2]/*.Freq*/++; + } else { + s.matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + //Assert((ush)dist < (ush)MAX_DIST(s) && + // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s.dyn_ltree[(_length_code[lc]+LITERALS+1) * 2]/*.Freq*/++; + s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; + } + +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility + +//#ifdef TRUNCATE_BLOCK +// /* Try to guess if it is profitable to stop the current block here */ +// if ((s.last_lit & 0x1fff) === 0 && s.level > 2) { +// /* Compute an upper bound for the compressed length */ +// out_length = s.last_lit*8; +// in_length = s.strstart - s.block_start; +// +// for (dcode = 0; dcode < D_CODES; dcode++) { +// out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]); +// } +// out_length >>>= 3; +// //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", +// // s->last_lit, in_length, out_length, +// // 100L - out_length*100L/in_length)); +// if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) { +// return true; +// } +// } +//#endif + + return (s.last_lit === s.lit_bufsize-1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +exports._tr_init = _tr_init; +exports._tr_stored_block = _tr_stored_block; +exports._tr_flush_block = _tr_flush_block; +exports._tr_tally = _tr_tally; +exports._tr_align = _tr_align; +},{"../utils/common":27}],39:[function(_dereq_,module,exports){ +'use strict'; + + +function ZStream() { + /* next input byte */ + this.input = null; // JS specific, because we have no pointers + this.next_in = 0; + /* number of bytes available at input */ + this.avail_in = 0; + /* total number of input bytes read so far */ + this.total_in = 0; + /* next output byte should be put there */ + this.output = null; // JS specific, because we have no pointers + this.next_out = 0; + /* remaining free space at output */ + this.avail_out = 0; + /* total number of bytes output so far */ + this.total_out = 0; + /* last error message, NULL if no error */ + this.msg = ''/*Z_NULL*/; + /* not visible by applications */ + this.state = null; + /* best guess about the data type: binary or text */ + this.data_type = 2/*Z_UNKNOWN*/; + /* adler32 value of the uncompressed data */ + this.adler = 0; +} + +module.exports = ZStream; +},{}]},{},[9]) +(9) +}); diff --git a/game/phantom.js b/game/phantom.js index 17e8a5cc5..8d72bafdd 100644 --- a/game/phantom.js +++ b/game/phantom.js @@ -1,29 +1,29 @@ -var fs = require('fs'); -var webpage = require('webpage') -var load = function(id){ - var page = webpage.create(); - page.settings.userAgent = 'NonameServer'; - page.open('file://'+fs.workingDirectory+'/index.html?server='+id, function(status) { - if(status !== 'success') { - console.log(fs.workingDirectory); - console.log('Unable to access network'); - } - setInterval(function(){ - if(page.evaluate(function(){ - if(!lib.node||!lib.node.clients||!lib.node.clients.length){ - return true; - } - else{ - return false; - } - })){ - page.close(); - load(id); - } - },600000); - }); -} - -load(1); -load(2); -load(3); +var fs = require('fs'); +var webpage = require('webpage') +var load = function(id){ + var page = webpage.create(); + page.settings.userAgent = 'NonameServer'; + page.open('file://'+fs.workingDirectory+'/index.html?server='+id, function(status) { + if(status !== 'success') { + console.log(fs.workingDirectory); + console.log('Unable to access network'); + } + setInterval(function(){ + if(page.evaluate(function(){ + if(!lib.node||!lib.node.clients||!lib.node.clients.length){ + return true; + } + else{ + return false; + } + })){ + page.close(); + load(id); + } + },600000); + }); +} + +load(1); +load(2); +load(3); diff --git a/game/pressure.js b/game/pressure.js index dbf205895..9e66e7e70 100644 --- a/game/pressure.js +++ b/game/pressure.js @@ -1,576 +1,576 @@ -// Pressure v2.1.1 | Created By Stuart Yamartino | MIT License | 2015 - 2017 -;(function(root, factory) { - if (typeof define === 'function' && define.amd) { - define([], factory); - } else if (typeof exports === 'object') { - module.exports = factory(); - } else { - root.Pressure = factory(); - } -}(this, function() { -'use strict'; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -//--------------------- Public API Section ---------------------// -// this is the Pressure Object, this is the only object that is accessible to the end user -// only the methods in this object can be called, making it the "public api" - -var Pressure = { - - // targets any device with Force or 3D Touch - set: function set(selector, closure, options) { - loopPressureElements(selector, closure, options); - }, - - - // set configuration options for global config - config: function config(options) { - Config.set(options); - }, - - - // the map method allows for interpolating a value from one range of values to another - // example from the Arduino documentation: https://www.arduino.cc/en/Reference/Map - map: function map(x, in_min, in_max, out_min, out_max) { - return _map.apply(null, arguments); - } -}; - -var Element = function () { - function Element(el, block, options) { - _classCallCheck(this, Element); - - this.routeEvents(el, block, options); - this.preventSelect(el, options); - } - - _createClass(Element, [{ - key: 'routeEvents', - value: function routeEvents(el, block, options) { - var type = Config.get('only', options); - // for devices that support pointer events - if (supportsPointer && (type === 'pointer' || type === null)) { - this.adapter = new AdapterPointer(el, block, options).bindEvents(); - } - // for devices that support 3D Touch - else if (supportsTouch && (type === 'touch' || type === null)) { - this.adapter = new Adapter3DTouch(el, block, options).bindEvents(); - } - // for devices that support Force Touch - else if (supportsMouse && (type === 'mouse' || type === null)) { - this.adapter = new AdapterForceTouch(el, block, options).bindEvents(); - } - // unsupported if it is requesting a type and your browser is of other type - else { - this.adapter = new Adapter(el, block).bindUnsupportedEvent(); - } - } - - // prevent the default action of text selection, "peak & pop", and force touch special feature - - }, { - key: 'preventSelect', - value: function preventSelect(el, options) { - if (Config.get('preventSelect', options)) { - el.style.webkitTouchCallout = "none"; - el.style.webkitUserSelect = "none"; - el.style.khtmlUserSelect = "none"; - el.style.MozUserSelect = "none"; - el.style.msUserSelect = "none"; - el.style.userSelect = "none"; - } - } - }]); - - return Element; -}(); - -/* -This is the base adapter from which all the other adapters extend. -*/ - -var Adapter = function () { - function Adapter(el, block, options) { - _classCallCheck(this, Adapter); - - this.el = el; - this.block = block; - this.options = options; - this.pressed = false; - this.deepPressed = false; - this.nativeSupport = false; - this.runningPolyfill = false; - this.runKey = Math.random(); - } - - _createClass(Adapter, [{ - key: 'setPressed', - value: function setPressed(boolean) { - this.pressed = boolean; - } - }, { - key: 'setDeepPressed', - value: function setDeepPressed(boolean) { - this.deepPressed = boolean; - } - }, { - key: 'isPressed', - value: function isPressed() { - return this.pressed; - } - }, { - key: 'isDeepPressed', - value: function isDeepPressed() { - return this.deepPressed; - } - }, { - key: 'add', - value: function add(event, set) { - this.el.addEventListener(event, set, false); - } - }, { - key: 'runClosure', - value: function runClosure(method) { - if (method in this.block) { - // call the closure method and apply nth arguments if they exist - this.block[method].apply(this.el, Array.prototype.slice.call(arguments, 1)); - } - } - }, { - key: 'fail', - value: function fail(event, runKey) { - if (Config.get('polyfill', this.options)) { - if (this.runKey === runKey) { - this.runPolyfill(event); - } - } else { - this.runClosure('unsupported', event); - } - } - }, { - key: 'bindUnsupportedEvent', - value: function bindUnsupportedEvent() { - var _this = this; - - this.add(supportsTouch ? 'touchstart' : 'mousedown', function (event) { - return _this.runClosure('unsupported', event); - }); - } - }, { - key: '_startPress', - value: function _startPress(event) { - if (this.isPressed() === false) { - this.runningPolyfill = false; - this.setPressed(true); - this.runClosure('start', event); - } - } - }, { - key: '_startDeepPress', - value: function _startDeepPress(event) { - if (this.isPressed() && this.isDeepPressed() === false) { - this.setDeepPressed(true); - this.runClosure('startDeepPress', event); - } - } - }, { - key: '_changePress', - value: function _changePress(force, event) { - this.nativeSupport = true; - this.runClosure('change', force, event); - } - }, { - key: '_endDeepPress', - value: function _endDeepPress() { - if (this.isPressed() && this.isDeepPressed()) { - this.setDeepPressed(false); - this.runClosure('endDeepPress'); - } - } - }, { - key: '_endPress', - value: function _endPress() { - if (this.runningPolyfill === false) { - if (this.isPressed()) { - this._endDeepPress(); - this.setPressed(false); - this.runClosure('end'); - } - this.runKey = Math.random(); - this.nativeSupport = false; - } else { - this.setPressed(false); - } - } - }, { - key: 'deepPress', - value: function deepPress(force, event) { - force >= 0.5 ? this._startDeepPress(event) : this._endDeepPress(); - } - }, { - key: 'runPolyfill', - value: function runPolyfill(event) { - this.increment = Config.get('polyfillSpeedUp', this.options) === 0 ? 1 : 10 / Config.get('polyfillSpeedUp', this.options); - this.decrement = Config.get('polyfillSpeedDown', this.options) === 0 ? 1 : 10 / Config.get('polyfillSpeedDown', this.options); - this.setPressed(true); - this.runClosure('start', event); - if (this.runningPolyfill === false) { - this.loopPolyfillForce(0, event); - } - } - }, { - key: 'loopPolyfillForce', - value: function loopPolyfillForce(force, event) { - if (this.nativeSupport === false) { - if (this.isPressed()) { - this.runningPolyfill = true; - force = force + this.increment > 1 ? 1 : force + this.increment; - this.runClosure('change', force, event); - this.deepPress(force, event); - setTimeout(this.loopPolyfillForce.bind(this, force, event), 10); - } else { - force = force - this.decrement < 0 ? 0 : force - this.decrement; - if (force < 0.5 && this.isDeepPressed()) { - this.setDeepPressed(false); - this.runClosure('endDeepPress'); - } - if (force === 0) { - this.runningPolyfill = false; - this.setPressed(true); - this._endPress(); - } else { - this.runClosure('change', force, event); - this.deepPress(force, event); - setTimeout(this.loopPolyfillForce.bind(this, force, event), 10); - } - } - } - } - }]); - - return Adapter; -}(); - -/* -This adapter is for Macs with Force Touch trackpads. -*/ - -var AdapterForceTouch = function (_Adapter) { - _inherits(AdapterForceTouch, _Adapter); - - function AdapterForceTouch(el, block, options) { - _classCallCheck(this, AdapterForceTouch); - - return _possibleConstructorReturn(this, (AdapterForceTouch.__proto__ || Object.getPrototypeOf(AdapterForceTouch)).call(this, el, block, options)); - } - - _createClass(AdapterForceTouch, [{ - key: 'bindEvents', - value: function bindEvents() { - this.add('webkitmouseforcewillbegin', this._startPress.bind(this)); - this.add('mousedown', this.support.bind(this)); - this.add('webkitmouseforcechanged', this.change.bind(this)); - this.add('webkitmouseforcedown', this._startDeepPress.bind(this)); - this.add('webkitmouseforceup', this._endDeepPress.bind(this)); - this.add('mouseleave', this._endPress.bind(this)); - this.add('mouseup', this._endPress.bind(this)); - } - }, { - key: 'support', - value: function support(event) { - if (this.isPressed() === false) { - this.fail(event, this.runKey); - } - } - }, { - key: 'change', - value: function change(event) { - if (this.isPressed() && event.webkitForce > 0) { - this._changePress(this.normalizeForce(event.webkitForce), event); - } - } - - // make the force the standard 0 to 1 scale and not the 1 to 3 scale - - }, { - key: 'normalizeForce', - value: function normalizeForce(force) { - return this.reachOne(_map(force, 1, 3, 0, 1)); - } - - // if the force value is above 0.995 set the force to 1 - - }, { - key: 'reachOne', - value: function reachOne(force) { - return force > 0.995 ? 1 : force; - } - }]); - - return AdapterForceTouch; -}(Adapter); - -/* -This adapter is more mobile devices that support 3D Touch. -*/ - -var Adapter3DTouch = function (_Adapter2) { - _inherits(Adapter3DTouch, _Adapter2); - - function Adapter3DTouch(el, block, options) { - _classCallCheck(this, Adapter3DTouch); - - return _possibleConstructorReturn(this, (Adapter3DTouch.__proto__ || Object.getPrototypeOf(Adapter3DTouch)).call(this, el, block, options)); - } - - _createClass(Adapter3DTouch, [{ - key: 'bindEvents', - value: function bindEvents() { - if (supportsTouchForceChange) { - this.add('touchforcechange', this.start.bind(this)); - this.add('touchstart', this.support.bind(this, 0)); - this.add('touchend', this._endPress.bind(this)); - } else { - this.add('touchstart', this.startLegacy.bind(this)); - this.add('touchend', this._endPress.bind(this)); - } - } - }, { - key: 'start', - value: function start(event) { - if (event.touches.length > 0) { - this._startPress(event); - this.touch = this.selectTouch(event); - if (this.touch) { - this._changePress(this.touch.force, event); - } - } - } - }, { - key: 'support', - value: function support(iter, event) { - var runKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.runKey; - - if (this.isPressed() === false) { - if (iter <= 6) { - iter++; - setTimeout(this.support.bind(this, iter, event, runKey), 10); - } else { - this.fail(event, runKey); - } - } - } - }, { - key: 'startLegacy', - value: function startLegacy(event) { - this.initialForce = event.touches[0].force; - this.supportLegacy(0, event, this.runKey, this.initialForce); - } - - // this checks up to 6 times on a touch to see if the touch can read a force value - // if the force value has changed it means the device supports pressure - // more info from this issue https://github.com/yamartino/pressure/issues/15 - - }, { - key: 'supportLegacy', - value: function supportLegacy(iter, event, runKey, force) { - if (force !== this.initialForce) { - this._startPress(event); - this.loopForce(event); - } else if (iter <= 6) { - iter++; - setTimeout(this.supportLegacy.bind(this, iter, event, runKey, force), 10); - } else { - this.fail(event, runKey); - } - } - }, { - key: 'loopForce', - value: function loopForce(event) { - if (this.isPressed()) { - this.touch = this.selectTouch(event); - setTimeout(this.loopForce.bind(this, event), 10); - this._changePress(this.touch.force, event); - } - } - - // link up the touch point to the correct element, this is to support multitouch - - }, { - key: 'selectTouch', - value: function selectTouch(event) { - if (event.touches.length === 1) { - return this.returnTouch(event.touches[0], event); - } else { - for (var i = 0; i < event.touches.length; i++) { - // if the target press is on this element - if (event.touches[i].target === this.el || this.el.contains(event.touches[i].target)) { - return this.returnTouch(event.touches[i], event); - } - } - } - } - - // return the touch and run a start or end for deep press - - }, { - key: 'returnTouch', - value: function returnTouch(touch, event) { - this.deepPress(touch.force, event); - return touch; - } - }]); - - return Adapter3DTouch; -}(Adapter); - -/* -This adapter is for devices that support pointer events. -*/ - -var AdapterPointer = function (_Adapter3) { - _inherits(AdapterPointer, _Adapter3); - - function AdapterPointer(el, block, options) { - _classCallCheck(this, AdapterPointer); - - return _possibleConstructorReturn(this, (AdapterPointer.__proto__ || Object.getPrototypeOf(AdapterPointer)).call(this, el, block, options)); - } - - _createClass(AdapterPointer, [{ - key: 'bindEvents', - value: function bindEvents() { - this.add('pointerdown', this.support.bind(this)); - this.add('pointermove', this.change.bind(this)); - this.add('pointerup', this._endPress.bind(this)); - this.add('pointerleave', this._endPress.bind(this)); - } - }, { - key: 'support', - value: function support(event) { - if (this.isPressed() === false) { - if (event.pressure === 0 || event.pressure === 0.5) { - this.fail(event, this.runKey); - } else { - this._startPress(event); - this._changePress(event.pressure, event); - } - } - } - }, { - key: 'change', - value: function change(event) { - if (this.isPressed() && event.pressure > 0 && event.pressure !== 0.5) { - this._changePress(event.pressure, event); - this.deepPress(event.pressure, event); - } - } - }]); - - return AdapterPointer; -}(Adapter); - -// This class holds the states of the the Pressure config - - -var Config = { - - // 'false' will make polyfill not run when pressure is not supported and the 'unsupported' method will be called - polyfill: true, - - // milliseconds it takes to go from 0 to 1 for the polyfill - polyfillSpeedUp: 1000, - - // milliseconds it takes to go from 1 to 0 for the polyfill - polyfillSpeedDown: 0, - - // 'true' prevents the selecting of text and images via css properties - preventSelect: true, - - // 'touch', 'mouse', or 'pointer' will make it run only on that type of device - only: null, - - // this will get the correct config / option settings for the current pressure check - get: function get(option, options) { - return options.hasOwnProperty(option) ? options[option] : this[option]; - }, - - - // this will set the global configs - set: function set(options) { - for (var k in options) { - if (options.hasOwnProperty(k) && this.hasOwnProperty(k) && k != 'get' && k != 'set') { - this[k] = options[k]; - } - } - } -}; - -//------------------- Helpers -------------------// - -// accepts jQuery object, node list, string selector, then called a setup for each element -var loopPressureElements = function loopPressureElements(selector, closure) { - var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - - // if a string is passed in as an element - if (typeof selector === 'string' || selector instanceof String) { - var elements = document.querySelectorAll(selector); - for (var i = 0; i < elements.length; i++) { - new Element(elements[i], closure, options); - } - // if a single element object is passed in - } else if (isElement(selector)) { - new Element(selector, closure, options); - // if a node list is passed in ex. jQuery $() object - } else { - for (var i = 0; i < selector.length; i++) { - new Element(selector[i], closure, options); - } - } -}; - -//Returns true if it is a DOM element -var isElement = function isElement(o) { - return (typeof HTMLElement === 'undefined' ? 'undefined' : _typeof(HTMLElement)) === "object" ? o instanceof HTMLElement : //DOM2 - o && (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string"; -}; - -// the map method allows for interpolating a value from one range of values to another -// example from the Arduino documentation: https://www.arduino.cc/en/Reference/Map -var _map = function _map(x, in_min, in_max, out_min, out_max) { - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -}; - -var supportsMouse = false; -var supportsTouch = false; -var supportsPointer = false; -var supportsTouchForce = false; -var supportsTouchForceChange = false; - -if (typeof window !== 'undefined') { - // only attempt to assign these in a browser environment. - // on the server, this is a no-op, like the rest of the library - if (typeof Touch !== 'undefined') { - // In Android, new Touch requires arguments. - try { - if (Touch.prototype.hasOwnProperty('force') || 'force' in new Touch()) { - supportsTouchForce = true; - } - } catch (e) {} - } - supportsTouch = 'ontouchstart' in window.document && supportsTouchForce; - supportsMouse = 'onmousemove' in window.document && !supportsTouch; - supportsPointer = 'onpointermove' in window.document; - supportsTouchForceChange = 'ontouchforcechange' in window.document; -} -return Pressure; -})); +// Pressure v2.1.1 | Created By Stuart Yamartino | MIT License | 2015 - 2017 +;(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.Pressure = factory(); + } +}(this, function() { +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +//--------------------- Public API Section ---------------------// +// this is the Pressure Object, this is the only object that is accessible to the end user +// only the methods in this object can be called, making it the "public api" + +var Pressure = { + + // targets any device with Force or 3D Touch + set: function set(selector, closure, options) { + loopPressureElements(selector, closure, options); + }, + + + // set configuration options for global config + config: function config(options) { + Config.set(options); + }, + + + // the map method allows for interpolating a value from one range of values to another + // example from the Arduino documentation: https://www.arduino.cc/en/Reference/Map + map: function map(x, in_min, in_max, out_min, out_max) { + return _map.apply(null, arguments); + } +}; + +var Element = function () { + function Element(el, block, options) { + _classCallCheck(this, Element); + + this.routeEvents(el, block, options); + this.preventSelect(el, options); + } + + _createClass(Element, [{ + key: 'routeEvents', + value: function routeEvents(el, block, options) { + var type = Config.get('only', options); + // for devices that support pointer events + if (supportsPointer && (type === 'pointer' || type === null)) { + this.adapter = new AdapterPointer(el, block, options).bindEvents(); + } + // for devices that support 3D Touch + else if (supportsTouch && (type === 'touch' || type === null)) { + this.adapter = new Adapter3DTouch(el, block, options).bindEvents(); + } + // for devices that support Force Touch + else if (supportsMouse && (type === 'mouse' || type === null)) { + this.adapter = new AdapterForceTouch(el, block, options).bindEvents(); + } + // unsupported if it is requesting a type and your browser is of other type + else { + this.adapter = new Adapter(el, block).bindUnsupportedEvent(); + } + } + + // prevent the default action of text selection, "peak & pop", and force touch special feature + + }, { + key: 'preventSelect', + value: function preventSelect(el, options) { + if (Config.get('preventSelect', options)) { + el.style.webkitTouchCallout = "none"; + el.style.webkitUserSelect = "none"; + el.style.khtmlUserSelect = "none"; + el.style.MozUserSelect = "none"; + el.style.msUserSelect = "none"; + el.style.userSelect = "none"; + } + } + }]); + + return Element; +}(); + +/* +This is the base adapter from which all the other adapters extend. +*/ + +var Adapter = function () { + function Adapter(el, block, options) { + _classCallCheck(this, Adapter); + + this.el = el; + this.block = block; + this.options = options; + this.pressed = false; + this.deepPressed = false; + this.nativeSupport = false; + this.runningPolyfill = false; + this.runKey = Math.random(); + } + + _createClass(Adapter, [{ + key: 'setPressed', + value: function setPressed(boolean) { + this.pressed = boolean; + } + }, { + key: 'setDeepPressed', + value: function setDeepPressed(boolean) { + this.deepPressed = boolean; + } + }, { + key: 'isPressed', + value: function isPressed() { + return this.pressed; + } + }, { + key: 'isDeepPressed', + value: function isDeepPressed() { + return this.deepPressed; + } + }, { + key: 'add', + value: function add(event, set) { + this.el.addEventListener(event, set, false); + } + }, { + key: 'runClosure', + value: function runClosure(method) { + if (method in this.block) { + // call the closure method and apply nth arguments if they exist + this.block[method].apply(this.el, Array.prototype.slice.call(arguments, 1)); + } + } + }, { + key: 'fail', + value: function fail(event, runKey) { + if (Config.get('polyfill', this.options)) { + if (this.runKey === runKey) { + this.runPolyfill(event); + } + } else { + this.runClosure('unsupported', event); + } + } + }, { + key: 'bindUnsupportedEvent', + value: function bindUnsupportedEvent() { + var _this = this; + + this.add(supportsTouch ? 'touchstart' : 'mousedown', function (event) { + return _this.runClosure('unsupported', event); + }); + } + }, { + key: '_startPress', + value: function _startPress(event) { + if (this.isPressed() === false) { + this.runningPolyfill = false; + this.setPressed(true); + this.runClosure('start', event); + } + } + }, { + key: '_startDeepPress', + value: function _startDeepPress(event) { + if (this.isPressed() && this.isDeepPressed() === false) { + this.setDeepPressed(true); + this.runClosure('startDeepPress', event); + } + } + }, { + key: '_changePress', + value: function _changePress(force, event) { + this.nativeSupport = true; + this.runClosure('change', force, event); + } + }, { + key: '_endDeepPress', + value: function _endDeepPress() { + if (this.isPressed() && this.isDeepPressed()) { + this.setDeepPressed(false); + this.runClosure('endDeepPress'); + } + } + }, { + key: '_endPress', + value: function _endPress() { + if (this.runningPolyfill === false) { + if (this.isPressed()) { + this._endDeepPress(); + this.setPressed(false); + this.runClosure('end'); + } + this.runKey = Math.random(); + this.nativeSupport = false; + } else { + this.setPressed(false); + } + } + }, { + key: 'deepPress', + value: function deepPress(force, event) { + force >= 0.5 ? this._startDeepPress(event) : this._endDeepPress(); + } + }, { + key: 'runPolyfill', + value: function runPolyfill(event) { + this.increment = Config.get('polyfillSpeedUp', this.options) === 0 ? 1 : 10 / Config.get('polyfillSpeedUp', this.options); + this.decrement = Config.get('polyfillSpeedDown', this.options) === 0 ? 1 : 10 / Config.get('polyfillSpeedDown', this.options); + this.setPressed(true); + this.runClosure('start', event); + if (this.runningPolyfill === false) { + this.loopPolyfillForce(0, event); + } + } + }, { + key: 'loopPolyfillForce', + value: function loopPolyfillForce(force, event) { + if (this.nativeSupport === false) { + if (this.isPressed()) { + this.runningPolyfill = true; + force = force + this.increment > 1 ? 1 : force + this.increment; + this.runClosure('change', force, event); + this.deepPress(force, event); + setTimeout(this.loopPolyfillForce.bind(this, force, event), 10); + } else { + force = force - this.decrement < 0 ? 0 : force - this.decrement; + if (force < 0.5 && this.isDeepPressed()) { + this.setDeepPressed(false); + this.runClosure('endDeepPress'); + } + if (force === 0) { + this.runningPolyfill = false; + this.setPressed(true); + this._endPress(); + } else { + this.runClosure('change', force, event); + this.deepPress(force, event); + setTimeout(this.loopPolyfillForce.bind(this, force, event), 10); + } + } + } + } + }]); + + return Adapter; +}(); + +/* +This adapter is for Macs with Force Touch trackpads. +*/ + +var AdapterForceTouch = function (_Adapter) { + _inherits(AdapterForceTouch, _Adapter); + + function AdapterForceTouch(el, block, options) { + _classCallCheck(this, AdapterForceTouch); + + return _possibleConstructorReturn(this, (AdapterForceTouch.__proto__ || Object.getPrototypeOf(AdapterForceTouch)).call(this, el, block, options)); + } + + _createClass(AdapterForceTouch, [{ + key: 'bindEvents', + value: function bindEvents() { + this.add('webkitmouseforcewillbegin', this._startPress.bind(this)); + this.add('mousedown', this.support.bind(this)); + this.add('webkitmouseforcechanged', this.change.bind(this)); + this.add('webkitmouseforcedown', this._startDeepPress.bind(this)); + this.add('webkitmouseforceup', this._endDeepPress.bind(this)); + this.add('mouseleave', this._endPress.bind(this)); + this.add('mouseup', this._endPress.bind(this)); + } + }, { + key: 'support', + value: function support(event) { + if (this.isPressed() === false) { + this.fail(event, this.runKey); + } + } + }, { + key: 'change', + value: function change(event) { + if (this.isPressed() && event.webkitForce > 0) { + this._changePress(this.normalizeForce(event.webkitForce), event); + } + } + + // make the force the standard 0 to 1 scale and not the 1 to 3 scale + + }, { + key: 'normalizeForce', + value: function normalizeForce(force) { + return this.reachOne(_map(force, 1, 3, 0, 1)); + } + + // if the force value is above 0.995 set the force to 1 + + }, { + key: 'reachOne', + value: function reachOne(force) { + return force > 0.995 ? 1 : force; + } + }]); + + return AdapterForceTouch; +}(Adapter); + +/* +This adapter is more mobile devices that support 3D Touch. +*/ + +var Adapter3DTouch = function (_Adapter2) { + _inherits(Adapter3DTouch, _Adapter2); + + function Adapter3DTouch(el, block, options) { + _classCallCheck(this, Adapter3DTouch); + + return _possibleConstructorReturn(this, (Adapter3DTouch.__proto__ || Object.getPrototypeOf(Adapter3DTouch)).call(this, el, block, options)); + } + + _createClass(Adapter3DTouch, [{ + key: 'bindEvents', + value: function bindEvents() { + if (supportsTouchForceChange) { + this.add('touchforcechange', this.start.bind(this)); + this.add('touchstart', this.support.bind(this, 0)); + this.add('touchend', this._endPress.bind(this)); + } else { + this.add('touchstart', this.startLegacy.bind(this)); + this.add('touchend', this._endPress.bind(this)); + } + } + }, { + key: 'start', + value: function start(event) { + if (event.touches.length > 0) { + this._startPress(event); + this.touch = this.selectTouch(event); + if (this.touch) { + this._changePress(this.touch.force, event); + } + } + } + }, { + key: 'support', + value: function support(iter, event) { + var runKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.runKey; + + if (this.isPressed() === false) { + if (iter <= 6) { + iter++; + setTimeout(this.support.bind(this, iter, event, runKey), 10); + } else { + this.fail(event, runKey); + } + } + } + }, { + key: 'startLegacy', + value: function startLegacy(event) { + this.initialForce = event.touches[0].force; + this.supportLegacy(0, event, this.runKey, this.initialForce); + } + + // this checks up to 6 times on a touch to see if the touch can read a force value + // if the force value has changed it means the device supports pressure + // more info from this issue https://github.com/yamartino/pressure/issues/15 + + }, { + key: 'supportLegacy', + value: function supportLegacy(iter, event, runKey, force) { + if (force !== this.initialForce) { + this._startPress(event); + this.loopForce(event); + } else if (iter <= 6) { + iter++; + setTimeout(this.supportLegacy.bind(this, iter, event, runKey, force), 10); + } else { + this.fail(event, runKey); + } + } + }, { + key: 'loopForce', + value: function loopForce(event) { + if (this.isPressed()) { + this.touch = this.selectTouch(event); + setTimeout(this.loopForce.bind(this, event), 10); + this._changePress(this.touch.force, event); + } + } + + // link up the touch point to the correct element, this is to support multitouch + + }, { + key: 'selectTouch', + value: function selectTouch(event) { + if (event.touches.length === 1) { + return this.returnTouch(event.touches[0], event); + } else { + for (var i = 0; i < event.touches.length; i++) { + // if the target press is on this element + if (event.touches[i].target === this.el || this.el.contains(event.touches[i].target)) { + return this.returnTouch(event.touches[i], event); + } + } + } + } + + // return the touch and run a start or end for deep press + + }, { + key: 'returnTouch', + value: function returnTouch(touch, event) { + this.deepPress(touch.force, event); + return touch; + } + }]); + + return Adapter3DTouch; +}(Adapter); + +/* +This adapter is for devices that support pointer events. +*/ + +var AdapterPointer = function (_Adapter3) { + _inherits(AdapterPointer, _Adapter3); + + function AdapterPointer(el, block, options) { + _classCallCheck(this, AdapterPointer); + + return _possibleConstructorReturn(this, (AdapterPointer.__proto__ || Object.getPrototypeOf(AdapterPointer)).call(this, el, block, options)); + } + + _createClass(AdapterPointer, [{ + key: 'bindEvents', + value: function bindEvents() { + this.add('pointerdown', this.support.bind(this)); + this.add('pointermove', this.change.bind(this)); + this.add('pointerup', this._endPress.bind(this)); + this.add('pointerleave', this._endPress.bind(this)); + } + }, { + key: 'support', + value: function support(event) { + if (this.isPressed() === false) { + if (event.pressure === 0 || event.pressure === 0.5) { + this.fail(event, this.runKey); + } else { + this._startPress(event); + this._changePress(event.pressure, event); + } + } + } + }, { + key: 'change', + value: function change(event) { + if (this.isPressed() && event.pressure > 0 && event.pressure !== 0.5) { + this._changePress(event.pressure, event); + this.deepPress(event.pressure, event); + } + } + }]); + + return AdapterPointer; +}(Adapter); + +// This class holds the states of the the Pressure config + + +var Config = { + + // 'false' will make polyfill not run when pressure is not supported and the 'unsupported' method will be called + polyfill: true, + + // milliseconds it takes to go from 0 to 1 for the polyfill + polyfillSpeedUp: 1000, + + // milliseconds it takes to go from 1 to 0 for the polyfill + polyfillSpeedDown: 0, + + // 'true' prevents the selecting of text and images via css properties + preventSelect: true, + + // 'touch', 'mouse', or 'pointer' will make it run only on that type of device + only: null, + + // this will get the correct config / option settings for the current pressure check + get: function get(option, options) { + return options.hasOwnProperty(option) ? options[option] : this[option]; + }, + + + // this will set the global configs + set: function set(options) { + for (var k in options) { + if (options.hasOwnProperty(k) && this.hasOwnProperty(k) && k != 'get' && k != 'set') { + this[k] = options[k]; + } + } + } +}; + +//------------------- Helpers -------------------// + +// accepts jQuery object, node list, string selector, then called a setup for each element +var loopPressureElements = function loopPressureElements(selector, closure) { + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + // if a string is passed in as an element + if (typeof selector === 'string' || selector instanceof String) { + var elements = document.querySelectorAll(selector); + for (var i = 0; i < elements.length; i++) { + new Element(elements[i], closure, options); + } + // if a single element object is passed in + } else if (isElement(selector)) { + new Element(selector, closure, options); + // if a node list is passed in ex. jQuery $() object + } else { + for (var i = 0; i < selector.length; i++) { + new Element(selector[i], closure, options); + } + } +}; + +//Returns true if it is a DOM element +var isElement = function isElement(o) { + return (typeof HTMLElement === 'undefined' ? 'undefined' : _typeof(HTMLElement)) === "object" ? o instanceof HTMLElement : //DOM2 + o && (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string"; +}; + +// the map method allows for interpolating a value from one range of values to another +// example from the Arduino documentation: https://www.arduino.cc/en/Reference/Map +var _map = function _map(x, in_min, in_max, out_min, out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +}; + +var supportsMouse = false; +var supportsTouch = false; +var supportsPointer = false; +var supportsTouchForce = false; +var supportsTouchForceChange = false; + +if (typeof window !== 'undefined') { + // only attempt to assign these in a browser environment. + // on the server, this is a no-op, like the rest of the library + if (typeof Touch !== 'undefined') { + // In Android, new Touch requires arguments. + try { + if (Touch.prototype.hasOwnProperty('force') || 'force' in new Touch()) { + supportsTouchForce = true; + } + } catch (e) {} + } + supportsTouch = 'ontouchstart' in window.document && supportsTouchForce; + supportsMouse = 'onmousemove' in window.document && !supportsTouch; + supportsPointer = 'onpointermove' in window.document; + supportsTouchForceChange = 'ontouchforcechange' in window.document; +} +return Pressure; +})); diff --git a/game/server.js b/game/server.js index ba349493c..0c9ceca36 100644 --- a/game/server.js +++ b/game/server.js @@ -1,358 +1,358 @@ -(function(){ - var WebSocketServer=require('ws').Server; - var wss=new WebSocketServer({port:8080}); - var bannedKeys=[]; - var bannedIps=[]; - - var rooms=[{},{},{},{},{},{}]; - var events=[]; - var clients={}; - var messages={ - enter:function(index,nickname,avatar,config,mode){ - this.nickname=nickname; - this.avatar=avatar; - var room=rooms[index]; - if(!room){ - index=0; - room=rooms[0]; - } - this.room=room; - delete this.status; - if(room.owner){ - if(room.servermode&&!room.owner._onconfig&&config&&mode){ - room.owner.sendl('createroom',index,config,mode); - room.owner._onconfig=this; - room.owner.nickname=nickname; - room.owner.avatar=avatar; - } - else if(!room.config){ - this.sendl('enterroomfailed'); - } - else{ - this.owner=room.owner; - this.owner.sendl('onconnection',this.wsid); - } - util.updaterooms(); - } - else{ - room.owner=this; - this.sendl('createroom',index); - } - }, - changeAvatar:function(nickname,avatar){ - this.nickname=nickname; - this.avatar=avatar; - util.updateclients(); - }, - server:function(cfg){ - if(cfg){ - this.servermode=true; - var room=rooms[cfg[0]]; - if(!room||room.owner){ - this.sendl('reloadroom',true); - } - else{ - room.owner=this; - this.room=room; - this.nickname=cfg[1]; - this.avatar=cfg[2]; - this.sendl('createroom',cfg[0],{},'auto') - } - } - else{ - for(var i=0;i=20){ - this.sendl('eventsdenied','total'); - } - else if(cfg.utc<=time){ - this.sendl('eventsdenied','time'); - } - else{ - cfg.nickname=cfg.nickname||'无名玩家'; - cfg.avatar=cfg.nickname||'caocao'; - cfg.creator=id; - cfg.id=util.getid(); - cfg.members=[id]; - events.unshift(cfg); - changed=true; - } - } - } - if(changed){ - util.updateevents(); - } - }, - config:function(config){ - var room=this.room; - if(room&&room.owner==this){ - if(room.servermode){ - room.servermode=false; - if(this._onconfig){ - if(clients[this._onconfig.wsid]){ - this._onconfig.owner=this; - this.sendl('onconnection',this._onconfig.wsid); - } - delete this._onconfig; - } - } - room.config=config; - } - util.updaterooms(); - }, - status:function(str){ - if(typeof str=='string'){ - this.status=str; - } - else{ - delete this.status; - } - util.updateclients(); - }, - send:function(id,message){ - if(clients[id]&&clients[id].owner==this){ - try{ - clients[id].send(message); - } - catch(e){ - clients[id].close(); - } - } - }, - close:function(id){ - if(clients[id]&&clients[id].owner==this){ - clients[id].close(); - } - }, - }; - var util={ - sendl:function(){ - var args=[]; - for(var i=0;i=20){ + this.sendl('eventsdenied','total'); + } + else if(cfg.utc<=time){ + this.sendl('eventsdenied','time'); + } + else{ + cfg.nickname=cfg.nickname||'无名玩家'; + cfg.avatar=cfg.nickname||'caocao'; + cfg.creator=id; + cfg.id=util.getid(); + cfg.members=[id]; + events.unshift(cfg); + changed=true; + } + } + } + if(changed){ + util.updateevents(); + } + }, + config:function(config){ + var room=this.room; + if(room&&room.owner==this){ + if(room.servermode){ + room.servermode=false; + if(this._onconfig){ + if(clients[this._onconfig.wsid]){ + this._onconfig.owner=this; + this.sendl('onconnection',this._onconfig.wsid); + } + delete this._onconfig; + } + } + room.config=config; + } + util.updaterooms(); + }, + status:function(str){ + if(typeof str=='string'){ + this.status=str; + } + else{ + delete this.status; + } + util.updateclients(); + }, + send:function(id,message){ + if(clients[id]&&clients[id].owner==this){ + try{ + clients[id].send(message); + } + catch(e){ + clients[id].close(); + } + } + }, + close:function(id){ + if(clients[id]&&clients[id].owner==this){ + clients[id].close(); + } + }, + }; + var util={ + sendl:function(){ + var args=[]; + for(var i=0;i