Updated Meso & Arrow drops + Aran change jobs fix + improved concurrency

Added meso drop data for many mobs that were missing mesos.
Enhanced arrow drop data, now dropping bundles instead of unitary items.
Fixed issues with several Aran change jobs crashing the player shortly after changing jobs.
Improved concurrency in MapleGuild, MapleAlliance and MaplePlayerShop.
New tools: MapleArrowFetcher and MapleMesoFetcher, that were used to compile the updated drop data info.
This commit is contained in:
ronancpl
2017-11-01 13:34:26 -02:00
parent 44949aea37
commit 64af2cfa00
192 changed files with 44793 additions and 271 deletions

315
docs/mcpq/blob/2042002.js Normal file
View File

@@ -0,0 +1,315 @@
var DISABLED = false;
var SavedLocationType = Packages.server.maps.SavedLocationType;
var MonsterCarnival = Packages.server.partyquest.mcpq.MonsterCarnival;
var MCTracker = Packages.server.partyquest.mcpq.MCTracker;
var MCParty = Packages.server.partyquest.mcpq.MCParty;
var MCField = Packages.server.partyquest.mcpq.MCField;
var status = -1;
var store = false;
var ctx = -1;
var storeInfo;
var purchaseId;
var purchaseCost;
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]];
var infoText = "You wish to know about the Monster Carnival? Very well. The Monster Carnival is a place of trilling battles and exciting competiton against people just as strong and motivated as yourself. You must summon monsters and defeat the monsters summoned by the opposing party. That's the essence of the Monster Carnival. Once you enter the Carnival Field, the task is to earn CP by hunter monsters from the opposing party and use those CP's to distract the opposing party from hunting monsters. There are three ways to distract the other party; Summon a Monster, Skill or Protector. Please remember this though, it's never a good idea to save up CP just for the sake of it. The CP's you've used will also help determine the winner and the loser of the carnival.";
var no = "You do not have enough Maple Coins for this item. Come back to me when you acquire more!";
function getGrade(cp) {
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("CPQ is temporarily unavailable.");
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("Unfortunately, you did not manage to win this round. Better luck next time!");
} 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("Your grade is: #b" + letterGrade + "\r\n\r\n#kEXP Reward: " + 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("Congratulations! You managed to defeat the enemy team!");
} 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("Your grade is: #b" + letterGrade + "\r\n\r\n#kEXP Reward: " + 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("What would you like to do? If you have never participated in the Monster Carnival, you'll need to know a thing or two about it before joining.\r\n\r\n#b#L0#Go to the Monster Carnival Field#l\r\n#L1#Learn about the Monster Carnival#l\r\n#L2#Trade Maple Coin#l");
} else if (status == 1) {
if (selection == 0) {
if (cm.getChar().getLevel() < MonsterCarnival.MIN_LEVEL || cm.getChar().getLevel() > MonsterCarnival.MAX_LEVEL) {
cm.sendOk("You must be between level " + MonsterCarnival.MIN_LEVEL + " and level " + MonsterCarnival.MAX_LEVEL + " to enter.");
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("Select a category:\r\n" +
"#L101##bTrade Maple Coins for Warrior Weapons\r\n" +
"#L102#Trade Maple Coins for Magician Weapons\r\n" +
"#L103#Trade Maple Coins for Bowman Weapons\r\n" +
"#L104#Trade Maple Coins for Thief Weapons\r\n" +
"#L105#Trade Maple Coins for Pirate Weapons\r\n" +
"#L106#Trade Maple Coins for a Necklace");
}
} 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("That store doesn't exist.");
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("Are you sure you want to purchase #i" + purchaseId + "#? You will have #r#e" + (cm.itemQuantity(coinId) - purchaseCost) + " " + coinIcon + "##k#n remaining.");
} else {
cm.sendOk("You don't have enough " + 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("Congratulations! Enjoy your new item.");
cm.dispose();
}
} else {
MCTracker.log("[MCPQ_Info] CONTEXT_TOWN: Invalid status 4");
}
}
}
}
function doExit() {
cm.warp(MonsterCarnival.MAP_LOBBY);
cm.sendOk("Hope you had fun in the Carnival PQ!");
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();
}
}

View File

@@ -0,0 +1,453 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package server.partyquest.mcpq;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import packet.creators.CarnivalPackets;
import packet.creators.PacketCreator;
import client.player.Player;
import server.life.MapleLifeFactory;
import server.life.MapleMonster;
import server.life.MobSkill;
import server.life.MobSkillFactory;
import server.life.SpawnPoint;
import server.life.Spawns;
import server.maps.Field;
import server.maps.FieldObject;
import server.maps.FieldObjectType;
import server.maps.reactors.MapleReactor;
import server.maps.reactors.MapleReactorFactory;
import server.partyquest.mcpq.MCField.MCTeam;
import static server.partyquest.mcpq.MCField.MCTeam.RED;
import server.partyquest.mcpq.MCWZData.MCGuardianGenPos;
import server.partyquest.mcpq.MCWZData.MCMobGenPos;
/**
* Keeps track of guardians and spawns in MCPQ.
*
* @author s4nta
*/
public class MCBattlefield {
private Field 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<>();
private Map<Integer, MCWZData.MCGuardianGenPos> blueGuardianIdToPos = new HashMap<>();
// These map Reactor Object IDs to guardian objects.
private Map<Integer, MCGuardian> redReactors = new HashMap<>();
private Map<Integer, MCGuardian> blueReactors = new HashMap<>();
// used for divided maps
// we use an arraylist here for easier random element lookup.
private List<MCWZData.MCGuardianGenPos> originalRedGuardianSpawns = new ArrayList<>();
private List<MCWZData.MCGuardianGenPos> originalBlueGuardianSpawns = new ArrayList<>();
// used for undivided map
private List<MCWZData.MCGuardianGenPos> originalGuardianSpawns = new ArrayList<>();
// used for divided maps
// we use an arraylist here for easier random element lookup.
private List<MCWZData.MCMobGenPos> originalRedSpawns = new ArrayList<>();
private List<MCWZData.MCMobGenPos> originalBlueSpawns = new ArrayList<>();
// use for undivided map
private List<MCWZData.MCMobGenPos> originalUnifiedSpawns = new ArrayList<>();
private List<SpawnPoint> originalSpawns = new ArrayList<>();
private List<SpawnPoint> addedSpawns = new ArrayList<>();
public MCBattlefield(Field 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 (Spawns sp : this.map.getSpawnPoints()) {
originalSpawns.add((SpawnPoint) 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);
break;
}
}
}
public void addSpawn(Player chr, int num) {
if (numMonstersSpawned > wzData.mobGenMax) {
chr.getClient().announce(CarnivalPackets.CarnivalMessage(3));
return;
}
MCWZData.MCSummonMob mobToSummon = wzData.summons.get(num);
MCWZData.MCMobGenPos spawnPos = getRandomSpawnPos(chr.getMCPQTeam());
MCField.MCTeam team = chr.getMCPQTeam();
if (spawnPos == null) {
chr.getClient().announce(CarnivalPackets.CarnivalMessage(2));
return;
}
int spendCp = mobToSummon.spendCP;
if (spendCp > chr.getAvailableCP()) {
readdSpawn(spawnPos, team);
chr.getClient().announce(CarnivalPackets.CarnivalMessage(1));
return;
}
chr.getMCPQField().loseCP(chr, spendCp);
this.map.broadcastMessage(CarnivalPackets.PlayerSummoned(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(Player 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(CarnivalPackets.CarnivalMessage(1));
return;
}
MCParty teamToApply = chr.getMCPQParty().getEnemy();
boolean success = teamToApply.applyMCSkill(skill);
if (success) {
chr.getMCPQField().loseCP(chr, spendCp);
map.broadcastMessage(CarnivalPackets.PlayerSummoned(MonsterCarnival.TAB_DEBUFF, num, chr.getName()));
} else {
chr.getClient().getSession().write(CarnivalPackets.CarnivalMessage(5));
}
}
public void readdSpawn(MCWZData.MCMobGenPos pos, MCField.MCTeam team) {
List<MCWZData.MCMobGenPos> lst = null;
if (this.wzData.mapDivided) {
if (null == team) {
return;
} else switch (team) {
case RED:
lst = originalRedSpawns;
break;
case BLUE:
lst = originalBlueSpawns;
break;
default:
return;
}
} else {
lst = originalUnifiedSpawns;
}
if (lst == null) {
return;
}
lst.add(pos);
}
public void spawnGuardian(Player chr, int num) {
if (numGuardiansSpawned > wzData.guardianGenMax) {
chr.getClient().announce(CarnivalPackets.CarnivalMessage(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(CarnivalPackets.CarnivalMessage(4));
return;
}
} else if (team == MCField.MCTeam.BLUE) {
if (blueGuardianIdToPos.containsKey(guardianId)) {
chr.getClient().announce(CarnivalPackets.CarnivalMessage(4));
return;
}
}
int spendCp = guardian.getSpendCP();
if (spendCp > chr.getAvailableCP()) {
chr.getClient().announce(CarnivalPackets.CarnivalMessage(1));
return;
}
chr.getMCPQField().loseCP(chr, spendCp);
this.map.broadcastMessage(CarnivalPackets.PlayerSummoned(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 (null == team) {
return;
} else switch (team) {
case RED:
reactor = new MapleReactor(MapleReactorFactory.getReactor(MonsterCarnival.GUARDIAN_RED), MonsterCarnival.GUARDIAN_RED);
reactor.setPosition(spawnPos);
redGuardianIdToPos.put(num, genPos);
break;
case BLUE:
reactor = new MapleReactor(MapleReactorFactory.getReactor(MonsterCarnival.GUARDIAN_BLUE), MonsterCarnival.GUARDIAN_BLUE);
reactor.setPosition(spawnPos);
blueGuardianIdToPos.put(num, genPos);
break;
default:
return;
}
reactor.setDelay(-1);
map.spawnReactor(reactor);
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);
updateMonsterBuffs();
}
public void onGuardianHit(Player 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(PacketCreator.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) {
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) {
originalBlueGuardianSpawns.add(guardianGenPos);
} else {
originalRedGuardianSpawns.add(guardianGenPos);
}
} else {
originalGuardianSpawns.add(guardianGenPos);
}
if (MonsterCarnival.DEBUG) {
System.out.println("Attempting to remove buff " + guard.getName());
}
updateMonsterBuffs();
}
}
private MCGuardianGenPos getRandomGuardianPos(MCTeam team) {
if (this.wzData.mapDivided) {
if (null == team) {
return null;
} else switch (team) {
case RED: {
int randIndex = (int) Math.floor(Math.random() * this.originalRedGuardianSpawns.size());
return originalRedGuardianSpawns.remove(randIndex);
}
case BLUE: {
int randIndex = (int) Math.floor(Math.random() * this.originalBlueGuardianSpawns.size());
return originalBlueGuardianSpawns.remove(randIndex);
}
default:
return null;
}
} else {
int randIndex = (int) Math.floor(Math.random() * this.originalGuardianSpawns.size());
return originalGuardianSpawns.remove(randIndex);
}
}
private MCMobGenPos getRandomSpawnPos(MCTeam team) {
List<MCMobGenPos> lst = null;
if (this.wzData.mapDivided) {
if (null == team) {
return null;
} else switch (team) {
case RED:
lst = originalRedSpawns;
break;
case BLUE:
lst = originalBlueSpawns;
break;
default:
return null;
}
} else {
lst = originalUnifiedSpawns;
}
if (lst == null) {
return null;
}
if (lst.isEmpty()) {
return null;
}
int randIndex = (int) Math.floor(Math.random() * lst.size());
return lst.remove(randIndex);
}
private void updateMonsterBuffs() {
List<MCGuardian> redGuardians = new ArrayList<>();
List<MCGuardian> blueGuardians = new ArrayList<>();
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 (FieldObject mmo : map.getAllMonsters()) {
if (mmo.getType() == FieldObjectType.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<>();
List<MCGuardian> blueGuardians = new ArrayList<>();
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);
}
}
}
}

850
docs/mcpq/blob/MCField.java Normal file
View File

@@ -0,0 +1,850 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package server.partyquest.mcpq;
import handling.channel.ChannelServer;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import packet.creators.CarnivalPackets;
import packet.creators.EffectPackets;
import packet.creators.PacketCreator;
import packet.transfer.write.OutPacket;
import client.player.Player;
import server.MapleStatEffect;
import server.itens.MapleItemInformationProvider;
import server.maps.Field;
import server.maps.FieldItem;
import server.maps.reactors.MapleReactor;
import server.maps.portal.Portal;
import tools.TimerTools.EventTimer;
/**
* 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<>();
private MCState state;
private Map<MCMaps, Field> mapInstances = new HashMap<>();
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
* @param warpPlayers
*/
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(OutPacket 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.
* @return
*/
public String getStatus() {
if (isFull()) {
return "";
}
if (this.state != MCState.LOBBY) {
return "";
}
String waitingParty = "";
if (this.red != null) {
String fmt = " <Party Size: %d, Average Level: %d>";
waitingParty = String.format(fmt, this.red.getSize(), this.red.getAverageLevel());
}
String fmt = "#L%d#Carnival Field %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("[MCPQ] A challenge has arrived! Click on the Assistant in your room to view it.");
}
/**
* 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.
* @return
*/
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 = "<Party Size: %d, Average Level: %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 = EventTimer.getInstance().schedule(new AcceptingRequestsTask(this, host), 1000 * MonsterCarnival.TIME_LOBBYWAIT); // 3 minutes
this.validateRoomTask = EventTimer.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 = EventTimer.getInstance().schedule(new GoBattlefieldTask(this), 1000 * 10); // 10 seconds
red.notice("[MCPQ] The Carnival PQ will start in 10 seconds!");
blue.notice("[MCPQ] The Carnival PQ will start in 10 seconds!");
}
}
/**
* Warps both parties in the field to the battlefield map.
*/
private void goBattle() {
Field 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("[MCPQ] The battle will start in 10 seconds!");
blue.notice("[MCPQ] The battle will start in 10 seconds!");
startBattleTask = EventTimer.getInstance().schedule(new BeginCarnivalTask(this), 1000 * MonsterCarnival.TIME_PREBATTLE); // 10 seconds
validateBattleTask = EventTimer.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(PacketCreator.ServerNotice(6, "[MCPQ] You have 10 minutes to kill monsters!"));
endBattleTask = EventTimer.getInstance().schedule(new EndBattleTask(this), 1000 * MonsterCarnival.TIME_BATTLE);
spawnMonstersTask = EventTimer.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(EffectPackets.ShowEffect(effectWin));
winner.broadcast(EffectPackets.PlaySound(soundWin));
loser.broadcast(EffectPackets.ShowEffect(effectLose));
loser.broadcast(EffectPackets.PlaySound(soundLose));
this.getMap(MCMaps.BATTLEFIELD).killAllMonsters(false);
this.getMap(MCMaps.BATTLEFIELD).clearDrops();
this.deregister(false);
EventTimer.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(Player 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(Player 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(CarnivalPackets.UpdatePersonalCP(chr));
this.announce(CarnivalPackets.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(Player 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(CarnivalPackets.UpdatePersonalCP(chr));
this.announce(CarnivalPackets.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(Player player, FieldItem 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 (Player chr : pty.getMembers()) {
if (chr.getStat().getCurrentHp() > 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(Player player) {
int cpLoss = Math.min(player.getAvailableCP(), MonsterCarnival.CP_LOSS_ON_DEATH);
this.announce(CarnivalPackets.PlayerDiedMessage(player, cpLoss));
this.loseCP(player, cpLoss);
player.getStat().addMPHP(30000, 30000);
player.changeMap(this.getMap(MCMaps.RESURRECT), this.getMap(MCMaps.RESURRECT).getPortal(0));
player.getClient().getSession().write(PacketCreator.GetClock(getTimeRemaining()));
player.getClient().getSession().write(CarnivalPackets.StartMonsterCarnival(player));
}
public void onPlayerDisconnected(Player 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(Player chr, int num) {
if (this.battlefield != null) {
battlefield.addSpawn(chr, num);
} else {
MCTracker.log("[MCPQ] Summoning guardian with null battlefield.");
}
}
public void onUseSkill(Player chr, int num) {
if (this.battlefield != null) {
battlefield.useSkill(chr, num);
} else {
MCTracker.log("[MCPQ] Summoning guardian with null battlefield.");
}
}
public void onGuardianSummon(Player chr, int num) {
if (this.battlefield != null) {
battlefield.spawnGuardian(chr, num);
} else {
MCTracker.log("[MCPQ] Summoning guardian with null battlefield.");
}
}
public void onGuardianHit(Player chr, MapleReactor reactor) {
if (this.battlefield != null) {
battlefield.onGuardianHit(chr, reactor);
} else {
MCTracker.log("[MCPQ] Hitting reactor with null battlefield.");
}
}
public void onRevive(Player player) {
MCTeam team = player.getMCPQTeam();
Portal portal;
if (team == MCTeam.RED) {
portal = getMap(MCMaps.BATTLEFIELD).getPortal("red_revive");
} else {
portal = getMap(MCMaps.BATTLEFIELD).getPortal("blue_revive");
}
player.changeMap(getMap(MCMaps.BATTLEFIELD), portal);
player.getClient().getSession().write(PacketCreator.GetClock(getTimeRemaining()));
player.getClient().getSession().write(CarnivalPackets.StartMonsterCarnival(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 Field 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 Field 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;
Field 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.
* @param field
*/
public ValidateLobbyTask(MCField field) {
this.field = field;
}
@Override
public void run() {
if (this.field.red == null) {
this.field.deregister(true);
return;
}
for (Player c : field.red.getMembers()) {
if (c.getMap() != field.getMap(MCMaps.LOBBY)) {
this.field.deregister(true);
return;
}
}
if (this.field.blue != null) {
for (Player 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<Player> members = Collections.unmodifiableCollection(field.red.getMembers());
for (Player c : members) {
if (c.getMap() != field.getMap(MCMaps.BATTLEFIELD) && c.getMap() != field.getMap(MCMaps.RESURRECT)) {
this.field.announce(CarnivalPackets.CarnivalLeave(MCTeam.RED.code, c.getName()));
red.removePlayer(c); // TODO: fix concurrent modification
}
if (c.getMap() == field.getMap(MCMaps.BATTLEFIELD) && !c.isAlive()) {
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 (Player c : members) {
if (c.getMap() != field.getMap(MCMaps.BATTLEFIELD) && c.getMap() != field.getMap(MCMaps.RESURRECT)) {
this.field.announce(CarnivalPackets.CarnivalLeave(MCTeam.BLUE.code, c.getName()));
blue.removePlayer(c);
}
if (c.getMap() == field.getMap(MCMaps.BATTLEFIELD) && !c.isAlive()) {
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<Player> chrs = this.host.getMembers();
for (Player 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() {
Field 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));
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package server.partyquest.mcpq;
/**
* 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;
}
}

315
docs/mcpq/blob/MCParty.java Normal file
View File

@@ -0,0 +1,315 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package server.partyquest.mcpq;
import community.MapleParty;
import community.MaplePartyCharacter;
import handling.channel.ChannelServer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import packet.creators.CarnivalPackets;
import packet.creators.PacketCreator;
import packet.transfer.write.OutPacket;
import client.player.Player;
import client.player.buffs.Disease;
import server.life.MobSkill;
import server.life.MobSkillFactory;
import server.maps.Field;
import server.partyquest.mcpq.MCField.MCTeam;
/**
*
* @author Sammy Guergachi <sguergachi at gmail.com>
*/
/**
* Provides an interface for Monster Carnival-specific party methods and variables.
*
* @author s4nta
*/
public class MCParty {
private MapleParty party;
private List<Player> characters = new ArrayList<>();
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;
Player 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<Player> members = getMembers();
for (Player chr : members) {
if (chr.getParty() == null || chr.getParty() != this.party) {
return false;
}
}
return true;
}
public int getAverageLevel() {
int sum = 0, num = 0;
for (Player chr : getMembers()) {
sum += chr.getLevel();
num += 1;
}
return sum / num;
}
public boolean checkLevels() {
if (MonsterCarnival.DEBUG) {
return true;
}
for (Player 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 (Player chr : getMembers()) {
if (chr.getClient().getChannel() != party.getLeader().getChannel()) return false;
}
return true;
}
public boolean checkMaps() {
if (MonsterCarnival.DEBUG) {
return true;
}
for (Player chr : getMembers()) {
if (chr.getMapId() != MonsterCarnival.MAP_LOBBY) return false;
}
return true;
}
public void warp(int map) {
for (Player chr : getMembers()) {
chr.changeMap(map);
}
}
public void warp(Field map) {
for (Player chr : getMembers()) {
chr.changeMap(map, map.getPortal(0));
}
}
public void warp(Field map, String portal) {
for (Player chr : getMembers()) {
chr.changeMap(map, map.getPortal(portal));
}
}
public void warp(MCField.MCMaps type) {
Field m = this.field.getMap(type);
for (Player chr : getMembers()) {
chr.changeMap(m, m.getPortal(0));
}
}
public void clock(int secs) {
for (Player chr : getMembers()) {
chr.getClient().announce(PacketCreator.GetClock(secs));
}
}
public void notice(String msg) {
broadcast(PacketCreator.ServerNotice(6, msg));
}
public void broadcast(OutPacket pkt) {
for (Player 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(Player 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 (Player 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(Player 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 (Player chr : getMembers()) {
MCParty.deregisterPlayer(chr);
chr.changeMap(MonsterCarnival.MAP_EXIT);
}
}
public void removePlayer(Player chr) {
characters.remove(chr);
deregisterPlayer(chr);
}
public void startBattle() {
for (Player chr : characters) {
chr.getClient().getSession().write(CarnivalPackets.StartMonsterCarnival(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());
Disease disease = Disease.getType(skill.getMobSkillID());
if (disease == null) {
disease = Disease.DARKNESS;
s = MobSkillFactory.getMobSkill(121, 6); // HACK: darkness
} else if (disease == Disease.POISON) {
return false;
}
// We only target players on the battlefield map.
if (skill.getTarget() == 2) {
for (Player chr : getMembers()) {
if (MonsterCarnival.isBattlefieldMap(chr.getMapId())) {
chr.giveDebuff(disease, s);
}
}
return true;
} else {
if (getRandomMember() != null) {
getRandomMember().giveDebuff(disease, 1, 30000L, disease.getDisease(), 1);
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<Player> getMembers() {
return this.characters;
}
public Player getRandomMember() {
List<Player> chrsOnMap = new ArrayList<>();
for (Player 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;
}
}

View File

@@ -0,0 +1,61 @@
package server.partyquest.mcpq;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
* 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;
}
}

View File

@@ -0,0 +1,107 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package server.partyquest.mcpq;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import provider.MapleData;
import provider.MapleDataProvider;
import provider.MapleDataProviderFactory;
import provider.MapleDataTool;
/**
*
* @author Sammy Guergachi <sguergachi at gmail.com>
*/
public class MCSkillFactory {
private static Map<Integer, MCSkill> mcSkills = new HashMap<>();
private static Map<Integer, MCGuardian> mcGuardians = new HashMap<>();
private static MapleDataProvider dataSource = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("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();
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package server.partyquest.mcpq;
import org.slf4j.LoggerFactory;
/**
* Logs various errors and also keeps data on Carnival PQ runs.
* @author s4nta
*/
public class MCTracker {
static org.slf4j.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);
}
}

View File

@@ -0,0 +1,153 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package server.partyquest.mcpq;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import provider.MapleData;
import provider.MapleDataTool;
/**
* Representation of the data tree inside of the Map.wz file.
*
* @author s4nta
*/
public class MCWZData {
public List<MCMobGenPos> mobGenPosList = new ArrayList<>();
public List<MCGuardianGenPos> guardianGenPosList = new ArrayList<>();
public Map<Integer, MCSummonMob> summons = new HashMap<>();
public Map<Integer, Integer> skills = new HashMap<>();
public Map<Integer, Integer> guardians = new HashMap<>();
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;
}
}
}

View File

@@ -0,0 +1,360 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package server.partyquest.mcpq;
import community.MapleParty;
import handling.channel.ChannelServer;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.slf4j.LoggerFactory;
import client.player.Player;
import client.player.buffs.Disease;
/**
*
* @author Sammy Guergachi <sguergachi at gmail.com>
*/
/**
* 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 org.slf4j.Logger log = LoggerFactory.getLogger(MonsterCarnival.class);
// Map of channel to a MonsterCarnival instance.
private static final HashMap<Integer, MonsterCarnival> instances = new HashMap<>();
/**
* 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.
* @param channel
* @return
*/
public static MonsterCarnival getMonsterCarnival(int channel) {
// TODO: synchronization?
if (channel < 1 || channel > 20) {
log.warn("Attempting to get a Monster Carnival instance for invalid channel.");
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<>();
/**
* 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();
}
/**
* 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(Player 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("Welcome to the #bCarnival PQ#k! Rooms 1-4 can hold 2-4 people, and rooms 5-6 can hold 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 Disease 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 >= 2 && size <= 4;
return size >= 1 && size <= 4;
case 5:
case 6:
// return size >= 3 && size <= 6;
return size >= 1 && 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 Disease[] DEBUFFS = {Disease.STUN, Disease.DARKNESS, Disease.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;
}