import {Player} from "../../schema/Player"; import {Card} from "../../schema/Card"; import {CardGameState} from "../../schema/CardGameState"; import { PlayerHandler } from "./PlayerHandler"; import CfgMan from "../CfgMan"; import { EffectCardType, GameCampType, GameUnitType, SkillEffectType, SkillRangeUnitType } from "../skill/SkillConst"; import { Pet } from "rooms/schema/Pet"; import {PetUpdateProcess, SkillParam, SkillResult, SkillTarget} from "../skill/SkillParam"; import { nosync, Room } from "colyseus"; import { Skill } from "../skill/Skill"; import { PetHandler } from "./PetHandler"; import { SKillEffectData, SkillInfoMsg } from "message/SkillInfo"; import { PetInfo } from "message/PetInfo"; import arrUtil from "../../../utils/array.util"; import { debugRoom } from "../../../common/Debug"; export class BattleHandler { private _cs: CardGameState; private _players: Map = new Map(); private _playerids: Map = new Map(); _room: Room; private _flowcount: number = 0; private _sktime: number; private _gamestart: boolean = false; private _cacheSkills: SkillTarget[] = []; private _cachePets: PetHandler[] = []; private _lastlinkph: PlayerHandler; //--------------------对外接口--player相关---(外部调用)---------------------------- public init(cs: CardGameState, room: Room){ this._cs = cs; this._room = room; this._gamestart = false; }; public delPlayer(aplayer: Player){ let id = aplayer.id + ''; let ph = this.getPlayer(aplayer); this._players.forEach((item: PlayerHandler) => { if(item._friend == ph){ item._friend = null; } }); this._players.delete(aplayer); this._playerids.delete(id); }; public updatePlayer(aplayerid: string, newplayer: Player){ let oldplayer = this._playerids.get(aplayerid + ''); let ph: PlayerHandler = null; if(oldplayer){ this._players.forEach((item: PlayerHandler) => { if(item._player == oldplayer){ item._player = newplayer; ph = item; } }); this._players.delete(oldplayer); if(ph){ this._players.set(newplayer, ph); this._playerids.set(aplayerid + '', newplayer); } } if(!ph){ this.addPlayer(newplayer); } }; public updatePlayerHero(aplayer: Player){ let ph = this.getPlayer(aplayer); if(!ph){ return false; } return ph.updateHero(); }; //---------------------------------------------------------------------------- public addPlayer(aplayer: Player): PlayerHandler{ let ph = new PlayerHandler(); ph.init(aplayer, this); this._players.forEach((item: PlayerHandler) => { if(item._player.team == aplayer.team && item._player != aplayer){ item._friend = ph; ph._friend = item; } }); this._players.set(aplayer, ph); this._playerids.set(aplayer.id + '', aplayer); return ph; }; public getPlayer(aplayer: Player): PlayerHandler{ return aplayer? this._players.get(aplayer): null; }; public getFriend(aplayer: PlayerHandler): PlayerHandler{ if(aplayer && aplayer._friend){ return aplayer._friend; } // 防止出错冗余处理 let res; for(let [key, obj] of this._players){ if(obj._friend == aplayer){ aplayer._friend = obj; res = obj; break; } } return res; }; public petIsValid(pet: PetHandler, players: PlayerHandler[], ct: GameUnitType): boolean{ if(!players || players.length == 0 || !pet || !pet.isAlive()){ return false; } let obj = players.find( (item: PlayerHandler) =>{ return item.isMyPet(pet); }); if(!obj){ return false; } switch(ct){ case GameUnitType.BATTLEUNIT: return true; case GameUnitType.HERO: return pet._isHero; case GameUnitType.PET: return !pet._isHero; default: return false; } }; public getFinalTarget(ut: SkillRangeUnitType, players: PlayerHandler[], dstpet: PetHandler, srcpet: PetHandler, senderpet: PetHandler, ct: GameUnitType, checktaunt: boolean, isonlypet: boolean): PetHandler { let pet = dstpet; let bok = false; switch(ut){ case SkillRangeUnitType.SELF: if(!pet){ pet = srcpet; } bok = !!pet; break; case SkillRangeUnitType.RANDOM_ONE: case SkillRangeUnitType.RANDOM_ONE_EXOWNER: case SkillRangeUnitType.RANDOM_ONE_EXSELF: { let expet:PetHandler = null; if(ut == SkillRangeUnitType.RANDOM_ONE_EXOWNER){ expet = senderpet; }else if(ut == SkillRangeUnitType.RANDOM_ONE_EXSELF){ expet = srcpet; } if(checktaunt){ let lst:PetHandler[] = []; players.forEach((item:PlayerHandler)=>{ item.findAllTauntPets(lst, isonlypet, expet); }); if(lst.length > 0){ pet = arrUtil.randomOne(lst); bok = true; } } if(!bok){ let lst:PetHandler[] = []; players.forEach((item:PlayerHandler)=>{ item.findAllPets(lst, isonlypet, expet); }); if(lst.length > 0){ pet = arrUtil.randomOne(lst); bok = true; } } } break; case SkillRangeUnitType.RANDOM_ONE_NOSHIELD: { let lst:PetHandler[] = []; players.forEach((item:PlayerHandler)=>{ item.findAllNoShieldPets(lst, isonlypet); }); if(lst.length > 0){ pet = arrUtil.randomOne(lst); bok = true; } } break; case SkillRangeUnitType.OWNER: pet = senderpet; bok = !!pet; break; case SkillRangeUnitType.MAXAP_ONE: { let lst:PetHandler[] = []; players.forEach((item:PlayerHandler)=>{ lst.push(item.getMaxAPPet(isonlypet)); }); pet = null; if(lst.length > 0){ lst.forEach(element => { if(!pet){ pet = element; }else if(pet.totalAP() < element.totalAP()){ pet = element; } }); } bok = !!pet; } break; case SkillRangeUnitType.MINAP_ONE: { let lst:PetHandler[] = []; players.forEach((item:PlayerHandler)=>{ lst.push(item.getMinAPPet(isonlypet)); }); pet = null; if(lst.length > 0){ lst.forEach(element => { if(!pet){ pet = element; }else if(pet.totalAP() > element.totalAP()){ pet = element; } }); } bok = !!pet; } break; case SkillRangeUnitType.PET_LAST: { let lst:PetHandler[] = []; players.forEach((item:PlayerHandler)=>{ let obj = item.getLastPet(); obj && lst.push(obj); }); if(lst.length > 0){ pet = arrUtil.randomOne(lst); bok = true; } }break; default: { bok = this.petIsValid(pet, players, ct); if(checktaunt && (!bok || !pet.isTaunt())){ for(let i = 0; i < players.length;i++){ let obj = players[i].findTauntPet(); if(obj){ pet = obj; bok = true; break; } } } } break; } return bok? pet: null; }; public buildSkillTarget(sk: Skill, src: PetHandler, dst: PetHandler | PlayerHandler): SkillTarget{ if(!dst){ return null; } let ut = GameUnitType.NONE; if(dst instanceof PetHandler){ ut = (dst as PetHandler)._isHero? GameUnitType.HERO: GameUnitType.PET; }else if(dst instanceof PlayerHandler){ ut = GameUnitType.PLAYER; } let owner = sk? sk._owner: null; return new SkillTarget(sk, src? src._owner: owner, src, dst, ut); }; public singleSkillTargets(sk: Skill, src: PetHandler, dst: PetHandler | PlayerHandler): SkillTarget[]{ let tgt = this.buildSkillTarget(sk, src, dst); if(this.isFlowing() && tgt){ this._cacheSkills.push(tgt); } return tgt? [tgt]: null; }; public singleSkillTarget(sk: Skill, src: PetHandler, dst: PetHandler | PlayerHandler): SkillTarget{ let tgt = this.buildSkillTarget(sk, src, dst); if(this.isFlowing() && tgt){ this._cacheSkills.push(tgt); } return tgt; }; public getSkillOppTargets(st: SkillTarget): SkillTarget[]{ let lst: SkillTarget[] = []; lst.push(st.oppClone()); if(this.isFlowing()){ this._cacheSkills.push(...lst); } return lst; }; public getSkillTargets(skill: Skill, param: SkillParam, lastph: PlayerHandler): SkillTarget[]{ let lst: SkillTarget[] = []; let players = this.getTargetPlayers(skill._data.friendlyid, param.srcplayer, param.dstplayer, lastph); if(players.length > 0){ switch(skill._data.targetid){ case GameUnitType.PLAYER: if(skill._data.rangeid == SkillRangeUnitType.PLAYER_BELINKED){ this._lastlinkph && lst.push( this.buildSkillTarget(skill, param.srcpet, this._lastlinkph) ); }else{ players.forEach((item:PlayerHandler)=>{ lst.push( this.buildSkillTarget(skill, param.srcpet, item) ); }); } break; case GameUnitType.HERO: players.forEach((item:PlayerHandler)=>{ lst.push( this.buildSkillTarget(skill, param.srcpet, item._self) ); }); break; case GameUnitType.BATTLEUNIT: case GameUnitType.PET: if(skill.isSingleTarget()){ let pet = this.getFinalTarget(skill._data.rangeid, players, param.dstpet, param.srcpet, skill._petowner, skill._data.targetid, !!skill._data.ridicule, skill._data.targetid == GameUnitType.PET); pet && lst.push( this.buildSkillTarget(skill, param.srcpet, pet) ); }else{ let expet = skill.getExPet(param.srcpet); players.forEach((item: PlayerHandler)=>{ item.exportAllPets(skill, param, expet, lst); }); } break; default: break; } } if(this.isFlowing() && lst.length > 0){ this._cacheSkills.push(...lst); } return lst; }; public getTargetPlayers(gct: GameCampType, src:PlayerHandler, dst:PlayerHandler, last: PlayerHandler): PlayerHandler[]{ let lst: PlayerHandler[] = []; switch(gct){ case GameCampType.SELFPLAYER: if(src && src.isAlive()){ lst.push(src); } break; case GameCampType.FRIEND: { let obj = this.getFriend(src); if(obj && obj.isAlive()){ lst.push(obj); } } break; case GameCampType.MYTEAM: if(src){ src.isAlive() && lst.push(src); let obj = this.getFriend(src); obj && obj.isAlive() && lst.push(obj); } break; case GameCampType.ENEMY: if(dst && dst != src && dst._friend != src && dst.isAlive()){ lst.push(dst); } break; case GameCampType.ENEMYTEAM: for(let [key, obj] of this._players){ if(obj != src && obj.isAlive()){ if(src && obj != src._friend){ lst.push(obj); } } } break; case GameCampType.ALLPLAYER: for(let [key, obj] of this._players){ if(obj && obj.isAlive()){ lst.push(obj); } } break; case GameCampType.RANDOM_ENEMY: { let tmp: PlayerHandler[] = []; for(let [key, obj] of this._players){ if(obj != src && obj.isAlive()){ if(src && obj != src._friend){ tmp.push(obj); } } } (tmp.length > 0) && lst.push(arrUtil.randomOne(tmp)); } break; case GameCampType.RANDOM_US: { let tmp: PlayerHandler[] = []; if(src){ src.isAlive() && tmp.push(src); let obj = this.getFriend(src); obj && obj.isAlive() && tmp.push(obj); } (tmp.length > 0) && lst.push(arrUtil.randomOne(tmp)); } break; case GameCampType.FACE_ENEMY: { let bfind = false; let player = this._room.getOppositePlayer(src.getId()); if(player){ let ph = this.getPlayer(player); if(ph && ph.isAlive()){ lst.push(ph); bfind = true; } } if(!bfind){ return this.getTargetPlayers(GameCampType.RANDOM_ENEMY, src, dst, last); } } break; case GameCampType.ALLPLAYER_EXSELF: for(let [key, obj] of this._players){ if(obj && obj.isAlive() && obj != src){ lst.push(obj); } } break; case GameCampType.LAST_HURT: if(last && last != src && last.isAlive()){ lst.push(last); } break; default: break; } return lst; }; public checkPets(pets: PetHandler[]){ pets.forEach((item: PetHandler) =>{ if(!item.isAlive()){ item.destroy(); }else{ item.resetBakAP(); } }); }; public isFlowing(){ return this._flowcount > 0; }; public beginFlow(step: string){ if(this._flowcount < 0){ debugRoom(`[beginFlow]${step}[error flowcount]${this._flowcount}`); this._flowcount = 0; }; if(this._flowcount == 0){ this._sktime = 0; this._cacheSkills.length = 0; this._cachePets.length = 0; } this._flowcount++; debugRoom(`[beginFlow]${step}|${this._flowcount}`); }; public endFlow(step: string){ let res = this._flowcount - 1; if(res < 0){ debugRoom(`[endFlow]${step}[error flowcount]${this._flowcount}`); res = 0; } debugRoom(`[endFlow]${step}|${res}`); if(res == 0){ if(this._cacheSkills.length > 0){ this.onSkillResultNotify(this._cacheSkills); this._cacheSkills.length = 0; } let nt = this._sktime * 1000; if(this._cachePets.length > 0){ if(nt > 0){ this._room.clock.setTimeout(()=>{ this.onUpdatePets(this._cachePets, null); this.checkPets(this._cachePets); this._cachePets.length = 0; }, nt); nt += 100; }else{ this.onUpdatePets(this._cachePets, null); this.checkPets(this._cachePets); this._cachePets.length = 0; } } this._flowcount = res; return nt; } this._flowcount = res; return 0; }; public onPlayerCardChanged(player: PlayerHandler){ this._players.forEach((item: PlayerHandler) => { item.onCardChanged(player); }); }; //--------------------对外接口(外部调用)---------------------------- /** * 使用卡片 * @param obj */ public useCard(obj: {srcplayer: Player, card: number, cardpoint: number, dbpt_cnt: number, eff_cnt: number, dstplayer: Player, dstpet: Pet, oldpos?: number}) :number{ if(!obj || !obj.card){ return 0; } let ph = this.getPlayer(obj.srcplayer); let dstph = this.getPlayer(obj.dstplayer); let dstpt = dstph? dstph.getPet(obj.dstpet): null; if(!ph){ return 0; } if(!dstpt && dstph){ dstpt = dstph._self; } this.beginFlow('useCard'); let pt = obj.cardpoint; let cfg = CfgMan.findEffCardCfg(obj.card); if(cfg && cfg.followdouble && obj.dbpt_cnt){ pt *= (obj.dbpt_cnt + 1); } if(!dstph){ dstph = ph; } let ps = new SkillParam(obj.card, pt, obj.eff_cnt, ph, null, dstph, dstpt); ph.useCard(ps, obj.oldpos); this.onUseCardEnd(ps); return this.endFlow('useCard'); }; /** * 使用技能 * @param obj */ public useSkill(obj:{ srcplayer: Player, skillid: number, dstplayer: Player, dstpet: Pet }){ }; /** * 确认玩家是否有换效果牌技能 * @param aplayer */ public hasTransEffCardSkill(aplayer: Player): boolean{ let ph = this.getPlayer(aplayer); return ph && ph.hasTransEffCardSkill(); }; /** * 获得玩家效果牌转换比率(几张普通卡转一张效果卡) * @param aplayer */ public getTransEffCardRate(aplayer: Player): number{ let ph = this.getPlayer(aplayer); return ph? ph.getTransEffCardRate(): 0; }; /** * 替换随从操作 * @param playerid * @param petpos * @param petid */ public replacePet(playerid: string, petpos: number, petid: string){ }; /** * 吃牌/胡牌确认[暂不用] * @param aplayer : 玩家 * @param fromplayer : 吃别人的牌,自己胡牌时此参数传空 */ public onCardLinkReady(aplayer: Player, fromplayer?: Player){ let ph = this.getPlayer(aplayer); let fromph = this.getPlayer(fromplayer); if(!ph){ return 0; } this.beginFlow('onCardLinkReady'); ph.onCardLinkReady(fromph); return this.endFlow('onCardLinkReady'); }; /** * 吃牌/胡牌结束 * @param aplayer :玩家 * @param linkcards :吃到的牌组信息 */ public onCardLinkOver(aplayer: Player, linkcards: Card[], fromplayer?: Player){ let ph = this.getPlayer(aplayer); let fromph = this.getPlayer(fromplayer); if(!ph){ return 0; } this.beginFlow('onCardLinkOver'); this._lastlinkph = fromph; ph.onCardLinkEnd(linkcards, fromph); this._lastlinkph = null; return this.endFlow('onCardLinkOver'); }; /** * 出牌结束(出单张牌) * @param aplayer : 玩家 * @param card : 单张牌 */ public onCardDiscarded(aplayer: Player, card: Card){ let ph = this.getPlayer(aplayer); if(!ph){ return 0; } this.beginFlow('onCardDiscarded'); ph.onCardDiscarded(card); return this.endFlow('onCardDiscarded'); }; /** * 使用卡牌结束(暂时自动回调,无需外部触发) * @param sp :使用卡牌相关操作 */ public onUseCardEnd(sp: SkillParam){ if(!sp){ return; } sp.srcplayer && sp.srcplayer.onUseCardEnd(sp); }; /** * 弃牌完成 //TODO:: 弃卡实装后须调用 * @param aplayer :玩家 * @param fromplayer : 谁使玩家弃牌的(自己主动弃牌的传空) * @param dropcards : 弃掉的牌组 */ public onCardDroped(aplayer: Player, dropcards: Card[], fromplayer?: Player){ let ph = this.getPlayer(aplayer); let fromph = this.getPlayer(fromplayer); if(!ph){ return 0; } // this.beginFlow('onCardDroped'); ph.onCardDroped(dropcards, fromph); // return this.endFlow('onCardDroped'); return 0; }; /** * 获取牌完成 * @param aplayer :玩家 * @param fromplayer : 谁使玩家获得牌的(自己主动获得牌的传空) * @param dropcards : 获得的牌组 */ public onCardGetted(aplayer: Player, getcards: Card[], fromplayer?: Player){ if(!this._gamestart){ return; } let ph = this.getPlayer(aplayer); let fromph = this.getPlayer(fromplayer); if(!ph){ return 0; } // this.beginFlow('onCardGetted'); ph.onCardGetted(getcards, fromph); // return this.endFlow('onCardGetted'); return 0; }; /** * 玩家回合开始 * @param aplayer */ public onPlayerRoundStart(aplayer: Player){ if(!this._gamestart){ this._gamestart = true; } let ph = this.getPlayer(aplayer); if(!ph){ return 0; } this.beginFlow('onPlayerRoundStart'); ph.onRoundStart(); return this.endFlow('onPlayerRoundStart'); }; /** * 玩家回合结束 * @param aplayer */ public onPlayerRoundEnd(aplayer: Player){ let ph = this.getPlayer(aplayer); if(!ph){ return 0; } this.beginFlow('onPlayerRoundEnd'); ph.onRoundEnd(); // this.checkPets(); return this.endFlow('onPlayerRoundEnd'); }; /** * 玩家死亡 * @param aplayer */ public onPlayerDead(aplayer: Player){ let ph = this.getPlayer(aplayer); ph && ph.die(); this._players.forEach((item: PlayerHandler) => { (item != ph) && item.onPlayerDie(ph); }); this.delPlayer(aplayer); }; /** * 一局游戏开始 */ public onGameStart(){ this._players.forEach((item: PlayerHandler) => { item.onGameStart(); }); }; /** * 一局游戏结束 */ public onGameEnd(){ this._players.forEach((item: PlayerHandler) => { item.clear(); }); this._players.clear(); this._playerids.clear(); this._gamestart = false; }; // end-------------------------------------------------- // --------------------调用外部接口函数-------------------------- public onAddPetNotify(apet: PetHandler){ return this._room.bAddPet(apet.exportInfoMsg()); }; public onDelPetNotify(apet: PetHandler){ return this._room.bRemovePet(apet.exportRemoveMsg()); }; public onAskReplacePetNotify(aplayer: PlayerHandler){ return this._room.sNeedChangePet(aplayer.getId()); }; public onUpdatePetNotify(apet: PetHandler, from: PetHandler){ this.onUpdatePets([apet], from, true); if(this.isFlowing()){ if(!this._cachePets.includes(apet)){ this._cachePets.push(apet); } }else{ let lst = [apet]; this.onUpdatePets(lst, from); this.checkPets(lst); } }; public onUpdatePetsNotify(pets: PetHandler[], from: PetHandler){ if(!pets || pets.length <= 0){ return; } this.onUpdatePets(pets, from, true); if(this.isFlowing()){ pets.forEach((item: PetHandler) =>{ if(!this._cachePets.includes(item)){ this._cachePets.push(item); } }); }else{ this.onUpdatePets(pets, from); this.checkPets(pets); } }; public onUpdatePets(pets: PetHandler[], from: PetHandler, isstat: boolean = false){ if(!pets || pets.length <= 0){ return; } let lst: PetInfo[] = []; pets.forEach((item: PetHandler) =>{ lst.push(item.exportInfo()); }); if(isstat){ this._room.updatePetStat(lst, from? from._owner.getId(): null); }else{ this._room.updatePet(lst, from? from._owner.getId(): null); } }; public onPlayerAddCardNotify(aplayer: PlayerHandler, count: number, maxcount: number, from?: PlayerHandler): number{ return this._room.addCard(aplayer.getId(), count, maxcount, 1, from? from.getId(): null); }; public onPlayerStealCardNotify(srcplayer: PlayerHandler, dstplayer: PlayerHandler, count: number): number{ return this._room.drawCardFromPlayer(srcplayer.getId(), dstplayer.getId(), count); }; public onPlayerAddDirectCardNotify(aplayer: PlayerHandler, count: number, cardid: number, from?: PlayerHandler){ return this._room.generateCard({player: aplayer.getId(), count, effectId: cardid, fromplayer: from? from.getId(): null}); }; public onPlayerDropCardNotify(aplayer: PlayerHandler, count: number, from?: PlayerHandler): number{ return this._room.giveUpCard(aplayer.getId(), count, from? from.getId(): null); }; public onSkillResultNotify(skillres: SkillTarget[]){ /** * indexOfAttack start: number: number*/ let __indexOfAttack = function(start: number): number { for(let i = start; i < skillres.length;i++){ if(skillres[i].bresok && skillres[i].isAttackSkill()){ return i; } } return -1; }; let __indexOfAttackBk = function(start: number, atkres: SkillTarget): number{ for(let i = start; i < skillres.length;i++){ let obj = skillres[i]; if(obj.bresok && obj.isAttackBackSkill() && atkres.dst == obj.srcPet() && obj.dst == atkres.srcPet()){ return i; } } return -1; }; if(!skillres || skillres.length <= 0){ return; } // 合并冲锋/反击 let nstart = 0; while(true){ if(nstart >= skillres.length){ break; } let natk = __indexOfAttack(nstart); if(natk < 0){ break; } nstart = natk + 1; let atkobj = skillres[natk]; let natkbk = __indexOfAttackBk(nstart, atkobj); if(natkbk){ let atkbkobj = skillres[natkbk]; atkbkobj.res.forEach((item: SkillResult) => { if(item.bsuccess){ if(item.effect_type == SkillEffectType.CHG_AP || item.effect_type == SkillEffectType.CHG_HP){ atkobj.success(item.effect_type, item.effect_res, true); } } }); skillres.splice(natkbk, 1); } } let checklst: PlayerHandler[] = []; let lst: SkillInfoMsg[] = []; let resmap: Map = new Map; let lastkey = ''; let cnt = 0; skillres.forEach((item: SkillTarget)=>{ let key = item.srcskillid + '|' + item.srcplayer.getId(); if(item.srcpet){ key += '|' + item.srcpet._idx; } if(lastkey != key && lastkey != ''){ cnt++; } lastkey = key; let realkey = key + '|' + cnt; let tmplst = resmap.get(realkey); if(!tmplst){ tmplst = [item]; resmap.set(realkey, tmplst); }else{ tmplst.push(item); } }); let skid = ''; let tm = 0; resmap.forEach((item: SkillTarget[]) =>{ let st = item[0]; lst.push(st.exportMsg(item)); tm += st.getLastTime(); skid += st.srcskillid + '|'; // st.isHurtSkill() && item.forEach((v: SkillTarget)=>{ // if(v.targetIsPet()){ // let ph = v.targetPlayer(); // if(!checklst.includes(ph)){ // checklst.push(ph); // } // } // }) }); if(this.isFlowing()){ this._sktime += tm; }else{ this._room.addScheduleTime(tm*1000, skid); } this._room.bMsgQueue(lst); // checklst.forEach((item: PlayerHandler) => { // item.checkPets(); // }); }; public onPlayerAddHPNotify(aplayer: PlayerHandler, addhp: number, from: PlayerHandler){ return this._room.updateHp(aplayer.getId(), addhp, from? from.getId(): null); }; //end------------------------------------------------ public onSkillResult(skillres: SkillTarget[]){ if(this.isFlowing()){ //已处理过 // this._cacheSkills.push(...skillres); }else{ this.onSkillResultNotify(skillres); } } }