【前言】
想必各位期待很久楓之谷V62擂台,想要修復但台灣資源少的可憐,國外資源又看不懂,本次教學參考國外修復教學消化吸收後,製作出專屬適合台灣區使用的V62擂台修復,在嘗試修付前請注意,您必須具備一定的語法觀念與邏輯,大致熟悉 "JS" 寫法,千萬不要才剛踏入私服界就想挑戰BOSS。

【教學前準備】
NetBeans IDE 6.5 繁體版 : 點此
論壇專用討論區 : 點此

【教學開始】

找到伺服器端口中的 sendops 文件,查看文件中以下項目,是否跟框內相同。
 MONSTER_CARNIVAL_START
 MONSTER_CARNIVAL_OBTAINED_CP
 MONSTER_CARNIVAL_PARTY_CP
 MONSTER_CARNIVAL_SUMMON
 MONSTER_CARNIVAL_DIED


 MONSTER_CARNIVAL_START = 0xE2
 MONSTER_CARNIVAL_OBTAINED_CP = 0xE3
 MONSTER_CARNIVAL_PARTY_CP = 0xE4
 MONSTER_CARNIVAL_SUMMON = 0xE5
 MONSTER_CARNIVAL_DIED = 0xE7




新增NPC腳本 / 2042000.JS




 /**  *  [MENTION=19862]id[/MENTION] 2042000
 *    [MENTION=806871]NPC[/MENTION] Spiegelmann
 *    [MENTION=836108]Function[/MENTION] Monster Carnival Lobby NPC
 * @author s4nta
 */

// Relevant Monster Carnival classes
var MonsterCarnival = net.sf.odinms.server.partyquest.MonsterCarnival;
var MCTracker = net.sf.odinms.server.partyquest.MCTracker;
var MCParty = net.sf.odinms.server.partyquest.MCParty;
var MCField = net.sf.odinms.server.partyquest.MCField;
var MCTeam = net.sf.odinms.server.partyquest.MCField.MCTeam;

// NPC variables
var status = -1;
var carnival, field;
var room = -1;

function start() {
    if (cm.getMapId() != 980000000) {
        MCTracker.log("助手通知 " + cm.getMapId() + " 由玩家 " + cm.getName());
        cm.sendOk("您沒有權限");
        cm.dispose();
        return;
    }
    action(1, 0, 0);
}

function action(mode, type, selection) {
    if (mode == -1) {
        cm.dispose();
        return;
    }
    if (mode == 1) status++;
    else status--;

    if (status == 0) {
        if (cm.getParty() == null) {
            cm.sendOk("您尚未組隊");
            cm.dispose();
            return;
        } else if (!cm.isLeader()) {
            cm.sendOk("如果您想參加怪物擂台,請透過隊長與我對話。");
            cm.dispose();
            return;
        }
        carnival = MonsterCarnival.getMonsterCarnival(cm.getPlayer().getClient().getChannel()); 
        cm.sendSimple(carnival.getNPCAvailableFields());
    } else if (status == 1) {
        room = selection;
        if (room < 1 || room > 6) {
            cm.sendOk("此房間無法使用");
            cm.dispose();
            return;
        }
        var code = carnival.registerStatus(cm.getParty(), selection);
        if (code == MonsterCarnival.STATUS_FIELD_FULL) {
            cm.sendOk("這個場地已滿")
        } else if (code == MonsterCarnival.STATUS_PARTY_SIZE) {
            cm.sendOk("您的隊伍人數不正確");
        } else if (code == MonsterCarnival.STATUS_PARTY_LEVEL) {
            cm.sendOk("請確認您的組員等級在30~50之間");
        } else if (code == MonsterCarnival.STATUS_PARTY_MISSING) {
            cm.sendOk("請確認您的組員是否都在該地圖中");
        } else if (code == MonsterCarnival.STATUS_FIELD_INVALID) {
            cm.sendOk("U未通過請求");
        }

        if (code == MonsterCarnival.STATUS_PROCEED) {
            field = carnival.getField(room);
            party = carnival.createParty(cm.getParty());
            field.register(party, MCTeam.RED);
            cm.sendOk("您將有三分鐘接受來自其他隊伍的挑戰");
        } else if (code == MonsterCarnival.STATUS_REQUEST) {
            cm.sendOk("發出要求至 " + room + ". 如果該隊伍接受挑戰即開始怪物擂台比賽。");
            field = carnival.getField(room);
            party = carnival.createParty(cm.getParty());
            field.request(party);
        }
        cm.dispose();
    }
}  



新增NPC腳本 / 2042002.JS




var DISABLED = false;

var SavedLocationType = Packages.net.sf.odinms.server.maps.SavedLocationType;

// Relevant Monster Carnival classes
var MonsterCarnival = Packages.net.sf.odinms.server.partyquest.MonsterCarnival;
var MCTracker = Packages.net.sf.odinms.server.partyquest.MCTracker;
var MCParty = Packages.net.sf.odinms.server.partyquest.MCParty;
var MCField = Packages.net.sf.odinms.server.partyquest.MCField;

// NPC variables
var status = -1;
var store = false;
var ctx = -1; //context
var storeInfo;
var purchaseId;
var purchaseCost;

// Reference
var coinId = 4001129;
var coinIcon = "#i" + coinId + "#";
var infoMaps = [220000000, 200000000, 103000000, 540000000]; // ludi, orbis, kerning, singapore
var gradeS = 600
var gradeA = 500
var gradeB = 400
var gradeC = 300
var gradeD = 200
var gradeE = 100

var expRewards = [[150000, 100000], // S Winner/Loser
                  [100000, 70000], // A Winner/Loser
                  [75000, 43250], // B Winner/Loser
                  [50000, 25000], // C Winner/Loser
                  [25000, 12500], // D Winner/Loser
                  [12500, 6250],  // E Winner/Loser
                  [5000, 2500]    // F Winner/Loser
                  ];

// Exchange stores
var warrior = [[1302004, 7], [1402006, 7], [1302009, 10], [1402007, 10],
               [1302010, 20], [1402003, 20], [1312006, 7], [1412004, 7],
               [1312007, 10], [1412005, 10], [1312018, 20], [1412003, 20],
               [1322015, 7], [1422008, 7], [1322016, 10], [1422007, 10],
               [1322017, 20], [1422005, 20], [1432003, 7], [1442003, 7],
               [1432005, 10], [1442009, 10], [1442005, 20], [1432004, 20]];

var magician = [[1372001, 7], [1382018, 7], [1372012, 10], [1382019, 10],
                [1382001, 20], [1372007, 20]];

var archer = [[1452006, 7], [1452007, 10], [1452008, 20], [1462005, 7],
              [1462006, 10], [1462007, 20]];

var thief = [[1472013, 7], [1472017, 10], [1472021, 20], [1332014, 7],
             [1332011, 10], [1332031, 10], [1332016, 20], [1332034, 20]];

var pirate = [[1482005, 7], [1482006, 10], [1482007, 20], [1492005, 7],
              [1492006, 10], [1492007, 20]];

var necklace = [[1122007, 50], [2041211, 40]];

// Long Text Descriptions
var infoText = "怪物擂台不像其他組隊任務,因為他是透過隊伍跟對隊伍間的競爭取得勝利,你的任務就是擊殺怪物取得CP分數,透過CP您可以召喚許多輔助、技能、怪物,以便您取得勝利。擊殺地圖中的怪物將有機率使你獲得楓葉硬幣,楓葉硬幣可以換取許多道具。";
var no = "您並沒有足夠的楓葉硬幣來兌換此道具。";

function getGrade(cp) {
    // Returns index of corresponding expRewards pair.
    if (cp >= gradeS) {
        return 0;
    } else if (cp >= gradeA) {
        return 1;
    } else if (cp >= gradeB) {
        return 2;
    } else if (cp >= gradeC) {
        return 3;
    } else if (cp >= gradeD) {
        return 4;
    } else if (cp >= gradeE) {
        return 5;
    } else {
        return 6;
    }
}

function isTownMap(map) {
    for (var i = 0; i < infoMaps.length; i++) {
        if (infoMaps[i] == map) {
            return true;
        }
    }
    return false;
}

function isExitMap(map) {
    return map == 980000010;
}

function isWinnerMap(map) {
    return (map >= 980000000 && map <= 980000700 && map % 10 == 3);
}

function isLoserMap(map) {
    return (map >= 980000000 && map <= 980000700 && map % 10 == 4);
}

var CONTEXT_NONE = -1;
var CONTEXT_TOWN = 0;
var CONTEXT_EXIT = 1;
var CONTEXT_WIN  = 2;
var CONTEXT_LOSE = 3;

function start() {  
    if (DISABLED) {
        cm.sendOk("怪物擂台暫時關閉");
        cm.dispose();
        return;
    }
    m = cm.getMapId();
    if (isTownMap(m)) {
        ctx = CONTEXT_TOWN;
    } else if (isExitMap(m)) {
        ctx = CONTEXT_EXIT;
    } else if (isWinnerMap(m)) {
        ctx = CONTEXT_WIN;
    } else if (isLoserMap(m)) {
        ctx = CONTEXT_LOSE;
    } else {
        ctx = CONTEXT_NONE;
    }

    action(1, 0, 0);
}

function doLoserMap(mode, type, selection) {
    if (cm.getPlayer().getMCPQParty() == null) {
        cm.warp(MonsterCarnival.MAP_LOBBY);
        cm.dispose();
        return;
    }

    if (mode == -1) {
        cm.dispose();
    } else {
        if (mode == 1) status++;
        else status--;

        if (status == 0) {
            cm.sendNext("非常可惜,您並沒有辦法贏得本場比賽,相信下次會更好。");
        } else if (status == 1) {
            var points = cm.getPlayer().getMCPQParty().getTotalCP();
            var grade = getGrade(points);
            var letterGrade = "ABCDF"[grade];
            var expReward = expRewards[grade][1];

            cm.sendNext("您的成績是: #b" + letterGrade + "\r\n\r\n#k經驗職獎勵: " + expReward);
            cm.gainExp(expReward);
        } else if (status == 2) {
            cm.warp(MonsterCarnival.MAP_LOBBY);
            cm.dispose();
        }
    }
}

function doWinnerMap(mode, type, selection) {
    if (cm.getPlayer().getMCPQParty() == null) {
        cm.warp(MonsterCarnival.MAP_LOBBY);
        cm.dispose();
        return;
    }

    if (mode == -1) {
        cm.dispose();
    } else {
        if (mode == 1) status++;
        else status--;

        if (status == 0) {
            cm.sendNext("恭喜 很高興你們獲得本場勝利。");
        } else if (status == 1) {
            var points = cm.getPlayer().getMCPQParty().getTotalCP();
            var grade = getGrade(points);
            var letterGrade = "ABCDF"[grade];
            var expReward = expRewards[grade][0];

            cm.sendNext("您的成績是 : #b" + letterGrade + "\r\n\r\n#k經驗職獎勵 : " + expReward);
            cm.gainExp(expReward);
        } else if (status == 2) {
            cm.warp(MonsterCarnival.MAP_LOBBY);
            cm.dispose();
        }
    }
}

function doTown(mode, type, selection) {
    if (mode == -1) {
        cm.sendOk("Be sure to vote for the server every 24 hours!");
        cm.dispose();
    } else {
        if (mode == 1) status++;
        else status--;

        if (status == 0) {
            cm.sendSimple("請問有甚麼需要幫忙的?如果你從沒參加過怪物擂台,你會需要再進入前知道一些事情。\r\n\r\n#b#L0#進入怪物擂台#l\r\n#L1#了解怪物擂台#l\r\n#L2#兌換楓葉硬幣#l");
        } else if (status == 1) {
            if (selection == 0) {
                if (cm.getChar().getLevel() < MonsterCarnival.MIN_LEVEL || cm.getChar().getLevel() > MonsterCarnival.MAX_LEVEL) {
                    cm.sendOk("您的等級平均值必須在" + MonsterCarnival.MIN_LEVEL + " 跟等級 " + MonsterCarnival.MAX_LEVEL + " 這中間");
                    cm.dispose();
                    return;
                }
                cm.getChar().saveLocation(SavedLocationType.MONSTER_CARNIVAL);
                cm.warp(MonsterCarnival.MAP_LOBBY, 4);
                cm.dispose();
                return;
            } else if (selection == 1) {
                cm.sendPrev(infoText);
                cm.dispose();
                return;
            } else if (selection == 2) {
                store = true;
                cm.sendSimple("請選擇一個種類:\r\n" +
                    "#L101##b兌換戰士類武器\r\n" +
                    "#L102#兌換法師類武器\r\n" +
                    "#L103#兌換弓箭手類武器\r\n" +
                    "#L104#兌換盜賊武器\r\n" +
                    "#L105#兌換海盜武器\r\n" +
                    "#L106#兌換怪物擂台項鍊");
            }
        } else if (status == 2) {
            if (store) {
                switch (selection) {
                    case 101:
                        storeInfo = warrior;
                        break;
                    case 102:
                        storeInfo = magician;
                        break;
                    case 103:
                        storeInfo = archer;
                        break;
                    case 104:
                        storeInfo = thief;
                        break;
                    case 105:
                        storeInfo = pirate;
                        break;
                    case 106:
                        storeInfo = necklace;
                        break;
                    default:
                        storeInfo = [];
                }
                if (storeInfo.length == 0) {
                    cm.sendOk("這個商店不存在");
                    cm.dispose();
                    return;
                }
                var storeText = "";
                for (var i = 0; i < storeInfo.length; ++i) {
                    var wepId = storeInfo[i][0];
                    var cost = storeInfo[i][1];
                    storeText += "#L" + i + "##v" + wepId + "# - #z" + wepId + "# - " + cost + " " + coinIcon + "#l\r\n";
                }
                cm.sendSimple(storeText);
            } else {
                MCTracker.log("[MCPQ_Info] CONTEXT_TOWN: Invalid status 2");
            }
        } else if (status == 3) {
            if (store) {
                purchaseId = storeInfo[selection][0];
                purchaseCost = storeInfo[selection][1];

                if (cm.haveItem(coinId, purchaseCost)) {
                    cm.sendYesNo("您確定要兌換 #i" + purchaseId + "#? 您將會獲得 #r#e" + (cm.itemQuantity(coinId) - purchaseCost) + " " + coinIcon + "##k#n 剩餘.");
                } else {
                    cm.sendOk("您並沒有足夠的 " + coinIcon + ".");
                    cm.dispose();
                }
            } else {
                MCTracker.log("[MCPQ_Info] CONTEXT_TOWN: Invalid status 3");
            }
        } else if (status == 4) {
            if (store) {
                if (cm.haveItem(coinId, purchaseCost)) {
                    cm.gainItem(coinId, -purchaseCost);
                    cm.gainItem(purchaseId);
                    cm.sendOk("恭喜,這是您的新道具");
                    cm.dispose();
                }
            } else {
                MCTracker.log("[MCPQ_Info] CONTEXT_TOWN: Invalid status 4");
            }
        }
    }
}

function doExit() {
    cm.warp(MonsterCarnival.MAP_LOBBY);
    cm.sendOk("希望您能在怪物擂台獲得快樂");
    cm.dispose();
}

function action(mode, type, selection) {
    switch (ctx) {
        case CONTEXT_TOWN:
            doTown(mode, type, selection);
            break;
        case CONTEXT_EXIT:
            doExit();
            break;
        case CONTEXT_LOSE:
            doLoserMap(mode, type, selection);
            break;
        case CONTEXT_WIN:
            doWinnerMap(mode, type, selection);
            break;
        default:
            MCTracker.log("[MCPQ_INFO] Invalid context (value: " + ctx + ")");
            cm.dispose();
    } }  



新增NPC腳本 2042003.JS



var MonsterCarnival = net.sf.odinms.server.partyquest.MonsterCarnival;
var MCTracker = net.sf.odinms.server.partyquest.MCTracker;
var MCParty = net.sf.odinms.server.partyquest.MCParty;
var MCField = net.sf.odinms.server.partyquest.MCField;
var MCTeam = net.sf.odinms.server.partyquest.MCField.MCTeam;

// NPC variables
var status = -1;
var carnival, field;
var room = -1;

function start() {
    if (!MonsterCarnival.isLobbyMap(cm.getMapId())) {
        MCTracker.log("Assistant called on invalid map " + cm.getMapId() + " by player " + cm.getName());
        cm.sendOk("您不是隊長,並沒有權限操作此項目。");
        cm.dispose();
        return;
    }
    action(1, 0, 0);
}

function action(mode, type, selection) {
    if (mode == -1) {
        cm.dispose();
        return;
    }
    if (mode == 1) status++;
    else status--;

    if (status == 0) {
        if (cm.getParty() == null) {
            cm.warp(MonsterCarnival.MAP_LOBBY);
            cm.dispose();
            return;
        }
        options = ["#L1#離開擂台等待室 #r#e(警告 : 如濫用此功能,將會永久禁止進入擂台)#b#n.#l",
                   "#L2#關閉對話#l"];
        if (cm.isLeader()) {
            options.unshift("#L0#查看等待的隊伍#l");
        }

        text = "歡迎來到怪物擂台。我是#r紅隊擂台助手#k.我能為您做什麼 ?#b\r\n";
        for (var i = 0; i < options.length; i++) {
            text += options[i];
            text += "\r\n";
        }
        cm.sendSimple(text);
    } else if (status == 1) {
        field = cm.getChar().getMCPQField();
        if (selection == 0) {
            if (!cm.isLeader()) {
                cm.sendOk("您並沒有權限");
                cm.dispose();
                return;
            }
            if (!field.hasPendingRequests()) {
                cm.sendOk("目前沒有等待中的隊伍");
                cm.dispose();
                return;
            }
            cm.sendSimple(field.getNPCRequestString());
        } else if (selection == 1) {
            if (field != null) {
                field.deregister(true);
            } else {
                cm.warp(MonsterCarnival.MAP_EXIT);
            }
            cm.dispose();
        } else {
            cm.dispose();
        }
    } else if (status == 2) {
        var code = field.acceptRequest(selection);
        if (code == 1) {
            cm.sendOk("接受挑戰");
        } else {
            cm.sendOk("出現未知的錯誤");
        }
        cm.dispose();
    }
}  



新增NPC腳本 2042004.JS



var MonsterCarnival = net.sf.odinms.server.partyquest.MonsterCarnival;
var MCTracker = net.sf.odinms.server.partyquest.MCTracker;
var MCParty = net.sf.odinms.server.partyquest.MCParty;
var MCField = net.sf.odinms.server.partyquest.MCField;
var MCTeam = net.sf.odinms.server.partyquest.MCField.MCTeam;

// NPC variables
var status = -1;
var carnival, field;
var room = -1;

function start() {
    if (!MonsterCarnival.isLobbyMap(cm.getMapId())) {
        MCTracker.log("Assistant called on invalid map " + cm.getMapId() + " by player " + cm.getName());
        cm.sendOk("您不是隊長,並沒有權限操作此項目。");
        cm.dispose();
        return;
    }
    action(1, 0, 0);
}

function action(mode, type, selection) {
    if (mode == -1) {
        cm.dispose();
        return;
    }
    if (mode == 1) status++;
    else status--;

    if (status == 0) {
        if (cm.getParty() == null) {
            cm.warp(MonsterCarnival.MAP_LOBBY);
            cm.dispose();
            return;
        }
        options = ["#L1#離開擂台等待室 #r#e(警告 : 如濫用此功能,將會永久禁止進入擂台)#b#n.#l",
                   "#L2#關閉對話#l"];
        if (cm.isLeader()) {
            options.unshift("#L0#查看等待的隊伍#l");
        }

        text = "歡迎來到怪物擂台。我是#r籃隊擂台助手#k.我能為您做什麼 ?#b\r\n";
        for (var i = 0; i < options.length; i++) {
            text += options[i];
            text += "\r\n";
        }
        cm.sendSimple(text);
    } else if (status == 1) {
        field = cm.getChar().getMCPQField();
        if (selection == 0) {
            if (!cm.isLeader()) {
                cm.sendOk("您並沒有權限");
                cm.dispose();
                return;
            }
            if (!field.hasPendingRequests()) {
                cm.sendOk("目前沒有等待中的隊伍");
                cm.dispose();
                return;
            }
            cm.sendSimple(field.getNPCRequestString());
        } else if (selection == 1) {
            if (field != null) {
                field.deregister(true);
            } else {
                cm.warp(MonsterCarnival.MAP_EXIT);
            }
            cm.dispose();
        } else {
            cm.dispose();
        }
    } else if (status == 2) {
        var code = field.acceptRequest(selection);
        if (code == 1) {
            cm.sendOk("接受挑戰");
        } else {
            cm.sendOk("出現未知的錯誤");
        }
        cm.dispose();
    }
}  


新增 scripts → portal → MCrevive(1,2,...6).js:

 MCrevive1

/*
This file is part of the OdinMS Maple Story Server
    Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc> 
                       Matthias Butz <matze@odinms.de>
                       Jan Christian Meyer <vimes@odinms.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License version 3
    as published by the Free Software Foundation. You may not use, modify
    or distribute this program under any other version of the
    GNU Affero General Public License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

importPackage(Packages.server.maps);

/*
[CelticMS] Monster Carnival Reviving Field 1
*/

function enter(pi) {
pi.warp(980000101, 0);
return true;
}



 MCrevive2

/*
This file is part of the OdinMS Maple Story Server
    Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc> 
                       Matthias Butz <matze@odinms.de>
                       Jan Christian Meyer <vimes@odinms.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License version 3
    as published by the Free Software Foundation. You may not use, modify
    or distribute this program under any other version of the
    GNU Affero General Public License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

importPackage(Packages.server.maps);

/*
[CelticMS] Monster Carnival Reviving Field 1
*/

function enter(pi) {
pi.warp(980000201, 0);
return true;
}



 MCrevive3

/*
This file is part of the OdinMS Maple Story Server
    Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc> 
                       Matthias Butz <matze@odinms.de>
                       Jan Christian Meyer <vimes@odinms.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License version 3
    as published by the Free Software Foundation. You may not use, modify
    or distribute this program under any other version of the
    GNU Affero General Public License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

importPackage(Packages.server.maps);

/*
[CelticMS] Monster Carnival Reviving Field 1
*/

function enter(pi) {
var portal = 0;
switch (pi.getPlayer().getTeam()) {
case 0:
portal = 4;
break;
case 1:
portal = 3;
break;
}
pi.warp(980000301, portal);
return true;
}



 MCrevive4

/*
This file is part of the OdinMS Maple Story Server
    Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc> 
                       Matthias Butz <matze@odinms.de>
                       Jan Christian Meyer <vimes@odinms.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License version 3
    as published by the Free Software Foundation. You may not use, modify
    or distribute this program under any other version of the
    GNU Affero General Public License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

importPackage(Packages.server.maps);

/*
[CelticMS] Monster Carnival Reviving Field 1
*/

function enter(pi) {
var portal = 0;
switch (pi.getPlayer().getTeam()) {
case 0:
portal = 4;
break;
case 1:
portal = 3;
break;
}
pi.warp(980000401, portal);
return true;
}



 MCrevive5

/*
This file is part of the OdinMS Maple Story Server
    Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc> 
                       Matthias Butz <matze@odinms.de>
                       Jan Christian Meyer <vimes@odinms.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License version 3
    as published by the Free Software Foundation. You may not use, modify
    or distribute this program under any other version of the
    GNU Affero General Public License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

importPackage(Packages.server.maps);

/*
[CelticMS] Monster Carnival Reviving Field 1
*/

function enter(pi) {
pi.warp(980000501, 0);
return true;
}



 MCrevive6

/*
This file is part of the OdinMS Maple Story Server
    Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc> 
                       Matthias Butz <matze@odinms.de>
                       Jan Christian Meyer <vimes@odinms.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License version 3
    as published by the Free Software Foundation. You may not use, modify
    or distribute this program under any other version of the
    GNU Affero General Public License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

importPackage(Packages.server.maps);

/*
[CelticMS] Monster Carnival Reviving Field 1
*/

function enter(pi) {
pi.warp(980000601, 0);
return true;
}



新增 scripts → portal → mc_out.js


/*
    This file is part of the OdinMS Maple Story Server
    Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
                       Matthias Butz <matze@odinms.de>
                       Jan Christian Meyer <vimes@odinms.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License version 3
    as published by the Free Software Foundation. You may not use, modify
    or distribute this program under any other version of the
    GNU Affero General Public License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

importPackage(Packages.net.sf.odinms.server.maps);

/*
Return from MCPQ map.
*/

function enter(pi) {
    var returnMap = pi.getPlayer().getSavedLocation(SavedLocationType.MONSTER_CARNIVAL);
    if (returnMap < 0) {
        returnMap = 100000000; // to fix people who entered the fm trough an unconventional way
    }
    var target = pi.getPlayer().getClient().getChannelServer().getMapFactory().getMap(returnMap);
    var targetPortal;

    if (returnMap == 230000000) {
        targetPortal = target.getPortal("market01");
    } else {
        targetPortal = target.getPortal("market00");
    }

    if (targetPortal == null)
        targetPortal = target.getPortal(0);

    if (pi.getPlayer().getMapId() != target) {
        pi.getPlayer().clearSavedLocation(SavedLocationType.MONSTER_CARNIVAL);
        pi.getPlayer().changeMap(target, targetPortal);
        return true;
    }
    return false;
}  



----【接著以下為SRC編譯項目】 ----

client/MapleCharacter.java
添加

private MCField.MCTeam MCPQTeam;private MCParty MCPQParty;
private MCField MCPQField;
private int availableCP = 0;
private int totalCP = 0;  


在 playerDead() 方法下新增:


if (player.getMap().isTown()) {    XPdummy *= 0.01;
} else if (MonsterCarnival.isBattlefieldMap(player.getMapId())) {
    XPdummy = 0;
}  



在 giveDebuff() 方法下新增字段,


public void giveDebuff(MapleDisease disease, MobSkill skill, boolean cpq) 


添加以下方法


public int getTeam() {    if (this.MCPQTeam == null) {
        return -1;
    }
    return this.MCPQTeam.code;
}

public MCField.MCTeam getMCPQTeam() {
    return MCPQTeam;
}

public void setMCPQTeam(MCField.MCTeam MCPQTeam) {
    this.MCPQTeam = MCPQTeam;
}

public MCParty getMCPQParty() {
    return MCPQParty;
}

public void setMCPQParty(MCParty MCPQParty) {
    this.MCPQParty = MCPQParty;
}

public MCField getMCPQField() {
    return MCPQField;
}

public void setMCPQField(MCField MCPQField) {
    this.MCPQField = MCPQField;
}

public int getAvailableCP() {
    return availableCP;
}

public void setAvailableCP(int availableCP) {
    this.availableCP = availableCP;
}

public int getTotalCP() {
    return totalCP;
}

public void setTotalCP(int totalCP) {
    this.totalCP = totalCP;
}

public void gainCP(int cp) {
    this.availableCP += cp;
    this.totalCP += cp;
}

public void loseCP(int cp) {
    this.availableCP -= cp;
}  


找到 client → MapleClient.java 
在  disconnect() 底下 onPlayerDisconnect 調用之後添加以下方法


if (chr.getMCPQField() != null) {    chr.getMCPQField().onPlayerDisconnected(player);

}  


找到 net → channel → handlers → ChangeMapHandler.java
在 executeStandardPath 區域內 !c.getPlayer().isAlive() 之前添加以下方法


if (player.getMCPQField() != null) {    player.getMCPQField().onPlayerRespawn(player);
    return;

}  


找到 net → channel → handlers → ItemPickupHandler.java
此處提供原文,供參考
( Add before items are added to inventory, or after mesos are handled: )
內容大致是,添加此方法時請在每個條件敘述前或是條件判斷後添加方法


else if (c.getPlayer().getMCPQField() != null) {   // CPQ Handling    boolean consumed = c.getPlayer().getMCPQField().onItemPickup(c.getPlayer(), mapitem);
    if (consumed) {
        c.getPlayer().getMap().broadcastMessage(MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 2, c.getPlayer().getId()),
                mapitem.getPosition());
        c.getPlayer().getCheatTracker().pickupComplete();
        c.getPlayer().getMap().removeMapObject(ob);
    } else {
        if (MapleInventoryManipulator.addFromDrop(c, mapitem.getItem(), true)) {
            c.getPlayer().getMap().broadcastMessage(
                    MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 2, c.getPlayer().getId()),
                    mapitem.getPosition());
            c.getPlayer().getCheatTracker().pickupComplete();
            c.getPlayer().getMap().removeMapObject(ob);
        } else {
            c.getPlayer().getCheatTracker().pickupComplete();
            return;
        }
    }

}  


找到 net → channel → handlers → PetLootHandler.java
添加以下方法


else if (c.getPlayer().getMCPQField() != null) {   // CPQ Handling
                    boolean consumed = c.getPlayer().getMCPQField().onItemPickup(c.getPlayer(), mapitem);
                    if (consumed) {
                        c.getPlayer().getMap().broadcastMessage(MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 2, c.getPlayer().getId()),
                                mapitem.getPosition());
                        c.getPlayer().getCheatTracker().pickupComplete();
                        c.getPlayer().getMap().removeMapObject(ob);
                    } else {
                        if (MapleInventoryManipulator.addFromDrop(c, mapitem.getItem(), true)) {
                            c.getPlayer().getMap().broadcastMessage(
                                    MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 2, c.getPlayer().getId()),
                                    mapitem.getPosition());
                            c.getPlayer().getCheatTracker().pickupComplete();
                            c.getPlayer().getMap().removeMapObject(ob);
                        } else {
                            c.getPlayer().getCheatTracker().pickupComplete();
                            return;
                        }
                    }

                }  


找到 net → channel → handlers →MonsterCarnivalHandler.java

添加或整個物件更換成以下方法



package net.sf.odinms.net.channel.handler;
import net.sf.odinms.client.MapleCharacter;
import net.sf.odinms.client.MapleClient;
import net.sf.odinms.net.AbstractMaplePacketHandler;
import net.sf.odinms.server.partyquest.mcpq.MCField;
import net.sf.odinms.server.partyquest.mcpq.MCTracker;
import net.sf.odinms.server.partyquest.mcpq.MonsterCarnival;
import net.sf.odinms.tools.data.input.SeekableLittleEndianAccessor;

/**
 * Packet handler for Monster Carnival.
 * @author s4nta
 */
public class MonsterCarnivalHandler extends AbstractMaplePacketHandler {

    @Override
    public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
        int tab = slea.readByte();
        int num = slea.readByte();
        MapleCharacter chr = c.getPlayer();

        if (MonsterCarnival.DEBUG) {
            MCTracker.log("[MCHandler] " + chr.getName() + " used tab "  + tab + " num " + num);
            System.out.println("[MCHandler] " + chr.getName() + " used tab "  + tab + " num " + num);
        }

        if (chr.getMCPQField() == null || chr.getMCPQParty() == null) {
            MCTracker.log("[MCHandler] " + chr.getName() + " attempting to use Monster Carnival handler without being in Monster Carnival");
            return;
        }

        MCField field = chr.getMCPQField();
        if (tab == 0) {
            field.onAddSpawn(c.getPlayer(), num);
        } else if (tab == 1) {
            field.onUseSkill(c.getPlayer(), num);
        } else if (tab == 2) { // status
            field.onGuardianSummon(c.getPlayer(), num);
        }
    }

}  


找到 server → MapleStatEffect.java
添加


private boolean consumeOnPickup, party;

private int cp, nuffSkill;  


添加以下方法到 loadFromData() 裡頭的 stat 解析中


ret.cp = MapleDataTool.getInt("cp", source, 0);ret.party = MapleDataTool.getInt("party", source, 0) > 0;
ret.consumeOnPickup = MapleDataTool.getInt("consumeOnPickup", source, 0) > 0;
ret.nuffSkill = MapleDataTool.getInt("nuffSkill", source, -1);


添加以下方法


public int getCP() {    return cp;
}

public boolean isParty() {
    return party;
}

public boolean isConsumeOnPickup() {
    return consumeOnPickup;
}

public int getNuffSkill() {
    return nuffSkill;

}  


找到 server → life → MapleLifeFactory.java
添加到 getMonster(int monsterId) 中


stats.setCp(MapleDataTool.getIntConvert("getCP", monsterInfoData, 0));  


找到 server → life → MapleMonster.java


private int team = -1;  


添加以下方法


public int getCP() {    return stats.getCp();
}

public int getTeam() {
    return team;
}

public void setTeam(int team) {
    this.team = team;
}

public void dispel() {
    if (!isAlive()) return;

    for (MonsterStatus i : MonsterStatus.values()) {
        if (monsterBuffs.contains(i)) {
            removeMonsterBuff(i);
            MaplePacket packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), Collections.singletonMap(i, Integer.valueOf(1)));
            map.broadcastMessage(packet, getPosition());
            if (getController() != null && !getController().isMapObjectVisible(MapleMonster.this)) {
                getController().getClient().getSession().write(packet);
            }
        }
    }

}  


server → life → MapleMonsterStats.java
添加以下繼承


private int cp;  


添加以下方法


public int getCp() {    return cp;
}

public void setCp(int cp) {
    this.cp = cp;

}  


找到 server→ life → MobSkill.java
添加到  applyEffect() 中


case 150:    monStat = MonsterStatus.WEAPON_ATTACK_UP;
    break;
case 151:
    monStat = MonsterStatus.WEAPON_DEFENSE_UP;
    break;
case 152:
    monStat = MonsterStatus.MAGIC_ATTACK_UP;
    break;
case 153:
    monStat = MonsterStatus.MAGIC_DEFENSE_UP;
    break;
case 154:
    monStat = MonsterStatus.ACC;
    break;
case 155:
    monStat = MonsterStatus.AVOID;
    break;
case 156:
    monStat = MonsterStatus.SPEED;

    break;  


找到 server → life → SpawnPoint.java
添加繼承


private int team = -1;  


添加函數


public SpawnPoint(MapleMonster monster, Point pos, int mobTime, int team) {    super();
    this.monster = monster;
    this.pos = new Point(pos);
    this.mobTime = mobTime;
    this.immobile = !monster.isMobile();
    this.nextPossibleSpawn = System.currentTimeMillis();
    this.team = team;

}  


在 spawnMonster(): 下添加怪物死亡監看器
( 安裝成功後,每次怪物被擊殺在伺服器CMD中會跑出訊息 )


if (team > -1) {    final int cp = mob.getCP();
    mob.addListener(new MonsterListener() {

        @Override
        public void monsterKilled(MapleMonster monster, MapleCharacter highestDamageChar) {
            if (highestDamageChar == null) {
                return;
            }

            if (highestDamageChar.getMCPQParty() == null) {
                MCTracker.log("Attempted to give CP to character without assigned MCPQ Party.");
                return;
            }

            highestDamageChar.getMCPQField().monsterKilled(highestDamageChar, cp);

        }
    });
    mob.setTeam(team);

}  


找到 server → maps → MapleMap.java
添加繼承


private boolean respawning = true;

private MCWZData mcpqData;  


添加方法


public final List<MapleMonster> getAllMonsters() {    return getAllMapObjects(MapleMapObjectType.MONSTER);
}

public void addMonsterSpawn(MapleMonster monster, int mobTime, int team) {
    Point newpos = calcPointBelow(monster.getPosition());
    newpos.y -= 1;
    SpawnPoint sp = new SpawnPoint(monster, newpos, mobTime, team);
    monsterSpawn.add(sp);
    if (!respawning) return;

    if (sp.shouldSpawn() || mobTime == -1) {
        sp.spawnMonster(this);
    }
}

public void setReactorState(MapleReactor reactor, byte state) {
    synchronized (this.mapobjects) {
        reactor.setState(state);
        broadcastMessage(MaplePacketCreator.triggerReactor(reactor, state));
    }
}

public <E extends MapleMapObject> List<E> getAllMapObjects(MapleMapObjectType type) {
    List<E> ret = new ArrayList<>();
    synchronized (mapobjects) {
        for (MapleMapObject l : mapobjects.values()) {
            if (l.getType() == type) {
                ret.add((E) l);
            }
        }
    }
    return ret;
}

public void clearDrops() {
    List<MapleMapItem> items = getAllMapObjects(MapleMapObjectType.ITEM);
    for (MapleMapItem itemmo : items) {
        removeMapObject(itemmo);
        broadcastMessage(MaplePacketCreator.removeItemFromMap(itemmo.getObjectId(), 0, 0));
    }
}

public Collection<SpawnPoint> getSpawnPoints() {
    return monsterSpawn;
}

public void respawn() {
    for (SpawnPoint sp : this.monsterSpawn) {
        if (sp.shouldSpawn()) {
            sp.spawnMonster(this);
        }
    }

}



找到 server → maps → MapleMapFactory.java
在 getMap() 方法中,找到PQ解析區域,在區域中添加以下方法


MapleData mcData = mapData.getChildByPath("monsterCarnival");if (mcData != null) {
    MCWZData mcpqInfo = new MCWZData(mcData);
    map.setMCPQData(mcpqInfo);
    map.setRespawning(false);

}  


在 getMap() 方法中,找到解析 mobTime 區域,添加以下方法



int team = MapleDataTool.getInt("team", life, -1);



更改 addMonsterSpawn() 方法,定義出新的 SpawnPoint 結構


map.addMonsterSpawn(monster, mobTime, team);  


添加以下方法


public MapleMap instanceMap(int mapid, boolean respawns, boolean npcs) {    return instanceMap(mapid, respawns, npcs, true);

}  


以及
( 此代碼結構與getMap相同,為避免翻譯上出現落差,我們提供原作者的原文本 )
where the code is the same as getMap but does not try to load from cache and does not store into cache.

PS : 本方法中,卡納團隊並未使用,可供參考


public MapleMap instanceMap(int mapid, boolean respawns, boolean npcs, boolean reactors)  


找到 server → maps → MapleReactor.java
在 hitReactor() 中,處理程序之前添加以下方法


if (c.getPlayer().getMCPQField() != null) {    c.getPlayer().getMCPQField().onGuardianHit(c.getPlayer(), this);
    return;

}  


創建一個文件路徑為 server → partyquest → mcpq
創建完畢後,添加以下所有物件

MCBattlefield.java


package net.sf.odinms.server.partyquest;
import net.sf.odinms.client.MapleCharacter;
import net.sf.odinms.server.life.*;
import net.sf.odinms.server.maps.*;
import net.sf.odinms.tools.MaplePacketCreator;
import net.sf.odinms.tools.MonsterCarnivalPacket;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;



/**
 * Keeps track of guardians and spawns in MCPQ.
 *
 * @author s4nta
 */
public class MCBattlefield {

    private MapleMap map;
    private MCWZData wzData;
    private int numGuardiansSpawned = 0;
    private int numMonstersSpawned = 0;
    // These map Guardian IDs (aka codes for status) to the guardian position.
    private Map<Integer, MCWZData.MCGuardianGenPos> redGuardianIdToPos = new HashMap<Integer, MCWZData.MCGuardianGenPos>();
    private Map<Integer, MCWZData.MCGuardianGenPos> blueGuardianIdToPos = new HashMap<Integer, MCWZData.MCGuardianGenPos>();
    // These map Reactor Object IDs to guardian objects.
    private Map<Integer, MCGuardian> redReactors = new HashMap<Integer, MCGuardian>();
    private Map<Integer, MCGuardian> blueReactors = new HashMap<Integer, MCGuardian>();
    // used for divided maps
    // we use an arraylist here for easier random element lookup.
    private List<MCWZData.MCGuardianGenPos> originalRedGuardianSpawns = new ArrayList<MCWZData.MCGuardianGenPos>();
    private List<MCWZData.MCGuardianGenPos> originalBlueGuardianSpawns = new ArrayList<MCWZData.MCGuardianGenPos>();
    // used for undivided map
    private List<MCWZData.MCGuardianGenPos> originalGuardianSpawns = new ArrayList<MCWZData.MCGuardianGenPos>();

    // used for divided maps
    // we use an arraylist here for easier random element lookup.
    private List<MCWZData.MCMobGenPos> originalRedSpawns = new ArrayList<MCWZData.MCMobGenPos>();
    private List<MCWZData.MCMobGenPos> originalBlueSpawns = new ArrayList<MCWZData.MCMobGenPos>();
    // use for undivided map
    private List<MCWZData.MCMobGenPos> originalUnifiedSpawns = new ArrayList<MCWZData.MCMobGenPos>();

    private List<SpawnPoint> originalSpawns = new ArrayList<SpawnPoint>();
    private List<SpawnPoint> addedSpawns = new ArrayList<SpawnPoint>();

    public MCBattlefield(MapleMap battleInstance) {
        this.map = battleInstance;
        fetchCarnivalData();
        getOriginalSpawnPoints();
        populateGuardianSpawns();
        populateMobSpawns();
    }

    private void fetchCarnivalData() {
        wzData = this.map.getMCPQData();
        if (wzData == null) {
            MCTracker.log("[MCPQ] Fetching carnival failed for map " + map.getId());
        }
    }

    private void getOriginalSpawnPoints() {
        for (SpawnPoint sp : this.map.getSpawnPoints()) {
            originalSpawns.add(sp);
        }
    }

    private void populateGuardianSpawns() {
        for (MCWZData.MCGuardianGenPos gpos : wzData.guardianGenPosList) {
            switch (gpos.team) {
                case 0:
                    originalRedGuardianSpawns.add(gpos);
                    break;
                case 1:
                    originalBlueGuardianSpawns.add(gpos);
                    break;
                default:
                    originalGuardianSpawns.add(gpos);
            }
        }
    }

    private void populateMobSpawns() {
        for (MCWZData.MCMobGenPos mpos : wzData.mobGenPosList) {
            switch (mpos.team) {
                case 0:
                    originalRedSpawns.add(mpos);
                    break;
                case 1:
                    originalBlueSpawns.add(mpos);
                    break;
                default:
                    originalUnifiedSpawns.add(mpos);
            }
        }
    }

    public void addSpawn(MapleCharacter chr, int num) {
        if (numMonstersSpawned > wzData.mobGenMax) {
            chr.getClient().announce(MonsterCarnivalPacket.CPQMessage(3));
            return;
        }

        MCWZData.MCSummonMob mobToSummon = wzData.summons.get(num);
        MCWZData.MCMobGenPos spawnPos = getRandomSpawnPos(chr.getMCPQTeam());

        MCField.MCTeam team = chr.getMCPQTeam();
        if (spawnPos == null) { // all positions used
            chr.getClient().announce(MonsterCarnivalPacket.CPQMessage(2));
            return;
        }

        int spendCp = mobToSummon.spendCP;
        if (spendCp > chr.getAvailableCP()) {
            readdSpawn(spawnPos, team);
            chr.getClient().announce(MonsterCarnivalPacket.CPQMessage(1));
            return;
        }

        chr.getMCPQField().loseCP(chr, spendCp);
        this.map.broadcastMessage(
                MonsterCarnivalPacket.CPQSummon(MonsterCarnival.TAB_SPAWNS, num, chr.getName()));
        numMonstersSpawned++; // TODO: AtomicInteger this

        MapleMonster monster = MapleLifeFactory.getMonster(mobToSummon.id);
        Point pos = new Point(spawnPos.x, spawnPos.y);
        SpawnPoint sp = new SpawnPoint(monster, pos, mobToSummon.mobTime, chr.getTeam());

        addedSpawns.add(sp);
        updateMonsterBuffs();
    }

    public void useSkill(MapleCharacter chr, int num) {
        if (!wzData.skills.containsKey(num)) {
            MCTracker.log("Attempting to use a null skill.");
            return;
        }
        int realSkill = wzData.skills.get(num);
        MCSkill skill = MCSkillFactory.getMCSkill(realSkill);
        // TODO: add skill cooldowns

        int spendCp = skill.getSpendCP();
        if (spendCp > chr.getAvailableCP()) {
            chr.getClient().announce(MonsterCarnivalPacket.CPQMessage(1));
            return;
        }

        MCParty teamToApply = chr.getMCPQParty().getEnemy();
        boolean success = teamToApply.applyMCSkill(skill);

        if (success) {
            chr.getMCPQField().loseCP(chr, spendCp);
            map.broadcastMessage(
                    MonsterCarnivalPacket.CPQSummon(MonsterCarnival.TAB_DEBUFF, num, chr.getName()));
        } else {
            chr.getClient().getSession().write(MonsterCarnivalPacket.CPQMessage(5));
        }
    }

    public void readdSpawn(MCWZData.MCMobGenPos pos, MCField.MCTeam team) {
        List<MCWZData.MCMobGenPos> lst = null;
        if (this.wzData.mapDivided) {
            if (team == MCField.MCTeam.RED) {
                lst = originalRedSpawns;
            } else if (team == MCField.MCTeam.BLUE) {
                lst = originalBlueSpawns;
            } else {
                return;
            }
        } else {
            lst = originalUnifiedSpawns;
        }

        if (lst == null) {
            return;
        }
        lst.add(pos);
    }

    /**
     * Spawns a guardian, buffing all monsters on the map.
     *   [MENTION=2000183830]para[/MENTION]m chr Character that summoned the guardian.
     *   [MENTION=2000183830]para[/MENTION]m num Which guardian was spawned (F1-F9).
     */
    public void spawnGuardian(MapleCharacter chr, int num) {
        if (numGuardiansSpawned > wzData.guardianGenMax) {
            chr.getClient().announce(MonsterCarnivalPacket.CPQMessage(3));
            return;
        }

        int guardianId = wzData.guardians.get(num);
        MCGuardian guardian = MCSkillFactory.getMCGuardian(guardianId);
        if (guardian == null) {
            MCTracker.log("Attempting to spawn invalid guardian.");
            return;
        }

        MCField.MCTeam team = chr.getMCPQTeam();
        if (team == MCField.MCTeam.RED) {
            if (redGuardianIdToPos.containsKey(guardianId)) {
                chr.getClient().announce(MonsterCarnivalPacket.CPQMessage(4));
                return;
            }
        } else if (team == MCField.MCTeam.BLUE) {
            if (blueGuardianIdToPos.containsKey(guardianId)) {
                chr.getClient().announce(MonsterCarnivalPacket.CPQMessage(4));
                return;
            }
        }
        int spendCp = guardian.getSpendCP();
        if (spendCp > chr.getAvailableCP()) {
            chr.getClient().announce(MonsterCarnivalPacket.CPQMessage(1));
            return;
        }

        chr.getMCPQField().loseCP(chr, spendCp);
        this.map.broadcastMessage(
                MonsterCarnivalPacket.CPQSummon(MonsterCarnival.TAB_GUARDIAN, num, chr.getName()));
        numGuardiansSpawned++; // TODO: AtomicInteger this
        MCWZData.MCGuardianGenPos genPos = getRandomGuardianPos(team);
        Point spawnPos = new Point(genPos.x, genPos.y);

        MapleReactor reactor;
        if (team == MCField.MCTeam.RED) {
            reactor = new MapleReactor(MapleReactorFactory.getReactor(MonsterCarnival.GUARDIAN_RED),
                    MonsterCarnival.GUARDIAN_RED);
            reactor.setPosition(spawnPos);

            redGuardianIdToPos.put(num, genPos);
        } else if (team == MCField.MCTeam.BLUE){
            reactor = new MapleReactor(MapleReactorFactory.getReactor(MonsterCarnival.GUARDIAN_BLUE),
                    MonsterCarnival.GUARDIAN_BLUE);
            reactor.setPosition(spawnPos);

            blueGuardianIdToPos.put(num, genPos);
        } else {
            return;
        }

        reactor.setDelay(-1); // do not respawn
        map.spawnReactor(reactor);

        // This must take place after spawning the reactor because it assigns
        // the reactor an object id.
        if (team == MCField.MCTeam.RED) {
            redReactors.put(reactor.getObjectId(), MCSkillFactory.getMCGuardian(num));
        } else {
            blueReactors.put(reactor.getObjectId(), MCSkillFactory.getMCGuardian(num));
        }

        map.setReactorState(reactor, (byte) 1); // trigger the reactor, make it visible -.-
        updateMonsterBuffs();
    }

    public void onGuardianHit(MapleCharacter chr, MapleReactor reactor) {
        if (MonsterCarnival.DEBUG) {
            System.out.println("STATE: " + reactor.getState());
        }
        MCField.MCTeam team = chr.getMCPQTeam();
        if (team == MCField.MCTeam.RED && reactor.getId() == MonsterCarnival.GUARDIAN_RED) {
            return;
        }
        if (team == MCField.MCTeam.BLUE && reactor.getId() == MonsterCarnival.GUARDIAN_BLUE) {
            return;
        }
        reactor.setState((byte) (reactor.getState() + 1));
        map.broadcastMessage(MaplePacketCreator.triggerReactor(reactor, reactor.getState()));

        if (reactor.getState() > 3) {
            int reactorObjId = reactor.getObjectId();
            map.destroyReactor(reactorObjId);

            MCGuardian guard;
            MCWZData.MCGuardianGenPos guardianGenPos;
            if (team == MCField.MCTeam.RED) {
                // if your team is red, you are attacking blue guardians
                guard = blueReactors.remove(reactorObjId);
                guardianGenPos = blueGuardianIdToPos.remove(guard.getType());
            } else {
                guard = redReactors.remove(reactorObjId);
                guardianGenPos = redGuardianIdToPos.remove(guard.getType());
            }
            numGuardiansSpawned--;

            if (MonsterCarnival.DEBUG) {
                System.out.println("Removing reactor with x = " + guardianGenPos.x);
            }
            if (wzData.mapDivided) {
                if (team == MCField.MCTeam.RED) {
                    // again, here, if you destroyed a blue guardian, then you are on the red team.
                    // it is important to note that team here refers to the character's team,
                    // not the reactor's.
                    originalBlueGuardianSpawns.add(guardianGenPos);
                } else {
                    originalRedGuardianSpawns.add(guardianGenPos);
                }
            } else {
                originalGuardianSpawns.add(guardianGenPos);
            }

            if (MonsterCarnival.DEBUG) {
                System.out.println("Attempting to remove buff " + guard.getName());
            }
            updateMonsterBuffs();
        }
    }

    /**
     * Gets a guardian position for a team. That is, if the player spawning this guardian is on the
     * red team, a position corresponding to somewhere on the blue team's side will be returned.
     * This clarification does not matter for undivided maps.
     *
     *   [MENTION=2000183830]para[/MENTION]m team Team that is summoning the guardian, and also the "team" value in the WZs.
     *   [MENTION=850422]return[/MENTION] A random generated position.
     */
    private MCWZData.MCGuardianGenPos getRandomGuardianPos(MCField.MCTeam team) {
        if (this.wzData.mapDivided) {
            if (team == MCField.MCTeam.RED) {
                int randIndex = (int) Math.floor(Math.random() * this.originalRedGuardianSpawns.size());
                return originalRedGuardianSpawns.remove(randIndex);
            } else if (team == MCField.MCTeam.BLUE) {
                int randIndex = (int) Math.floor(Math.random() * this.originalBlueGuardianSpawns.size());
                return originalBlueGuardianSpawns.remove(randIndex);
            } else {
                return null;
            }
        } else {
            int randIndex = (int) Math.floor(Math.random() * this.originalGuardianSpawns.size());
            return originalGuardianSpawns.remove(randIndex);
        }
    }

    /**
     * Gets a guardian position for a team. That is, if the player spawning this guardian is on the
     * red team, a position corresponding to somewhere on the blue team's side will be returned.
     * This clarification does not matter for undivided maps.
     *
     *   [MENTION=2000183830]para[/MENTION]m team Team that is summoning the guardian, and also the "team" value in the WZs.
     *   [MENTION=850422]return[/MENTION] A random generated position.
     */
    private MCWZData.MCMobGenPos getRandomSpawnPos(MCField.MCTeam team) {
        List<MCWZData.MCMobGenPos> lst = null;
        if (this.wzData.mapDivided) {
            if (team == MCField.MCTeam.RED) {
                lst = originalRedSpawns;
            } else if (team == MCField.MCTeam.BLUE) {
                lst = originalBlueSpawns;
            } else {
                return null;
            }
        } else {
            lst = originalUnifiedSpawns;
        }

        if (lst == null) {
            return null;
        }
        if (lst.size() == 0) {
            return null;
        }
        int randIndex = (int) Math.floor(Math.random() * lst.size());
        return lst.remove(randIndex);
    }

    private void updateMonsterBuffs() {
        List<MCGuardian> redGuardians = new ArrayList<MCGuardian>();
        List<MCGuardian> blueGuardians = new ArrayList<MCGuardian>();

        for (MCGuardian g : this.redReactors.values()) {
            redGuardians.add(g);
            if (MonsterCarnival.DEBUG) {
                System.out.println("update buff red " + g.getMobSkillID());
            }
        }
        for (MCGuardian g : this.blueReactors.values()) {
            blueGuardians.add(g);
            if (MonsterCarnival.DEBUG) {
                System.out.println("update buff blue " + g.getMobSkillID());
            }
        }

        for (MapleMapObject mmo : map.getAllMonsters()) {
            if (mmo.getType() == MapleMapObjectType.MONSTER) {
                MapleMonster mob = ((MapleMonster) mmo);
                mob.dispel();

                if (mob.getTeam() == MCField.MCTeam.RED.code) {
                    applyGuardians(mob, redGuardians);
                } else if (mob.getTeam() == MCField.MCTeam.BLUE.code) {
                    applyGuardians(mob, blueGuardians);
                } else {
                    MCTracker.log("[MCPQ] Attempting to give guardians to mob without team.");
                }
            }
        }
    }

    private void giveMonsterBuffs(MapleMonster mob) {
        List<MCGuardian> redGuardians = new ArrayList<MCGuardian>();
        List<MCGuardian> blueGuardians = new ArrayList<MCGuardian>();

        for (MCGuardian g : this.redReactors.values()) {
            redGuardians.add(g);
            if (MonsterCarnival.DEBUG) {
                System.out.println("update buff red " + g.getMobSkillID());
            }
        }
        for (MCGuardian g : this.blueReactors.values()) {
            blueGuardians.add(g);
            if (MonsterCarnival.DEBUG) {
                System.out.println("update buff blue " + g.getMobSkillID());
            }
        }

        if (mob.getTeam() == MCField.MCTeam.RED.code) {
            applyGuardians(mob, redGuardians);
        } else if (mob.getTeam() == MCField.MCTeam.BLUE.code) {
            applyGuardians(mob, blueGuardians);
        } else {
            MCTracker.log("[MCPQ] Attempting to give guardians to mob without team.");
        }
    }

    private void applyGuardians(MapleMonster mob, List<MCGuardian> guardians) {
        for (MCGuardian g : guardians) {
            MobSkill sk = MobSkillFactory.getMobSkill(g.getMobSkillID(), g.getLevel());
            sk.applyEffect(null, mob, true);
        }
    }

    public void spawningTask() {
        for (SpawnPoint sp : originalSpawns) {
            if (sp.shouldSpawn()) {
                MapleMonster mob = sp.spawnMonster(this.map);
                giveMonsterBuffs(mob);
            }
        }
        for (SpawnPoint sp : addedSpawns) {
            if (sp.shouldSpawn()) {
                MapleMonster mob = sp.spawnMonster(this.map);
                giveMonsterBuffs(mob);
            }
        }

    } }


MCField.java


package net.sf.odinms.server.partyquest;
import net.sf.odinms.client.MapleCharacter;
import net.sf.odinms.net.MaplePacket;
import net.sf.odinms.net.channel.ChannelServer;
import net.sf.odinms.server.MapleItemInformationProvider;
import net.sf.odinms.server.MaplePortal;
import net.sf.odinms.server.MapleStatEffect;
import net.sf.odinms.server.TimerManager;
import net.sf.odinms.server.maps.MapleMap;
import net.sf.odinms.server.maps.MapleMapItem;
import net.sf.odinms.server.maps.MapleReactor;
import net.sf.odinms.tools.MaplePacketCreator;
import net.sf.odinms.tools.MonsterCarnivalPacket;

import java.util.*;
import java.util.concurrent.ScheduledFuture;

/**
 * Keeps track of a specific field (1-6). Handles all packet broadcasting, etc.
 * @author s4nta
 */
public class MCField {

    /**
     * Different teams for MCPQ.
     */
    public enum MCTeam {
        RED(0),
        BLUE(1),
        NONE(-1);
        public final int code;

        MCTeam(int code) {
            this.code = code;
        }

        public final int getEnemyTeamCode() {
            return Math.abs(this.code - 1);
        }
    }

    /**
     * Represents the current state of the field.
     */
    public enum MCState {
        LOBBY,
        BATTLE,
        END;
    }

    /**
     * Keys to access different map instances relating to this field.
     */
    public enum MCMaps {
        LOBBY(0),
        BATTLEFIELD(1),
        RESURRECT(2),
        VICTORY(3),
        DEFEAT(4),
        NONE(-1);
        private final int code;

        MCMaps(int code) {
            this.code = code;
        }

        public static MCMaps getByCode(int code) {
            for (MCMaps m : values()) {
                if (m.code == code) {
                    return m;
                }
            }
            return NONE;
        }
    }

    private int arena;
    private ChannelServer cserv;
    private MCParty red, blue;
    private List<MCParty> requests = new ArrayList<MCParty>();
    private MCState state;
    private Map<MCMaps, MapleMap> mapInstances = new HashMap<MCMaps, MapleMap>();
    private long startTime;
    private MCBattlefield battlefield;

    // Timer Tasks
    private ScheduledFuture<?> acceptRequestsTask, validateRoomTask, startBattleTask,
            validateBattleTask, runBattleTask, endBattleTask,
            spawnMonstersTask;

    public MCField(int arena, ChannelServer cserv, MCParty red, MCParty blue) {
        this.arena = arena;
        this.cserv = cserv;
        this.red = red;
        this.blue = blue;
        this.state = MCState.LOBBY;
    }

    public boolean isFull() {
        if (MonsterCarnival.DEBUG) {
            return false;
        }
        return this.red != null && this.blue != null;
    }

    public boolean needsRequest() {
        return this.red != null && this.blue == null;
    }

    /**
     * Resets the state of the field, warping out players and resetting tasks.
     *   [MENTION=2000183830]para[/MENTION]m warpPlayers Warp out players or not
     */
    public void deregister(boolean warpPlayers) {
        if (warpPlayers) {
            if (this.red != null) {
                this.red.deregisterPlayers();
            }
            if (this.blue != null) {
                this.blue.deregisterPlayers();
            }
        }


        this.red = null;
        this.blue = null;
        this.requests.clear();
        this.state = MCState.LOBBY;

        if (this.acceptRequestsTask != null) {
            this.acceptRequestsTask.cancel(true);
            this.acceptRequestsTask = null;
        }

        if (this.validateRoomTask != null) {
            this.validateRoomTask.cancel(true);
            this.validateRoomTask = null;
        }

        if (this.startBattleTask != null) {
            this.startBattleTask.cancel(true);
            this.startBattleTask = null;
        }

        if (this.endBattleTask != null) {
            this.endBattleTask.cancel(true);
            this.endBattleTask = null;
        }

        if (this.runBattleTask != null) {
            this.runBattleTask.cancel(true);
            this.runBattleTask = null;
        }

        if (this.validateBattleTask != null) {
            this.validateBattleTask.cancel(true);
            this.validateBattleTask = null;
        }

        if (this.spawnMonstersTask != null) {
            this.spawnMonstersTask.cancel(true);
            this.spawnMonstersTask = null;
        }

        for (MCMaps mapType : MCMaps.values()) {
            this.mapInstances.remove(mapType);
        }
    }

    public MCParty getRed() {
        return red;
    }

    public MCParty getBlue() {
        return blue;
    }

    public void announce(MaplePacket pkt) {
        if (this.red != null) {
            red.broadcast(pkt);
        } else {
            MCTracker.log("[MCPQ] Trying to announce packet to red when it is null.");
        }

        if (this.blue != null) {
            blue.broadcast(pkt);
        } else {
            MCTracker.log("[MCPQ] Trying to announce packet to blue when it is null.");
        }
    }

    /**
     * Gets a string representing the status of this room for the Spiegelmann NPC.
     *   [MENTION=850422]return[/MENTION] String representing the room's status.
     */
    public String getStatus() {
        if (isFull()) {
            return "";
        }
        if (this.state != MCState.LOBBY) {
            return "";
        }
        String waitingParty = "";
        if (this.red != null) {
            String fmt = " <隊伍人數: %d, 平均等級: %d>";
            waitingParty = String.format(fmt, this.red.getSize(), this.red.getAverageLevel());
        }
        String fmt = "#L%d#怪物擂台戰場 %d%s#l\r\n";
        return String.format(fmt, this.arena, this.arena, waitingParty);
    }

    /**
     * Attempts to register a party in this field. If success, then all players in the party
     * will be warped to the waiting lobby. All players in the party will also have relevant
     * CPQ information assigned to them.
     *
     *   [MENTION=2000183830]para[/MENTION]m party The party to register.
     */
    public void register(MCParty party, MCTeam team) {
        if (this.red == null && team == MCTeam.RED) {
            party.setTeam(team);
            this.red = party;
        } else if (this.blue == null && team == MCTeam.BLUE) {
            party.setTeam(team);
            this.blue = party;
        } else {
            MCTracker.log("Attempting to register party when team is already set.");
            return;
        }
        party.setField(this);
        party.updatePlayers();
        party.warp(MCMaps.LOBBY);
        onPartyRegistered(party, team);
    }

    /**
     * Sends a challenge to the party in this field.
     *
     *   [MENTION=2000183830]para[/MENTION]m party The party requesting a challenge.
     */
    public void request(MCParty party) {
        if (this.red == null) {
            MCTracker.log("Attempting to request when waiting team is null.");
            this.deregister(true);
            return;
        }
        requests.add(party);
        this.red.notice("【怪物擂台訊息】已經有一個隊伍前來挑戰,請點選助手查看");
    }

    /**
     * Accepts a challenge from a team.
     *   [MENTION=2000183830]para[/MENTION]m index Index of team in requests.
     *   [MENTION=850422]return[/MENTION] 1 if the challenge was accepted successfully, 0 otherwise.
     */
    public int acceptRequest(int index) {
        MCParty toAccept = this.requests.get(index);
        register(toAccept, MCTeam.BLUE);
        return 1;
    }

    /**
     * Checks for pending requests.
     *
     *   [MENTION=850422]return[/MENTION] True if there are pending requests.
     */
    public boolean hasPendingRequests() {
        return requests.size() > 0;
    }

    /**
     * Gets a pending list of requests, formatted for use in a NPC. Also cleans up the requests,
     * getting rid of MCParties that no longer exist.
     *
     *   [MENTION=850422]return[/MENTION] Formatted list of requests for NPC.
     */
    public String getNPCRequestString() {
        StringBuilder sb = new StringBuilder("Here is the list of pending requests:\r\n\r\n#b");
        for (MCParty pty : requests) {
            if (!pty.exists()) {
                continue;
            }
            sb.append("#L").append(requests.indexOf(pty)).append("#");
            String fmt = "<隊伍人數: %d, 平均等級: %d>";
            fmt = String.format(fmt, pty.getSize(), pty.getAverageLevel());
            sb.append(fmt);
            sb.append("#l\r\n");
        }
        return sb.toString();
    }

    /**
     * Starts a R3 minute waiting timer in the lobby for teams to accept requests.
     * If the timer completes the countdown, teams are deregistered and sent back to the lobby.
     */
    private void startLobbyTask(MCParty host) {
        host.clock(MonsterCarnival.TIME_LOBBYWAIT);

        this.acceptRequestsTask = TimerManager.getInstance().schedule(
                new AcceptingRequestsTask(this, host),
                1000 * MonsterCarnival.TIME_LOBBYWAIT); // 3 minutes
        this.validateRoomTask = TimerManager.getInstance().register(
                new ValidateLobbyTask(this),
                1000, 1000); // repeat every second
    }

    /**
     * Event handling for when a team registers.
     *
     *   [MENTION=2000183830]para[/MENTION]m party Party that registers.
     *   [MENTION=2000183830]para[/MENTION]m team Team of party.
     */
    private void onPartyRegistered(MCParty party, MCTeam team) {
        if (team == MCTeam.RED) {
            startLobbyTask(party);
        }
        if (team == MCTeam.BLUE) { // both teams are in
            this.validateRoomTask.cancel(true);
            this.acceptRequestsTask.cancel(true);
            blue.clock(10);
            red.clock(10);

            this.startBattleTask = TimerManager.getInstance().schedule(
                    new GoBattlefieldTask(this), 1000 * 10); // 10 seconds

            red.notice("【怪物擂台訊息】怪物擂台比賽將在10秒內開始");
            blue.notice("【怪物擂台訊息】怪物擂台比賽將在10秒內開始");
        }
    }

    /**
     * Warps both parties in the field to the battlefield map.
     */
    private void goBattle() {
        MapleMap map = getMap(MCMaps.BATTLEFIELD);
        if (MonsterCarnival.DEBUG)
            System.out.println("warping to battle " + map + " " + map.getId());
        if (red != null) {
            red.warp(map, "red00");
        } else {
            MCTracker.log("[MCPQ] Trying to warp red party when it is null.");
        }

        if (blue != null) {
            blue.warp(map, "blue00");
        } else {
            MCTracker.log("[MCPQ] Trying to warp blue party when it is null.");
        }

        red.clock(MonsterCarnival.TIME_PREBATTLE);
        blue.clock(MonsterCarnival.TIME_PREBATTLE);

        red.notice("【怪物擂台訊息】怪物擂台將在10秒內開始");
        blue.notice("【怪物擂台訊息】怪物擂台將在10秒內開始");

        startBattleTask = TimerManager.getInstance().schedule(new BeginCarnivalTask(this),
                1000 * MonsterCarnival.TIME_PREBATTLE); // 10 seconds

        validateBattleTask = TimerManager.getInstance().register(new ValidateBattlefieldTask(this),
                1000, 500); // check every second

        battlefield = new MCBattlefield(getMap(MCMaps.BATTLEFIELD));

        red.setEnemy(blue);
        blue.setEnemy(red);
    }

    private void beginCarnival() {
        red.clock(MonsterCarnival.TIME_BATTLE);
        blue.clock(MonsterCarnival.TIME_BATTLE);
        startTime = System.currentTimeMillis();

        getMap(MCMaps.BATTLEFIELD).broadcastMessage(
                MaplePacketCreator.serverNotice(6, "您將有十分鐘的時間進行戰鬥"));

        endBattleTask = TimerManager.getInstance().schedule(new EndBattleTask(this),
                1000 * MonsterCarnival.TIME_BATTLE);
        spawnMonstersTask = TimerManager.getInstance().register(new SpawnTask(this.battlefield),
                1000 * 5);

    }

    public void endBattle(MCParty winner, MCParty loser) {
        endBattle(winner, loser, false);
    }

    public void endBattle(MCParty winner, MCParty loser, boolean abnormal) {
        // TODO: Abnormal win codes to prevent exploits

        validateBattleTask.cancel(true);
        spawnMonstersTask.cancel(true);

        MCWZData cpqData = this.getMap(MCMaps.BATTLEFIELD).getMCPQData();
        String effectWin = cpqData.effectWin;
        String effectLose = cpqData.effectLose;
        String soundWin = cpqData.soundWin;
        String soundLose = cpqData.soundLose;

        winner.broadcast(MaplePacketCreator.showEffect(effectWin));
        winner.broadcast(MaplePacketCreator.playSound(soundWin));
        loser.broadcast(MaplePacketCreator.showEffect(effectLose));
        loser.broadcast(MaplePacketCreator.playSound(soundLose));

        this.getMap(MCMaps.BATTLEFIELD).killAllMonsters(false);
        this.getMap(MCMaps.BATTLEFIELD).clearDrops();
        this.deregister(false);

        TimerManager.getInstance().schedule(
                new WarpEndBattleTask(this, winner, loser),
                1000 * 3);
    }

    /**
     * Handles CP gain and packet updates when a monster is killed.
     *   [MENTION=2000183830]para[/MENTION]m chr Character that kills the monster.
     *   [MENTION=2000183830]para[/MENTION]m cp CP gained.
     */
    public void monsterKilled(MapleCharacter chr, int cp) {
        if (MonsterCarnival.DEBUG) {
            // System.out.println(chr.getName() + " killed for +" + cp + " CP");
        }
        // TODO: Personal stats for CP gain
        this.gainCP(chr, cp);
    }

    /**
     * Handles game logic and packet broadcasting for CP gain.
     * Broadcasts personal CP update to chr, and broadcasts party CP update
     * to the entire field.
     *
     *   [MENTION=2000183830]para[/MENTION]m chr Character that gains CP.
     *   [MENTION=2000183830]para[/MENTION]m cp CP gained.
     */
    public void gainCP(MapleCharacter chr, int cp) {
        if (cp < 0) {
            MCTracker.log("[MCPQ] Adding negative CP.");
            if (MonsterCarnival.DEBUG) {
                System.out.println("Adding negative CP: stacktrace");
                new Exception().printStackTrace();
            }
        }
        MCParty pty = chr.getMCPQParty();
        chr.gainCP(cp);
        pty.gainCP(cp);
        chr.getClient().announce(MonsterCarnivalPacket.updatePersonalCP(chr));
        this.announce(MonsterCarnivalPacket.updatePartyCP(pty));
    }

    /**
     * Subtracts from available CP while leaving total CP untouched.
     *
     *   [MENTION=2000183830]para[/MENTION]m chr Character that loses CP.
     *   [MENTION=2000183830]para[/MENTION]m cp CP lost (should be positive number).
     */
    public void loseCP(MapleCharacter chr, int cp) {
        if (cp < 0) {
            MCTracker.log("[MCPQ] Losing negative CP.");
            if (MonsterCarnival.DEBUG) {
                System.out.println("Adding negative CP: stacktrace");
                new Exception().printStackTrace();
            }
        }
        MCParty pty = chr.getMCPQParty();
        chr.loseCP(cp);
        pty.loseCP(cp);
        chr.getClient().announce(MonsterCarnivalPacket.updatePersonalCP(chr));
        this.announce(MonsterCarnivalPacket.updatePartyCP(pty));
    }

    /**
     * Handles a player looting an item.
     *   [MENTION=2000183830]para[/MENTION]m player Player that picked up the object.
     *   [MENTION=2000183830]para[/MENTION]m mapitem Object picked up.
     *
     *   [MENTION=850422]return[/MENTION] True if pickup was successful, false otherwise.
     */
    public boolean onItemPickup(MapleCharacter player, MapleMapItem mapitem) {
        if (mapitem == null) {
            MCTracker.log("[MCPQ] Attempting to loot null object.");
            return false;
        }
        int itemid = mapitem.getItem().getItemId();
        if (!MonsterCarnival.isCPQConsumeItem(itemid)) {
            return false;
        }
        MCParty pty = player.getMCPQParty();
        MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
        MapleStatEffect itemEffect = ii.getItemEffect(itemid);
        if (!itemEffect.isConsumeOnPickup()) {
            return false;
        }

        if (itemEffect.isParty()) {
            for (MapleCharacter chr : pty.getMembers()) {
                if (chr.getHp() > 0) {
                    itemEffect.applyTo(chr);
                }
            }
        } else { // Single Target Item
            itemEffect.applyTo(player);
        }
        // Status items
        if (itemEffect.getNuffSkill() != -1) {
            MCSkill debuff = MCSkillFactory.getMCSkill(itemEffect.getNuffSkill());
            if (debuff == null) {
                MCTracker.log("[MCPQ] debuff skill is null " + itemEffect.getNuffSkill());
                return false;
            }

            pty.getEnemy().applyMCSkill(debuff);
        }

        if (itemEffect.getCP() > 0) {
            this.gainCP(player, itemEffect.getCP());
        }

        return true;
    }

    public void onPlayerRespawn(MapleCharacter player) {
        int cpLoss = Math.min(player.getAvailableCP(), MonsterCarnival.CP_LOSS_ON_DEATH);
        this.announce(MonsterCarnivalPacket.CPQDied(player, cpLoss));
        this.loseCP(player, cpLoss);
        player.addMPHP(30000, 30000);
        player.changeMap(this.getMap(MCMaps.RESURRECT), this.getMap(MCMaps.RESURRECT).getPortal(0));
        player.getClient().getSession().write(MaplePacketCreator.getClock(getTimeRemaining()));
        player.getClient().getSession().write(MonsterCarnivalPacket.startCPQ(player));
    }

    public void onPlayerDisconnected(MapleCharacter player) {
        MCParty pty = player.getMCPQParty();
        if (pty != null) {
            pty.removePlayer(player);
        } else {
            MCTracker.log("[MCPQ] Attempting to run player disconnect event when party is null for character " + player.getName());
        }
    }

    public void onAddSpawn(MapleCharacter chr, int num) {
        if (this.battlefield != null) {
            battlefield.addSpawn(chr, num);
        } else {
            MCTracker.log("[MCPQ] Summoning guardian with null battlefield.");
        }
    }

    public void onUseSkill(MapleCharacter chr, int num) {
        if (this.battlefield != null) {
            battlefield.useSkill(chr, num);
        } else {
            MCTracker.log("[MCPQ] Summoning guardian with null battlefield.");
        }
    }

    public void onGuardianSummon(MapleCharacter chr, int num) {
        if (this.battlefield != null) {
            battlefield.spawnGuardian(chr, num);
        } else {
            MCTracker.log("[MCPQ] Summoning guardian with null battlefield.");
        }
    }

    public void onGuardianHit(MapleCharacter chr, MapleReactor reactor) {
        if (this.battlefield != null) {
            battlefield.onGuardianHit(chr, reactor);
        } else {
            MCTracker.log("[MCPQ] Hitting reactor with null battlefield.");
        }
    }

    public void onRevive(MapleCharacter player) {
        MCTeam team = player.getMCPQTeam();
        MaplePortal portal;
        if (team == MCTeam.RED) {
            portal = getMap(MCMaps.BATTLEFIELD).getPortal("紅隊_復活");
        } else {
            portal = getMap(MCMaps.BATTLEFIELD).getPortal("藍隊_復活");
        }

        player.changeMap(getMap(MCMaps.BATTLEFIELD), portal);
        player.getClient().getSession().write(MaplePacketCreator.getClock(getTimeRemaining()));
        player.getClient().getSession().write(MonsterCarnivalPacket.startCPQ(player));
    }

    public int getTimeRemaining() {
        // TODO: add support for setting an explicit endTime instead of using the hack with MonsterCarnival variables
        return (int) ((startTime + 1000 * MonsterCarnival.TIME_BATTLE) - System.currentTimeMillis()) / 1000;
    }


    // Map Instances

    /**
     * Returns the map instance for a requested map. Creates a new map instance if unavailable.
     *   [MENTION=2000183830]para[/MENTION]m type Map instance to return.
     *   [MENTION=850422]return[/MENTION] The instanced map.
     */
    public MapleMap getMap(MCMaps type) {
        if (this.mapInstances.containsKey(type)) {
            return this.mapInstances.get(type);
        }
        return createInstanceMap(type);
    }

    /**
     * Attempts to create an instanced map, based on the type passed in. Also creates a mapping in
     * this.mapInstances.
     *
     *   [MENTION=2000183830]para[/MENTION]m type Type of map to generate.
     *   [MENTION=850422]return[/MENTION] MapleMap for the instanced map if type is supported, otherwise null.
     */
    private MapleMap createInstanceMap(MCMaps type) {
        int mapid = -1;
        switch (type) {
            case LOBBY:
                mapid = MonsterCarnival.getLobbyMap(this.arena);
                break;
            case BATTLEFIELD:
                mapid = MonsterCarnival.getBattleFieldMap(this.arena);
                break;
            case RESURRECT:
                mapid = MonsterCarnival.getResurrectionMap(this.arena);
                break;
            case VICTORY:
                mapid = MonsterCarnival.getVictoriousMap(this.arena);
                break;
            case DEFEAT:
                mapid = MonsterCarnival.getDefeatedMap(this.arena);
                break;
        }
        if (mapid == -1) return null;
        MapleMap mapInstance = this.cserv.getMapFactory().instanceMap(mapid, true, true);
        this.mapInstances.put(type, mapInstance);
        return mapInstance;
    }

    // Timer Tasks

    public class ValidateLobbyTask implements Runnable {

        private final MCField field;

        /**
         * Timer task to ensure all players are on the right field.
         * If anything is wrong with the parties, the field is deregistered.
         *   [MENTION=2000183830]para[/MENTION]m field Field to run the validation task on.
         */
        public ValidateLobbyTask(MCField field) {
            this.field = field;
        }

        @Override
        public void run() {
            if (this.field.red == null) {
                this.field.deregister(true);
                return;
            }
            for (MapleCharacter c : field.red.getMembers()) {
                if (c.getMap() != field.getMap(MCMaps.LOBBY)) {
                    this.field.deregister(true);
                    return;
                }
            }
            if (this.field.blue != null) {
                for (MapleCharacter c : field.blue.getMembers()) {
                    if (c.getMap() != field.getMap(MCMaps.LOBBY)) {
                        this.field.deregister(true);
                        return;
                    }
                }
            }
        }
    }

    public class ValidateBattlefieldTask implements Runnable {

        private final MCField field;

        /**
         * Timer task to ensure all players are on the right field.
         * If anything is wrong with the parties, the field is deregistered.
         *   [MENTION=2000183830]para[/MENTION]m field Field to run the validation task on.
         */
        public ValidateBattlefieldTask(MCField field) {
            this.field = field;
        }

        @Override
        public void run() {
            if (this.field.red == null || field.red.getSize() == 0) {
                MCTracker.log("[MCPQ] Red team null when validating battlefield");
                field.endBattle(blue, red);
                return;
            }
            Collection<MapleCharacter> members = Collections.unmodifiableCollection(field.red.getMembers());
            for (MapleCharacter c : members) {
                if (c.getMap() != field.getMap(MCMaps.BATTLEFIELD) &&
                        c.getMap() != field.getMap(MCMaps.RESURRECT)) {
                    this.field.announce(MonsterCarnivalPacket.leaveCPQ(MCTeam.RED.code, c.getName()));
                    red.removePlayer(c); // TODO: fix concurrent modification
                }
                if (c.getMap() == field.getMap(MCMaps.BATTLEFIELD) && c.isDead()) {
                    this.field.onPlayerRespawn(c);
                }
            }
            if (this.field.blue == null || field.blue.getSize() == 0) {
                MCTracker.log("[MCPQ] Blue team null when validating battlefield");
                field.endBattle(red, blue);
                return;
            }
            members = Collections.unmodifiableCollection(field.blue.getMembers());
            for (MapleCharacter c : members) {
                if (c.getMap() != field.getMap(MCMaps.BATTLEFIELD) &&
                        c.getMap() != field.getMap(MCMaps.RESURRECT)) {
                    this.field.announce(MonsterCarnivalPacket.leaveCPQ(MCTeam.BLUE.code, c.getName()));
                    blue.removePlayer(c);
                }
                if (c.getMap() == field.getMap(MCMaps.BATTLEFIELD) && c.isDead()) {
                    this.field.onPlayerRespawn(c);
                }
            }
        }
    }

    public class AcceptingRequestsTask implements Runnable {

        private final MCField field;
        private final MCParty host;

        /**
         * Runs a task that counts down for 3 minutes, then warps the hosting party out.
         *
         *   [MENTION=2000183830]para[/MENTION]m field Field to accept requests on.
         *   [MENTION=2000183830]para[/MENTION]m host Hosting party that will be warped out if they do not accept a request
         *             within 3 minutes.
         */
        public AcceptingRequestsTask(MCField field, MCParty host) {
            this.field = field;
            this.host = host;
        }

        @Override
        public void run() {
            Collection<MapleCharacter> chrs = this.host.getMembers();
            for (MapleCharacter c : chrs) {
                c.changeMap(MonsterCarnival.MAP_LOBBY);
            }
            this.field.deregister(true);
        }
    }

    public class GoBattlefieldTask implements Runnable {

        private final MCField field;

        public GoBattlefieldTask(MCField field) {
            this.field = field;
        }

        @Override
        public void run() {
            field.goBattle();

            field.red.startBattle();
            field.blue.startBattle();

            field.state = MCState.BATTLE;
        }
    }

    public class BeginCarnivalTask implements Runnable {

        private final MCField field;

        public BeginCarnivalTask(MCField field) {
            this.field = field;
        }

        @Override
        public void run() {
            MapleMap map = field.getMap(MCMaps.BATTLEFIELD);
            map.beginSpawning();
            field.beginCarnival();
        }
    }

    /* I have no idea why this doesn't work normally :/ */
    public class SpawnTask implements Runnable {

        private final MCBattlefield battleMap;

        public SpawnTask(MCBattlefield field) {
            this.battleMap = field;
        }

        @Override
        public void run() {
            // TODO: adjust spawn rates based on cp
            battleMap.spawningTask();
        }
    }

    public class EndBattleTask implements Runnable {
        private final MCField field;

        public EndBattleTask(MCField field) {
            this.field = field;
        }

        @Override
        public void run() {
            MCParty winner, loser;
            if (field.red.getTotalCP() > field.blue.getTotalCP()) {
                winner = field.red;
                loser = field.blue;
            } else if (field.red.getTotalCP() < field.blue.getTotalCP()) {
                winner = field.blue;
                loser = field.red;
            } else {
                // if tied: random chance
                // TODO: proper extension of time
                if (Math.random() < .5) {
                    winner = field.red;
                    loser = field.blue;
                } else {
                    winner = field.blue;
                    loser = field.red;
                }
            }

            field.state = MCState.END;
            field.endBattle(winner, loser);
        }
    }

    public class WarpEndBattleTask implements Runnable {

        private final MCField field;
        private final MCParty winner, loser;

        public WarpEndBattleTask(MCField field, MCParty winner, MCParty loser) {
            this.field = field;
            this.winner = winner;
            this.loser = loser;
        }

        @Override
        public void run() {
            winner.warp(field.getMap(MCMaps.VICTORY));
            loser.warp(field.getMap(MCMaps.DEFEAT));
        }

    } }


MCGuardian.java



package net.sf.odinms.server.partyquest;
/**
 * Object representing MCGuardian in Skill.wz/MCGuardian.img.
 *
 * @author s4nta
 */
public class MCGuardian {
    private final int type, spendCP, mobSkillID, level;
    private String name, desc;

    public MCGuardian(int type, int spendCP, int mobSkillID, int level) {
        this.type = type;
        this.mobSkillID = mobSkillID;
        this.level = level;
        this.spendCP = spendCP;
    }

    public int getType() {
        return type;
    }

    public int getSpendCP() {
        return spendCP;
    }

    public int getMobSkillID() {
        return mobSkillID;
    }

    public int getLevel() {
        return level;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

}


MCParty.java


package net.sf.odinms.server.partyquest;
import net.sf.odinms.client.MapleCharacter;
import net.sf.odinms.client.MapleDisease;
import net.sf.odinms.net.MaplePacket;
import net.sf.odinms.net.channel.ChannelServer;
import net.sf.odinms.net.world.MapleParty;
import net.sf.odinms.net.world.MaplePartyCharacter;
import net.sf.odinms.server.life.MobSkill;
import net.sf.odinms.server.life.MobSkillFactory;
import net.sf.odinms.server.maps.MapleMap;
import net.sf.odinms.tools.MaplePacketCreator;
import net.sf.odinms.tools.MonsterCarnivalPacket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import net.sf.odinms.server.partyquest.MCField.MCTeam;

/**
 * Provides an interface for Monster Carnival-specific party methods and variables.
 *
 * @author s4nta
 */
public class MCParty {

    private MapleParty party;
    private List<MapleCharacter> characters = new ArrayList<MapleCharacter>();
    private int availCP = 0;
    private int totalCP = 0;
    private MCField.MCTeam team = MCField.MCTeam.NONE;
    private MCField field;
    private MCParty enemy;

    public MCParty(MapleParty party) {
        this.party = party;
        for (MaplePartyCharacter chr : party.getMembers()) {
            if (!chr.isOnline()) continue;
            MapleCharacter c = ChannelServer.getInstance(chr.getChannel()).getPlayerStorage().getCharacterById(chr.getId());

            characters.add(c);
        }
    }

    public int getSize() {
        return this.characters.size();
    }

    /**
     * Checks if the underlying MapleParty still exists in the same way it did when it was created.
     * That is, if there were no players who left the party.
     *
     * [MENTION=850422]return[/MENTION] True if the underlying MapleParty still exists in its original format.
     */
    public boolean exists() {
        Collection<MapleCharacter> members = getMembers();
        for (MapleCharacter chr : members) {
            if (chr.getParty() == null || chr.getParty() != this.party) {
                return false;
            }
        }
        return true;
    }

    public int getAverageLevel() {
        int sum = 0, num = 0;
        for (MapleCharacter chr : getMembers()) {
            sum += chr.getLevel();
            num += 1;
        }
        return sum / num;
    }

    public boolean checkLevels() {
        if (MonsterCarnival.DEBUG) {
            return true;
        }
        for (MapleCharacter chr : getMembers()) {
            int lv = chr.getLevel();
            if (lv < MonsterCarnival.MIN_LEVEL || lv > MonsterCarnival.MAX_LEVEL) {
                return false;
            }
        }
        return true;
    }

    public boolean checkChannels() {
        if (MonsterCarnival.DEBUG) {
            return true;
        }
        for (MapleCharacter chr : getMembers()) {
            if (chr.getClient().getChannel() != party.getLeader().getChannel()) return false;
        }
        return true;
    }

    public boolean checkMaps() {
        if (MonsterCarnival.DEBUG) {
            return true;
        }
        for (MapleCharacter chr : getMembers()) {
            if (chr.getMapId() != MonsterCarnival.MAP_LOBBY) return false;
        }
        return true;
    }

    public void warp(int map) {
        for (MapleCharacter chr : getMembers()) {
            chr.changeMap(map);
        }
    }

    public void warp(MapleMap map) {
        for (MapleCharacter chr : getMembers()) {
            chr.changeMap(map, map.getPortal(0));
        }
    }

    public void warp(MapleMap map, String portal) {
        for (MapleCharacter chr : getMembers()) {
            chr.changeMap(map, map.getPortal(portal));
        }
    }

    public void warp(MCField.MCMaps type) {
        MapleMap m = this.field.getMap(type);
        for (MapleCharacter chr : getMembers()) {
            chr.changeMap(m, m.getPortal(0));
        }
    }

    public void clock(int secs) {
        for (MapleCharacter chr : getMembers()) {
            chr.getClient().announce(MaplePacketCreator.getClock(secs));
        }
    }

    public void notice(String msg) {
        broadcast(MaplePacketCreator.serverNotice(6, msg));
    }

    public void broadcast(MaplePacket pkt) {
        for (MapleCharacter chr : getMembers()) {
            chr.getClient().announce(pkt);
        }
    }

    public void broadcast(byte[] pkt) {
        for (MapleCharacter chr : getMembers()) {
            chr.getClient().announce(pkt);
        }
    }

    /**
     * Sets MCPQTeam, MCPQParty, and MCPQField for a given character.
     * [MENTION=2000183830]para[/MENTION]m chr Character to update.
     */
    public void updatePlayer(MapleCharacter chr) {
        chr.setMCPQTeam(this.team);
        chr.setMCPQParty(this);
        chr.setMCPQField(this.field);
    }

    /**
     * Sets MCPQTeam, MCPQParty, and MCPQ field for all characters in the party.
     * Unlike deregisterPlayers, this method does NOT warp players to the lobby map.
     */
    public void updatePlayers() {
        for (MapleCharacter chr : getMembers()) {
            this.updatePlayer(chr);
        }
    }

    /**
     * Resets MCPQ variables for a given character.
     * [MENTION=2000183830]para[/MENTION]m chr Character to reset.
     */
    public static void deregisterPlayer(MapleCharacter chr) {
        chr.setMCPQTeam(MCTeam.NONE);
        chr.setMCPQParty(null);
        chr.setMCPQField(null);

        chr.setAvailableCP(0);
        chr.setTotalCP(0);
    }

    /**
     * Resets MCPQ variables for all characters in the party.
     * Unlike updatePlayers, this method DOES warp players to the lobby map.
     */
    public void deregisterPlayers() {
        for (MapleCharacter chr : getMembers()) {
            MCParty.deregisterPlayer(chr);
            chr.changeMap(MonsterCarnival.MAP_EXIT);
        }
    }

    public void removePlayer(MapleCharacter chr) {
        characters.remove(chr);
        deregisterPlayer(chr);
    }

    public void startBattle() {
        for (MapleCharacter chr : characters) {
            chr.getClient().getSession().write(MonsterCarnivalPacket.startCPQ(chr));
        }
    }

    /**
     * Uses some amount of available CP.
     * [MENTION=2000183830]para[/MENTION]m use A positive integer to be subtracted from available CP.
     */
    public void loseCP(int use) {
        // TODO: locks?
        if (use < 0) {
            System.err.println("Attempting to use negative CP.");
        }
        this.availCP -= use;
    }

    public void gainCP(int gain) {
        // TODO: locks?
        this.availCP += gain;
        this.totalCP += gain;
    }

    public MCParty getEnemy() {
        return enemy;
    }

    public void setEnemy(MCParty enemy) {
        this.enemy = enemy;
    }

    /**
     * Applies a MCSkill to the entire team. This is used on the team's own players
     * because it is called when the enemy team uses a debuff/cube of darkness.
     * [MENTION=2000183830]para[/MENTION]m skill Skill to apply.
     * [MENTION=850422]return[/MENTION] True if skill was applied, false otherwise.
     */
    public boolean applyMCSkill(MCSkill skill) {
        MobSkill s = MobSkillFactory.getMobSkill(skill.getMobSkillID(), skill.getLevel());
        MapleDisease disease = MapleDisease.getType(skill.getMobSkillID());
        if (disease == null) {
            disease = MapleDisease.DARKNESS;
            s = MobSkillFactory.getMobSkill(121, 6); // HACK: darkness
        } else if (disease == MapleDisease.POISON) {
            return false;
        }

        // We only target players on the battlefield map.
        if (skill.getTarget() == 2) {
            for (MapleCharacter chr : getMembers()) {
                if (MonsterCarnival.isBattlefieldMap(chr.getMapId())) {
                    chr.giveDebuff(disease, s, true);
                }
            }
            return true;
        } else {
            if (getRandomMember() != null) {
                getRandomMember().giveDebuff(disease, s, true);
                return true;
            } else {
                return false;
            }
        }
    }

    public void setField(MCField field) {
        this.field = field;
    }

    public void setTeam(MCTeam newTeam) {
        this.team = newTeam;
    }

    public MCTeam getTeam() {
        return team;
    }

    /**
     * Returns a collection of online members in the party.
     * [MENTION=850422]return[/MENTION] Online MCParty members.
     */
    public Collection<MapleCharacter> getMembers() {
        return this.characters;
    }

    public MapleCharacter getRandomMember() {
        List<MapleCharacter> chrsOnMap = new ArrayList<MapleCharacter>();
        for (MapleCharacter chr : this.characters) {
            if (MonsterCarnival.isBattlefieldMap(chr.getMapId())) {
                chrsOnMap.add(chr);
            }
        }
        if (chrsOnMap.isEmpty()) {
            return null;
        }
        return chrsOnMap.get(new Random().nextInt(chrsOnMap.size()));
    }

    public int getAvailableCP() {
        return availCP;
    }

    public int getTotalCP() {
        return totalCP;
    }

}


MCSkill.java


package net.sf.odinms.server.partyquest;
/**
 * Object representing MCSkill in Skill.wz/MCSkill.img.
 *
 * @author s4nta
 */
public class MCSkill {
    private final int id, target, mobSkillID, level, spendCP;
    private String name, desc;

    public MCSkill(int id, int target, int mobSkillID, int level, int spendCP) {
        this.id = id;
        this.target = target;
        this.mobSkillID = mobSkillID;
        this.level = level;
        this.spendCP = spendCP;
    }

    public int getId() {
        return id;
    }

    public int getTarget() {
        return target;
    }

    public int getMobSkillID() {
        return mobSkillID;
    }

    public int getLevel() {
        return level;
    }

    public int getSpendCP() {
        return spendCP;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

}


MCSkillFactory.java


package net.sf.odinms.server.partyquest;
import net.sf.odinms.provider.MapleData;
import net.sf.odinms.provider.MapleDataProvider;
import net.sf.odinms.provider.MapleDataProviderFactory;
import net.sf.odinms.provider.MapleDataTool;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Handles parsing of MCGuardian and MCSkill inside of Skill.wz.
 * Based off of MobSkillFactory.
 * @author s4nta
 */
public class MCSkillFactory {

    private static Map<Integer, MCSkill> mcSkills = new HashMap<Integer, MCSkill>();
    private static Map<Integer, MCGuardian> mcGuardians = new HashMap<Integer, MCGuardian>();
    private static MapleDataProvider dataSource = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("net.sf.odinms.wzpath") + "/Skill.wz"));
    private static MapleData mcSkillRoot = dataSource.getData("MCSkill.img");
    private static MapleData mcGuardianRoot = dataSource.getData("MCGuardian.img");
    private static ReentrantReadWriteLock skillLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock guardianLock = new ReentrantReadWriteLock();

    public static MCSkill getMCSkill(int skillId) {
        skillLock.readLock().lock();
        try {
            MCSkill ret = mcSkills.get(skillId);
            if (ret != null) {
                return ret;
            }
        } finally {
            skillLock.readLock().unlock();
        }
        skillLock.writeLock().lock();
        try {
            MCSkill ret;
            ret = mcSkills.get(skillId);
            if (ret == null) {
                MapleData skillData = mcSkillRoot.getChildByPath(String.valueOf(skillId));
                if (skillData != null) {
                    int target = MapleDataTool.getInt("target", skillData, 0);
                    int spendCP = MapleDataTool.getInt("spendCP", skillData, 0);
                    int mobSkillID = MapleDataTool.getInt("mobSkillID", skillData, 0);
                    int level = MapleDataTool.getInt("level", skillData, 0);
                    ret = new MCSkill(skillId, target, mobSkillID, level, spendCP);

                    if (MonsterCarnival.DEBUG) {
                        String name = MapleDataTool.getString("name", skillData, "");
                        String desc = MapleDataTool.getString("desc", skillData, "");
                        ret.setName(name);
                        ret.setDesc(desc);
                    }
                    mcSkills.put(skillId, ret);
                }
            }
            return ret;
        } finally {
            skillLock.writeLock().unlock();
        }
    }

    public static MCGuardian getMCGuardian(int id) {
        guardianLock.readLock().lock();
        try {
            MCGuardian ret = mcGuardians.get(id);
            if (ret != null) {
                return ret;
            }
        } finally {
            guardianLock.readLock().unlock();
        }
        guardianLock.writeLock().lock();
        try {
            MCGuardian ret;
            ret = mcGuardians.get(id);
            if (ret == null) {
                MapleData skillData = mcGuardianRoot.getChildByPath(String.valueOf(id));
                if (skillData != null) {
                    int type = MapleDataTool.getInt("type", skillData, 0);
                    int spendCP = MapleDataTool.getInt("spendCP", skillData, 0);
                    int mobSkillID = MapleDataTool.getInt("mobSkillID", skillData, 0);
                    int level = MapleDataTool.getInt("level", skillData, 0);
                    ret = new MCGuardian(type, spendCP, mobSkillID, level);

                    if (MonsterCarnival.DEBUG) {
                        String name = MapleDataTool.getString("name", skillData, "");
                        String desc = MapleDataTool.getString("desc", skillData, "");
                        ret.setName(name);
                        ret.setDesc(desc);
                    }
                    mcGuardians.put(type, ret);
                }
            }
            return ret;
        } finally {
            guardianLock.writeLock().unlock();
        }
    }

}


MCTracker.java


package net.sf.odinms.server.partyquest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Logs various errors and also keeps data on Carnival PQ runs.
 * @author s4nta
 */
public class MCTracker {

    static Logger log = LoggerFactory.getLogger(MCTracker.class);

    // TODO:
    // Add field-specific info
    // Add methods for calls from different files
    // Maybe write own version of FilePrinter?

    static final String PATH = "Reports/MCPQ.txt";

    public static void log(String msg) {
        System.out.println(msg);
        log.debug(msg);
    }

}


MCWZData.java


package net.sf.odinms.server.partyquest;
import net.sf.odinms.provider.MapleData;
import net.sf.odinms.provider.MapleDataTool;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Representation of the data tree inside of the Map.wz file.
 *
 * @author s4nta
 */
public class MCWZData {

    public List<MCMobGenPos> mobGenPosList = new ArrayList<MCMobGenPos>();
    public List<MCGuardianGenPos> guardianGenPosList = new ArrayList<MCGuardianGenPos>();
    public Map<Integer, MCSummonMob> summons = new HashMap<Integer, MCSummonMob>();
    public Map<Integer, Integer> skills = new HashMap<Integer, Integer>();
    public Map<Integer, Integer> guardians = new HashMap<Integer, Integer>();

    public String effectWin, effectLose, soundWin, soundLose;
    public int rewardMapWin, rewardMapLose;

    public int mobGenMax, guardianGenMax;
    public boolean mapDivided;
    public int deathCP;
    public int reactorRed, reactorBlue;

    public MCWZData(MapleData src) {
        parse(src);
    }

    public void parse(MapleData src) {
        populateMobGenPos(src.getChildByPath("mobGenPos"));
        populateSummonMobs(src.getChildByPath("mob"));
        effectWin = MapleDataTool.getString("effectWin", src);
        effectLose = MapleDataTool.getString("effectLose", src);
        soundWin = MapleDataTool.getString("soundWin", src);
        soundLose = MapleDataTool.getString("soundLose", src);

        rewardMapWin = MapleDataTool.getInt("rewardMapWin", src);
        rewardMapLose = MapleDataTool.getInt("rewardMapLose", src);

        populateSkills(src.getChildByPath("skill"));
        populateGuardianGenPos(src.getChildByPath("guardianGenPos"));
        populateGuardians(src.getChildByPath("guardian"));

        mobGenMax = MapleDataTool.getInt("mobGenMax", src, 20); // HACK: 20 default
        guardianGenMax = MapleDataTool.getInt("guardianGenMax", src, 20); // HACK: 20 default

        mapDivided = MapleDataTool.getInt("mapDivided", src) > 0;

        deathCP = MapleDataTool.getInt("deathCP", src);
        reactorRed = MapleDataTool.getInt("reactorRed", src);
        reactorBlue = MapleDataTool.getInt("reactorBlue", src);
    }

    private void populateMobGenPos(MapleData src) {
        for (MapleData n : src) {
            MCMobGenPos nn = new MCMobGenPos(MapleDataTool.getInt("x", n, 0),
                    MapleDataTool.getInt("y", n, 0),
                    MapleDataTool.getInt("fh", n, 0),
                    MapleDataTool.getInt("cy", n, 0),
                    MapleDataTool.getInt("team", n, -1));
            mobGenPosList.add(nn);
        }
    }

    private void populateSummonMobs(MapleData src) {
        for (MapleData n : src) {
            int id = Integer.parseInt(n.getName());
            MCSummonMob mcs = new MCSummonMob(
                    MapleDataTool.getInt("id", n, 0),
                    MapleDataTool.getInt("spendCP", n, 0),
                    MapleDataTool.getInt("mobTime", n, 0)
            );

            this.summons.put(id, mcs);
        }
    }

    private void populateSkills(MapleData src) {
        for (MapleData n : src) {
            int key = Integer.parseInt(n.getName());
            int val = MapleDataTool.getInt(n);

            skills.put(key, val);
        }
    }

    private void populateGuardianGenPos(MapleData src) {
        for (MapleData n : src) {
            MCGuardianGenPos nn = new MCGuardianGenPos(MapleDataTool.getInt("x", n, 0),
                    MapleDataTool.getInt("y", n, 0),
                    MapleDataTool.getInt("f", n, 0),
                    MapleDataTool.getInt("team", n, -1));
            guardianGenPosList.add(nn);
        }
    }

    private void populateGuardians(MapleData src) {
        for (MapleData n : src) {
            int key = Integer.parseInt(n.getName());
            int val = MapleDataTool.getInt(n);

            guardians.put(key, val);
        }
    }

    public class MCMobGenPos {

        public final int x, y, fh, cy, team;

        private MCMobGenPos(int x, int y, int fh, int cy, int team) {
            this.x = x;
            this.y = y;
            this.fh = fh;
            this.cy = cy;
            this.team = team;
        }
    }

    public class MCGuardianGenPos {

        public final int x, y, f, team;

        private MCGuardianGenPos(int x, int y, int f, int team) {
            this.x = x;
            this.y = y;
            this.f = f;
            this.team = team;
        }
    }

    public class MCSummonMob {

        public final int id, spendCP, mobTime;

        private MCSummonMob(int id, int spendCP, int mobTime) {
            this.id = id;
            this.spendCP = spendCP;
            this.mobTime = mobTime;
        }
    }

}


MonsterCarnival.java


package net.sf.odinms.server.partyquest;
import net.sf.odinms.client.MapleCharacter;
import net.sf.odinms.client.MapleDisease;
import net.sf.odinms.net.channel.ChannelServer;
import net.sf.odinms.net.world.MapleParty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * Processes game logic for Monster Carnival PQ.
 *
 * TODO: Display IGNs/Jobs/Level in Pending Requests
 * TODO: fix reactor handling and make it less hacky
 * TODO: fix cube of darkness and make it less hacky
 *
 * @author s4nta
 */
public class MonsterCarnival {

    // Logger
    static Logger log = LoggerFactory.getLogger(MonsterCarnival.class);

    // Map of channel to a MonsterCarnival instance.
    private static final HashMap<Integer, MonsterCarnival> instances = new HashMap<Integer, MonsterCarnival>();

    /**
     * Returns the MonsterCarnival instance for a channel. Creates a new one and maps it
     * if it does not exist.
     *  [MENTION=2000183830]para[/MENTION]m channel Channel to check for.
     *  [MENTION=850422]return[/MENTION] MonsterCarnival instance for a channel.
     */
    public static MonsterCarnival getMonsterCarnival(int channel) {
        // TODO: synchronization?
        if (channel < 1 || channel > 20) {
            log.warn("發現無法進入的怪物擂台.");
            return null;
        }
        if (instances.containsKey(channel)) {
            return instances.get(channel);
        }
        ChannelServer cserv = ChannelServer.getInstance(channel);
        if (cserv == null) {
            log.error("ChannelServer instance for channel " + channel + " is null.");
            return null;
        }
        MonsterCarnival inst = new MonsterCarnival(cserv);
        instances.put(channel, inst);
        return inst;
    }

    // Instance variables
    private ChannelServer cserv;
    private Map<Integer, MCField> fields = new HashMap<Integer, MCField>();

    /**
     * Constructor for a MonsterCarnival instance.
     *  [MENTION=2000183830]para[/MENTION]m cserv Channel server for this instance.
     */
    public MonsterCarnival(ChannelServer cserv) {
        this.cserv = cserv;
        this.initFields();
    }

    private int redCP, blueCP, redTotalCP, blueTotalCP;
public int getCP(int team) {
if (team == 0) {
return redCP;
                } else if (team == 1) {
return blueCP;
                } else {
throw new RuntimeException("Equipe desconhecida");
              }
}
 
    public int getTotalCP(int team) {
if (team == 0) {
return redTotalCP;
                } else if (team == 1) {
return blueTotalCP;
                } else {
throw new RuntimeException("Equipe desconhecida");
                }
}

    /**
     * Initializes empty fields for the instance.
     */
    private void initFields() {
        for (int i = 1; i <= NUM_FIELDS; i++) {
            fields.put(i, new MCField(i, this.cserv, null, null));
        }
    }

    /**
     * Gets the field with a specified ID.
     *  [MENTION=2000183830]para[/MENTION]m id ID of field to retrieve.
     *  [MENTION=850422]return[/MENTION]
     */
    public MCField getField(int id) {
        if (id >= 1 && id <= NUM_FIELDS) {
            return fields.get(id);
        }
        return null;
    }

    /**
     * Checks if a party can join a field or not.
     *
     *  [MENTION=2000183830]para[/MENTION]m pty Party to register.
     *  [MENTION=2000183830]para[/MENTION]m room Room to join.
     *  [MENTION=850422]return[/MENTION] Different code based on status. OK if successful.
     */
    public int registerStatus(MapleParty pty, int room) {
        if (!isValidField(room)) {
            return STATUS_FIELD_INVALID;
        }
        MCField field = this.getField(room);
        if (field.isFull()) {
            return STATUS_FIELD_FULL;
        }
        MCParty party = new MCParty(pty);
        if (!sizeCheck(party.getSize(), room)) {
            return STATUS_PARTY_SIZE;
        }
        boolean levelCheck = party.checkLevels();
        if (!levelCheck) {
            return STATUS_PARTY_LEVEL;
        }
        boolean chanCheck = party.checkChannels();
        if (!chanCheck) {
            return STATUS_PARTY_MISSING;
        }
        boolean mapCheck = party.checkMaps();
        if (!mapCheck) {
            return STATUS_PARTY_MISSING;
        }
        if (field.needsRequest()) {
            return STATUS_REQUEST;
        }
        return STATUS_PROCEED;
    }

    /**
     * Creates a new MCParty based on a regular MapleParty object.
     *  [MENTION=2000183830]para[/MENTION]m pty Party to base off of.
     *  [MENTION=850422]return[/MENTION] Newly created MCParty.
     */
    public MCParty createParty(MapleParty pty) {
        return new MCParty(pty);
    }

    public void resetPlayer(MapleCharacter chr) {
        MCParty.deregisterPlayer(chr);
        chr.changeMap(MAP_LOBBY);
    }



    /**
     * Returns a String containing information about lobby waiting rooms.
     *
     *  [MENTION=850422]return[/MENTION] String containing lobby information formatted for NPC.
     */
    public String getNPCAvailableFields() {
        StringBuilder sb = new StringBuilder();
        sb.append("歡迎來到 #b怪物擂台#k!房間1-4可進入2-4人、房間5-6可進入3-6人\r\n#b");
        for (int i = 1; i <= NUM_FIELDS; i++) {
            MCField field = this.fields.get(i);
            sb.append(field.getStatus());
        }

        return sb.toString();
    }

    // Reference Information

    // Game Constants
    public static final int CP_LOSS_ON_DEATH = 10;
    public static final int TIME_PREBATTLE = 10;
    public static final int TIME_BATTLE = 600;
    public static final int TIME_LOBBYWAIT = 180;
    public static final int TAB_SPAWNS   = 0;
    public static final int TAB_DEBUFF   = 1;
    public static final int TAB_GUARDIAN = 2;

    /**
     * Gets a random debuff for (Mini) Cube of Darkness.
     *  [MENTION=850422]return[/MENTION] Random MapleDisease.
     */
    public static MapleDisease getRandomDebuff() {
        return DEBUFFS[new Random().nextInt(DEBUFFS.length)];
    }

    /**
     * Checks party size. Information from hidden-street MCPQ page.
     *  [MENTION=2000183830]para[/MENTION]m size Size of the party.
     *  [MENTION=2000183830]para[/MENTION]m field Field to check for.
     *  [MENTION=850422]return[/MENTION] True if party size is okay, False otherwise.
     */
    public static final boolean sizeCheck(int size, int field) {
        if (DEBUG) {
            return true;
        }
        switch (field) {
            case 1:
            case 2:
            case 3:
            case 4:
                return size >= 1 && size <= 4;
            case 5:
            case 6:
                return size >= 3 && size <= 6;
            default:
                return false;
        }
    }

    public static final boolean isValidField(int field) {
        return field >= 1 && field <= 6;
    }

    public static final int getLobbyMap(int field) {
        if (field < 1 || field > NUM_FIELDS) {
            log.warn("Attempting to get lobby map for invalid field.");
            return MAP_EXIT;
        }
        return 980000000 + field * 100;
    }

    public static final boolean isLobbyMap(int mapid) {
        switch (mapid) {
            case 980000100:
            case 980000200:
            case 980000300:
            case 980000400:
            case 980000500:
            case 980000600:
                return true;
            default:
                return false;
        }
    }

    public static final int getBattleFieldMap(int field) {
        if (field < 1 || field > NUM_FIELDS) {
            log.warn("Attempting to get battlefield map for invalid field.");
            return MAP_EXIT;
        }
        return 980000000 + field * 100 + 1;
    }

    public static final boolean isBattlefieldMap(int mapid) {
        switch (mapid) {
            case 980000101:
            case 980000201:
            case 980000301:
            case 980000401:
            case 980000501:
            case 980000601:
                return true;
            default:
                return false;
        }
    }

    public static final int getResurrectionMap(int field) {
        if (field < 1 || field > NUM_FIELDS) {
            log.warn("Attempting to get resurrection map for invalid field.");
            return MAP_EXIT;
        }
        return 980000000 + field * 100 + 2;
    }

    public static final int getVictoriousMap(int field) {
        if (field < 1 || field > NUM_FIELDS) {
            log.warn("Attempting to get victory map for invalid field.");
            return MAP_EXIT;
        }
        return 980000000 + field * 100 + 3;
    }

    public static final int getDefeatedMap(int field) {
        if (field < 1 || field > NUM_FIELDS) {
            log.warn("Attempting to get defeat map for invalid field.");
            return MAP_EXIT;
        }
        return 980000000 + field * 100 + 4;
    }

    public static final boolean isCPQConsumeItem(int itemid) {
        switch (itemid) {
            case ITEM_CP_1:
            case ITEM_CP_2:
            case ITEM_CP_3:
            case ITEM_PTY_ELIX:
            case ITEM_PTY_PELIX:
            case ITEM_PTY_ALLC:
            case ITEM_MINICUBE:
            case ITEM_DARKCUBE:
            case ITEM_STUNNER:
            case ITEM_IND_WHITE:
            case ITEM_IND_MANA:
            case ITEM_IND_ELIX:
            case ITEM_IND_PELIX:
            case ITEM_IND_ALLC:
            case ITEM_PTY_MANA:
                return true;
        }
        return false;
    }

    // Error Codes
    // Note: These would be in an enum, but since these will be used in a NPC, they are not.
    public static final int STATUS_FIELD_FULL    = 0;
    public static final int STATUS_PARTY_SIZE    = 1;
    public static final int STATUS_PARTY_LEVEL   = 2;
    public static final int STATUS_PARTY_MISSING = 3;
    public static final int STATUS_FIELD_INVALID = 4;
    public static final int STATUS_REQUEST       = 98;
    public static final int STATUS_PROCEED       = 99;

    // Maps
    public static final int MAP_LOBBY = 980000000;
    public static final int MAP_EXIT  = 980000010;

    // NPCs
    public static final int NPC_LOBBY     = 2042000;
    public static final int NPC_ENTER     = 2042001; // Warp in from outside
    public static final int NPC_INFO      = 2042002;
    public static final int NPC_ASST_RED  = 2042003;
    public static final int NPC_ASST_BLUE = 2042004;

    // Items
    public static final int ITEM_CP_1      = 2022157;
    public static final int ITEM_CP_2      = 2022158;
    public static final int ITEM_CP_3      = 2022159;
    public static final int ITEM_PTY_MANA  = 2022160;
    public static final int ITEM_PTY_ELIX  = 2022161;
    public static final int ITEM_PTY_PELIX = 2022162;
    public static final int ITEM_PTY_ALLC  = 2022163;
    public static final int ITEM_MINICUBE  = 2022164;
    public static final int ITEM_DARKCUBE  = 2022165;
    public static final int ITEM_STUNNER   = 2022166;
    public static final int ITEM_IND_WHITE = 2022174;
    public static final int ITEM_IND_ELIX  = 2022175;
    public static final int ITEM_IND_PELIX = 2022176;
    public static final int ITEM_IND_MANA  = 2022177;
    public static final int ITEM_IND_ALLC  = 2022178;

    // Guardians
    public static final int GUARDIAN_RED = 9980000;
    public static final int GUARDIAN_BLUE = 9980001;



    // Debuffs
    public static final MapleDisease[] DEBUFFS = {
            MapleDisease.STUN,
            MapleDisease.DARKNESS,
            MapleDisease.WEAKEN}; // intentionally leave out a few

    // Miscellaneous
    public static final int MIN_LEVEL  = 30;
    public static final int MAX_LEVEL  = 50;
    public static final int NUM_FIELDS = 6;

    // Debug

    public static final boolean DEBUG = false; }


找到 tools → packet → MonsterCarnivalPacket.java
將所有內容更換為以下方法


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.sf.odinms.tools;

import net.sf.odinms.client.MapleCharacter;
import net.sf.odinms.net.MaplePacket;
import net.sf.odinms.net.SendPacketOpcode;
import net.sf.odinms.server.partyquest.MCParty;
import net.sf.odinms.tools.data.output.MaplePacketLittleEndianWriter;

public class MonsterCarnivalPacket {

    public static MaplePacket startCPQ(MapleCharacter chr) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
        mplew.writeShort(SendPacketOpcode.MONSTER_CARNIVAL_START.getValue());
        mplew.write(chr.getTeam()); //team
        mplew.writeShort(chr.getAvailableCP()); //Available CP
        mplew.writeShort(chr.getTotalCP()); //Total Obtained CP
        mplew.writeShort(chr.getMCPQField().getRed().getAvailableCP()); //Available CP of the team
        mplew.writeShort(chr.getMCPQField().getRed().getTotalCP()); //Total Obtained CP of the team
        mplew.writeShort(chr.getMCPQField().getBlue().getAvailableCP()); //Available CP of the team
        mplew.writeShort(chr.getMCPQField().getBlue().getAvailableCP()); //Total Obtained CP of the team
        mplew.writeShort(0); //Probably useless nexon shit
        mplew.writeLong(0); //Probably useless nexon shit
        return mplew.getPacket();
    }



    public static MaplePacket updatePersonalCP(MapleCharacter chr) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
        mplew.writeShort(SendPacketOpcode.MONSTER_CARNIVAL_OBTAINED_CP.getValue());
        mplew.writeShort(chr.getAvailableCP()); //Obtained CP - Used CP
        mplew.writeShort(chr.getTotalCP()); //Total Obtained CP
        return mplew.getPacket();
    }

    public static MaplePacket updatePartyCP(MCParty pty) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
        mplew.writeShort(SendPacketOpcode.MONSTER_CARNIVAL_PARTY_CP.getValue());
        mplew.write(pty.getTeam().code); //Team where the points are given to.
        mplew.writeShort(pty.getAvailableCP()); //Obtained CP - Used CP
        mplew.writeShort(pty.getTotalCP()); //Total Obtained CP
        return mplew.getPacket();
    }

    public static MaplePacket CPQSummon(int tab, int num, String name) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
        mplew.writeShort(SendPacketOpcode.MONSTER_CARNIVAL_SUMMON.getValue());
        mplew.write(tab); //Tab
        mplew.write(num); //Number of summon inside the tab
        mplew.writeMapleAsciiString(name); //Name of the player that summons
        return mplew.getPacket();
    }

    public static MaplePacket CPQDied(MapleCharacter player, int loss) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
        mplew.writeShort(SendPacketOpcode.MONSTER_CARNIVAL_DIED.getValue());
        mplew.write(player.getTeam()); //Team
        mplew.writeMapleAsciiString(player.getName()); //Name of the player that died
        mplew.write(loss); //Lost CP
        return mplew.getPacket();
    }

    /**
     * Sends a CPQ Message
     *
     * Possible values for <code>message</code>:<br>
     * 1: You don't have enough CP to continue.
     * 2: You can no longer summon the Monster.
     * 3: You can no longer summon the being.
     * 4: This being is already summoned.
     * 5:     This request has failed due to an unknown error.
     *
     *     [MENTION=2000183830]para[/MENTION]m message Displays a message inside Carnival PQ
     **/
    public static MaplePacket CPQMessage(int message) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
        mplew.writeShort(SendPacketOpcode.MONSTER_CARNIVAL_MESSAGE.getValue());
        mplew.write(message); //Message
        return mplew.getPacket();
    }

    public static MaplePacket leaveCPQ(int team, String name) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
        mplew.writeShort(SendPacketOpcode.MONSTER_CARNIVAL_LEAVE.getValue());
        mplew.write(0); //Something?
        mplew.write(team); //Team
        mplew.writeMapleAsciiString(name); //Player name
        return mplew.getPacket();
    }


}


找到 tools → MaplePacketCreator.java:
在 spawnMonsterInternal: 中將 mplew.writeShort(newSpawn ? -2 : -1)  更換成


mplew.write(newSpawn ? -2 : -1);

mplew.write(life.getTeam());


在 spawnPlayerMapObject: 中 return mplew.getPacket(); 上方添加以下方法


mplew.write(chr.getTeam());



教學完畢,如再架設過程中遇到問題,請至此專用討論區討論 「點我
請勿再本網頁底下留言,切記勿什麼都不懂就想學習架設。

發表評論

Subscribe to Posts | Subscribe to Comments

- Copyright © AICL社群娛樂集團 - 網站區 - Date A Live - Powered by Blogger - Designed by Johanes Djogan -