forked from mirrors/webxoss-core
2426 lines
No EOL
71 KiB
JavaScript
2426 lines
No EOL
71 KiB
JavaScript
'use strict';
|
||
|
||
function Player (game,io,mainDeck,lrigDeck) {
|
||
// 引用
|
||
this.game = game;
|
||
// this.client = open('../WEBXOSS_Client/test.html');
|
||
// this.client = new Client(this.input.bind(this));
|
||
this.io = io;
|
||
io.listener = function () {
|
||
if (!this.input.apply(this,arguments)) {
|
||
console.warn('Invalid input!');
|
||
console.warn(arguments);
|
||
var effectSource = this.game.getEffectSource();
|
||
if (effectSource) {
|
||
console.warn('EffectSource cid: %s',effectSource.cid);
|
||
}
|
||
// console.warn('IP Address: %s',this.io.socket.request.connection.remoteAddress);
|
||
}
|
||
}.bind(this);
|
||
|
||
// 注册
|
||
game.register(this);
|
||
|
||
// 储存的数据
|
||
this.listener = {}; // 监听器(监听玩家输入).
|
||
this.msgQueue = []; // 要向客户端发送的消息队列.
|
||
// 消息不是直接发送给客户端,而是先入队,
|
||
// 然后在调用堆栈的最后将整个队列发送.
|
||
this.messagePacks = []; // 用于保存录像.
|
||
this.rebuildCount = 0;
|
||
this.coin = 0;
|
||
|
||
// 快捷方式
|
||
this.hands = []; // 手牌
|
||
this.signis = [];
|
||
this.lrig = null;
|
||
this.opponent = null; // 对手(Player对象)
|
||
this.crossed = [];
|
||
|
||
// 区域
|
||
this.mainDeck = new Zone(game,this,'MainDeck','up',mainDeck);
|
||
this.lrigDeck = new Zone(game,this,'LrigDeck','checkable up',lrigDeck);
|
||
this.handZone = new Zone(game,this,'HandZone','checkable up inhand');
|
||
this.lrigZone = new Zone(game,this,'LrigZone','checkable up faceup');
|
||
this.signiZones = [new Zone(game,this,'SigniZone','up faceup bottom'),
|
||
new Zone(game,this,'SigniZone','up faceup bottom'),
|
||
new Zone(game,this,'SigniZone','up faceup bottom')];
|
||
this.enerZone = new Zone(game,this,'EnerZone','checkable faceup');
|
||
this.checkZone = new Zone(game,this,'CheckZone','checkable up faceup');
|
||
this.trashZone = new Zone(game,this,'TrashZone','checkable up faceup');
|
||
this.lrigTrashZone = new Zone(game,this,'LrigTrashZone','checkable up faceup');
|
||
this.lifeClothZone = new Zone(game,this,'LifeClothZone','');
|
||
this.excludedZone = new Zone(game,this,'ExcludedZone','checkable up faceup');
|
||
|
||
// 时点
|
||
// this.onLrigChange = new Timming(game);
|
||
// this.onSignisChange = new Timming(game);
|
||
this.onUseSpell = new Timming(game);
|
||
this.onUseArts = new Timming(game);
|
||
this.onCrash = new Timming(game);
|
||
this.onSigniBanished = new Timming(game);
|
||
this.onTurnStart = new Timming(game);
|
||
this.onTurnEnd = new Timming(game);
|
||
this.onSummonSigni = new Timming(game);
|
||
this.onCardMove = new Timming(game);
|
||
this.onBurstTriggered = new Timming(game);
|
||
this.onAttack = new Timming(game);
|
||
this.onAttackPrevented = new Timming(game);
|
||
this.onRebuild = new Timming(game);
|
||
this.onAttackPhaseStart = new Timming(game);
|
||
this.onGrowPhaseStart = new Timming(game);
|
||
this.onMainPhaseStart = new Timming(game);
|
||
this.onTurnEnd2 = new Timming(game); // 注: 详见 Phase.js 的 endPhase 函数.
|
||
this.onHeaven = new Timming(game);
|
||
this.onSigniFreezed = new Timming(game);
|
||
this.onSigniLeaveField = new Timming(game);
|
||
this.onDiscard = new Timming(game);
|
||
this.onDoubleCrashed = new Timming(game);
|
||
this.onDraw = new Timming(game);
|
||
this.onRemoveVirus = new Timming(game);
|
||
this.onTrapTriggered = new Timming(game);
|
||
|
||
// 附加属性
|
||
this.skipGrowPhase = false;
|
||
this.guardLimit = 0;
|
||
this.banishTrash = false;
|
||
this.ignoreGrowCost = false;
|
||
this.lrigAttackBanned = false; // 与 lrig.canNotAttack 不同,不会被无效.
|
||
this.wontBeCrashed = false;
|
||
this.wontBeCrashedExceptDamage = false;
|
||
this._trashLifeClothCount = 0;
|
||
this.forceSigniAttack = false;
|
||
this.drawCount = 2;
|
||
this._ionaUltimaMaiden = false; // <究极/少女 伊绪奈>
|
||
this.twoSignisLimit = false; // <白罗星 土星> <开辟者 塔维尔=TRE>
|
||
this.spellCancelable = true;
|
||
this.artsBanned = false;
|
||
this.trashSigniBanned = false;
|
||
this._ViolenceSplashCount = 0; // <暴力飞溅>
|
||
this._DreiDioDaughter = 0; // <DREI=恶英娘>
|
||
this.powerChangeBanned = false;
|
||
this.skipSigniAttackStep = false;
|
||
this.skipLrigAttackStep = false;
|
||
this.addCardToHandBanned = false;
|
||
this.spellBanned = false;
|
||
this.skipEnerPhase = false;
|
||
this.ignoreLimitingOfArtsAndSpell = false;
|
||
this.ignoreLimitingOfLevel5Signi = false; // <紡ぐ者>
|
||
this.summonPowerLimit = 0;
|
||
this.additionalRevealCount = 0; // <反复的独立性 网格>
|
||
this.useBikouAsWhiteCost = false; // <不思議な童話 メルへ>
|
||
this.burstTwice = false; // <Burst Rush>
|
||
this.wontBeDamaged = false; // <音阶的右律 G>
|
||
this.wontBeDamagedByOpponentLrig = false; // <紡ぐ者>
|
||
this.actionEffectBanned = false;
|
||
this.charmedActionEffectBanned = false; // <黒幻蟲 アラクネ・パイダ>
|
||
this.canNotGrow = false; // <ドント・グロウ>
|
||
this._HammerChance = false; // <ハンマー・チャンス>
|
||
this._VierVX = false; // <VIER=维克斯>
|
||
this._RanergeOriginalSpear = []; // <原槍 ラナジェ>
|
||
this.reducedGrowCostWhite = 0;
|
||
this.reducedGrowCostBlack = 0;
|
||
this.reducedGrowCostRed = 0;
|
||
this.reducedGrowCostBlue = 0;
|
||
this.reducedGrowCostGreen = 0;
|
||
this.reducedGrowCostColorless = 0;
|
||
this.attackCount = 0; // <暴风警报>
|
||
this._stormWarning = false; // <暴风警报>
|
||
this.guardBannedLevels = []; // <缚魔炎 花代·叁>
|
||
this.discardOnAttackPhase = false; // <雪月風火 花代・肆>
|
||
this.signiStartUpBanned = false; // <呪われし数字 666>
|
||
this.lrigStartUpBanned = false; // <花咲乱 游月·叁>
|
||
this.signiAttackCountLimit = Infinity;
|
||
this.signiTotalAttackCountLimit = Infinity;
|
||
this.lrigAttackCountLimit = Infinity;
|
||
this.canNotGuard = false;
|
||
this.wontLoseGame = false; // <紅蓮乙女 遊月・肆>
|
||
this.canNotBeBanished = false;
|
||
this.canNotBeBounced = false;
|
||
this.signiCanNotGainAbility = false;
|
||
this.canNotBeDownedByOpponentEffect = false;
|
||
this.canNotUseColorlessSigni = false; // <绿罗植 世界树>
|
||
this.canNotUseColorlessSpell = false; // <绿罗植 世界树>
|
||
this.skipNextTurn = false;
|
||
|
||
this.usedActionEffects = [];
|
||
this.chain = null;
|
||
this.inResonaAction = null; // 是否正在执行共鸣单位的出现条件. (同时也是正在执行出现条件的那只共鸣单位)
|
||
this.inActionEffectCost = false; // 是否正在支付起动能力的COST
|
||
this.bannedCards = []; // <漆黑之棺>
|
||
this.oneArtEachTurn = false; // <博愛の使者 サシェ・リュンヌ>
|
||
}
|
||
|
||
// Player.prototype.rockPaperScissorsAsyn = function () {
|
||
// var player = this;
|
||
// player.output({
|
||
// type: 'ROCK_PAPER_SCISSORS',
|
||
// content: {}
|
||
// });
|
||
// return new Callback(function (callback) {
|
||
// player.listen('ROCK_PAPER_SCISSORS',function (input) {
|
||
// input = +input;
|
||
// if (!(input>=0 && input<=2)) return false;
|
||
// return function () {
|
||
// callback(input);
|
||
// };
|
||
// });
|
||
// });
|
||
// };
|
||
|
||
// 玩家设置lrig
|
||
// (从LRIG卡组里选择等级0的卡片,背面表示放置到LRIG区)
|
||
Player.prototype.setupLrigAsyn = function () {
|
||
var cards = this.lrigDeck.cards.filter(function (card) {
|
||
return (card.type === 'LRIG') && (card.level === 0);
|
||
});
|
||
return Callback.immediately().callback(this,function () {
|
||
if (cards.length === 1) return cards[0];
|
||
return this.selectAsyn('LEVEL0_LRIG',cards);;
|
||
}).callback(this,function (card) {
|
||
card.moveTo(this.lrigZone,{faceup: false});
|
||
});
|
||
};
|
||
|
||
// 玩家抽起始手牌(5张)
|
||
Player.prototype.setupHands = function () {
|
||
this.draw(5);
|
||
};
|
||
|
||
// 玩家重抽手牌
|
||
// (将不需要的卡片返回主卡组并洗牌,再从主卡组顶端抽和返回的卡片数量相同的卡片.)
|
||
Player.prototype.redrawAsyn = function () {
|
||
return this.selectSomeAsyn('DISCARD_AND_REDRAW',this.hands).callback(this,function (cards) {
|
||
this.game.moveCards(cards,this.mainDeck);
|
||
this.shuffle();
|
||
this.draw(cards.length);
|
||
});
|
||
};
|
||
|
||
// 玩家洗切牌组
|
||
Player.prototype.shuffle = function (cards) {
|
||
if (!cards) {
|
||
cards = this.mainDeck.cards;
|
||
}
|
||
var len = cards.length;
|
||
|
||
if (!len) return;
|
||
|
||
var oldSids = cards.map(function (card) {
|
||
return {
|
||
a: this.game.getSid(this,card),
|
||
b: this.game.getSid(this.opponent,card)
|
||
};
|
||
},this);
|
||
|
||
for (var i = 0; i < len-1; i++) {
|
||
var r = this.game.rand(i,len-1);
|
||
var tmp = cards[i];
|
||
cards[i] = cards[r];
|
||
cards[r] = tmp;
|
||
}
|
||
|
||
cards.forEach(function (card,idx) {
|
||
this.game.setSid(this,card,oldSids[idx].a);
|
||
this.game.setSid(this.opponent,card,oldSids[idx].b);
|
||
},this);
|
||
|
||
this.game.output({
|
||
type: 'SHUFFLE',
|
||
content: {
|
||
cards: cards
|
||
}
|
||
});
|
||
};
|
||
|
||
// 玩家设置生命护甲
|
||
// (从主卡组顶将7张卡依次重叠放置到生命护甲区)
|
||
Player.prototype.setupLifeCloth = function () {
|
||
var cards = this.mainDeck.getTopCards(7);
|
||
this.game.moveCards(cards,this.lifeClothZone);
|
||
};
|
||
|
||
// OPEN!!
|
||
Player.prototype.open = function () {
|
||
this.lrig.faceup();
|
||
this.setCoin(this.coin); // output
|
||
};
|
||
|
||
// 玩家把lrig和所有signi竖置 (竖置阶段)
|
||
Player.prototype.up = function () {
|
||
var cards = concat(this.lrig,this.signis);
|
||
if (!cards.length) return;
|
||
this.game.packOutputs(function () {
|
||
this.game.frame(this,function () {
|
||
cards.forEach(function (card) {
|
||
if (card.frozen) {
|
||
card.frozen = false;
|
||
} else {
|
||
card.up();
|
||
}
|
||
},this);
|
||
});
|
||
},this);
|
||
};
|
||
|
||
// 玩家从卡组抽取n张卡
|
||
// 返回:
|
||
// cards: Array,抽到的卡,长度可能少于n(卡组没有足够的卡可以抽),也可能为空数组.
|
||
Player.prototype.draw = function (n) {
|
||
if (this.addCardToHandBanned) return [];
|
||
var cards = this.mainDeck.getTopCards(n);
|
||
if (!cards.length) return [];
|
||
if (this.game.phase.isAttackPhase()) {
|
||
var count = this.game.getData(this,'attackPhaseDrawCount') || 0;
|
||
count += cards.length;
|
||
this.game.setData(this,'attackPhaseDrawCount',count);
|
||
}
|
||
this.onDraw.trigger();
|
||
this.game.moveCards(cards,this.handZone);
|
||
return cards;
|
||
};
|
||
|
||
// 被动充能
|
||
// 从卡组最上面拿n张卡放到能量区.
|
||
// 跟主动充能 player.chargeAsyn() 不一样.
|
||
Player.prototype.enerCharge = function (n) {
|
||
var cards = this.mainDeck.getTopCards(n);
|
||
this.game.moveCards(cards,this.enerZone);
|
||
return cards;
|
||
};
|
||
|
||
// 舍弃n张卡
|
||
Player.prototype.discardAsyn = function (n) {
|
||
if (!this.hands.length || !n) return Callback.immediately([]);
|
||
if (this.hands.length < n) {
|
||
n = this.hands.length;
|
||
}
|
||
return this.selectSomeAsyn('DISCARD',this.hands,n,n).callback(this,function (cards) {
|
||
return this.discardCards(cards);
|
||
});
|
||
};
|
||
|
||
// 随机舍弃n张卡
|
||
Player.prototype.discardRandomly = function (n) {
|
||
if (!isNum(n)) n = 1;
|
||
var cards = [];
|
||
var hands = this.hands.slice();
|
||
for (var i = 0; i < n; i++) {
|
||
if (!hands.length) break;
|
||
var idx = this.game.rand(0,hands.length - 1);
|
||
cards.push(hands[idx]);
|
||
removeFromArr(hands[idx],hands);
|
||
}
|
||
return this.discardCards(cards);
|
||
};
|
||
|
||
// 舍弃指定的卡
|
||
Player.prototype.discardCards = function (cards) {
|
||
return this.game.trashCards(cards);
|
||
};
|
||
|
||
// 玩家进行充能操作 (主动充能)
|
||
// 玩家从手牌或自己场上的SIGNI选一张卡放到能力区.
|
||
Player.prototype.chargeAsyn = function () {
|
||
var cards = concat(this.hands,this.signis);
|
||
if (!cards.length) return Callback.never();
|
||
return this.selectAsyn('CHARGE',cards).callback(this,function (card) {
|
||
return this.game.blockAsyn(this,function () {
|
||
card.moveTo(this.enerZone);
|
||
});
|
||
});
|
||
};
|
||
|
||
// 玩家结束充能阶段
|
||
Player.prototype.endEnerPhaseAsyn = function () {
|
||
return this.selectAsyn('END_ENER_PHASE');
|
||
};
|
||
|
||
// 玩家进行成长操作 (主动成长)
|
||
Player.prototype.growAsyn = function () {
|
||
var cards = this.lrigDeck.cards.filter(function (card) {
|
||
return card.canGrow(this.ignoreGrowCost);
|
||
},this);
|
||
if (!cards.length) return Callback.never();
|
||
return this.selectAsyn('GROW',cards).callback(this,function (card) {
|
||
return this.game.blockAsyn(this,function () {
|
||
// return Callback.immediately().callback(this,function () {
|
||
// if (!card.growActionAsyn) return;
|
||
// return card.growActionAsyn();
|
||
// }).callback(this,function () {
|
||
// if (this.ignoreGrowCost) return;
|
||
// return this.payCostAsyn(card);
|
||
// }).callback(this,function () {
|
||
// var colorChanged = (card.color !== this.lrig.color);
|
||
// card.moveTo(this.lrigZone,{
|
||
// up: this.lrig.isUp
|
||
// });
|
||
// if (colorChanged) this.game.outputColor();
|
||
// });
|
||
return card.growAsyn();
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.resetSignisAsyn = function () {
|
||
var signis = [];
|
||
var totalLevel = this.signis.reduce(function (total,signi) {
|
||
return total + signi.level;
|
||
},0);
|
||
// 超出 SIGNI 数量限制
|
||
if (this.signis.length > this.getSigniAmountLimit()) {
|
||
signis = this.signis;
|
||
} else if (totalLevel > this.lrig.limit) {
|
||
// 超出界限
|
||
signis = this.signis;
|
||
} else {
|
||
// 限制及等级
|
||
signis = this.signis.filter(function (signi) {
|
||
return (!signi.checkLimiting()) || (signi.level > this.lrig.level);
|
||
},this);
|
||
}
|
||
if (!signis.length) return Callback.immediately();
|
||
return this.selectAsyn('TRASH_SIGNI',signis).callback(this,function (card) {
|
||
return this.game.blockAsyn(this,function () {
|
||
card.trash();
|
||
});
|
||
}).callback(this,this.resetSignisAsyn);
|
||
};
|
||
|
||
Player.prototype.resetAccesAsyn = function() {
|
||
var signis = this.signis.filter(function (signi) {
|
||
return (signi.getAccedCards().length > signi.maxAcceCount);
|
||
},this);
|
||
return Callback.forEach(signis,function (signi) {
|
||
signi.beSelectedAsTarget();
|
||
var cards = signi.getAccedCards();
|
||
var count = cards.length - signi.maxAcceCount;
|
||
return this.player.selectSomeAsyn('TRASH',cards,count,count).callback(this,function (cards) {
|
||
this.game.trashCards(cards);
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.getSigniAmountLimit = function () {
|
||
if (this._ionaUltimaMaiden) return 1;
|
||
if (this.twoSignisLimit) return 2;
|
||
return 3;
|
||
};
|
||
|
||
// 玩家结束成长阶段
|
||
Player.prototype.endGrowPhaseAsyn = function () {
|
||
return this.selectAsyn('END_GROW_PHASE');
|
||
};
|
||
|
||
// 玩家召唤SIGNI
|
||
Player.prototype.summonSigniAsyn = function () {
|
||
var cards = this.hands.filter(function (card) {
|
||
return card.canSummon() && (!this.summonPowerLimit || (card.power < this.summonPowerLimit));
|
||
},this);
|
||
if (!cards.length) {
|
||
return Callback.never();
|
||
}
|
||
return this.selectAsyn('SUMMON_SIGNI',cards).callback(this,function (card) {
|
||
return this.selectSummonZoneAsyn(true,card.rise).callback(this,function (zone) {
|
||
if (!zone) return;
|
||
return this.game.blockAsyn(this,function () {
|
||
return card.handleSummonAsyn(zone);
|
||
});
|
||
});
|
||
});
|
||
};
|
||
|
||
// 玩家召唤共鸣SIGNI
|
||
Player.prototype.summonResonaSigniAsyn = function (arg) {
|
||
if (!arg) arg = {};
|
||
var cards = this.getResonas(arg);
|
||
if (!cards.length) {
|
||
return Callback.never();
|
||
}
|
||
return this.selectAsyn('RESONA',cards).callback(this,function (card) {
|
||
return this.summonResonaAsyn(card);
|
||
});
|
||
};
|
||
|
||
Player.prototype.getResonas = function (arg) {
|
||
return this.lrigDeck.cards.filter(function (card) {
|
||
if (!card.resona) return false;
|
||
var phase = '';
|
||
if (arg.spellCutIn) {
|
||
phase = 'spellCutIn';
|
||
} else if (this.game.phase.status === 'mainPhase') {
|
||
phase = 'mainPhase';
|
||
} else if (this.game.phase.isAttackPhase()) {
|
||
phase = 'attackPhase';
|
||
}
|
||
if (!inArr(phase,card.resonaPhases)) return false;
|
||
var resonaAsyn = card.resonaCondition();
|
||
if (!resonaAsyn) return false;
|
||
card.resonaAsyn = resonaAsyn;
|
||
return true;
|
||
},this);
|
||
};
|
||
|
||
Player.prototype.summonResonaAsyn = function (card) {
|
||
this.inResonaAction = card;
|
||
return card.resonaAsyn().callback(this,function (resonaArg) {
|
||
this.inResonaAction = null;
|
||
return this.selectSummonZoneAsyn(false).callback(this,function (zone) {
|
||
if (!zone) return;
|
||
return this.game.blockAsyn(this,function () {
|
||
return card.handleSummonAsyn(zone)
|
||
});
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.selectSummonZoneAsyn = function (optional,rise) {
|
||
var zones = this.getSummonZones(null,rise);
|
||
if (!zones.length) {
|
||
return Callback.immediately(null);
|
||
}
|
||
if (optional) return this.selectOptionalAsyn('SUMMON_SIGNI_ZONE',zones);
|
||
return this.selectAsyn('SUMMON_SIGNI_ZONE',zones);
|
||
};
|
||
|
||
Player.prototype.getSummonZones = function (signis,rise) {
|
||
if (!signis) signis = this.signis;
|
||
var forcedZones = [];
|
||
var zones = this.signiZones.filter(function (zone,idx) {
|
||
if (zone.disabled) return false;
|
||
var signi = zone.getSigni();
|
||
if (rise) {
|
||
if (!signi || !rise(signi)) return false;
|
||
} else {
|
||
if (signi && inArr(signi,signis)) return false;
|
||
}
|
||
var opposingSigni = this.opponent.signiZones[2-idx].getSigni();
|
||
if (opposingSigni && opposingSigni.forceSummonZone) {
|
||
forcedZones.push(zone);
|
||
}
|
||
return true;
|
||
},this);
|
||
if (forcedZones.length) {
|
||
zones = forcedZones;
|
||
}
|
||
return zones.slice();
|
||
};
|
||
|
||
// 玩家废弃SIGNI
|
||
Player.prototype.trashSigniAsyn = function () {
|
||
if (this.trashSigniBanned) return Callback.never();
|
||
var signis = this.signis.filter(function (signi) {
|
||
return !signi.resona && !signi.canNotBeTrashedBySelf;
|
||
},this);
|
||
if (!signis.length) return Callback.never();
|
||
return this.selectAsyn('TRASH_SIGNI',signis).callback(this,function (card) {
|
||
return this.game.blockAsyn(this,function () {
|
||
card.trash();
|
||
});
|
||
});
|
||
};
|
||
|
||
// 玩家使用魔法
|
||
Player.prototype.useSpellAsyn = function () {
|
||
var cards = this.hands.filter(function (card) {
|
||
return card.canUse();
|
||
});
|
||
if (!cards.length) return Callback.never();
|
||
|
||
return this.selectAsyn('USE_SPELL',cards).callback(this,this.handleSpellAsyn);
|
||
};
|
||
|
||
Player.prototype.handleSpellAsyn = function (card,ignoreCost,costObj,arg) {
|
||
if (!costObj) costObj = card;
|
||
if (!arg) arg = {};
|
||
var effect,target,costArg;
|
||
this.game.setData(this,'flagSpellUsed',true);
|
||
var count = this.game.getData(this,'CodeHeartAMS') || 0;
|
||
this.game.setData(this,'CodeHeartAMS',count + 1);
|
||
|
||
this.game.spellToCutIn = card;
|
||
return Callback.immediately().callback(this,function () {
|
||
// ------ 块开始 ------
|
||
this.game.blockStart();
|
||
// 1. 放到检查区.
|
||
card.moveTo(this.checkZone);
|
||
|
||
// 被 米璐璐恩 抢夺,转移控制权
|
||
// 要在 moveTo 之后改变 player .
|
||
card.player = this;
|
||
|
||
// 2. 支付费用.
|
||
// 无视支付费用
|
||
if (ignoreCost) return { // 返回 costArg 对象
|
||
enerCards: [],
|
||
enerColors: []
|
||
};
|
||
return this.payCostAsyn(costObj);
|
||
}).callback(this,function (_costArg) {
|
||
costArg = _costArg;
|
||
// 如果魔法卡的效果不止一个,选择其中一个发动
|
||
if (card.spellEffects.length === 1) {
|
||
effect = card.spellEffects[0];
|
||
} else {
|
||
return this.selectAsyn('SPELL_EFFECT',card.spellEffects).callback(this,function (eff) {
|
||
effect = eff;
|
||
return this.opponent.showEffectsAsyn([eff]);
|
||
});
|
||
}
|
||
}).callback(this,function () {
|
||
// 3. 娶(划掉)取对象.
|
||
card.activate();
|
||
if (effect.getTargets) {
|
||
// 简单的取对象,即从目标卡片中选一张. (也可以不选,空发)
|
||
if (effect.targetCovered) {
|
||
// 从废弃区等[卡片可能被覆盖的区域]取对象
|
||
return this.selectAsyn('TARGET',effect.getTargets.call(card)).callback(this,function (card) {
|
||
if (!card) return card;
|
||
return this.opponent.showCardsAsyn([card]).callback(this,function () {
|
||
return card;
|
||
});
|
||
});
|
||
}
|
||
return this.selectTargetAsyn(effect.getTargets.call(card));
|
||
}
|
||
if (effect.getTargetAdvancedAsyn) {
|
||
// 复杂(高级)的取对象.
|
||
return effect.getTargetAdvancedAsyn.call(card,costArg);
|
||
}
|
||
}).callback(this,function (t) {
|
||
target = t;
|
||
return this.game.blockEndAsyn();
|
||
// ------ 块结束 ------
|
||
}).callback(this,function () {
|
||
// 4. 魔法切入.
|
||
return this.opponent.useSpellCutInArtsAsyn();
|
||
}).callback(this,function (canceled) {
|
||
// 5. 处理.
|
||
// "处理+放置到废弃区"放在1个block里.
|
||
return this.game.blockAsyn(effect.source,this,function () {
|
||
return Callback.immediately().callback(this,function () {
|
||
// "结束这个回合",处理直接结束.
|
||
if (this.game.phase.checkForcedEndTurn()) return;
|
||
// "不会被取消"
|
||
if (!this.spellCancelable) {
|
||
canceled = false;
|
||
}
|
||
// 触发"使用魔法"时点,不管是否被取消
|
||
var event = {
|
||
card: effect.source
|
||
};
|
||
effect.source.player.onUseSpell.trigger(event);
|
||
// 如果被取消,处理直接结束.
|
||
if (canceled) return;
|
||
// 如果目标丢失,处理直接结束. (除非设置了dontCheckTarget)
|
||
if (effect.getTargets && !effect.dontCheckTarget) {
|
||
if (!inArr(target,effect.getTargets.call(card))) {
|
||
return;
|
||
}
|
||
}
|
||
return effect.actionAsyn.call(card,target,costArg);
|
||
}).callback(this,function () {
|
||
// 6. 放到废弃区
|
||
// 恢复控制权
|
||
card.player = card.owner;
|
||
this.game.spellToCutIn = null;
|
||
if (card.zone !== this.checkZone) return;
|
||
return this.game.blockAsyn(this,function () {
|
||
// <皮露露可 APEX>
|
||
if (arg.excludeAfterUse) {
|
||
card.exclude()
|
||
} else {
|
||
card.trash();
|
||
}
|
||
});
|
||
}).callback(this,function () {
|
||
// <混乱交织> 的特殊处理
|
||
if (this.game.phase.checkForcedEndTurn()) return;
|
||
if (canceled !== '_crossScramble') return;
|
||
return this.opponent.handleSpellAsyn(card,true);
|
||
});
|
||
});
|
||
});
|
||
};
|
||
|
||
// 技艺的处理
|
||
Player.prototype.handleArtsAsyn = function (card,ignoreCost) {
|
||
var effects,costArg,control;
|
||
var encored = false;
|
||
var costObj = card.getChainedCostObj();
|
||
if (ignoreCost) costObj = {};
|
||
var count = this.game.getData(this,'artsCount') || 0;
|
||
this.game.setData(this,'artsCount',count+1);
|
||
this.game.setData(this,'flagArtsUsed',true);
|
||
// 五色电影卡
|
||
if (card.cid !== 1167) {
|
||
this.game.setData(this,'flagArcDestruct',true);
|
||
}
|
||
if (card.cid !== 1526) {
|
||
this.game.setData(this,'flagDestructOut',true);
|
||
}
|
||
return Callback.immediately().callback(this,function () {
|
||
// ------ 块开始 ------
|
||
this.game.blockStart();
|
||
// 1. 放到检查区
|
||
card.moveTo(this.checkZone);
|
||
if (card.beforeUseAsyn) return card.beforeUseAsyn();
|
||
}).callback(this,function () {
|
||
// bet
|
||
if (card.betAdvancedAsyn) {
|
||
return card.betAdvancedAsyn(costObj).callback(this,function (c) {
|
||
costObj = c;
|
||
});
|
||
}
|
||
if (!card.bet) return;
|
||
if (this.coin < card.bet) return;
|
||
var bettedCost = Object.create(costObj);
|
||
if (card.bettedCost && !ignoreCost) {
|
||
bettedCost = card.getChainedCostObj(card.bettedCost);
|
||
}
|
||
bettedCost.costCoin = card.bet;
|
||
if (!this.enoughCost(costObj)) {
|
||
// 必须 bet
|
||
return costObj = bettedCost;
|
||
}
|
||
return this.confirmAsyn('CONFIRM_BET').callback(this,function (answer) {
|
||
if (!answer) return;
|
||
costObj = bettedCost;
|
||
})
|
||
}).callback(this,function () {
|
||
// 如果效果不止一个,选择其中n个发动
|
||
if (card.artsEffects.length === 1) {
|
||
effects = card.artsEffects.slice();
|
||
} else {
|
||
var min,max;
|
||
if (!card.getMinEffectCount || !card.getMaxEffectCount) {
|
||
min = max = 1;
|
||
} else {
|
||
min = card.getMinEffectCount(costObj);
|
||
max = card.getMaxEffectCount(costObj);
|
||
}
|
||
return this.selectSomeAsyn('ARTS_EFFECT',card.artsEffects,min,max,false).callback(this,function (effs) {
|
||
effects = effs;
|
||
if (card.costChangeAfterChoose) {
|
||
card.costChangeAfterChoose.call(card,costObj,effs);
|
||
}
|
||
return this.opponent.showEffectsAsyn(effs);
|
||
});
|
||
}
|
||
}).callback(this,function () {
|
||
// encore 费用,约定: 除了颜色费用,其它属性直接覆盖
|
||
if (!card.encore) return;
|
||
var encoredCost = Object.create(costObj);
|
||
encoredCost.source = card;
|
||
var enerCostProps = [
|
||
'costColorless',
|
||
'costWhite',
|
||
'costBlack',
|
||
'costRed',
|
||
'costBlue',
|
||
'costGreen',
|
||
];
|
||
for (var prop in card.encore) {
|
||
if (inArr(prop,enerCostProps)) {
|
||
encoredCost[prop] += card.encore[prop];
|
||
} else {
|
||
encoredCost[prop] = card.encore[prop];
|
||
}
|
||
}
|
||
if (!this.enoughCost(encoredCost)) return;
|
||
return this.confirmAsyn('CONFIRM_ENCORE').callback(this,function (answer) {
|
||
if (!answer) return;
|
||
costObj = encoredCost;
|
||
encored = true;
|
||
});
|
||
}).callback(this,function () {
|
||
// 2. 支付费用
|
||
return this.payCostAsyn(costObj);
|
||
}).callback(this,function (_costArg) {
|
||
costArg = _costArg;
|
||
card.activate();
|
||
control = {
|
||
backToDeck: false,
|
||
rtn: null // 当有多个效果时,这个作为返回值. <ブルー・パニッシュ>
|
||
};
|
||
// 3. 处理
|
||
this.onUseArts.trigger({
|
||
card: card
|
||
});
|
||
return this.game.blockAsyn(card,this,function () {
|
||
return Callback.forEach(effects,function (effect) {
|
||
return effect.actionAsyn.call(card,costArg,control);
|
||
},this);
|
||
});
|
||
}).callback(this,function (rtn) {
|
||
// 4. 放到LRIG废弃区
|
||
this.chain = card.chain; // 连锁
|
||
if (encored || control.backToDeck) {
|
||
card.moveTo(card.player.lrigDeck);
|
||
} else {
|
||
card.moveTo(card.player.lrigTrashZone);
|
||
}
|
||
// ------ 块结束 ------
|
||
return this.game.blockEndAsyn().callback(this,function () {
|
||
return control.rtn || rtn;
|
||
});
|
||
});
|
||
};
|
||
|
||
|
||
// 玩家使用【魔法切入】的技艺(和起动效果和召唤魔法切入共鸣单位)
|
||
Player.prototype.useSpellCutInArtsAsyn = function () {
|
||
var canceled = false;
|
||
function loopAsyn () {
|
||
if (this.game.phase.checkForcedEndTurn()) {
|
||
return Callback.immediately(true);
|
||
}
|
||
var cards = this.lrigDeck.cards.filter(function (card) {
|
||
return card.canUse('spellCutIn');
|
||
});
|
||
cards = cards.concat(this.getResonas({spellCutIn: true}));
|
||
concat(this.signis,this.lrig).forEach(function (card) {
|
||
var hasSpellCutInEffect = card.actionEffects.some(function (effect) {
|
||
return this.canUseActionEffect(effect,{spellCutIn: true});
|
||
},this);
|
||
if (hasSpellCutInEffect) cards.push(card);
|
||
},this);
|
||
// 选择一张魔法切入的技艺(或持有魔法切入的起动效果的卡)
|
||
return this.selectOptionalAsyn('SPELL_CUT_IN',cards,true).callback(this,function (card) {
|
||
if (!card) return true;
|
||
if (card.type === 'ARTS') {
|
||
// 如果选择的是ARTS
|
||
return this.handleArtsAsyn(card).callback(this,function (c) {
|
||
if (c) canceled = c;
|
||
return false;
|
||
});
|
||
} else if (card.resona && inArr('spellCutIn',card.resonaPhases)) {
|
||
// 如果选择的是魔法切入的共鸣单位
|
||
return this.summonResonaAsyn(card);
|
||
} else {
|
||
// 如果选择的是持有起动效果的卡
|
||
var effects = card.actionEffects.filter(function (effect) {
|
||
return this.canUseActionEffect(effect,{spellCutIn: true});
|
||
},this);
|
||
if (!effects.length) return false;
|
||
return Callback.immediately().callback(this,function () {
|
||
if (effects.length === 1) return effects[0];
|
||
return this.selectAsyn('USE_ACTION_EFFECT',effects);
|
||
}).callback(this,function (effect) {
|
||
return this.handleActionEffectAsyn(effect);
|
||
}).callback(this,function (c) {
|
||
if (c) canceled = c;
|
||
return false;
|
||
});
|
||
}
|
||
}).callback(this,function (done) {
|
||
if (canceled || done) return canceled;
|
||
// 重复上述步骤 (直至魔法被取消或没有魔法切入或玩家放弃使用)
|
||
return loopAsyn.call(this);
|
||
});
|
||
}
|
||
return loopAsyn.call(this);
|
||
};
|
||
|
||
// 玩家使用【主要阶段】的技艺
|
||
Player.prototype.useMainPhaseArtsAsyn = function () {
|
||
var cards = this.lrigDeck.cards.filter(function (card) {
|
||
return card.canUse('mainPhase');
|
||
});
|
||
if (!cards.length) return Callback.never();
|
||
return this.selectAsyn('USE_ARTS',cards).callback(this,function (card) {
|
||
return this.handleArtsAsyn(card);
|
||
});
|
||
};
|
||
|
||
Player.prototype.canUseActionEffect = function (effect,arg) {
|
||
if (!arg) arg = {};
|
||
if (this.actionEffectBanned) return false;
|
||
if (this.charmedActionEffectBanned && effect.source.charm) return false;
|
||
if (effect.source.abilityLost) return false;
|
||
// inHand
|
||
if (effect.source.zone === this.handZone && !effect.activatedInHand) return false;
|
||
if (effect.source.zone !== this.handZone && effect.activatedInHand) return false;
|
||
// inTrashZone
|
||
if (effect.source.zone === this.trashZone && !effect.activatedInTrashZone) return false;
|
||
if (effect.source.zone !== this.trashZone && effect.activatedInTrashZone) return false;
|
||
// inEnerZone
|
||
if (effect.source.zone === this.enerZone && !effect.activatedInEnerZone) return false;
|
||
if (effect.source.zone !== this.enerZone && effect.activatedInEnerZone) return false;
|
||
// attackPhase && spellCutIn
|
||
if (!arg.ignoreTimming) {
|
||
if (arg.spellCutIn) {
|
||
if (!effect.spellCutIn) return false;
|
||
} else {
|
||
if (this.game.phase.isAttackPhase()) {
|
||
if (!effect.attackPhase) return false;
|
||
} else {
|
||
if (effect.attackPhase && !effect.mainPhase) return false;
|
||
}
|
||
}
|
||
}
|
||
// onAttack
|
||
if (arg.onAttack && !effect.onAttack) return false;
|
||
if (!arg.onAttack && effect.onAttack) return false;
|
||
// cross
|
||
if (effect.cross && !effect.source.crossed) return false;
|
||
// wisdom
|
||
if (effect.wisdom && (effect.source.player.getWisdom() !== effect.wisdom)) return false;
|
||
// once
|
||
if (effect.once && inArr(effect,this.usedActionEffects)) return false;
|
||
// condition
|
||
if (effect.useCondition && !effect.useCondition.call(effect.source,arg)) return false;
|
||
// <混沌之键主 乌姆尔=FYRA>
|
||
if (effect.activatedInTrashZone) {
|
||
if (this.game.getData(effect.source,'zeroActionCostInTrash')) {
|
||
return true;
|
||
}
|
||
}
|
||
var obj = Object.create(effect);
|
||
if (obj.costColorless) {
|
||
obj.costColorless += effect.source.attachedCostColorless;
|
||
} else {
|
||
obj.costColorless = effect.source.attachedCostColorless;
|
||
}
|
||
if (arg.ignoreExceedCost) {
|
||
obj.costExceed = 0;
|
||
}
|
||
return this.enoughCost(obj);
|
||
};
|
||
|
||
// 玩家使用起动效果
|
||
Player.prototype.useActionEffectAsyn = function () {
|
||
var effects = [];
|
||
var cards = concat(this.lrig,this.signis,this.trashZone.cards,this.hands,this.enerZone.cards);
|
||
cards.forEach(function (card) {
|
||
card.actionEffects.forEach(function (effect) {
|
||
if (effect.spellCutIn) return;
|
||
if (this.canUseActionEffect(effect)) {
|
||
effects.push(effect);
|
||
}
|
||
},this);
|
||
},this);
|
||
if (!effects.length) return Callback.never();
|
||
return this.selectAsyn('USE_ACTION_EFFECT',effects).callback(this,function (effect) {
|
||
return this.handleActionEffectAsyn(effect,{
|
||
cancelable: true,
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.useOnAttackActionEffectAsyn = function (event) {
|
||
var effects = this.lrig.actionEffects.filter(function (effect) {
|
||
return this.canUseActionEffect(effect,{
|
||
onAttack: true,
|
||
event: event,
|
||
});
|
||
},this);
|
||
if (!effects.length) return Callback.immediately();
|
||
return this.selectOptionalAsyn('LAUNCH',effects).callback(this,function (effect) {
|
||
if (!effect) return;
|
||
return this.handleActionEffectAsyn(effect,{event: event});
|
||
});
|
||
};
|
||
|
||
Player.prototype.handleActionEffectAsyn = function (effect,arg) {
|
||
if (!arg) arg = {};
|
||
return this.game.blockAsyn(this,function () {
|
||
var obj = Object.create(effect);
|
||
if (obj.costColorless) {
|
||
obj.costColorless += effect.source.attachedCostColorless;
|
||
} else {
|
||
obj.costColorless = effect.source.attachedCostColorless;
|
||
}
|
||
if (arg.ignoreExceedCost) {
|
||
obj.costExceed = 0;
|
||
}
|
||
// <混沌之键主 乌姆尔=FYRA>
|
||
if (effect.activatedInTrashZone) {
|
||
if (this.game.getData(effect.source,'zeroActionCostInTrash')) {
|
||
obj = {};
|
||
this.game.setData(effect.source,'zeroActionCostInTrash',false);
|
||
}
|
||
}
|
||
this.inActionEffectCost = true;
|
||
return this.payCostAsyn(obj,arg.cancelable).callback(this,function (costArg) {
|
||
this.inActionEffectCost = false;
|
||
if (!costArg) return; // canceled
|
||
effect.source.activate();
|
||
this.usedActionEffects.push(effect);
|
||
return this.game.blockAsyn(effect.source,this,function () {
|
||
return effect.actionAsyn.call(effect.source,costArg,arg);
|
||
});
|
||
});
|
||
});
|
||
};
|
||
|
||
// 玩家结束主要阶段
|
||
Player.prototype.endMainPhaseAsyn = function () {
|
||
return this.selectAsyn('END_MAIN_PHASE');
|
||
};
|
||
|
||
// // 玩家使用【攻击阶段】的技艺(和起动效果)
|
||
// Player.prototype.useAttackPhaseArtsAsyn = function () {
|
||
// var cards = this.lrigDeck.cards.filter(function (card) {
|
||
// return card.canUse('attackPhase');
|
||
// });
|
||
// concat(this.signis,this.lrig).forEach(function (card) {
|
||
// var hasAttackPhaseEffect = card.actionEffects.some(function (effect) {
|
||
// return effect.attackPhase && this.canUseActionEffect(effect);
|
||
// },this);
|
||
// if (hasAttackPhaseEffect) cards.push(card);
|
||
// },this);
|
||
// if (!cards.length) return Callback.never();
|
||
// // 选择一张【攻击阶段】的技艺(或持有【攻击阶段】的起动效果的卡)
|
||
// return this.selectAsyn('USE_ARTS',cards).callback(this,function (card) {
|
||
// if (card.type === 'ARTS') {
|
||
// // 如果选择的是ARTS
|
||
// return this.handleArtsAsyn(card);
|
||
// } else {
|
||
// // 如果选择的是持有起动效果的卡
|
||
// var effects = card.actionEffects.filter(function (effect) {
|
||
// return effect.attackPhase && this.canUseActionEffect(effect);
|
||
// },this);
|
||
// if (!effects.length) return;
|
||
// return Callback.immediately().callback(this,function () {
|
||
// if (effects.length === 1) return effects[0];
|
||
// return this.selectAsyn('USE_ACTION_EFFECT',effects);
|
||
// }).callback(this,function (effect) {
|
||
// return this.handleActionEffectAsyn(effect);
|
||
// });
|
||
// }
|
||
// });
|
||
// };
|
||
|
||
Player.prototype.useAttackPhaseArtsAsyn = function () {
|
||
var cards = this.lrigDeck.cards.filter(function (card) {
|
||
return card.canUse('attackPhase');
|
||
});
|
||
if (!cards.length) return Callback.never();
|
||
return this.selectAsyn('USE_ARTS',cards).callback(this,function (card) {
|
||
return this.handleArtsAsyn(card);
|
||
});
|
||
};
|
||
|
||
Player.prototype.useAttackPhaseActionEffect = function () {
|
||
var cards = concat(this.signis,this.lrig,this.trashZone.cards,this.hands).filter(function (card) {
|
||
return card.actionEffects.some(function (effect) {
|
||
return this.canUseActionEffect(effect);
|
||
},this);
|
||
},this);
|
||
if (!cards.length) return Callback.never();
|
||
return this.selectAsyn('USE_ACTION_EFFECT',cards).callback(this,function (card) {
|
||
var effects = card.actionEffects.filter(function (effect) {
|
||
return this.canUseActionEffect(effect);
|
||
},this);
|
||
if (!effects.length) return;
|
||
return Callback.immediately().callback(this,function () {
|
||
if (effects.length === 1) return effects[0];
|
||
return this.selectAsyn('USE_ACTION_EFFECT',effects);
|
||
}).callback(this,function (effect) {
|
||
return this.handleActionEffectAsyn(effect);
|
||
});
|
||
});
|
||
};
|
||
|
||
// 玩家结束技艺使用步骤
|
||
Player.prototype.endArtsStepAsyn = function () {
|
||
return this.selectAsyn('END_ARTS_STEP');
|
||
};
|
||
|
||
// 玩家进行SIGNI攻击
|
||
Player.prototype.signiAttackAsyn = function () {
|
||
var cards = this.signis.filter(function (card) {
|
||
return card.canAttack();
|
||
});
|
||
if (!cards.length) return Callback.never();
|
||
return this.selectAsyn('SIGNI_ATTACK',cards).callback(this,function (card) {
|
||
return card.attackAsyn();
|
||
});
|
||
};
|
||
|
||
// 玩家结束SIGNI攻击步骤
|
||
Player.prototype.endSigniAttackStepAsyn = function () {
|
||
// 必须进行攻击的卡
|
||
var cards = this.signis.filter(function (card) {
|
||
return (this.forceSigniAttack || card.mustAttack) && card.canAttack() && !card.attackCostColorless;
|
||
},this);
|
||
if (cards.length) return Callback.never();
|
||
return this.selectAsyn('END_SIGNI_ATTACK_STEP');
|
||
};
|
||
|
||
// 玩家进行LRIG攻击
|
||
Player.prototype.lrigAttackAsyn = function () {
|
||
if (this.lrigAttackBanned) return Callback.never();
|
||
var cards = [this.lrig].filter(function (card) {
|
||
return card.canAttack();
|
||
});
|
||
if (!cards.length) return Callback.never();
|
||
return this.selectAsyn('LRIG_ATTACK',cards).callback(this,function (card) {
|
||
return card.attackAsyn();
|
||
});
|
||
};
|
||
|
||
// 防御
|
||
// callback(succ)
|
||
// succ: 表示是否成功防御
|
||
Player.prototype.guardAsyn = function () {
|
||
if (this.canNotGuard) return Callback.immediately(false);
|
||
var cards = this.hands.filter(function (card) {
|
||
return card.guardFlag && (card.level > this.guardLimit) && !inArr(card.level,this.guardBannedLevels);
|
||
},this);
|
||
return this.selectOptionalAsyn('GUARD',cards,true).callback(this,function (card) {
|
||
if (!card) return false;
|
||
card.moveTo(this.trashZone);
|
||
return true;
|
||
});
|
||
};
|
||
|
||
// 玩家结束LRIG攻击步骤
|
||
Player.prototype.endLrigAttackStepAsyn = function () {
|
||
return this.selectAsyn('END_LRIG_ATTACK_STEP');
|
||
};
|
||
|
||
// 玩家被击溃(噗
|
||
Player.prototype.crashAsyn = function (n,arg) {
|
||
if (n === undefined) n = 1;
|
||
if (arg === undefined) arg = {};
|
||
var source = arg.source || this.game.getEffectSource();
|
||
var attack = !!arg.attack;
|
||
var lancer = !!arg.lancer;
|
||
var doubleCrash = !!arg.doubleCrash;
|
||
var damage = !!arg.damage;
|
||
var tag = arg.tag || '';
|
||
|
||
if (this.wontBeCrashed) return Callback.immediately(false);
|
||
if (this.wontBeCrashedExceptDamage && !damage) return Callback.immediately(false);
|
||
|
||
var cards = this.lifeClothZone.getTopCards(n);
|
||
if (!cards.length) return Callback.immediately(false);
|
||
var crossLifeCloth = (tag === 'crossLifeCloth'); // <幻水 希拉>
|
||
var effectSource = this.game.getEffectSource();
|
||
return this.game.blockAsyn(this,function () {
|
||
// 放到检查区并触发 onBurst 和 onCrash .
|
||
// 根据<幻竜姫 スヴァローグ>的FAQ,
|
||
// 无迸发的卡立即进入能量区;
|
||
// 有迸发的卡在迸发解决后进入能量区.
|
||
this.game.frame(this,function () {
|
||
cards.forEach(function (card) {
|
||
// <多元描写>
|
||
if (effectSource && (effectSource.player === this.opponent)) {
|
||
this.game.setData(this,'_PluralismDepiction',true);
|
||
}
|
||
|
||
card.moveTo(this.checkZone);
|
||
var event = {
|
||
source: source,
|
||
lancer: lancer
|
||
};
|
||
this.onCrash.trigger(event);
|
||
var shouldBurst = card.onBurst.effects.length &&
|
||
(tag !== 'dontTriggerBurst') &&
|
||
(!source || !source.disableBurst)
|
||
if (shouldBurst) {
|
||
// 迸发
|
||
card.onBurst.trigger({crossLifeCloth: crossLifeCloth}); // 注意<DYNAMITE>
|
||
} else {
|
||
card.handleBurstEnd(crossLifeCloth);
|
||
}
|
||
},this);
|
||
if (doubleCrash && (cards.length === 2)) {
|
||
this.onDoubleCrashed.trigger();
|
||
}
|
||
});
|
||
}).callback(this,function () {
|
||
return true;
|
||
});
|
||
};
|
||
|
||
Player.prototype.damageAsyn = function () {
|
||
if (this.wontBeDamaged) return Callback.immediately(false);
|
||
if (this.wontBeDamagedByOpponentLrig) {
|
||
var source = this.game.getEffectSource();
|
||
if (source === this.opponent.lrig) {
|
||
return Callback.immediately(false);
|
||
}
|
||
}
|
||
if (!this.lifeClothZone.cards.length) {
|
||
if (this.game.win(this.opponent)) return Callback.never();
|
||
return Callback.immediately(false);
|
||
}
|
||
return this.crashAsyn(1,{damage: true});
|
||
};
|
||
|
||
// 向玩家展示一些卡,通常用于公开探寻的卡.
|
||
Player.prototype.showCardsAsyn = function (cards,label) {
|
||
var player = this;
|
||
// player.opponent.output({
|
||
// type: 'WAIT_FOR_OPPONENT',
|
||
// content: {
|
||
// operation: 'CONFIRM'
|
||
// }
|
||
// });
|
||
player.output({
|
||
type: 'SHOW_CARDS',
|
||
content: {
|
||
label: label || 'CONFIRM',
|
||
cards: cards,
|
||
pids: cards.map(function (card) {
|
||
return card.pid;
|
||
})
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen('OK',function (input) {
|
||
return function () {
|
||
callback(cards);
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.showCardsByIdAsyn = function (ids,label) {
|
||
var player = this;
|
||
// player.opponent.output({
|
||
// type: 'WAIT_FOR_OPPONENT',
|
||
// content: {
|
||
// operation: 'CONFIRM'
|
||
// }
|
||
// });
|
||
player.output({
|
||
type: 'SHOW_CARDS_BY_ID',
|
||
content: {
|
||
label: label || 'CONFIRM',
|
||
ids: ids
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen('OK',function (input) {
|
||
return function () {
|
||
callback();
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.revealAsyn = function (n) {
|
||
return Callback.immediately().callback(this,function () {
|
||
var source = this.game.getEffectSource();
|
||
if (!source) return 0;
|
||
if (source.player !== this) return 0;
|
||
if (!this.additionalRevealCount) return 0;
|
||
return this.selectNumberAsyn('REVEAL_MORE',0,this.additionalRevealCount,this.additionalRevealCount);
|
||
}).callback(this,function (num) {
|
||
n += num;
|
||
var cards = this.mainDeck.getTopCards(n);
|
||
return this.showCardsAsyn(cards).callback(this,function () {
|
||
return this.opponent.showCardsAsyn(cards).callback(this,function () {
|
||
return cards;
|
||
});
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.showColorsAsyn = function (colors) {
|
||
var player = this;
|
||
// player.opponent.output({
|
||
// type: 'WAIT_FOR_OPPONENT',
|
||
// content: {
|
||
// operation: 'CONFIRM'
|
||
// }
|
||
// });
|
||
player.output({
|
||
type: 'SHOW_COLORS',
|
||
content: {
|
||
colors: colors
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen('OK',function (input) {
|
||
return function () {
|
||
callback();
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.showCardTypesAsyn = function (types) {
|
||
var player = this;
|
||
// player.opponent.output({
|
||
// type: 'WAIT_FOR_OPPONENT',
|
||
// content: {
|
||
// operation: 'CONFIRM'
|
||
// }
|
||
// });
|
||
player.output({
|
||
type: 'SHOW_TYPES',
|
||
content: {
|
||
types: types
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen('OK',function (input) {
|
||
return function () {
|
||
callback();
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.showEffectsAsyn = function (effects) {
|
||
var player = this;
|
||
// player.opponent.output({
|
||
// type: 'WAIT_FOR_OPPONENT',
|
||
// content: {
|
||
// operation: 'CONFIRM'
|
||
// }
|
||
// });
|
||
player.output({
|
||
type: 'SHOW_EFFECTS',
|
||
content: {
|
||
effects: effects.map(function (eff) {
|
||
return eff.description;
|
||
})
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen('OK',function (input) {
|
||
return function () {
|
||
callback();
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.showTextAsyn = function (title,type,content) {
|
||
var player = this;
|
||
// player.opponent.output({
|
||
// type: 'WAIT_FOR_OPPONENT',
|
||
// content: {
|
||
// operation: 'CONFIRM'
|
||
// }
|
||
// });
|
||
player.output({
|
||
type: 'SHOW_TEXT',
|
||
content: {
|
||
type: type,
|
||
title: title,
|
||
content: content
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen('OK',function (input) {
|
||
return function () {
|
||
callback();
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.selectNumberAsyn = function (label,min,max,defaultValue) {
|
||
if (defaultValue === undefined) {
|
||
defaultValue = min;
|
||
}
|
||
var player = this;
|
||
player.output({
|
||
type: 'SELECT_NUMBER',
|
||
content: {
|
||
label: label,
|
||
min: min,
|
||
max: max,
|
||
defaultValue: defaultValue
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen(label,function (num) {
|
||
num = num >>> 0;
|
||
if (!((num >= min) && (num <= max))) return false;
|
||
return function () {
|
||
callback(num);
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.declareAsyn = function (min,max) {
|
||
return this.selectNumberAsyn('DECLARE',min,max).callback(this,function (num) {
|
||
return this.opponent.showTextAsyn('DECLARE','number',num).callback(this,function () {
|
||
return num;
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.declareCardIdAsyn = function () {
|
||
return this.selectCardIdAsyn('DECLARE').callback(this,function (pid) {
|
||
return this.opponent.showCardsByIdAsyn([pid],'DECLARE').callback(this,function () {
|
||
return pid;
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.selectTextAsyn = function (label,texts,type) {
|
||
var player = this;
|
||
if (!texts.length) return Callback.immediately(null);
|
||
player.output({
|
||
type: 'SELECT_TEXT',
|
||
content: {
|
||
label: label,
|
||
texts: texts,
|
||
type: type || ''
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen(label,function (idx) {
|
||
idx = idx >>> 0;
|
||
var text = texts[idx];
|
||
if (!text) return false;
|
||
return function () {
|
||
callback(text);
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.selectCardIdAsyn = function (label) {
|
||
var player = this;
|
||
player.output({
|
||
type: 'SELECT_CARD_ID',
|
||
content: {
|
||
label: label
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen(label,function (pid) {
|
||
pid = pid >>> 0;
|
||
if (!CardInfo[pid]) return false;
|
||
return function () {
|
||
callback(pid);
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.confirmAsyn = function (text) {
|
||
var player = this;
|
||
player.output({
|
||
type: 'CONFIRM',
|
||
content: {
|
||
text: text
|
||
}
|
||
});
|
||
return new Callback(function (callback) {
|
||
player.listen('OK',function (answer) {
|
||
return function () {
|
||
callback(!!answer);
|
||
};
|
||
});
|
||
});
|
||
};
|
||
|
||
// 令玩家获得cards的pid.
|
||
Player.prototype.informCards = function (cards) {
|
||
this.output({
|
||
type: 'INFORM_CARDS',
|
||
content: {
|
||
cards: cards,
|
||
pids: cards.map(function (card) {
|
||
return card.pid;
|
||
},this)
|
||
}
|
||
});
|
||
};
|
||
|
||
// needEner(obj)
|
||
// obj是一个包含 costWhite 等属性的对象 (cost对象),
|
||
// 如果obj的所有cost为零,返回false,否则true
|
||
Player.prototype.needEner = function (obj) {
|
||
if (obj.costChange) {
|
||
obj = obj.costChange();
|
||
}
|
||
var costs = [obj.costColorless,obj.costWhite,obj.costBlack,obj.costRed,obj.costBlue,obj.costGreen];
|
||
return costs.some(function (cost) {
|
||
return cost > 0;
|
||
});
|
||
};
|
||
|
||
Player.prototype.getTotalEnerCost = function (obj,original) {
|
||
if (!original && obj.costChange) {
|
||
obj = obj.costChange();
|
||
}
|
||
var props = [
|
||
'costColorless',
|
||
'costWhite',
|
||
'costBlack',
|
||
'costRed',
|
||
'costBlue',
|
||
'costGreen',
|
||
];
|
||
var total = 0;
|
||
props.forEach(function (prop) {
|
||
total += (original? this.game.getOriginalValue(obj,prop) : obj[prop]) || 0;
|
||
},this);
|
||
return total;
|
||
};
|
||
|
||
Player.prototype.needCost = function (obj) {
|
||
if (obj.costChange) {
|
||
obj = obj.costChange();
|
||
}
|
||
if (obj.costCoin) return true;
|
||
if (obj.costDown && obj.source) return true;
|
||
if (obj.costAsyn && obj.source) return true;
|
||
if (obj.costExceed && obj.source) return true;
|
||
if (this.needEner(obj)) return true;
|
||
return false;
|
||
};
|
||
|
||
Player.prototype.checkRequirements = function (items, requirements) {
|
||
var n = requirements.length
|
||
var len = 1 << n
|
||
var arr = (typeof Uint8Array === 'undefined') ? new Array(len) : new Uint8Array(len)
|
||
// 第 i 条不等式。
|
||
// 假设 n == 4,即有 4 种元需求:
|
||
// i 为从 0000 到 1111,从右到左分别称为第 1,2,3,4 位
|
||
// 当 i == 0110 时,代表不等式左边为第 2、3 个集合的并集
|
||
for (var i = 0; i < len; i++) {
|
||
var filters = []
|
||
for (var j = 0; j < n; j++) {
|
||
if (i & (1 << j)) {
|
||
filters.push(requirements[j].filter)
|
||
// 不等式的右边(先把需求数加上去,满足的时候减 1,减至 0 则满足该不等式)
|
||
arr[i] += requirements[j].count
|
||
}
|
||
}
|
||
// 遍历能量区,满足条件的减 1
|
||
for (var j = 0; j < items.length; j++) {
|
||
var item = items[j]
|
||
if (filters.some(function (filter) {
|
||
return filter(item)
|
||
})) {
|
||
// 减至 0,该不等式成立
|
||
if (!--arr[i]) break
|
||
}
|
||
}
|
||
if (arr[i]) return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
Player.prototype.cardToInteger = function (card) {
|
||
var int = 0
|
||
if (card.hasColor('white')) int |= 1 // 0b00001
|
||
if (card.hasColor('black')) int |= 2 // 0b00010
|
||
if (card.hasColor('green')) int |= 4 // 0b00100
|
||
if (card.hasColor('blue')) int |= 8 // 0b01000
|
||
if (card.hasColor('red')) int |= 16 // 0b10000
|
||
return int
|
||
}
|
||
Player.prototype.canUseBikou = function (obj) {
|
||
return obj.useBikouAsWhiteCost || this.useBikouAsWhiteCost
|
||
}
|
||
Player.prototype.encodeCard = function (card, costObj) {
|
||
// card => 01011 (红蓝绿黑白)
|
||
var int = 0
|
||
if (card.multiEner) {
|
||
int = 31 // 0b11111
|
||
} else {
|
||
int = this.cardToInteger(card)
|
||
}
|
||
// 美巧
|
||
if (this.canUseBikou(costObj) && card.hasClass('美巧')) {
|
||
int |= 1
|
||
}
|
||
if (card.trashAsWhiteCost) {
|
||
int |= 1
|
||
}
|
||
// <小剑 三日月>
|
||
if (card._MikamuneSmallSword) {
|
||
int |= this.cardToInteger(this.lrig)
|
||
}
|
||
return int
|
||
}
|
||
Player.prototype.encodeCost = function (cost, withoutFilter) {
|
||
// cost => [{ count: 3, mask: 0b00001, filter: () => ... }, ...]
|
||
var requirements = []
|
||
if (cost.costColorless) {
|
||
if (cost._2286) {
|
||
requirements.push({ count: cost.costColorless, mask: this.cardToInteger(this.lrig) })
|
||
} else {
|
||
requirements.push({ count: cost.costColorless, mask: 0 })
|
||
}
|
||
}
|
||
if (cost.costWhite) requirements.push({ count: cost.costWhite, mask: 1 })
|
||
if (cost.costBlack) requirements.push({ count: cost.costBlack, mask: 2 })
|
||
if (cost.costGreen) requirements.push({ count: cost.costGreen, mask: 4 })
|
||
if (cost.costBlue) requirements.push({ count: cost.costBlue, mask: 8 })
|
||
if (cost.costRed) requirements.push({ count: cost.costRed, mask: 16 })
|
||
if (!withoutFilter) {
|
||
requirements.forEach(function (item) {
|
||
item.filter = function (int) {
|
||
return !item.mask || (int & item.mask)
|
||
}
|
||
})
|
||
}
|
||
return requirements
|
||
}
|
||
Player.prototype._checkEner = function (cards,cost) {
|
||
var items = cards.map(this.encodeCard.bind(this))
|
||
var requirements = this.encodeCost(cost)
|
||
var enough = this.checkRequirements(items,requirements)
|
||
var total = requirements.reduce(function (total,requirement) {
|
||
return total + requirement.count
|
||
}, 0)
|
||
return {
|
||
left: enough? items.length - total : -1,
|
||
}
|
||
}
|
||
|
||
// 御先狐...
|
||
Player.prototype.checkEner = function (cards,obj,ignoreReplacement) {
|
||
if (obj.costChange) {
|
||
obj = obj.costChange();
|
||
}
|
||
obj = Object.create(obj);
|
||
obj.costGreen = obj.costGreen || 0;
|
||
cards = cards.slice();
|
||
var osakiCards = cards.filter(function (card) {
|
||
return card._KosakiPhantomBeast;
|
||
},this);
|
||
var minOsaki = 0;
|
||
var maxOsaki = Math.min(osakiCards.length,Math.floor(obj.costGreen/2));
|
||
for (var i = 0; i <= maxOsaki; i++) {
|
||
var result = this._checkEner(cards,obj);
|
||
if (ignoreReplacement || (result.left >= 0)) break;
|
||
minOsaki++;
|
||
removeFromArr(osakiCards[i],cards);
|
||
obj.costGreen = Math.max(0,obj.costGreen - 3);
|
||
}
|
||
result.osakiCards = osakiCards;
|
||
result.minOsaki = minOsaki;
|
||
result.maxOsaki = maxOsaki;
|
||
return result;
|
||
};
|
||
// _checkEner(cards,obj)
|
||
// obj是个cost对象,cards是用来支付能量的卡.
|
||
// 返回一个带 left,minBikou,maxBikou,bikouCards 属性的对象.
|
||
// left: 支付后剩余的卡片数. (若为负数,表示无法完成支付)
|
||
// minBikou: 表示至少要支付的美巧数量.
|
||
// maxBikou: 表示至多可以支付的美巧数量.
|
||
// bikouCards: 可以用于代替白色费用的美巧卡.
|
||
// 注: 由于后来出现了<美しき弦奏 コントラ>,这里的美巧也指这张卡.
|
||
// Player.prototype._checkEner = function (cards,obj,ignoreReplacement) {
|
||
// var minBikou = 0;
|
||
// var maxBikou = 0;
|
||
// var bikouCards = [];
|
||
// // 以下变量表示对应颜色的卡的盈余量. (盈余=存在-需求,负数则表示不足)
|
||
// // 减掉需求
|
||
// var colorless = -obj.costColorless || 0;
|
||
// var white = -obj.costWhite || 0;
|
||
// var black = -obj.costBlack || 0;
|
||
// var red = -obj.costRed || 0;
|
||
// var blue = -obj.costBlue || 0;
|
||
// var green = -obj.costGreen || 0;
|
||
// var multi = 0;
|
||
// // 美巧
|
||
// var useBikou = !ignoreReplacement && this.canUseBikou(obj);
|
||
// var bikou = 0;
|
||
// var costWhite = obj.costWhite || 0;
|
||
// // <小剑 三日月>
|
||
// var mikamune = 0;
|
||
// var lrig = this.lrig;
|
||
|
||
// // 加上存在
|
||
// cards.forEach(function (card) {
|
||
// if (card.multiEner) multi++;
|
||
// else if (card.color === 'colorless') colorless++;
|
||
// else if (card.color === 'white' ) white++;
|
||
// else if (card.color === 'black' ) black++;
|
||
// else if (card.color === 'red' ) red++;
|
||
// else if (card.color === 'blue' ) blue++;
|
||
// else if (card.color === 'green' ) green++;
|
||
// // <小剑 三日月>
|
||
// if (card._MikamuneSmallSword && !card.multiEner && (card.color !== lrig.color)) {
|
||
// mikamune++;
|
||
// }
|
||
// },this);
|
||
// // <小剑 三日月>
|
||
// // 借与LRIG颜色相同的卡
|
||
// if (lrig.color === 'white') white += mikamune;
|
||
// else if (lrig.color === 'black') black += mikamune;
|
||
// else if (lrig.color === 'red') red += mikamune;
|
||
// else if (lrig.color === 'blue') blue += mikamune;
|
||
// else if (lrig.color === 'green') green += mikamune;
|
||
// else mikamune = 0;
|
||
// // 美巧
|
||
// if (useBikou) {
|
||
// bikouCards = cards.filter(function (card) {
|
||
// return card.hasClass('美巧');
|
||
// },this);
|
||
// } else {
|
||
// bikouCards = cards.filter(function (card) {
|
||
// return card.trashAsWhiteCost; // <美しき弦奏 コントラ>
|
||
// },this);
|
||
// }
|
||
// bikou = bikouCards.length;
|
||
|
||
// // 于是此时变量的值即为盈余值. (负数表示不足)
|
||
|
||
// // 先考虑白色
|
||
// if (white >= 0) {
|
||
// maxBikou = Math.min(bikou,costWhite);
|
||
// colorless += white; // 盈余的数量加到无色上.
|
||
// } else {
|
||
// minBikou = Math.min(-white,bikou);
|
||
// bikou += white; // 不足的数量用美巧代替.
|
||
// green += white; // 注意绿色也同时减少了.
|
||
// if (bikou < 0) {
|
||
// // 若用美巧代替之后还是不足,则用万花色代替.
|
||
// multi += bikou; // 不足的数量用万花色代替.
|
||
// green -= bikou; // 注意因为用万花色代替了,所以绿色的盈余增加.
|
||
// if (multi < 0) {
|
||
// // 万花色不足,无法完成支付.
|
||
// return {left: -1,minBikou: 0,maxBikou: 0,bikouCards: []};
|
||
// }
|
||
// } else {
|
||
// maxBikou = Math.min(bikou,costWhite - minBikou);
|
||
// }
|
||
// }
|
||
|
||
// // 然后考虑剩下的颜色,除了无色
|
||
// if (![black,red,blue,green].every(function (count) {
|
||
// if (count >= 0) {
|
||
// colorless += count; // 盈余的数量加到无色上.
|
||
// return true;
|
||
// }
|
||
// multi += count; // 不足的数量用万花色代替.
|
||
// return multi >= 0; // 万花色不足,无法完成支付.
|
||
// })) return {left: -1,minBikou: 0,maxBikou: 0,bikouCards: []};
|
||
|
||
// maxBikou = minBikou + Math.min(maxBikou,Math.max(0,green) + multi);
|
||
// minBikou = Math.max(0,minBikou - multi);
|
||
|
||
// // 最后考虑无色.
|
||
// colorless += multi; // 盈余的数量加到无色上.
|
||
// colorless -= mikamune; // 还回从<小剑 三日月>借来的卡.
|
||
|
||
// return {
|
||
// left: colorless, // 此时无色的盈余量即为支付能力后剩下的卡片数.
|
||
// bikouCards: bikouCards,
|
||
// minBikou: minBikou,
|
||
// maxBikou: maxBikou
|
||
// };
|
||
// };
|
||
|
||
// 注意 costSigniColorless 是指任意颜色的signi作为cost
|
||
// Player.prototype.enoughSigniCost = function (obj) {
|
||
// var white = obj.costSigniWhite || 0;
|
||
// var black = obj.costSigniBlack || 0;
|
||
// var red = obj.costSigniRed || 0;
|
||
// var blue = obj.costSigniBlue || 0;
|
||
// var green = obj.costSigniGreen || 0;
|
||
// var colorless = obj.costSigniColorless || 0;
|
||
// for (var i = 0; i < this.hands.length; i++) {
|
||
// var card = this.hands[i];
|
||
// if (card.type !== 'SIGNI') continue;
|
||
|
||
// if (card.color === 'white') {
|
||
// white? white-- : colorless--;
|
||
// } else if (card.color === 'black') {
|
||
// black? black-- : colorless--;
|
||
// } else if (card.color === 'red') {
|
||
// red? red-- : colorless--;
|
||
// } else if (card.color === 'blue') {
|
||
// blue? blue-- : colorless--;
|
||
// } else if (card.color === 'green') {
|
||
// green? green-- : colorless--;
|
||
// } else {
|
||
// colorless--;
|
||
// }
|
||
// }
|
||
// return [white,black,red,blue,green,colorless].some(function (need) {
|
||
// return need > 0;
|
||
// });
|
||
// };
|
||
|
||
// 玩家是否有足够的能量来支付obj指定的费用
|
||
Player.prototype.enoughEner = function (obj) {
|
||
if (obj.costChange) {
|
||
obj = obj.costChange();
|
||
}
|
||
return this.checkEner(this.enerZone.cards,obj).left >= 0;
|
||
};
|
||
Player.prototype.enoughExceed = function (obj) {
|
||
var lrig = obj.source;
|
||
return lrig.zone.cards.length > obj.costExceed;
|
||
};
|
||
Player.prototype.enoughCost = function (obj) {
|
||
if (obj.costChange) {
|
||
obj = obj.costChange();
|
||
}
|
||
if (obj.costCoin && obj.costCoin > this.coin) return false;
|
||
if (obj.costDown && obj.source && !obj.source.isUp) return false;
|
||
if (obj.costCondition && obj.source) {
|
||
if (!obj.costCondition.call(obj.source)) return false;
|
||
}
|
||
if (obj.costExceed && obj.source) {
|
||
if (!this.enoughExceed(obj)) return false;
|
||
}
|
||
if (!this.enoughEner(obj)) return false;
|
||
return true;
|
||
};
|
||
|
||
// Player.prototype.selectSigniCost = function (obj) {
|
||
// if ((!this.needSigniCost(obj))) return Callback.immediately([]);
|
||
// };
|
||
// 要求玩家选择能量
|
||
Player.prototype.selectEnerAsyn = function (obj,cancelable) {
|
||
if (obj.costChange) {
|
||
obj = obj.costChange();
|
||
}
|
||
if (!this.needEner(obj)) return Callback.immediately([]);
|
||
|
||
var player = this;
|
||
player.output({
|
||
type: 'PAY_ENER',
|
||
content: {
|
||
source: obj.source || 0,
|
||
cancelable: !!cancelable,
|
||
cards: this.enerZone.cards,
|
||
integers: this.enerZone.cards.map(this.encodeCard.bind(this)),
|
||
requirements: this.encodeCost(obj, true),
|
||
},
|
||
});
|
||
// player.opponent.output({
|
||
// type: 'WAIT_FOR_OPPONENT',
|
||
// content: {
|
||
// operation: 'PAY_ENER'
|
||
// }
|
||
// });
|
||
|
||
return new Callback(function (callback) {
|
||
player.listen('PAY_ENER',function (idxs) {
|
||
if (idxs === null) {
|
||
// cancel
|
||
if (!cancelable) return false;
|
||
return function () {
|
||
callback(null);
|
||
}
|
||
}
|
||
if (!isArr(idxs)) return false;
|
||
var cards = idxs.map(function (idx) {
|
||
return player.enerZone.cards[idx];
|
||
});
|
||
var legal = cards.every(function (card,idx) {
|
||
return inArr(card,player.enerZone.cards) && cards.indexOf(card) >= idx;
|
||
});
|
||
if (!legal || player.checkEner(cards,obj,true).left !== 0) return false;
|
||
return function () {
|
||
callback(cards);
|
||
}
|
||
})
|
||
});
|
||
};
|
||
|
||
Player.prototype.selectExceedAsyn = function (obj) {
|
||
var cards = obj.source.zone.cards.slice(1);
|
||
var n = obj.costExceed;
|
||
return this.selectSomeAsyn('PAY_EXCEED',cards,n,n);
|
||
};
|
||
|
||
// 支付Cost
|
||
Player.prototype.payCostAsyn = function (obj,cancelable) {
|
||
this.game.frameStart();
|
||
return Callback.immediately().callback(this,function () {
|
||
if (obj.costChangeAsyn) {
|
||
cancelable = false;
|
||
// 需要异步操作的费用改变,如<虹彩・横置>等.
|
||
return obj.costChangeAsyn().callback(this,function (o) {
|
||
obj = o;
|
||
});
|
||
} else if (obj.costChange) {
|
||
// 费用改变
|
||
obj = obj.costChange();
|
||
}
|
||
}).callback(this,function () {
|
||
// 御先狐
|
||
var o = this.checkEner(this.enerZone.cards,obj);
|
||
if (o.left < 0) {
|
||
throw new Error('No enough ener to pay! obj.cid:' + obj.cid);
|
||
}
|
||
if (o.maxOsaki) {
|
||
var min = o.minOsaki;
|
||
var max = o.maxOsaki;
|
||
return this.selectSomeAsyn('TRASH_OSAKI',o.osakiCards,min,max).callback(this,function (cards) {
|
||
if (!cards.length) return;
|
||
cancelable = false;
|
||
this.game.trashCards(cards);
|
||
obj = Object.create(obj);
|
||
obj.costGreen -= cards.length * 3;
|
||
if (obj.costGreen < 0) obj.costGreen = 0;
|
||
});
|
||
}
|
||
}).callback(this,function () {
|
||
// 选择能量
|
||
var costArg = {};
|
||
return this.selectEnerAsyn(obj,cancelable).callback(this,function (cards) {
|
||
if (!cards) {
|
||
// 取消
|
||
this.game.frameEnd();
|
||
return null;
|
||
}
|
||
return Callback.immediately().callback(this,function () {
|
||
costArg.enerCards = cards;
|
||
costArg.enerColors = cards.map(function (card) {
|
||
if (card.multiEner) return 'multi';
|
||
return card.color;
|
||
},this);
|
||
this.game.trashCards(cards);
|
||
// 超越
|
||
if (obj.costExceed && obj.source) {
|
||
return this.selectExceedAsyn(obj).callback(this,function (cards) {
|
||
costArg.exceedCards = cards;
|
||
this.game.trashCards(cards,{isExceedCost: true});
|
||
});
|
||
}
|
||
}).callback(this,function () {
|
||
// 横置
|
||
if (obj.costDown && obj.source) {
|
||
obj.source.down();
|
||
}
|
||
// Coin
|
||
if (obj.costCoin) {
|
||
this.loseCoins(obj.costCoin);
|
||
costArg.bet = obj.costCoin;
|
||
}
|
||
// 其它
|
||
if (obj.costAsyn) {
|
||
if (obj.source) return obj.costAsyn.call(obj.source);
|
||
return obj.costAsyn();
|
||
}
|
||
}).callback(this,function (others) {
|
||
costArg.others = others;
|
||
this.game.frameEnd();
|
||
return costArg;
|
||
});
|
||
});
|
||
});
|
||
};
|
||
|
||
// player.selectAsyn(label,cards,optional,needConfirm)
|
||
// 玩家从cards中选一张卡,
|
||
// 若cards为null或空数组:
|
||
// 若!needConfirm,那么立即callback(null).
|
||
// 否则等玩家确认,然后callback(null).
|
||
// 若cards非空:
|
||
// 若optional,玩家可以不选(返回null).
|
||
// 否则,玩家必须从cards中选一张卡,callback返回这张卡.
|
||
// callback(null|card)
|
||
Player.prototype.selectAsyn = function (label,cards,optional,needConfirm) {
|
||
if (cards && !cards.length && !needConfirm) {
|
||
return Callback.immediately(null);
|
||
}
|
||
|
||
if (!cards) {
|
||
return this.selectSomeAsyn(label,[]).callback(this,function () {
|
||
return null;
|
||
});
|
||
}
|
||
|
||
var min = optional? 0 : 1;
|
||
return this.selectSomeAsyn(label,cards,min,1).callback(this,function (selectedCards) {
|
||
return selectedCards[0] || null;
|
||
});
|
||
};
|
||
|
||
Player.prototype.selectOptionalAsyn = function (label,cards,needConfirm) {
|
||
return this.selectAsyn(label,cards,true,needConfirm);
|
||
};
|
||
|
||
Player.prototype.selectTargetAsyn = function (cards,optional,needConfirm) {
|
||
return this.selectAsyn('TARGET',cards,optional,needConfirm).callback(this,function (card) {
|
||
if (card) {
|
||
card.beSelectedAsTarget();
|
||
}
|
||
return card;
|
||
});
|
||
};
|
||
|
||
Player.prototype.selectTargetOptionalAsyn = function (cards,needConfirm) {
|
||
return this.selectTargetAsyn(cards,true,needConfirm);
|
||
};
|
||
|
||
Player.prototype.selectSomeTargetsAsyn = function (cards,min,max,careOrder) {
|
||
return this.selectSomeAsyn('TARGET',cards,min,max,careOrder).callback(this,function (selectedCards) {
|
||
selectedCards.forEach(function (card) {
|
||
card.beSelectedAsTarget();
|
||
},this);
|
||
return selectedCards;
|
||
})
|
||
};
|
||
|
||
Player.prototype.selectSomeAsyn = function (label,items,min,max,careOrder,extraCards) {
|
||
// 过滤 shadow 目标
|
||
items = items.filter(item => {
|
||
var source = this.game.getEffectSource();
|
||
if (item.shadow && source && source.player !== item.player) {
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
if (!(min >= 0)) min = 0;
|
||
if (max === undefined || max < 0) {
|
||
max = items.length;
|
||
}
|
||
min = Math.min(min,items.length);
|
||
max = Math.min(max,items.length);
|
||
careOrder = !!careOrder;
|
||
extraCards = extraCards || [];
|
||
|
||
var cards = items;
|
||
var descriptions = [];
|
||
var sample = items[0];
|
||
if (sample && sample.source) {
|
||
// 选项是效果
|
||
cards = items.map(function (effect) {
|
||
return effect.source || null;
|
||
});
|
||
descriptions = items.map(function (effect) {
|
||
return effect.description || '';
|
||
});
|
||
}
|
||
|
||
var player = this;
|
||
player.output({
|
||
type: 'SELECT',
|
||
content: {
|
||
label: label,
|
||
options: cards,
|
||
descriptions: descriptions,
|
||
extraCards: extraCards,
|
||
extraPids: extraCards.map(function (card) {
|
||
return card.pid;
|
||
}),
|
||
min: min,
|
||
max: max,
|
||
careOrder: careOrder
|
||
}
|
||
});
|
||
// player.opponent.output({
|
||
// type: 'WAIT_FOR_OPPONENT',
|
||
// content: {
|
||
// operation: '' // 注意!
|
||
// }
|
||
// });
|
||
return new Callback(function (callback) {
|
||
player.listen(label,function (idxs) {
|
||
if (!isArr(idxs)) return false;
|
||
if (idxs.length > max) return false;
|
||
if (idxs.length < min) return false;
|
||
if (!careOrder) {
|
||
idxs.sort(function (a,b) {
|
||
return a - b;
|
||
});
|
||
}
|
||
var selectedItems = idxs.map(function (idx) {
|
||
if ((idx >= 0) && (idx < items.length)) {
|
||
return items[idx];
|
||
}
|
||
return null;
|
||
},this);
|
||
var legal = selectedItems.every(function (item,i) {
|
||
return item && (selectedItems.indexOf(item) === i);
|
||
},this);
|
||
if (!legal) return false;
|
||
return function () {
|
||
callback(selectedItems);
|
||
}
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.selectByFilterAsyn = function (filter,optional,cards) {
|
||
var cards = filter? cards.filter(filter) : cards;
|
||
return optional? this.selectTargetOptionalAsyn(cards) : this.selectTargetAsyn(cards);
|
||
};
|
||
|
||
Player.prototype.selectOpponentSigniAsyn = function (filter,optional) {
|
||
return this.selectByFilterAsyn(filter,optional,this.opponent.signis);
|
||
};
|
||
|
||
Player.prototype.selectSelfSigniAsyn = function (filter,optional) {
|
||
return this.selectByFilterAsyn(filter,optional,this.signis);
|
||
};
|
||
|
||
Player.prototype.searchAsyn = function (filter,max,min,dontShow) {
|
||
var cards = this.mainDeck.cards.filter(filter,this);
|
||
max = Math.min(max,cards.length);
|
||
min = min || 0;
|
||
min = Math.min(min,cards.length);
|
||
return this.selectSomeAsyn('SEEK',cards,min,max,false,this.mainDeck.cards).callback(this,function (cards) {
|
||
if (dontShow) {
|
||
this.shuffle();
|
||
return cards;
|
||
}
|
||
return this.opponent.showCardsAsyn(cards).callback(this,function () {
|
||
this.shuffle();
|
||
return cards;
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.seekAsyn = function (filter,max,min,dontShow) {
|
||
return this.searchAsyn(filter,max,min,dontShow).callback(this,function (cards) {
|
||
this.game.moveCards(cards,this.handZone);
|
||
return cards;
|
||
});
|
||
};
|
||
|
||
Player.prototype.seekAndSummonAsyn = function (filter,n,dontTriggerStartUp) {
|
||
var done = false;
|
||
var rtnCards = [];
|
||
this.game.frameStart();
|
||
return Callback.loop(this,n,function () {
|
||
if (done) return;
|
||
var cards = this.mainDeck.cards.filter(function (card) {
|
||
return card.canSummon() && filter(card);
|
||
},this);
|
||
return this.selectSomeAsyn('SEEK',cards,0,1,false,this.mainDeck.cards).callback(this,function (cards) {
|
||
var card = cards[0];
|
||
if (!card) {
|
||
done = true;
|
||
return;
|
||
};
|
||
rtnCards.push(card);
|
||
return card.summonAsyn(false,dontTriggerStartUp);
|
||
});
|
||
}).callback(this,function () {
|
||
this.shuffle();
|
||
this.game.frameEnd();
|
||
return rtnCards;
|
||
});
|
||
};
|
||
|
||
Player.prototype.pickCardAsyn = function (filter,min,max,zone) {
|
||
if (!isNum(min)) min = 1;
|
||
if (!isNum(max)) max = 1;
|
||
if (!zone) zone = this.trashZone;
|
||
var cards = filter? zone.cards.filter(filter) : zone.cards;
|
||
return this.selectSomeAsyn('ADD_TO_HAND',cards,min,max).callback(this,function (cards) {
|
||
if (!cards.length) return;
|
||
return this.opponent.showCardsAsyn(cards).callback(this,function () {
|
||
this.game.moveCards(cards,this.handZone);
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.rebornAsyn = function (filter,count,arg) {
|
||
if (!isNum(count)) count = 1;
|
||
if (!arg) arg = {};
|
||
var done = false;
|
||
return Callback.loop(this,count,function () {
|
||
if (done) return;
|
||
var cards = this.trashZone.cards.filter(function (card) {
|
||
if (filter && !filter(card)) return false;
|
||
return card.canSummon();
|
||
},this);
|
||
return this.selectAsyn('SUMMON_SIGNI',cards).callback(this,function (card) {
|
||
if (!card) return done = true;
|
||
return card.summonAsyn(false,arg.dontTriggerStartUp,arg.down);
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.rearrangeOpponentSignisAsyn = function () {
|
||
return this.rearrangeSignisAsyn(this.opponent);
|
||
};
|
||
|
||
Player.prototype.rearrangeSignisAsyn = function (whos) {
|
||
if (!whos) whos = this;
|
||
var done = false;
|
||
var signis = whos.signis.filter(function (signi) {
|
||
return !signi.isEffectFiltered();
|
||
},this);
|
||
var zones = whos.signiZones.filter(function (zone) {
|
||
if (zone.disabled) return false;
|
||
return (!zone.cards.length) || (!zone.cards[0].isEffectFiltered());
|
||
},this);
|
||
|
||
var changedSignis = [];
|
||
return Callback.loop(this,2,function () {
|
||
if (done) return;
|
||
if (!signis.length || (zones.length <= 1)) return;
|
||
return this.selectTargetOptionalAsyn(signis).callback(this,function (signi) {
|
||
if (!signi) {
|
||
done = true;
|
||
return;
|
||
}
|
||
removeFromArr(signi,signis);
|
||
var _zones = zones.filter(function (zone) {
|
||
return (zone !== signi.zone);
|
||
},this);
|
||
return this.selectOptionalAsyn('RESET_SIGNI_ZONE',_zones).callback(this,function (zone) {
|
||
if (!zone) return;
|
||
removeFromArr(zone,zones);
|
||
var card = zone.cards[0];
|
||
if (signi.changeSigniZone(zone)) {
|
||
if (!inArr(signi,changedSignis)) changedSignis.push(signi);
|
||
if (card && !inArr(card,changedSignis)) changedSignis.push(card);
|
||
}
|
||
});
|
||
});
|
||
}).callback(this,function () {
|
||
return changedSignis;
|
||
});
|
||
};
|
||
|
||
Player.prototype.listen = function (label,handle) {
|
||
this.listener[label] = handle;
|
||
};
|
||
|
||
Player.prototype.input = function (data) {
|
||
// if (!isArr(data)) return false;
|
||
// if (data.length !== 2) return false;
|
||
// var label = data[0];
|
||
// var input = data[1];
|
||
if (!isObj(data)) return false;
|
||
var label = data.label;
|
||
var input = data.input;
|
||
if (!isStr(label)) return false;
|
||
var handle = this.listener[label];
|
||
if (!isFunc(handle)) return false;
|
||
var callback = handle(input);
|
||
if (!isFunc(callback)) return false;
|
||
this.listener = {};
|
||
// var p = (this === this.game.hostPlayer)? 'hostPlayer' : 'guestPlayer';
|
||
// if (!window.inputs) window.inputs = '';
|
||
// window.inputs += ('game.'+p+'.input("'+label+'",['+input.toString()+']);');
|
||
// console.time(label);
|
||
callback();
|
||
// console.timeEnd(label);
|
||
this.game.sendMsgQueue();
|
||
if (this.game.winner) {
|
||
this.game.gameover();
|
||
}
|
||
return true;
|
||
};
|
||
|
||
Player.prototype.output = function (msgObj) {
|
||
// var start = Date.now();
|
||
msgObj = this.handleMsgObj(msgObj);
|
||
// var end = Date.now();
|
||
// console.log('player.handleMsgObj() '+(end-start)+'ms');
|
||
this.msgQueue.push(msgObj);
|
||
};
|
||
|
||
Player.prototype.sendMsgQueue = function () {
|
||
var queue = this.msgQueue.slice();
|
||
this.messagePacks.push(queue);
|
||
this.io.send(queue);
|
||
this.msgQueue.length = 0;
|
||
};
|
||
|
||
Player.prototype.handleMsgObj = function (v) {
|
||
if (isArr(v)) {
|
||
var arr = v;
|
||
var newArr = []
|
||
arr.forEach(function (item) {
|
||
newArr.push(this.handleMsgObj(item));
|
||
},this);
|
||
return newArr;
|
||
}
|
||
if (isObj(v)) {
|
||
var obj = v;
|
||
var sid = this.game.getSid(this,obj);
|
||
if (isNum(sid)) return sid;
|
||
var newObj = {};
|
||
for (var x in obj) {
|
||
newObj[x] = this.handleMsgObj(obj[x]);
|
||
}
|
||
return newObj;
|
||
}
|
||
return v;
|
||
};
|
||
|
||
Player.prototype.ignoreGrowCostInNextTurn = function () {
|
||
this.ignoreGrowCost = true;
|
||
};
|
||
|
||
Player.prototype.trashLifeClothWhenTurnEnd = function (n) {
|
||
this._trashLifeClothCount += n;
|
||
};
|
||
|
||
Player.prototype.getCharms = function () {
|
||
var charms = [];
|
||
this.signis.forEach(function (signi) {
|
||
if (signi.charm) {
|
||
charms.push(signi.charm);
|
||
}
|
||
},this);
|
||
return charms;
|
||
};
|
||
|
||
Player.prototype.setCrossPair = function () {
|
||
// 清除已有CP
|
||
this.crossed.forEach(function (pair) {
|
||
pair.crossed = null;
|
||
},this);
|
||
this.crossed = [];
|
||
|
||
var card = this.signiZones[1].cards[0];
|
||
if (!card) return;
|
||
if (!card.crossLeft && !card.crossRight) return;
|
||
function checkMatch (zone,cross) {
|
||
if (!zone) return null;
|
||
var card = zone.getSigni();
|
||
if (!card) return null;
|
||
var cids = concat(cross);
|
||
var matched = cids.some(function (cid) {
|
||
return (card.cid === cid);
|
||
},this);
|
||
return matched? card : null;
|
||
}
|
||
// 3 CROSS
|
||
if (card.crossLeft && card.crossRight) {
|
||
if (!checkMatch(this.signiZones[0],card.crossRight)) return;
|
||
if (!checkMatch(this.signiZones[2],card.crossLeft)) return;
|
||
this.crossed = this.signis.slice();
|
||
this.signis.forEach(function (signi) {
|
||
signi.crossed = this.signis.slice();
|
||
},this);
|
||
return;
|
||
}
|
||
// 2 CROSS
|
||
var zone = card.crossRight? this.signiZones[0] : this.signiZones[2];
|
||
var pair = checkMatch(zone,card.crossLeft || card.crossRight);
|
||
if (!pair) return;
|
||
this.crossed = [card,pair];
|
||
card.crossed = [card,pair];
|
||
pair.crossed = [card,pair];
|
||
};
|
||
|
||
Player.prototype.gainCoins = function(count) {
|
||
this.setCoin(this.coin + count);
|
||
};
|
||
|
||
Player.prototype.loseCoins = function(count) {
|
||
this.setCoin(this.coin - count);
|
||
};
|
||
|
||
Player.prototype.setCoin = function(count) {
|
||
var coin = this.coin;
|
||
this.coin = Math.max(0, Math.min(5, count));
|
||
if (this.lrig.isFaceup) {
|
||
this.game.output({
|
||
type: 'COIN_CHANGE',
|
||
content: {
|
||
player: this,
|
||
coin: this.coin,
|
||
},
|
||
});
|
||
}
|
||
};
|
||
|
||
Player.prototype.getInfectedZones = function() {
|
||
return this.signiZones.filter(function (zone) {
|
||
return zone.virus;
|
||
},this);
|
||
};
|
||
|
||
Player.prototype.getInfectedCards = function() {
|
||
return this.signis.filter(function (signi) {
|
||
return signi.isInfected();
|
||
},this);
|
||
};
|
||
|
||
Player.prototype.infectZoneAsyn = function() {
|
||
var zones = this.opponent.signiZones;
|
||
return this.selectAsyn('TARGET',zones).callback(this,function (zone) {
|
||
if (!zone) return null;
|
||
zone.putVirus();
|
||
return zone;
|
||
});
|
||
};
|
||
|
||
Player.prototype.setTrapFromDeckTopAsyn = function(count,max,forced) {
|
||
if (!isNum(max)) max = 1;
|
||
var cards = this.mainDeck.getTopCards(count);
|
||
this.informCards(cards);
|
||
var done = false;
|
||
return Callback.loop(this,max,function () {
|
||
if (done) return;
|
||
return this.selectAsyn('TARGET',cards,!forced).callback(this,function (card) {
|
||
if (!card) return done = true;
|
||
removeFromArr(card,cards);
|
||
return this.selectAsyn('TARGET',this.signiZones).callback(this,function (zone) {
|
||
card.trapTo(zone);
|
||
});
|
||
});
|
||
}).callback(this,function () {
|
||
var len = cards.length;
|
||
if (!len) return;
|
||
return this.selectSomeAsyn('SET_ORDER',cards,len,len,true).callback(this,function (cards) {
|
||
this.mainDeck.moveCardsToBottom(cards);
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.getTraps = function() {
|
||
return this.signiZones.filter(function (zone) {
|
||
return zone.trap;
|
||
}).map(function (zone) {
|
||
return zone.trap;
|
||
});
|
||
};
|
||
|
||
Player.prototype.handleEffectAsyn = function(effect) {
|
||
if (!effect.checkCondition()) return Callback.immediately();
|
||
var card = effect.source;
|
||
card.beSelectedAsTarget();
|
||
return card.player.opponent.showEffectsAsyn([effect]).callback(this,function () {
|
||
return this.game.blockAsyn(card,function () {
|
||
return effect.handleAsyn(false);
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.addLifeCloth = function(count) {
|
||
if (!isNum(count)) count = 1;
|
||
var cards = this.mainDeck.getTopCards(count);
|
||
return this.game.moveCards(cards,this.lifeClothZone);
|
||
};
|
||
|
||
Player.prototype.pickCardsFromDeckTopAsyn = function(count,filter,max) {
|
||
if (!isNum(max)) max = 1;
|
||
var cards = this.mainDeck.getTopCards(count);
|
||
if (!cards.length) return;
|
||
this.informCards(cards);
|
||
var targets = cards.filter(filter);
|
||
return Callback.immediately().callback(this,function () {
|
||
if (!targets.length) return;
|
||
return this.selectSomeAsyn('ADD_TO_HAND',targets,0,max,false,cards).callback(this,function (targets) {
|
||
return this.opponent.showCardsAsyn(targets).callback(this,function () {
|
||
this.game.moveCards(targets,this.handZone);
|
||
cards = cards.filter(function (card) {
|
||
return !inArr(card,targets);
|
||
},this);
|
||
});
|
||
});
|
||
}).callback(this,function () {
|
||
var len = cards.length;
|
||
if (!len) return;
|
||
return this.selectSomeAsyn('SET_ORDER',cards,len,len,true).callback(this,function (cards) {
|
||
this.mainDeck.moveCardsToBottom(cards);
|
||
});
|
||
});
|
||
};
|
||
|
||
Player.prototype.getWisdom = function() {
|
||
var wisdom = 0;
|
||
this.signis.forEach(function (signi) {
|
||
if (!signi.hasClass('英知')) return;
|
||
wisdom += signi.level;
|
||
},this);
|
||
return wisdom;
|
||
};
|
||
|
||
global.Player = Player; |