org.skfiy.typhon.spi.war.WarProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.skfiy.typhon.spi.war.WarProvider.java

Source

/*
 * Copyright 2014 The Skfiy Open Association.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.skfiy.typhon.spi.war;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.cache.Cache;
import javax.cache.Caching;
import javax.inject.Inject;
import javax.management.ObjectName;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math.util.MathUtils;
import org.apache.commons.modeler.ManagedBean;
import org.skfiy.typhon.AbstractComponent;
import org.skfiy.typhon.Typhons;
import org.skfiy.typhon.dobj.BSaSkill;
import org.skfiy.typhon.dobj.HeroItemDobj;
import org.skfiy.typhon.dobj.JoinSkill;
import org.skfiy.typhon.domain.FightGroup;
import org.skfiy.typhon.domain.Friend;
import org.skfiy.typhon.domain.HeroPropertyKeys;
import org.skfiy.typhon.domain.IHeroEntity;
import org.skfiy.typhon.domain.ITroop;
import org.skfiy.typhon.domain.Normal;
import org.skfiy.typhon.domain.Player;
import org.skfiy.typhon.domain.Troop;
import org.skfiy.typhon.domain.VacantData;
import org.skfiy.typhon.domain.item.HeroItem;
import org.skfiy.typhon.domain.item.IFightItem;
import org.skfiy.typhon.domain.item.IFightItem.Shot;
import org.skfiy.typhon.domain.item.Race;
import org.skfiy.typhon.domain.item.SuccorObject;
import org.skfiy.typhon.packet.MultipleValue;
import org.skfiy.typhon.packet.Packet;
import org.skfiy.typhon.packet.PacketSuccor;
import org.skfiy.typhon.packet.SingleValue;
import org.skfiy.typhon.script.Script;
import org.skfiy.typhon.script.ScriptManager;
import org.skfiy.typhon.session.Session;
import org.skfiy.typhon.session.SessionContext;
import org.skfiy.typhon.session.SessionManager;
import org.skfiy.typhon.session.SessionUtils;
import org.skfiy.typhon.spi.CacheKeys;
import org.skfiy.typhon.spi.ItemProvider;
import org.skfiy.typhon.spi.RoleProvider;
import org.skfiy.typhon.spi.hero.HeroProvider;
import org.skfiy.typhon.spi.society.Society;
import org.skfiy.typhon.spi.society.SocietyProvider;
import org.skfiy.typhon.spi.war.WarCombo.Point;
import org.skfiy.typhon.util.ComponentUtils;
import org.skfiy.typhon.util.FastRandom;
import org.skfiy.typhon.util.MBeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.skfiy.typhon.domain.IHeroEntity.Rabbet;

/**
 *
 * @author Kevin Zou <kevinz@skfiy.org>
 */
public class WarProvider extends AbstractComponent {

    private static final Logger LOG = LoggerFactory.getLogger(WarProvider.class);

    private static final int ROW_1ST = 0;
    private static final int ROW_2ND = 1;
    private static final int ROW_3RD = 2;

    private static final double MIN_FACTOR = 0.95;
    private final Random HOLD_POINT_RANDOM = new FastRandom();
    private final Random FACTOR_RANDOM = new FastRandom();

    private static final FastRandom HERO_ID_RANDOM = new FastRandom();

    private ObjectName oname;

    // ???
    private RaceFactorData[] raceFactors;
    private TerrainFactorData[] terrainFactors;
    private double[] furyFactors;
    private final Map<String, BSaSkill> bsaSkills = new HashMap<>();
    private final Map<Integer, Map<String, JoinSkill>> joinSkills = new HashMap<>();
    private final List<Map<Shot, Double>> combosRatioFactors = new ArrayList<>(15);

    @Inject
    private ScriptManager scriptManager;
    @Inject
    private RoleProvider roleProvider;
    @Inject
    private SessionManager sessionManager;
    @Inject
    private ItemProvider itemProvider;
    @Inject
    private HeroProvider heroProvider;
    @Inject
    private SocietyProvider societyProvider;

    private Cache<Integer, VacantData> roleVacantDataCache;

    @Override
    protected void doInit() {
        loadRaceWarFactor();
        loadTerrainWarFactor();
        loadFuryWarFactor();
        loadCombosRatioFactor();
        loadBSaSkillConfig(); // ??
        loadJoinSkillConfig(); // ??

        // register mbean
        ManagedBean managedBean = MBeanUtils.findManagedBean(getClass());
        MBeanUtils.registerComponent(this, managedBean);

        roleVacantDataCache = Caching.getCacheManager().getCache(CacheKeys.ROLE_VACANT_DATA_CACHE_KEY);
    }

    @Override
    protected void doReload() {
        // doInit();
    }

    @Override
    protected void doDestroy() {
        if (oname != null) {
            MBeanUtils.REGISTRY.unregisterComponent(oname);
        }
    }

    /**
     *
     * @param packet
     */
    public void searchSuccor(Packet packet) {
        Player player = SessionUtils.getPlayer();
        Normal normal = player.getNormal();

        MultipleValue result = MultipleValue.createResult(packet);

        List<Friend> friends = normal.getFriends();
        Collections.shuffle(friends);

        int fc = Typhons.getInteger("typhon.spi.war.commendatoryFriendCount", 10);
        int fl = fc - 5;
        if (friends.size() < fl) {
            fl = friends.size();
        }

        int levelLimit = Typhons.getInteger("typhon.spi.war.commendatoryFriendLevelLimit", 5);
        int minLevelLimit = normal.getLevel() - levelLimit;
        int maxLevelLimit = normal.getLevel() + levelLimit;

        for (Friend friend : friends) {
            if (result.getVals().size() >= fl) {
                break;
            }
            if (chectSuccor(normal, friend.getRid())) {
                continue;
            }
            if (reset(friend.getRid(), minLevelLimit, maxLevelLimit)) {
                result.addVal(newPacketSuccor(friend.getRid(), null));
            }
        }

        Iterator<Cache.Entry<Integer, VacantData>> it = roleVacantDataCache.iterator();
        Cache.Entry<Integer, VacantData> entry;
        while (it.hasNext()) {
            if (result.getVals().size() >= fc) {
                break;
            }

            entry = it.next();
            VacantData vacantData = entry.getValue();
            if (vacantData == null || chectSuccor(normal, vacantData.getRid())) {
                continue;
            }

            int level = vacantData
                    .findHeroProperty(vacantData.getFightGroup(vacantData.getLastFidx())[vacantData.getCaptain()])
                    .getLevel();
            if (vacantData.getRid() == player.getRole().getRid() || level < minLevelLimit || level > maxLevelLimit
                    || normal.findFriend(vacantData.getRid()) != null) {
                continue;
            }

            result.addVal(newPacketSuccor(vacantData.getRid(), vacantData));
        }

        HeroItem primaryHero = normal.getFightGroup(normal.getLastFidx()).getHeroItem(FightGroup.PRIMARY_POS);
        // ?
        for (int i = result.getVals().size(); i < fc; i++) {
            PacketSuccor succor = new PacketSuccor();
            succor.setName(randomName((List) result.getVals()));
            succor.setIid(randomSuccorHeroId());
            succor.setLevel(normal.getLevel());
            succor.setStar(primaryHero.getStar().name());
            succor.setPowerGuess((int) (primaryHero.getPowerGuess() * (1 + HERO_ID_RANDOM.nextInt(10) / 10)));
            succor.setPlayerLevel(normal.getLevel());
            succor.setLadder(primaryHero.getLadder());
            result.addVal(succor);
        }

        SessionContext.getSession().write(result);
    }

    private boolean chectSuccor(Normal normal, int rid) {
        boolean bool = false;
        int CD = Typhons.getInteger("typhon.spi.Warprovider.CD") * 60 * 1000;
        for (SuccorObject object : normal.getSuccors()) {
            if (object.getRid() == rid && (System.currentTimeMillis() - object.getTime()) < CD) {
                bool = true;
            }
        }
        return bool;
    }

    private boolean reset(int rid, int minLevelLimit, int maxLevelLimit) {
        Session otherSession = sessionManager.getSession(rid);
        boolean bool = false;
        int level;
        if (otherSession != null) {
            Player player = SessionUtils.getPlayer(otherSession);
            Normal benormal = player.getNormal();
            FightGroup fightGroup = benormal.getFightGroup(benormal.getLastFidx());
            level = fightGroup.getHeroItems()[fightGroup.getCaptain()].getLevel();
        } else {
            VacantData vacantData = roleProvider.loadVacantData(rid);
            level = vacantData
                    .findHeroProperty(vacantData.getFightGroup(vacantData.getLastFidx())[vacantData.getCaptain()])
                    .getLevel();
        }
        if (level >= minLevelLimit && level <= maxLevelLimit) {
            bool = true;
        }
        return bool;
    }

    /**
     * ??.
     *
     * @param packet ??
     */
    public void loadSuccorData(SingleValue packet) {
        Player player = SessionUtils.getPlayer();
        Normal normal = player.getNormal();
        HeroItem primaryHero = normal.getFightGroup(normal.getLastFidx()).getHeroItems()[FightGroup.PRIMARY_POS];

        int rid = (int) packet.getVal();
        JSONObject json;
        if (rid <= 0) {
            json = buildSuccorData(primaryHero);
        } else {
            IHeroEntity heroEntity;
            Session beSession = sessionManager.getSession(rid);

            if (beSession != null) {
                Normal beNormal = SessionUtils.getPlayer(beSession).getNormal();
                FightGroup fightGroup = beNormal.getFightGroup(beNormal.getLastFidx());
                heroEntity = fightGroup.getHeroItem(fightGroup.getCaptain());
            } else {
                VacantData vacantData = roleProvider.loadVacantData(rid);
                heroEntity = vacantData.getPrimaryHero();
            }

            if (primaryHero.getExtraAtk() > heroEntity.getExtraAtk()) {
                json = buildSuccorData(primaryHero);
            } else {
                json = new JSONObject();

                int tong = heroEntity.getExtraTong();
                int wu = heroEntity.getExtraWu();
                int zhi = heroEntity.getExtraZhi();

                int atk = heroEntity.getExtraAtk();
                int def = heroEntity.getExtraDef();
                int matk = heroEntity.getExtraMatk();
                int mdef = heroEntity.getExtraMdef();
                int hp = heroEntity.getExtraHp();

                int critRate = heroEntity.getExtraCritRate();
                int decritRate = heroEntity.getExtraDecritRate();
                int critMagn = heroEntity.getExtraCritMagn();

                int parryRate = heroEntity.getExtraParryRate();
                int deparryRate = heroEntity.getExtraDeparryRate();
                int parryValue = heroEntity.getExtraParryValue();

                if (heroEntity.getRabbets() != null) {
                    for (Rabbet rabbet : heroEntity.getRabbets()) {
                        tong += rabbet.getTong();
                        wu += rabbet.getWu();
                        zhi += rabbet.getZhi();

                        atk += rabbet.getAtk();
                        def += rabbet.getDef();
                        matk += rabbet.getMatk();
                        mdef += rabbet.getMdef();
                        hp += rabbet.getHp();

                        critRate += rabbet.getCritRate();
                        decritRate += rabbet.getDecritRate();
                        critMagn += rabbet.getCritMagn();

                        parryRate += rabbet.getParryRate();
                        deparryRate += rabbet.getDeparryRate();
                        parryValue += rabbet.getParryValue();
                    }
                }

                json.put("extraTong", tong);
                json.put("extraWu", wu);
                json.put("extraZhi", zhi);

                json.put("extraAtk", atk);
                json.put("extraDef", def);
                json.put("extraMatk", matk);
                json.put("extraMdef", mdef);
                json.put("extraHp", hp);

                json.put("extraCritRate", critRate);
                json.put("extraDecritRate", decritRate);
                json.put("extraCritMagn", critMagn);
                json.put("extraParryRate", parryRate);
                json.put("extraDeparryRate", deparryRate);
                json.put("extraParryValue", parryValue);
            }
            json.put("star", heroEntity.getStar());
        }

        json.put("id", packet.getId());
        json.put("type", Packet.Type.rs);
        player.getSession().write(packet.getNs(), json);
    }

    /**
     *
     * @param lab
     * @param troop
     * @param hero
     * @return
     */
    public FightObject newFightObject(int lab, Troop troop, IHeroEntity hero) {
        HeroItemDobj heroItemDobj = itemProvider.getItem(hero.getId());

        FightObject fo = new FightObject(lab, hero.getLevel(), hero.getLadder(), hero.getStar(), heroItemDobj);

        fo.setBaseAtk(getAtk(troop, hero));
        fo.setMaxDef(getDef(troop, hero));

        fo.setBaseMatk(getMatk(troop, hero));
        fo.setMaxMdef(getMdef(troop, hero));
        fo.setMaxHp((int) (getHp(troop, hero) * Typhons.getDouble("typhon.spi.pvp.hpMagn", 1.5)));

        fo.setMaxCritRate(getCritRate(troop, hero));
        fo.setMaxDecritRate(getDecritRate(troop, hero));
        fo.setMaxCritMagn(getCritMagn(troop, hero));

        fo.setMaxParryRate(getParryRate(troop, hero));
        fo.setMaxDeparryRate(getDeparryRate(troop, hero));
        fo.setMaxParryValue(getParryValue(troop, hero));

        fo.setShots(getHeroItemDobjShots(heroItemDobj, hero.getLadder()));
        return fo;
    }

    /**
     *
     * @param heroItemDobj
     * @param ladder
     * @return
     */
    public Shot[] getHeroItemDobjShots(HeroItemDobj heroItemDobj, int ladder) {
        if (ladder < 2) {
            return heroItemDobj.getShots1();
        } else if (ladder < 4) {
            return heroItemDobj.getShots2();
        } else if (ladder < 7) {
            return heroItemDobj.getShots3();
        } else if (ladder < 11) {
            return heroItemDobj.getShots4();
        } else {
            return heroItemDobj.getShots5();
        }
    }

    // ===========================Begin ???==================================================
    public FightObject loadSuccorFightObject(int rid, int fgidx, FightObject primaryFobj) {
        if (rid <= 0) { // 
            int atk = (int) (primaryFobj.getAtk() * 1.1);
            int def = (int) (primaryFobj.getDef() * 1.1);
            int matk = (int) (primaryFobj.getMatk() * 1.1);
            int mdef = (int) (primaryFobj.getMdef() * 1.1);
            int hp = (int) (primaryFobj.getHp() * 1.1);
            int critRate = (int) (primaryFobj.getCritRate() * 1.1);
            int decritRate = (int) (primaryFobj.getDecritRate() * 1.1);
            int critMagn = (int) (primaryFobj.getCritMagn() * 1.1);
            int parryRate = (int) (primaryFobj.getParryRate() * 1.1);
            int deparryRate = (int) (primaryFobj.getDeparryRate() * 1.1);
            int parryValue = (int) (primaryFobj.getParryValue() * 1.1);

            FightObject fo = new FightObject(9, primaryFobj.getLevel(), primaryFobj.getLadder(),
                    primaryFobj.getStar(), (HeroItemDobj) itemProvider.getItem(randomSuccorHeroId()));
            fo.setBaseAtk(atk);
            fo.setMaxDef(def);

            fo.setBaseMatk(matk);
            fo.setMaxMdef(mdef);
            fo.setMaxHp(hp);

            fo.setMaxCritRate(critRate);
            fo.setMaxDecritRate(decritRate);
            fo.setMaxCritMagn(critMagn);

            fo.setMaxParryRate(parryRate);
            fo.setMaxDeparryRate(deparryRate);
            fo.setMaxParryValue(parryValue);
            return fo;
        } else {
            IHeroEntity heroEntity;
            ITroop itroop;
            HeroItemDobj heroItemDobj;

            Session session = sessionManager.getSession(rid);
            if (session != null) {
                Normal normal = SessionUtils.getPlayer(session).getNormal();
                FightGroup fightGroup = normal.getFightGroup(fgidx);
                heroEntity = (HeroItem) normal.player().getHeroBag().findNode(fightGroup.getSuccor()).getItem();
                itroop = normal;
            } else {
                VacantData vacantData = roleProvider.loadVacantData(rid);
                heroEntity = vacantData.findHeroProperty(vacantData.getPvpSuccorIid());
                itroop = vacantData;
            }

            heroItemDobj = (HeroItemDobj) itemProvider.getItem(heroEntity.getId());
            FightObject fo = new FightObject(9, heroEntity.getLevel(), heroEntity.getLadder(), heroEntity.getStar(),
                    heroItemDobj);
            int atk = 0;
            int def = 0;
            int matk = 0;
            int mdef = 0;
            int hp = 0;
            int critRate = 0;
            int decritRate = 0;
            int critMagn = 0;
            int parryRate = 0;
            int deparryRate = 0;
            int parryValue = 0;

            for (int i = 0; i < ITroop.MAX_TROOP_SIZE; i++) {
                Troop troop = itroop.getTroop(ITroop.Type.valueOf(i));
                atk += troop.getAtk() + troop.getAcePack().getAtk();
                def += troop.getDef() + troop.getAcePack().getDef();
                matk += troop.getMatk() + troop.getAcePack().getMatk();
                mdef += troop.getMdef() + troop.getAcePack().getMdef();
                hp += troop.getHp() + troop.getAcePack().getHp();
                critRate += troop.getCritRate() + troop.getAcePack().getCritRate();
                decritRate += troop.getDecritRate() + troop.getAcePack().getDecritRate();
                critMagn += troop.getCritMagn() + troop.getAcePack().getCritMagn();
                parryRate += troop.getParryRate() + troop.getAcePack().getParryRate();
                deparryRate += troop.getDeparryRate() + troop.getAcePack().getDeparryRate();
                parryValue += troop.getParryValue() + troop.getAcePack().getParryValue();
            }

            fo.setBaseAtk(heroProvider.getAtk(heroEntity) + atk / ITroop.MAX_TROOP_SIZE);
            fo.setMaxDef(heroProvider.getDef(heroEntity) + def / ITroop.MAX_TROOP_SIZE);

            fo.setBaseMatk(heroProvider.getMatk(heroEntity) + matk / ITroop.MAX_TROOP_SIZE);
            fo.setMaxMdef(heroProvider.getMdef(heroEntity) + mdef / ITroop.MAX_TROOP_SIZE);
            fo.setMaxHp(heroProvider.getHp(heroEntity) + hp / ITroop.MAX_TROOP_SIZE);

            fo.setMaxCritRate(heroProvider.getCritRate(heroEntity) + critRate / ITroop.MAX_TROOP_SIZE / 500);
            fo.setMaxDecritRate(heroProvider.getDecritRate(heroEntity) + decritRate / ITroop.MAX_TROOP_SIZE / 500);
            fo.setMaxCritMagn(heroProvider.getCritMagn(heroEntity) + critMagn / ITroop.MAX_TROOP_SIZE / 100);

            fo.setMaxParryRate(heroProvider.getParryRate(heroEntity) + parryRate / ITroop.MAX_TROOP_SIZE / 500);
            fo.setMaxDeparryRate(
                    heroProvider.getDeparryRate(heroEntity) + deparryRate / ITroop.MAX_TROOP_SIZE / 600);
            fo.setMaxParryValue(heroProvider.getParryValue(heroEntity) + parryValue / ITroop.MAX_TROOP_SIZE);

            return fo;
        }
    }

    // ===========================End ???====================================================
    //=============================== Begin ========================================================
    public int getAtk(Troop troop, IHeroEntity heroEntity) {
        int atk = heroProvider.getAtk(heroEntity);

        if (troop != null) {
            atk += troop.getAtk() + troop.getAcePack().getAtk();
        }
        return (int) atk;
    }

    public int getDef(Troop troop, IHeroEntity heroEntity) {
        int def = heroProvider.getDef(heroEntity);

        if (troop != null) {
            def += troop.getDef() + troop.getAcePack().getDef();
        }
        return (int) def;
    }

    public int getMatk(Troop troop, IHeroEntity heroEntity) {
        int matk = heroProvider.getMatk(heroEntity);

        if (troop != null) {
            matk += troop.getMatk() + troop.getAcePack().getMatk();
        }
        return (int) matk;
    }

    public int getMdef(Troop troop, IHeroEntity heroEntity) {
        int mdef = heroProvider.getMdef(heroEntity);

        if (troop != null) {
            mdef += troop.getMdef() + troop.getAcePack().getMdef();
        }
        return (int) mdef;
    }

    public int getHp(Troop troop, IHeroEntity heroEntity) {
        int hp = heroProvider.getHp(heroEntity);

        if (troop != null) {
            hp += troop.getHp() + troop.getAcePack().getHp();
        }
        return hp;
    }

    public double getCritRate(Troop troop, IHeroEntity heroEntity) {
        double critRate = heroProvider.getCritRate(heroEntity);

        if (troop != null) {
            critRate += troop.getCritRate() + troop.getAcePack().getCritRate() / 500;
        }
        return critRate;
    }

    public double getDecritRate(Troop troop, IHeroEntity heroEntity) {
        double decritRate = heroProvider.getDecritRate(heroEntity);

        if (troop != null) {
            decritRate += troop.getDecritRate() + troop.getAcePack().getDecritRate() / 500;
        }
        return decritRate;
    }

    public double getCritMagn(Troop troop, IHeroEntity heroEntity) {
        double critMagn = heroProvider.getCritMagn(heroEntity);

        if (troop != null) {
            critMagn += troop.getCritMagn() + troop.getAcePack().getCritMagn() / 100;
        }
        return critMagn;
    }

    public double getParryRate(Troop troop, IHeroEntity heroEntity) {
        double parryRate = heroProvider.getParryRate(heroEntity);

        if (troop != null) {
            parryRate += troop.getParryRate() + troop.getAcePack().getParryRate() / 500;
        }
        return parryRate;
    }

    public double getDeparryRate(Troop troop, IHeroEntity heroEntity) {
        double deparryRate = heroProvider.getDeparryRate(heroEntity);

        if (troop != null) {
            deparryRate += troop.getDeparryRate() + troop.getAcePack().getDeparryRate() / 600;
        }
        return deparryRate;
    }

    public double getParryValue(Troop troop, IHeroEntity heroEntity) {
        double parryValue = heroProvider.getParryValue(heroEntity);

        if (troop != null) {
            parryValue += troop.getParryValue();
            parryValue *= (1 + troop.getAcePack().getParryValue() / 100);
        }
        return parryValue;
    }

    //=============================== End ==========================================================
    /**
     * .
     *
     * @param c 
     * @return 
     */
    public double getFuryFactor(int c) {
        double r = 1;
        if (c >= 0 && c <= 10) {
            r = furyFactors[c];
        }
        return r;
    }

    /**
     * ?.
     *
     * @param a 
     * @param b 
     * @return 
     */
    public double getRaceFactor(Race a, Race b) {
        for (RaceFactorData data : raceFactors) {
            if (a == data.Name) {
                switch (b) {
                case Ce:
                    return data.Ce;
                case Bu:
                    return data.Bu;
                case Qi:
                    return data.Qi;
                case Gong:
                    return data.Gong;
                case Che:
                    return data.Che;
                }
            }
        }
        return 1;
    }

    /**
     * ??.
     *
     * @param r ?
     * @param t 
     * @return 
     */
    public double getTerrainFactor(Race r, Terrain t) {
        for (TerrainFactorData data : terrainFactors) {
            if (r == data.Name) {
                switch (t) {
                case SLu:
                    return data.SLu;
                case SLing:
                    return data.SLing;
                case PYuan:
                    return data.PYuan;
                case SDi:
                    return data.SDi;
                case CGuan:
                    return data.CGuan;
                }
            }
        }
        return 1;
    }

    /**
     * ?.
     *
     * @param warInfo
     */
    public void prepare(WarInfo warInfo) {
        // 
        desorbLedaerSkill(warInfo, warInfo.getAttackerEntity());
        desorbLedaerSkill(warInfo, warInfo.getDefenderEntity());
    }

    /**
     *
     * @param warInfo
     * @return
     */
    public WarReport finalAttack(WarInfo warInfo) {
        prepare(warInfo);

        WarReport warReport = new WarReport();
        warReport.setAttackerEntity(newWarReportEntity(warInfo.getAttackerEntity()));
        warReport.setDefenderEntity(newWarReportEntity(warInfo.getDefenderEntity()));

        // CACHE
        warInfo.setWarReport(warReport);

        WarReport.Round round;
        WarReport.Effect effect = WarReport.Effect.C;

        for (;;) {
            round = new WarReport.Round();
            if (warInfo.getNextDire() == Direction.S) {
                warInfo.setNextDire(Direction.N);
                effect = attackX(warInfo, Direction.S, round, round.getSdetails(), round.getSbufDetails());
            }

            if (effect == WarReport.Effect.C && warInfo.getNextDire() == Direction.N) {
                warInfo.setNextDire(Direction.S);
                effect = attackX(warInfo, Direction.N, round, round.getNdetails(), round.getNbufDetails());

                if (effect != WarReport.Effect.C) {
                    effect = (effect == WarReport.Effect.W ? WarReport.Effect.D : WarReport.Effect.W);
                }
            }

            // FIXME ?
            round.setSjson(JSON.toJSON(warInfo.getAttackerEntity()));
            round.setNjson(JSON.toJSON(warInfo.getDefenderEntity()));

            warReport.addRound(round);

            // 10??, 
            if (warInfo.getRound() >= Typhons.getInteger("typhon.spi.pvp.allRounds", 10)) {
                effect = WarReport.Effect.D;
            }

            warInfo.setRound(warInfo.getRound() + 1);
            // ?
            if (effect != WarReport.Effect.C) {
                warReport.setEffect(effect);
                break;
            }
        }

        return warReport;
    }

    public void attack(Shot shot, WarInfo warInfo, FightObject aobj, WarInfo.Entity attackerEntity,
            WarInfo.Entity defenderEntity, Direction dire, List<Object> outputs) {

        if (aobj.getStatus() == FightObject.Status.CONFUSION) {
            List<FightObject> goals = new ArrayList<>();
            goals.add(defenderEntity.findFightGoal(true));

            outputs.add(_GJi(warInfo, aobj, goals));
            return;
        }

        switch (shot) {
        case GJi: {
            int num = 1;
            if (aobj.getRace() == Race.Gong) {
                num = 2;
            }
            outputs.add(_GJi(warInfo, aobj, defenderEntity.findFightGoals(num)));
            break;
        }
        case BSa: {
            Script script = scriptManager.getScript("war.bs." + aobj.getBsaSkill());
            MultiAttackResult mar = (MultiAttackResult) script.invoke(null, new BSaWapper(attackerEntity,
                    defenderEntity, aobj, bsaSkills.get(aobj.getBsaSkill()), warInfo));
            if (mar != null) {
                AttackEntry sae = new AttackEntry();
                sae.setLab(aobj.getLab());
                mar.setSource(sae);
                mar.setShot(shot);
                outputs.add(mar);
            }
            break;
        }
        case FYu: {
            outputs.add(fyuAttack(warInfo, attackerEntity.getDire(), aobj));
            break;
        }
        case JCe: {
            int num = 1;
            if (aobj.getRace() == Race.Gong) {
                num = 2;
            }
            outputs.add(_JCe(warInfo, aobj, defenderEntity.findFightGoals(num)));
            break;
        }
        case QXi: {
            FightObject goal = defenderEntity.findFightGoal();
            outputs.add(_QXi(warInfo, aobj, goal));
            break;
        }
        case Q7: {
            if (aobj.getFury() >= aobj.getMaxFury()) {
                Script script = scriptManager.getScript("war.bs." + aobj.getBsaSkill());
                MultiAttackResult mar = (MultiAttackResult) script.invoke(null, new BSaWapper(attackerEntity,
                        defenderEntity, aobj, bsaSkills.get(aobj.getBsaSkill()), warInfo));
                if (mar != null) {
                    AttackEntry sae = new AttackEntry();
                    sae.setLab(aobj.getLab());
                    mar.setSource(sae);
                    mar.setShot(Shot.Q7);
                    outputs.add(mar);
                }
            } else {
                aobj.incrementFury(1);
                aobj.removeAllDebuff();

                // result
                AttackEntry source = new AttackEntry();
                source.setLab(aobj.getLab());

                AttackResult ar = new AttackResult();
                ar.setShot(shot);
                ar.setSource(source);

                outputs.add(ar);
            }
            break;
        }
        case YHu: {
            // ?
            AttackEntry source = new AttackEntry();
            source.setLab(aobj.getLab());

            int num = 1;
            if (attackerEntity.getSuccor().getRace() == Race.Gong) {
                num = 2;
            }
            AttackResult ar = _YHu(warInfo, attackerEntity.getSuccor(), defenderEntity.findFightGoals(num));
            ar.setSource(source);
            outputs.add(ar);
            break;
        }
        }
    }

    /**
     *
     * @param warInfo
     * @param dire
     * @param aobj
     * @return
     */
    public Object fyuAttack(WarInfo warInfo, Direction dire, FightObject aobj) {
        Script script = scriptManager.getScript("war.BufferSkillScriptFactory");
        BufferSkill bufferSkill = (BufferSkill) script.invoke(null, new Object[] { warInfo, dire, aobj, Shot.FYu });
        return bufferSkill.onBefore();
    }

    /**
     *
     * @param warInfo
     * @param dire
     * @param combo
     * @param bobjs
     * @param j
     * @param joinSkill
     * @return
     */
    public ComboResult skillComboAoeAttack(WarInfo warInfo, Direction dire, WarCombo combo, List<FightObject> bobjs,
            double j, JoinSkill joinSkill) {
        double factor = getAttackFactor(); // 
        double x = calculateSkillComboX(warInfo, combo, false);
        double f1;
        double s;
        double r;

        double dehp;

        double _cr = 0;
        double _cm = 0;
        for (FightObject fo : combo.getFightObjects()) {
            _cr += fo.getCritRate();
            _cm += fo.getCritMagn();
        }
        _cr /= combo.getFightObjectSize();
        _cm /= combo.getFightObjectSize();

        ComboResult cr = new ComboResult();
        cr.setShot(combo.getShot());
        cr.setCount(combo.getComboCount());

        double e_m = 1;
        // 4,5?combo?1.5
        if (!"none".equals(joinSkill.getJointArea()) && joinSkill.getAttackerCntNeeded() > 3) {
            e_m = 1.5;
        }

        for (FightObject bobj : bobjs) {
            f1 = getFuryFactor(bobj.getFury()); // 
            s = 1; // ?
            r = 1; // ?

            dehp = Math.max((factor * (x - (bobj.getDef() + bobj.getMdef()) / 2 * f1) * s * r * j), x / 10);
            if (LOG.isDebugEnabled()) {
                LOG.debug(
                        "{} = <{} * ({} - {} * {}) * {} * {} * {}> -> <_cr(): {}, _pr(): {}, r: {}>",
                        dehp, factor, x, bobj.getDef(), f1, s, r, _cr, _cm, r);
            }

            if (!joinSkill.getJointArea().equals(bobj.getArea().name())) {
                dehp *= e_m;
            }

            int hp = (int) dehp;
            bobj.decrementHp(hp);

            AttackEntry ae = new AttackEntry();
            ae.setLab(bobj.getLab());
            ae.setVal(hp);
            cr.addTarget(ae);
        }
        return cr;
    }

    /**
     *
     * @param fightObjects
     * @param holdPoints
     * @return
     */
    public Collection<WarCombo> calculateCombo(List<FightObject> fightObjects, int[] holdPoints) {
        Shot[][] table = toShotTable(fightObjects, holdPoints);
        Map<Shot, WarCombo> combos = new HashMap<>();

        Shot shot, temp;
        int x1, y1, x2, y2, x3, y3;
        for (int i = 0; i < table.length; i++) {
            Shot[] shots = table[i];
            for (int j = 0; j < 3; j++) { // ?
                shot = shots[j];
                x1 = i;
                y1 = j;

                if (i == 0) {
                    x2 = x1 + 1;
                    y2 = y1 + 1;

                    x3 = x2 + 1;
                    y3 = y2 + 1;
                    temp = isCombo(shot, table[x2][y2], table[x3][y3]);
                    if (temp != null) {
                        plusComboPoint(combos, temp, x1, y1, fightObjects.get(y1));
                        plusComboPoint(combos, temp, x2, y2, fightObjects.get(y2));
                        plusComboPoint(combos, temp, x3, y3, fightObjects.get(y3));
                    }
                } else if (i == 2) { // ?
                    x2 = x1 - 1;
                    y2 = y1 + 1;

                    x3 = x2 - 1;
                    y3 = y2 + 1;
                    temp = isCombo(shot, table[x2][y2], table[x3][y3]);
                    if (temp != null) {
                        plusComboPoint(combos, temp, x1, y1, fightObjects.get(y1));
                        plusComboPoint(combos, temp, x2, y2, fightObjects.get(y2));
                        plusComboPoint(combos, temp, x3, y3, fightObjects.get(y3));
                    }
                }

                // 
                x3 = x2 = x1;
                y2 = y1 + 1;
                y3 = y2 + 1;

                temp = isCombo(shot, table[x2][y2], table[x3][y3]);
                if (temp != null) {
                    plusComboPoint(combos, temp, x1, y1, fightObjects.get(y1));
                    plusComboPoint(combos, temp, x2, y2, fightObjects.get(y2));
                    plusComboPoint(combos, temp, x3, y3, fightObjects.get(y3));

                    if (temp == Shot.Q7) {
                        if (j == 0) {
                            temp = table[i][3];
                            if (temp != Shot.Miss && temp != Shot.Q7) {
                                plusComboPoint(combos, temp, x1, y1, fightObjects.get(y1));
                                plusComboPoint(combos, temp, x2, y2, fightObjects.get(y2));
                                plusComboPoint(combos, temp, x3, y3, fightObjects.get(y3));
                            } else if (temp == Shot.Q7) {
                                temp = table[i][4];
                                if (temp != Shot.Miss && temp != Shot.Q7) {
                                    // 1combo
                                    plusComboPoint(combos, temp, x1, y1, fightObjects.get(y1));
                                    plusComboPoint(combos, temp, x2, y2, fightObjects.get(y2));
                                    plusComboPoint(combos, temp, x3, y3, fightObjects.get(y3));
                                }
                            }
                        } else if (j == 1) {
                            // First
                            temp = table[i][0];
                            if (temp != Shot.Miss && temp != Shot.Q7) {
                                plusComboPoint(combos, temp, i, j, fightObjects.get(j));
                                plusComboPoint(combos, temp, i, j + 1, fightObjects.get(j + 1));
                                plusComboPoint(combos, temp, i, j + 2, fightObjects.get(j + 2));
                            }

                            // Last
                            temp = table[i][4];
                            if (temp != Shot.Miss && temp != Shot.Q7) {
                                plusComboPoint(combos, temp, i, j + 1, fightObjects.get(y1));
                                plusComboPoint(combos, temp, i, j + 2, fightObjects.get(y2));
                                plusComboPoint(combos, temp, i, j + 3, fightObjects.get(y3));
                            }
                            //====================================================//
                        } else if (j == 2 && table[i][1] != Shot.Miss) {
                            temp = table[i][1];
                            if (temp == Shot.Q7) {
                                temp = table[i][0];
                            }

                            if (temp != Shot.Miss && temp != Shot.Q7) {
                                plusComboPoint(combos, temp, i, 2, fightObjects.get(2));
                                plusComboPoint(combos, temp, i, 3, fightObjects.get(3));
                                plusComboPoint(combos, temp, i, 4, fightObjects.get(4));
                            }
                        }
                    }
                } // 7?

            }
        }

        if (LOG.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder();
            for (WarCombo combo : combos.values()) {
                sb.append("\n").append(StringUtils.leftPad(combo.getShot().name(), 4));
                sb.append("(").append(StringUtils.leftPad(String.valueOf(combo.getComboCount()), 2)).append(")");
                sb.append(": {");
                for (Point point : combo.getPoints()) {
                    sb.append(StringUtils.leftPad(table[point.getX()][point.getY()].name(), 4));
                    sb.append("[").append(point.getX()).append(", ").append(point.getY()).append("] ");
                }
                sb.deleteCharAt(sb.length() - 1);
                sb.append("}");
            }
            LOG.debug(sb.toString());
        }

        // ??
        List<WarCombo> warCombos = new ArrayList<>(combos.values());
        Collections.sort(warCombos);
        return warCombos;
    }

    /**
     *
     * @param s1
     * @param s2
     * @param s3
     * @return
     */
    protected Shot isCombo(IFightItem.Shot s1, IFightItem.Shot s2, IFightItem.Shot s3) {
        IFightItem.Shot rs = s1.equals(s2);
        if (rs != null) {
            return rs.equals(s3);
        }
        return null;
    }

    /**
     *
     * @param fo
     * @param p
     * @return
     */
    protected Shot getPrevShot(FightObject fo, int p) {
        return getShot(fo, p - 1);
    }

    /**
     *
     * @param fo
     * @param p
     * @return
     */
    protected Shot getNextShot(FightObject fo, int p) {
        return getShot(fo, p + 1);
    }

    /**
     *
     * @param fo
     * @param p
     * @return
     */
    protected Shot getShot(FightObject fo, int p) {
        Shot[] shots = fo.getShots();
        if (p < 0) {
            return shots[shots.length + p];
        } else if (p >= shots.length) {
            return shots[p - shots.length];
        }
        return shots[p];
    }

    /**
     *
     * @param critRate
     * @param decritRate
     * @param critMagn
     * @return
     */
    public double getCritMagn(double critRate, double decritRate, double critMagn) {
        if (getCritRate() <= critRate - decritRate) {
            return critMagn;
        }
        return 1D;
    }

    /**
     *
     * @param afo
     * @param bfo
     * @return
     */
    protected double getCritMagn(FightObject afo, FightObject bfo) {
        // ?
        if (getCritRate() <= afo.getCritRate() - bfo.getDecritRate()) {
            return afo.getCritMagn();
        }
        return 1F;
    }

    /**
     *
     * @param parryRate
     * @param deparryRate
     * @param parryValue
     * @return
     */
    public double getParryValue(double parryRate, double deparryRate, double parryValue) {
        if (getCritRate() <= parryRate - deparryRate) {
            return parryValue;
        }
        return 0D;
    }

    /**
     *
     * @param afo
     * @param bfo
     * @return
     */
    protected double getParryValue(FightObject afo, FightObject bfo) {
        // ?
        if (getCritRate() <= afo.getParryRate() - bfo.getDeparryRate()) {
            return afo.getParryValue();
        }
        return 0F;
    }

    /**
     *
     * @param terrain
     * @param afo
     * @param bfo
     * @param atk
     * @param def
     * @param j
     * @return
     */
    public int attack1(Terrain terrain, FightObject afo, FightObject bfo, int atk, int def, double j) {
        double factor = getAttackFactor(); // 
        double p1 = getTerrainFactor(afo.getRace(), terrain); // 
        double f = getFuryFactor(afo.getFury()); // 
        double f1 = getFuryFactor(bfo.getFury()); // 
        double r = getCritMagn(afo, bfo); // ?
        double g1 = getParryValue(bfo, afo); // 

        double dehp = factor * (atk * p1 * f - def * f1) * r * j * Typhons.getDouble("typhon.spi.pvp.damageMagn", 1)
                - g1;
        int a = (int) (Math.max(dehp, atk / 10) - g1);
        int hp = Math.max(a, 1);
        if (LOG.isDebugEnabled()) {
            LOG.debug("a:{}->b:{} - {}={} * ({} * {} * {} - {} * {}) * {} -> {}", afo.getHeroId(), bfo.getHeroId(),
                    hp, factor, atk, p1, f, def, f1, r, j, g1);
        }
        return hp;
    }

    /**
     * ?,?.
     *
     * @param terrain 
     * @param afo 
     * @param bfo 
     * @param atk 
     * @param def 
     * @param j ?
     * @return ??
     */
    public AttackEntry attack0(Terrain terrain, FightObject afo, FightObject bfo, int atk, int def, double j) {
        double p1 = getTerrainFactor(afo.getRace(), terrain); // 
        double f = getFuryFactor(afo.getFury()); // 
        double f1 = getFuryFactor(bfo.getFury()); // 
        double r = getCritMagn(afo, bfo); // ?
        double g1 = getParryValue(bfo, afo); // 

        AttackEntry ae = attack0(atk, def, p1, f, f1, r, j, g1);
        ae.setLab(bfo.getLab());
        return ae;
    }

    /**
     * ?. ?, , ?.
     *
     * @param atk 
     * @param def 
     * @param p1 
     * @param f 
     * @param f1 
     * @param r ?
     * @param j ?
     * @param g1 
     * @return ?, , ?
     */
    public AttackEntry attack0(int atk, int def, double p1, double f, double f1, double r, double j, double g1) {
        double factor = getAttackFactor(); // 
        double dehp = factor * (atk * p1 * f - def * f1) * r * j
                * Typhons.getDouble("typhon.spi.pvp.damageMagn", 1);
        int a = (int) (Math.max(dehp, atk / 10) - g1);
        int hp = Math.max(a, 1);

        if (LOG.isDebugEnabled()) {
            LOG.debug("{}={} * ({} * {} * {} - {} * {}) * {} * {} -> {}", hp, factor, atk, p1, f, def, f1, r, j,
                    g1);
        }

        AttackEntry ae = new AttackEntry();
        ae.setVal(hp);
        ae.crited(r);
        ae.parried(g1);
        return ae;
    }

    private AttackResult _GJi(WarInfo warInfo, FightObject aobj, List<FightObject> goals) {
        // ?
        AttackEntry source = new AttackEntry();
        source.setLab(aobj.getLab());

        AttackResult ar = new AttackResult();
        int atk = aobj.getAtk();
        if (aobj.getStatus() == FightObject.Status.CONFUSION) {
            atk = Math.max(aobj.getAtk(), aobj.getMatk());
        }

        for (FightObject goal : goals) {
            AttackEntry ae = attack0(warInfo.getTerrain(), aobj, goal, atk, goal.getDef(), 1);
            goal.decrementHp((int) ae.getVal());

            ae.setLab(goal.getLab());
            ar.setShot(Shot.GJi);
            ar.setSource(source);
            ar.addTarget(ae);
        }
        return ar;
    }

    private AttackResult _JCe(WarInfo warInfo, FightObject aobj, List<FightObject> goals) {
        // ?
        AttackEntry source = new AttackEntry();
        source.setLab(aobj.getLab());

        AttackResult ar = new AttackResult();

        for (FightObject goal : goals) {
            AttackEntry ae = attack0(warInfo.getTerrain(), aobj, goal, aobj.getMatk(), goal.getMdef(), 1);
            goal.decrementHp((int) ae.getVal());

            ae.setLab(goal.getLab());

            ar.setShot(Shot.JCe);
            ar.setSource(source);
            ar.addTarget(ae);
        }
        return ar;
    }

    private AttackResult _QXi(WarInfo warInfo, FightObject aobj, FightObject goal) {
        double j = 0.6;
        // 02
        if (goal.getFury() <= 0) {
            j *= 2;
        }

        int atk;
        int def;
        if (aobj.getAtk() > aobj.getMatk()) {
            atk = aobj.getAtk();
            def = goal.getDef();
        } else {
            atk = aobj.getMatk();
            def = goal.getMdef();
        }

        AttackEntry ae = attack0(warInfo.getTerrain(), aobj, goal, atk, def, j);
        goal.decrementHp((int) ae.getVal());
        goal.decrementFury(1);

        ae.setLab(goal.getLab());

        // ?
        AttackEntry source = new AttackEntry();
        source.setLab(aobj.getLab());

        AttackResult ar = new AttackResult();
        ar.setShot(Shot.QXi);
        ar.setSource(source);
        ar.addTarget(ae);
        return ar;
    }

    private AttackResult _YHu(WarInfo warInfo, FightObject aobj, List<FightObject> goals) {
        AttackResult ar = new AttackResult();
        ar.setShot(Shot.YHu);

        int atk;
        int def;

        for (FightObject goal : goals) {
            if (aobj.getAtk() > aobj.getMatk()) {
                atk = aobj.getAtk();
                def = goal.getDef();
            } else {
                atk = aobj.getMatk();
                def = goal.getMdef();
            }

            AttackEntry ae = attack0(warInfo.getTerrain(), aobj, goal, atk, def, 1);
            goal.decrementHp((int) ae.getVal());

            ae.setLab(goal.getLab());
            ar.addTarget(ae);
        }
        return ar;
    }

    //    private 
    //======================== Begin ===============================================================
    private void desorbLedaerSkill(WarInfo warInfo, WarInfo.Entity entity) {
        FightObject fo = entity.getFightObject(entity.getCaptain());
        if (fo.getLadderSkill() != null) {
            Script script = scriptManager.getScript(("war.cs." + fo.getLadderSkill()).intern());

            // 
            List<FightObject> list = new ArrayList<>(entity.getFightObjects());
            list.add(entity.getSuccor());
            script.invoke(null, list);

            // ?
            script = scriptManager.getScript("war.cs." + entity.getSuccor().getLadderSkill().intern());
            script.invoke(null, list);
        }
    }

    private WarReport.Effect attackX(WarInfo warInfo, Direction dire, WarReport.Round round, List<Object> details,
            List<Object> bufDetails) {
        int[] holdPoints = randomHoldPoints(); // ?
        switch (dire) {
        case N:
            round.setNholdPoints(holdPoints);
            break;
        case S:
            round.setSholdPoints(holdPoints);
            break;
        }

        return attack(holdPoints, warInfo, dire, details, bufDetails);
    }

    public WarReport.Effect attack(int[] holdPoints, WarInfo warInfo, Direction dire, List<Object> details,
            List<Object> bufDetails) {

        WarInfo.Entity attackerEntity;
        WarInfo.Entity defenderEntity;
        switch (dire) {
        case S:
            attackerEntity = warInfo.getAttackerEntity();
            defenderEntity = warInfo.getDefenderEntity();
            break;
        default:
            attackerEntity = warInfo.getDefenderEntity();
            defenderEntity = warInfo.getAttackerEntity();
        }

        // 
        attackerEntity.incrementAtkCount();

        // BufferSkill
        Object bufferSkillRs;
        for (FightObject fo : attackerEntity.getFightObjects()) {
            if (!fo.isDead() && fo.getBufferSkills().size() > 0) {
                for (Object bs : fo.getBufferSkills().toArray()) {
                    bufferSkillRs = ((BufferSkill) bs).onAfter();
                    if (bufferSkillRs != null) {
                        bufDetails.add(bufferSkillRs);
                    }
                }
            }
        }

        // BUFFER
        if (attackerEntity.isOver()) {
            return WarReport.Effect.D;
        }

        Collection<WarCombo> combos;
        FightObject source;
        FightObject goal;

        for (int i = 0; i < holdPoints.length; i++) {
            source = attackerEntity.getFightObject(i);
            if (source.isDead() || source.getStatus() == FightObject.Status.SLEEPING) {
                continue;
            }

            attack(source.getShot(holdPoints[i]), warInfo, source, attackerEntity, defenderEntity, dire, details);

            if (attackerEntity.isOver()) {
                return WarReport.Effect.D;
            }

            if (defenderEntity.isOver()) {
                return WarReport.Effect.W;
            }
        }

        combos = calculateCombo(attackerEntity.getFightObjects(), holdPoints);
        for (WarCombo warCombo : combos) {
            switch (warCombo.getShot()) {
            case GJi: {
                double j = combosRatioFactors.get(warCombo.getComboCount() - 1).get(warCombo.getShot());
                goal = defenderEntity.findFightGoal();

                AttackEntry ae = new AttackEntry();
                ae.setLab(goal.getLab());

                int sumHp = 0;
                for (FightObject fobj : warCombo.getFightObjects()) {
                    sumHp += attack1(warInfo.getTerrain(), fobj, goal, fobj.getAtk(), goal.getDef(), 1);
                }

                int hp = (int) (sumHp * j);
                goal.decrementHp(hp);
                ae.setVal(hp);

                // result
                ComboResult cr = new ComboResult();
                cr.setCount(warCombo.getComboCount());
                cr.setShot(Shot.GJi);
                cr.addTarget(ae);
                details.add(cr);
                break;
            }
            case FYu: {
                ComboResult cr = new ComboResult();
                cr.setCount(warCombo.getComboCount());
                cr.setShot(Shot.FYu);

                double j = combosRatioFactors.get(warCombo.getComboCount() - 1).get(warCombo.getShot());
                int hp;
                for (FightObject fo : attackerEntity.getFightObjects()) {
                    if (fo.isDead()) {
                        continue;
                    }

                    hp = (int) (fo.getMaxHp() * j);
                    fo.incrementHp(hp);

                    AttackEntry ae = new AttackEntry();
                    ae.setLab(fo.getLab());
                    ae.setVal(hp);
                    cr.addTarget(ae);
                }

                details.add(cr);
                break;
            }
            case JCe: {
                double j = combosRatioFactors.get(warCombo.getComboCount() - 1).get(warCombo.getShot());
                goal = defenderEntity.findFightGoal();

                AttackEntry ae = new AttackEntry();
                ae.setLab(goal.getLab());

                int sumHp = 0;
                for (FightObject fobj : warCombo.getFightObjects()) {
                    sumHp += attack1(warInfo.getTerrain(), fobj, goal, fobj.getMatk(), goal.getMdef(), 1);
                }

                int hp = (int) (sumHp * j);
                goal.decrementHp(hp);
                ae.setVal(hp);

                ComboResult cr = new ComboResult();
                cr.setCount(warCombo.getComboCount());
                cr.setShot(warCombo.getShot());
                cr.addTarget(ae);
                details.add(cr);
                break;
            }
            case QXi: {
                double j = combosRatioFactors.get(warCombo.getComboCount() - 1).get(warCombo.getShot());
                goal = defenderEntity.findFightGoal();

                AttackEntry ae = new AttackEntry();
                ae.setLab(goal.getLab());

                int atk;
                int def;
                int sumHp = 0;
                for (FightObject fobj : warCombo.getFightObjects()) {
                    if (fobj.getAtk() > fobj.getMatk()) {
                        atk = fobj.getAtk();
                        def = goal.getDef();
                    } else {
                        atk = fobj.getMatk();
                        def = goal.getMdef();
                    }

                    sumHp += attack1(warInfo.getTerrain(), fobj, goal, atk, def, 1);
                }

                int hp = (int) (sumHp * j);
                goal.decrementHp(hp);
                goal.decrementFury(1);

                ae.setVal(hp);

                // result
                ComboResult cr = new ComboResult();
                cr.setCount(warCombo.getComboCount());
                cr.setShot(Shot.QXi);
                cr.addTarget(ae);
                details.add(cr);
                break;
            }
            case BSa: {
                double j = combosRatioFactors.get(warCombo.getComboCount() - 1).get(warCombo.getShot());
                JoinSkill js = joinSkills.get(warCombo.getFightObjectSize()).get(warCombo.getAreaString());
                ComboResult cr = skillComboAoeAttack(warInfo, dire, warCombo,
                        defenderEntity.findFightGoals(js.getNumOfTargets()), js.getFactor() * j, js);

                if (!"none".equals(js.getJointArea()) && js.getAttackerCntNeeded() >= 4) {
                    for (FightObject fo : attackerEntity.getFightObjects()) {
                        if (fo.isDead()) {
                            continue;
                        }

                        if (js.getAttackerCntNeeded() == 4) {
                            new Shu4SkillBuffer(warInfo, dire, fo).onBefore();
                        } else if (js.getAttackerCntNeeded() == 5) {
                            new Shu5SkillBuffer(warInfo, dire, fo).onBefore();
                        }
                    }
                }
                details.add(cr);
                break;
            }
            case YHu: {
                double j = combosRatioFactors.get(warCombo.getComboCount() - 1).get(warCombo.getShot());
                FightObject succor = attackerEntity.getSuccor();
                Script script = scriptManager.getScript("war.bs." + succor.getBsaSkill());

                ComboResult cr = new ComboResult();
                cr.setCount(warCombo.getComboCount());
                cr.setShot(Shot.YHu);

                MultiAttackResult mar = (MultiAttackResult) script.invoke(null, new BSaWapper(attackerEntity,
                        defenderEntity, succor, bsaSkills.get(succor.getBsaSkill()), warInfo, j));
                if (mar != null) {
                    cr.setTargets(mar.getTargets());
                }
                details.add(cr);
                break;
            }
            case Q7: {
                if (warCombo.getComboCount() <= 1) {
                    for (FightObject fo : attackerEntity.getFightObjects()) {
                        fo.incrementFury(1);
                    }
                } else {
                    // ?
                    scriptManager.getScript("war.Q7ComboScript").invoke(null,
                            new Q7ComboWapper(warInfo, attackerEntity, warCombo));
                }

                ComboResult cr = new ComboResult();
                cr.setCount(warCombo.getComboCount());
                cr.setShot(Shot.Q7);

                details.add(cr);
                break;
            }
            }

            if (attackerEntity.isOver()) {
                return WarReport.Effect.D;
            }

            if (defenderEntity.isOver()) {
                return WarReport.Effect.W;
            }
        }

        return WarReport.Effect.C;
    }

    /**
     *
     * @param we
     * @return
     */
    public WarReport.Entity newWarReportEntity(WarInfo.Entity we) {
        WarReport.Entity rs = new WarReport.Entity();
        rs.setRoleName(we.getRoleName());
        rs.setLevel(we.getLevel());
        rs.setPowerGuess(we.getPowerGuess());

        rs.setSuccorIid(we.getSuccor().getHeroId());

        // FIXME Test
        rs.setSuccorAtk(we.getSuccor().getAtk());
        rs.setSuccorFury(we.getSuccor().getFury());

        List<JSONObject> heros = new ArrayList<>(we.getFightObjects().size());
        for (FightObject fo : we.getFightObjects()) {
            JSONObject json = new JSONObject();
            json.put(HeroPropertyKeys.ID, fo.getHeroId());
            json.put(HeroPropertyKeys.LADDER, fo.getLadder());
            json.put(HeroPropertyKeys.STAR, fo.getStar());
            json.put(HeroPropertyKeys.LEVEL, fo.getLevel());
            json.put(HeroPropertyKeys.HP, fo.getMaxHp());
            json.put(HeroPropertyKeys.FURY, fo.getFury());

            // FIXME Test Data
            json.put(HeroPropertyKeys.ATK, fo.getAtk());
            json.put(HeroPropertyKeys.DEF, fo.getDef());
            json.put(HeroPropertyKeys.MATK, fo.getMatk());
            json.put(HeroPropertyKeys.MDEF, fo.getMdef());
            json.put(HeroPropertyKeys.CRIT_RATE, MathUtils.round(fo.getCritRate(), 3));
            json.put(HeroPropertyKeys.DECRIT_RATE, MathUtils.round(fo.getDecritRate(), 3));
            json.put(HeroPropertyKeys.CRIT_MAGN, MathUtils.round(fo.getCritMagn(), 3));
            json.put(HeroPropertyKeys.PARRY_RATE, MathUtils.round(fo.getParryRate(), 3));
            json.put(HeroPropertyKeys.DEPARRY_RATE, MathUtils.round(fo.getDeparryRate(), 3));
            json.put(HeroPropertyKeys.PARRY_VALUE, MathUtils.round(fo.getParryValue(), 3));

            heros.add(json);
        }
        rs.setHeros(heros);

        return rs;
    }

    /**
     *
     * @return
     */
    public String randomSuccorHeroId() {
        String[] ids = Typhons.getProperty("typhon.spi.pve.buildHeroIds").split(",");
        int i = HERO_ID_RANDOM.nextInt(ids.length);
        return ids[i];
    }

    private PacketSuccor newPacketSuccor(int rid, VacantData vacantData) {
        Session otherSession = sessionManager.getSession(rid);
        PacketSuccor succor;
        int societyId;
        if (otherSession != null) {
            Normal otherNormal = SessionUtils.getPlayer(otherSession).getNormal();
            FightGroup fightGroup = otherNormal.getFightGroup(otherNormal.getLastFidx());
            HeroItem primaryHero = fightGroup.getHeroItem(fightGroup.getCaptain());

            succor = new PacketSuccor();
            succor.setRid(otherNormal.player().getRole().getRid());
            succor.setName(otherNormal.player().getRole().getName());
            succor.setPlayerLevel(otherNormal.getLevel());

            succor.setIid(primaryHero.getId());
            succor.setLevel(primaryHero.getLevel());
            succor.setStar(primaryHero.getStar().name());
            succor.setPowerGuess(primaryHero.getPowerGuess());
            succor.setLadder(primaryHero.getLadder());
            succor.setAvatar(otherNormal.getAvatar());
            succor.setAvatarBorder(otherNormal.getAvatarBorder());
            societyId = otherNormal.getSocietyId();
        } else {
            if (vacantData == null) {
                vacantData = roleProvider.loadVacantData(rid);
            }

            succor = new PacketSuccor();
            succor.setRid(vacantData.getRid());
            succor.setName(vacantData.getName());
            succor.setIid(vacantData.getPrimaryHero().getId());
            succor.setPlayerLevel(vacantData.getLevel());

            succor.setStar(vacantData.getPrimaryHero().getStar().name());
            succor.setPowerGuess(vacantData.getPrimaryHero().getPowerGuess());
            succor.setLevel(vacantData.getPrimaryHero().getLevel());
            succor.setLadder(vacantData.getPrimaryHero().getLadder());
            succor.setAvatar(vacantData.getAvatar());
            succor.setAvatarBorder(vacantData.getAvatarBorder());
            societyId = vacantData.getSocietyId();
        }

        Society society = societyProvider.findBySid(societyId);
        if (society != null) {
            succor.setSocietyName(society.getName());
        }
        return succor;
    }

    /**
     *
     * @return
     */
    private int[] randomHoldPoints() {
        int[] points = { HOLD_POINT_RANDOM.nextInt(14), HOLD_POINT_RANDOM.nextInt(14),
                HOLD_POINT_RANDOM.nextInt(14), HOLD_POINT_RANDOM.nextInt(14), HOLD_POINT_RANDOM.nextInt(14) };
        return points;
    }

    //======================== End =================================================================
    private double calculateSkillComboX(WarInfo warInfo, WarCombo combo, boolean b) {
        double sum = 0;
        FightObject fo;
        double atk;
        double f;
        double p1;

        StringBuilder sb = null;
        if (LOG.isDebugEnabled()) {
            sb = new StringBuilder("(");
        }

        for (Point point : combo.getPoints()) {
            fo = point.getFightObject();
            atk = Math.max(fo.getAtk(), fo.getMatk());
            f = getFuryFactor(fo.getFury());
            // 
            if (b) {
                p1 = getTerrainFactor(fo.getRace(), warInfo.getTerrain());
            } else {
                p1 = 1;
            }
            sum += atk * p1 * f;

            if (LOG.isDebugEnabled()) {
                sb.append("(").append(fo.getHeroId()).append(": ");
                sb.append(atk).append(" * ").append(p1).append(" * ");
                sb.append(f).append(") + ");
            }
        }

        double res = sum / combo.getPoints().size();
        if (LOG.isDebugEnabled()) {
            sb.deleteCharAt(sb.length() - 1).deleteCharAt(sb.length() - 1);
            sb.append(")");
            LOG.debug("{} = {} / {}", res, sum, combo.getPoints().size());
        }
        return res;
    }

    private void plusComboPoint(Map<Shot, WarCombo> combos, Shot shot, int x, int y, FightObject fo) {
        WarCombo warCombo = combos.get(shot);
        if (warCombo == null) {
            warCombo = new WarCombo(shot);
            combos.put(shot, warCombo);
        }

        warCombo.addPoint(new WarCombo.Point(x, y, fo));
    }

    private Shot[][] toShotTable(List<FightObject> fightObjects, int[] holdPoints) {
        Shot[][] table = new Shot[3][5];
        for (int i = 0; i < fightObjects.size(); i++) {
            FightObject fightObject = fightObjects.get(i);
            if (!fightObject.isAvailable()) {
                table[ROW_1ST][i] = IFightItem.Shot.None;
                table[ROW_2ND][i] = IFightItem.Shot.None;
                table[ROW_3RD][i] = IFightItem.Shot.None;
            } else {
                table[ROW_1ST][i] = getNextShot(fightObject, holdPoints[i]);
                table[ROW_2ND][i] = getShot(fightObject, holdPoints[i]);
                table[ROW_3RD][i] = getPrevShot(fightObject, holdPoints[i]);
            }
        }

        if (LOG.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder();
            sb.append("\n++++++++++++++++++++++++++++++++++++");
            for (Shot[] shots : table) {
                sb.append("\n+");
                for (Shot shot : shots) {
                    sb.append(org.apache.commons.lang3.StringUtils.center(shot.name(), 6)).append("+");
                }
                sb.append("\n++++++++++++++++++++++++++++++++++++");
            }
            LOG.debug(sb.toString());
        }
        return table;
    }

    //=======================================??===================================================
    private void loadTerrainWarFactor() {
        JSONArray array = JSON.parseArray(ComponentUtils.readDataFile("terrain_war_factor.json"));
        TerrainFactorData[] factors = new TerrainFactorData[array.size()];
        for (int i = 0; i < factors.length; i++) {
            factors[i] = new TerrainFactorData(array.getJSONObject(i));
        }
        terrainFactors = factors;
    }

    private void loadRaceWarFactor() {
        JSONArray array = JSON.parseArray(ComponentUtils.readDataFile("race_war_factor.json"));
        RaceFactorData[] factors = new RaceFactorData[array.size()];
        for (int i = 0; i < factors.length; i++) {
            factors[i] = new RaceFactorData(array.getJSONObject(i));
        }
        raceFactors = factors;
    }

    private void loadFuryWarFactor() {
        JSONArray array = JSON.parseArray(ComponentUtils.readDataFile("fury_war_factor.json"));
        double[] factors = new double[array.size()];
        for (int i = 0; i < factors.length; i++) {
            factors[i] = array.getJSONObject(i).getDouble("v");
        }
        furyFactors = factors;
    }

    private void loadCombosRatioFactor() {
        JSONArray array = JSON.parseArray(ComponentUtils.readDataFile("client/combos_ratio.json"));
        for (int i = 0; i < array.size(); i++) {
            JSONObject json = array.getJSONObject(i);
            Map<Shot, Double> factors = new HashMap<>(json.size());

            for (String k : json.keySet()) {
                factors.put(Shot.valueOf(k), json.getDoubleValue(k));
            }
            combosRatioFactors.add(factors);
        }
    }

    private void loadBSaSkillConfig() {
        List<BSaSkill> list = JSON.parseArray(ComponentUtils.readDataFile("BSa_skill.json"), BSaSkill.class);
        for (BSaSkill bs : list) {
            bsaSkills.put(bs.getId(), bs);
        }
    }

    private void loadJoinSkillConfig() {
        List<JoinSkill> list = JSON.parseArray(ComponentUtils.readDataFile("Joint_skill.json"), JoinSkill.class);
        for (JoinSkill js : list) {
            Map<String, JoinSkill> map = joinSkills.get(js.getAttackerCntNeeded());
            if (map == null) {
                map = new HashMap<>(3);
                joinSkills.put(js.getAttackerCntNeeded(), map);
            }
            map.put(js.getJointArea(), js);
        }
    }

    //========================================================================================
    private double getAttackFactor() {
        return (MIN_FACTOR + FACTOR_RANDOM.nextInt(11) / 100D);
    }

    private double getCritRate() {
        return (FACTOR_RANDOM.nextInt(101) / 100D);
    }

    private String randomName(List<PacketSuccor> vals) {
        String name;

        f1: for (;;) {
            name = roleProvider.randomName();
            for (PacketSuccor ps : vals) {
                if (name.equals(ps.getName())) {
                    continue f1;
                }
            }
            return name;
        }
    }

    private JSONObject buildSuccorData(HeroItem hero) {
        JSONObject json = new JSONObject();
        double p = Typhons.getDouble("typhon.spi.war.succorMagn");
        json.put("extraTong", hero.getExtraTong() * p);
        json.put("extraWu", hero.getExtraWu() * p);
        json.put("extraZhi", hero.getExtraZhi() * p);
        json.put("extraAtk", hero.getExtraAtk() * p);
        json.put("extraDef", hero.getExtraDef() * p);
        json.put("extraMatk", hero.getExtraMatk() * p);
        json.put("extraMdef", hero.getExtraMdef() * p);
        json.put("extraHp", hero.getExtraHp() * p);
        json.put("extraCritRate", hero.getExtraCritRate() * p);
        json.put("extraDecritRate", hero.getExtraDecritRate() * p);
        json.put("extraCritMagn", hero.getExtraCritMagn() * p);
        json.put("extraParryRate", hero.getExtraParryRate() * p);
        json.put("extraParryValue", hero.getExtraParryValue() * p);
        json.put("extraDeparryRate", hero.getExtraDeparryRate() * p);
        json.put("star", hero.getStar());
        json.put("level", hero.getLevel());
        return json;
    }

    private class RaceFactorData {

        Race Name;
        double Ce;
        double Bu;
        double Qi;
        double Gong;
        double Che;

        RaceFactorData(JSONObject obj) {
            Name = Race.valueOf(obj.getString("Name"));
            Ce = obj.getDouble("Ce");
            Bu = obj.getDouble("Bu");
            Qi = obj.getDouble("Qi");
            Gong = obj.getDouble("Gong");
            Che = obj.getDouble("Che");
        }

    }

    private class TerrainFactorData {

        Race Name;
        double SLu;
        double SLing;
        double PYuan;
        double SDi;
        double CGuan;

        TerrainFactorData(JSONObject obj) {
            Name = Race.valueOf(obj.getString("Name"));
            SLu = obj.getDouble("SLu");
            SLing = obj.getDouble("SLing");
            PYuan = obj.getDouble("PYuan");
            SDi = obj.getDouble("SDi");
            CGuan = obj.getDouble("CGuan");
        }
    }
}