package server.quest; import client.MapleCharacter; import client.MapleQuestStatus; import constants.GameConstants; import database.DatabaseConnection; import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import scripting.NPCScriptManager; import server.farm.MapleFarmQuestRequirement; import tools.Pair; import tools.StringUtil; import tools.packet.CField; import tools.packet.CField.EffectPacket; public class MapleQuest implements Serializable { private static final long serialVersionUID = 9179541993413738569L; private static final Map<Integer, MapleQuest> quests = new LinkedHashMap<>(); private static final Map<Integer, MapleQuest> farmQuests = new LinkedHashMap<>(); protected int id; protected final List<MapleQuestRequirement> startReqs = new LinkedList<>(); protected final List<MapleQuestRequirement> completeReqs = new LinkedList<>(); protected final List<MapleFarmQuestRequirement> farmStartReqs = new LinkedList<>(); protected final List<MapleFarmQuestRequirement> farmCompleteReqs = new LinkedList<>(); protected final List<MapleQuestAction> startActs = new LinkedList<>(); protected final List<MapleQuestAction> completeActs = new LinkedList<>(); protected final Map<String, List<Pair<String, Pair<String, Integer>>>> partyQuestInfo = new LinkedHashMap<>(); //[rank, [more/less/equal, [property, value]]] protected final Map<Integer, Integer> relevantMobs = new LinkedHashMap<>(); private boolean autoStart = false, autoPreComplete = false, repeatable = false, customend = false, blocked = false, autoAccept = false, autoComplete = false, scriptedStart = false; private int viewMedalItem = 0, selectedSkillID = 0; protected String name = ""; protected MapleQuest(final int id) { this.id = id; } private static MapleQuest loadQuest(ResultSet rs, PreparedStatement psr, PreparedStatement psa, PreparedStatement pss, PreparedStatement psq, PreparedStatement psi, PreparedStatement psp) throws SQLException { final MapleQuest ret = new MapleQuest(rs.getInt("questid")); ret.name = rs.getString("name"); ret.autoStart = rs.getInt("autoStart") > 0; ret.autoPreComplete = rs.getInt("autoPreComplete") > 0; ret.autoAccept = rs.getInt("autoAccept") > 0; ret.autoComplete = rs.getInt("autoComplete") > 0; ret.viewMedalItem = rs.getInt("viewMedalItem"); ret.selectedSkillID = rs.getInt("selectedSkillID"); ret.blocked = rs.getInt("blocked") > 0; //ult.explorer quests will dc as the item isn't there... psr.setInt(1, ret.id); ResultSet rse = psr.executeQuery(); while (rse.next()) { final MapleQuestRequirementType type = MapleQuestRequirementType.getByWZName(rse.getString("name")); final MapleQuestRequirement req = new MapleQuestRequirement(ret, type, rse); if (type.equals(MapleQuestRequirementType.interval)) { ret.repeatable = true; } else if (type.equals(MapleQuestRequirementType.normalAutoStart)) { ret.repeatable = true; ret.autoStart = true; } else if (type.equals(MapleQuestRequirementType.startscript)) { ret.scriptedStart = true; } else if (type.equals(MapleQuestRequirementType.endscript)) { ret.customend = true; } else if (type.equals(MapleQuestRequirementType.mob)) { for (Pair<Integer, Integer> mob : req.getDataStore()) { ret.relevantMobs.put(mob.left, mob.right); } } if (rse.getInt("type") == 0) { ret.startReqs.add(req); } else { ret.completeReqs.add(req); } } rse.close(); psa.setInt(1, ret.id); rse = psa.executeQuery(); while (rse.next()) { final MapleQuestActionType ty = MapleQuestActionType.getByWZName(rse.getString("name")); if (rse.getInt("type") == 0) { //pass it over so it will set ID + type once done if (ty == MapleQuestActionType.item && ret.id == 7103) { //pap glitch continue; } ret.startActs.add(new MapleQuestAction(ty, rse, ret, pss, psq, psi)); } else { if (ty == MapleQuestActionType.item && ret.id == 7102) { //pap glitch continue; } ret.completeActs.add(new MapleQuestAction(ty, rse, ret, pss, psq, psi)); } } rse.close(); psp.setInt(1, ret.id); rse = psp.executeQuery(); while (rse.next()) { if (!ret.partyQuestInfo.containsKey(rse.getString("rank"))) { ret.partyQuestInfo.put(rse.getString("rank"), new ArrayList<Pair<String, Pair<String, Integer>>>()); } ret.partyQuestInfo.get(rse.getString("rank")).add(new Pair<>(rse.getString("mode"), new Pair<>(rse.getString("property"), rse.getInt("value")))); } rse.close(); return ret; } private static MapleQuest loadFarmQuest(ResultSet rs, PreparedStatement psr) throws SQLException { final MapleQuest ret = new MapleQuest(rs.getInt("questid")); ret.name = rs.getString("name"); ret.repeatable = rs.getInt("repeatable") > 0; psr.setInt(1, ret.id); try (ResultSet rse = psr.executeQuery()) { while (rse.next()) { final MapleFarmQuestRequirement req = new MapleFarmQuestRequirement(ret, ret.repeatable, rse); if (rse.getInt("type") == 0) { ret.farmStartReqs.add(req); } else { ret.farmCompleteReqs.add(req); } } } return ret; } public List<Pair<String, Pair<String, Integer>>> getInfoByRank(final String rank) { return partyQuestInfo.get(rank); } public boolean isPartyQuest() { return partyQuestInfo.size() > 0; } public final int getSkillID() { return selectedSkillID; } public final String getName() { return name; } public final List<MapleQuestAction> getCompleteActs() { return completeActs; } public static void initQuests() { try { Connection con = DatabaseConnection.getConnection(); PreparedStatement psr; PreparedStatement psa; PreparedStatement pss; PreparedStatement psq; PreparedStatement psi; PreparedStatement psp; try (PreparedStatement ps = con.prepareStatement("SELECT * FROM wz_questdata")) { psr = con.prepareStatement("SELECT * FROM wz_questreqdata WHERE questid = ?"); psa = con.prepareStatement("SELECT * FROM wz_questactdata WHERE questid = ?"); pss = con.prepareStatement("SELECT * FROM wz_questactskilldata WHERE uniqueid = ?"); psq = con.prepareStatement("SELECT * FROM wz_questactquestdata WHERE uniqueid = ?"); psi = con.prepareStatement("SELECT * FROM wz_questactitemdata WHERE uniqueid = ?"); psp = con.prepareStatement("SELECT * FROM wz_questpartydata WHERE questid = ?"); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { quests.put(rs.getInt("questid"), loadQuest(rs, psr, psa, pss, psq, psi, psp)); } } } try (PreparedStatement ps = con.prepareStatement("SELECT * FROM wz_farmquestdata")) { psr = con.prepareStatement("SELECT * FROM wz_farmquestreqdata WHERE questid = ?"); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { farmQuests.put(rs.getInt("questid"), loadFarmQuest(rs, psr)); } } } psr.close(); psa.close(); pss.close(); psq.close(); psi.close(); psp.close(); } catch (SQLException e) { } } public static MapleQuest getInstance(int id) { MapleQuest ret = quests.get(id); if (ret == null) { ret = new MapleQuest(id); quests.put(id, ret); //by this time we have already initialized } return ret; } public static MapleQuest getFarmInstance(int id) { MapleQuest ret = farmQuests.get(id); if (ret == null) { ret = new MapleQuest(id); farmQuests.put(id, ret); //by this time we have already initialized } return ret; } public static Collection<MapleQuest> getAllInstances() { return quests.values(); } public static Collection<MapleQuest> getAllFarmInstances() { return farmQuests.values(); } public boolean canStart(MapleCharacter c, Integer npcid) { if (c.getQuest(this).getStatus() != 0 && !(c.getQuest(this).getStatus() == 2 && repeatable)) { return false; } if (blocked && !c.isGM()) { return false; } //if (autoAccept) { // return true; //need script //} for (MapleQuestRequirement r : startReqs) { if (r.getType() == MapleQuestRequirementType.dayByDay && npcid != null) { //everyday. we don't want ok forceComplete(c, npcid); return false; } if (!r.check(c, npcid)) { return false; } } return true; } public boolean canComplete(MapleCharacter c, Integer npcid) { if (c.getQuest(this).getStatus() != 1) { return false; } if (blocked && !c.isGM()) { return false; } if (autoComplete && npcid != null && viewMedalItem <= 0) { forceComplete(c, npcid); return false; //skip script } for (MapleQuestRequirement r : completeReqs) { if (!r.check(c, npcid)) { return false; } } return true; } public boolean canCompleteFarm(MapleCharacter c) { if (c.getQuest(this).getStatus() != 1) { return false; } for (MapleFarmQuestRequirement r : farmCompleteReqs) { if (!r.check(c, repeatable)) { return false; } } return true; } public final void RestoreLostItem(final MapleCharacter c, final int itemid) { if (blocked && !c.isGM()) { return; } for (final MapleQuestAction a : startActs) { if (a.RestoreLostItem(c, itemid)) { break; } } } public void start(MapleCharacter c, int npc) { if ((autoStart || checkNPCOnMap(c, npc)) && canStart(c, npc)) { for (MapleQuestAction a : startActs) { if (!a.checkEnd(c, null)) { //just in case return; } } for (MapleQuestAction a : startActs) { a.runStart(c, null); } if (!customend) { forceStart(c, npc, null); } else { NPCScriptManager.getInstance().startQuest(c.getClient(), npc, getId()); } } } public void complete(MapleCharacter c, int npc) { complete(c, npc, null); } public void complete(MapleCharacter c, int npc, Integer selection) { if (c.getMap() != null && (autoPreComplete || checkNPCOnMap(c, npc)) && canComplete(c, npc)) { for (MapleQuestAction a : completeActs) { if (!a.checkEnd(c, selection)) { return; } } forceComplete(c, npc); for (MapleQuestAction a : completeActs) { a.runEnd(c, selection); } // we save forfeits only for logging purposes, they shouldn't matter anymore // completion time is set by the constructor c.getClient().getSession().write(EffectPacket.showForeignEffect(12)); // Quest completion c.getMap().broadcastMessage(c, EffectPacket.showForeignEffect(c.getId(), 12), false); } } public void forfeit(MapleCharacter c) { if (c.getQuest(this).getStatus() != (byte) 1) { return; } final MapleQuestStatus oldStatus = c.getQuest(this); final MapleQuestStatus newStatus = new MapleQuestStatus(this, (byte) 0); newStatus.setForfeited(oldStatus.getForfeited() + 1); newStatus.setCompletionTime(oldStatus.getCompletionTime()); c.updateQuest(newStatus); } public void forceStart(MapleCharacter c, int npc, String customData) { final MapleQuestStatus newStatus = new MapleQuestStatus(this, (byte) 1, npc); newStatus.setForfeited(c.getQuest(this).getForfeited()); newStatus.setCompletionTime(c.getQuest(this).getCompletionTime()); newStatus.setCustomData(customData); c.updateQuest(newStatus); } public void forceStartHillaGang(List<MapleCharacter> party, int npc, String customData) { for (MapleCharacter chr : party) { final MapleQuestStatus newStatus = new MapleQuestStatus(this, (byte) 1, npc); newStatus.setForfeited(chr.getQuest(this).getForfeited()); newStatus.setCompletionTime(chr.getQuest(this).getCompletionTime()); newStatus.setCustomData(customData); chr.updateQuest(newStatus); } } public void forceComplete(MapleCharacter c, int npc) { final MapleQuestStatus newStatus = new MapleQuestStatus(this, (byte) 2, npc); newStatus.setForfeited(c.getQuest(this).getForfeited()); c.updateQuest(newStatus); } public int getId() { return id; } public Map<Integer, Integer> getRelevantMobs() { return relevantMobs; } private boolean checkNPCOnMap(MapleCharacter player, int npcid) { //mir = 1013000 return (GameConstants.isEvan(player.getJob()) && npcid == 1013000) || npcid == 9000040 || npcid == 9000066 || (player.getMap() != null && player.getMap().containsNPC(npcid)); } public int getMedalItem() { return viewMedalItem; } public boolean isBlocked() { return blocked; } public void socomplete(MapleCharacter c, int npc) { for (MapleQuestAction a : this.completeActs) { if (!a.checkEnd(c, null)) { return; } } forceComplete(c, npc); for (MapleQuestAction a : this.completeActs) { a.runEnd(c, null); } c.getClient().getSession().write(CField.EffectPacket.showForeignEffect(12)); c.getMap().broadcastMessage(c, CField.EffectPacket.showForeignEffect(c.getId(), 12), false); } public static enum MedalQuest { Beginner(29005, 29015, 15, new int[]{100000000, 100020400, 100040000, 101000000, 101020300, 101040300, 102000000, 102020500, 102030400, 102040200, 103000000, 103020200, 103030400, 103040000, 104000000, 104020000, 106020100, 120000000, 120020400, 120030000}), ElNath(29006, 29012, 50, new int[]{200000000, 200010100, 200010300, 200080000, 200080100, 211000000, 211030000, 211040300, 211041200, 211041800}), LudusLake(29007, 29012, 40, new int[]{222000000, 222010400, 222020000, 220000000, 220020300, 220040200, 221020701, 221000000, 221030600, 221040400}), Underwater(29008, 29012, 40, new int[]{230000000, 230010400, 230010200, 230010201, 230020000, 230020201, 230030100, 230040000, 230040200, 230040400}), MuLung(29009, 29012, 50, new int[]{251000000, 251010200, 251010402, 251010500, 250010500, 250010504, 250000000, 250010300, 250010304, 250020300}), NihalDesert(29010, 29012, 70, new int[]{261030000, 261020401, 261020000, 261010100, 261000000, 260020700, 260020300, 260000000, 260010600, 260010300}), MinarForest(29011, 29012, 70, new int[]{240000000, 240010200, 240010800, 240020401, 240020101, 240030000, 240040400, 240040511, 240040521, 240050000}), Sleepywood(29014, 29015, 50, new int[]{105000000, 105000000, 105010100, 105020100, 105020300, 105030000, 105030100, 105030300, 105030500, 105030500}); //repeated map public int questid, level, lquestid; public int[] maps; private MedalQuest(int questid, int lquestid, int level, int[] maps) { this.questid = questid; //infoquest = questid -2005, customdata = questid -1995 this.level = level; this.lquestid = lquestid; this.maps = maps; //note # of maps } } public boolean hasStartScript() { return scriptedStart; } public boolean hasEndScript() { return customend; } public static String formatInfoString(Map<String, Object> args) { String result = ""; int i = 0; for (Entry<String, Object> arg : args.entrySet()) { result += arg.getKey(); result += '='; result += String.valueOf(arg.getValue()); if (args.size() - 1 > i) { result += ";"; } i++; } return result; } public static Map<String, Object> decodeInfoString(String args) { Map<String, Object> result = new LinkedHashMap(); for (String arg : args.split(";")) { String[] entry = arg.split("="); String key = entry[0]; String value = entry[1]; if (StringUtil.isNumber(entry[1])) { try { result.put(key, Integer.parseInt(value)); continue; } catch (Exception ex) { } } result.put(key, value); } return result; } }