/* * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ package quests.Q350_EnhanceYourWeapon; import java.io.File; import java.util.StringTokenizer; import java.util.logging.Level; import javax.xml.parsers.DocumentBuilderFactory; import javolution.util.FastList; import javolution.util.FastMap; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import com.l2jserver.Config; import com.l2jserver.gameserver.model.L2ItemInstance; import com.l2jserver.gameserver.model.L2Object; import com.l2jserver.gameserver.model.L2Skill; import com.l2jserver.gameserver.model.actor.L2Attackable; import com.l2jserver.gameserver.model.actor.L2Attackable.AbsorberInfo; import com.l2jserver.gameserver.model.actor.L2Npc; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.quest.Quest; import com.l2jserver.gameserver.model.quest.QuestState; import com.l2jserver.gameserver.model.quest.State; import com.l2jserver.gameserver.network.SystemMessageId; import com.l2jserver.gameserver.network.serverpackets.InventoryUpdate; import com.l2jserver.gameserver.network.serverpackets.SystemMessage; import com.l2jserver.util.Rnd; public class Q350_EnhanceYourWeapon extends Quest { private static final String qn = "350_EnhanceYourWeapon"; private static final int[] STARTING_NPCS = { 30115, 30856, 30194 }; private static final int RED_SOUL_CRYSTAL0_ID = 4629; private static final int GREEN_SOUL_CRYSTAL0_ID = 4640; private static final int BLUE_SOUL_CRYSTAL0_ID = 4651; private final FastMap _soulCrystals = new FastMap(); // > private final FastMap> _npcLevelingInfos = new FastMap>(); private static enum AbsorbCrystalType { LAST_HIT, FULL_PARTY, PARTY_ONE_RANDOM, PARTY_RANDOM } private static final class SoulCrystal { private final int _level; private final int _itemId; private final int _leveledItemId; public SoulCrystal(int level, int itemId, int leveledItemId) { _level = level; _itemId = itemId; _leveledItemId = leveledItemId; } public final int getLevel() { return _level; } public final int getItemId() { return _itemId; } public final int getLeveledItemId() { return _leveledItemId; } } private static final class LevelingInfo { private final AbsorbCrystalType _absorbCrystalType; private final boolean _isSkillNeeded; private final int _chance; public LevelingInfo(AbsorbCrystalType absorbCrystalType, boolean isSkillNeeded, int chance) { _absorbCrystalType = absorbCrystalType; _isSkillNeeded = isSkillNeeded; _chance = chance; } public final AbsorbCrystalType getAbsorbCrystalType() { return _absorbCrystalType; } public final boolean isSkillNeeded() { return _isSkillNeeded; } public final int getChance() { return _chance; } } private boolean check(QuestState st) { for (int i = 4629; i < 4665; i++) if (st.getQuestItemsCount(i) > 0) return true; return false; } private void load() { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(false); factory.setIgnoringComments(true); File file = new File(Config.DATAPACK_ROOT + "/data/scripts/quests/Q350_EnhanceYourWeapon/data.xml"); if (!file.exists()) { _log.severe("[EnhanceYourWeapon] Missing data.xml. The quest wont work without it!"); return; } Document doc = factory.newDocumentBuilder().parse(file); Node first = doc.getFirstChild(); if (first != null && "list".equalsIgnoreCase(first.getNodeName())) { for (Node n = first.getFirstChild(); n != null; n = n.getNextSibling()) { if ("crystal".equalsIgnoreCase(n.getNodeName())) { for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) { if ("item".equalsIgnoreCase(d.getNodeName())) { NamedNodeMap attrs = d.getAttributes(); Node att = attrs.getNamedItem("itemId"); if (att == null) { _log.severe("[EnhanceYourWeapon] Missing itemId in Crystal List, skipping"); continue; } int itemId = Integer.parseInt(attrs.getNamedItem("itemId").getNodeValue()); att = attrs.getNamedItem("level"); if (att == null) { _log.severe("[EnhanceYourWeapon] Missing level in Crystal List itemId: "+itemId+", skipping"); continue; } int level = Integer.parseInt(attrs.getNamedItem("level").getNodeValue()); att = attrs.getNamedItem("leveledItemId"); if (att == null) { _log.severe("[EnhanceYourWeapon] Missing leveledItemId in Crystal List itemId: "+itemId+", skipping"); continue; } int leveledItemId = Integer.parseInt(attrs.getNamedItem("leveledItemId").getNodeValue()); _soulCrystals.put(itemId, new SoulCrystal(level, itemId, leveledItemId)); } } } else if ("npc".equalsIgnoreCase(n.getNodeName())) { for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) { if ("item".equalsIgnoreCase(d.getNodeName())) { NamedNodeMap attrs = d.getAttributes(); Node att = attrs.getNamedItem("npcId"); if (att == null) { _log.severe("[EnhanceYourWeapon] Missing npcId in NPC List, skipping"); continue; } int npcId = Integer.parseInt(att.getNodeValue()); FastMap temp = new FastMap(); for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling()) { boolean isSkillNeeded = false; int chance = 5; AbsorbCrystalType absorbType = AbsorbCrystalType.LAST_HIT; if ("detail".equalsIgnoreCase(cd.getNodeName())) { attrs = cd.getAttributes(); att = attrs.getNamedItem("absorbType"); if (att != null) absorbType = Enum.valueOf(AbsorbCrystalType.class, att.getNodeValue()); att = attrs.getNamedItem("chance"); if (att != null) chance = Integer.parseInt(att.getNodeValue()); att = attrs.getNamedItem("skill"); if (att != null) isSkillNeeded = Boolean.parseBoolean(att.getNodeValue()); Node att1 = attrs.getNamedItem("maxLevel"); Node att2 = attrs.getNamedItem("levelList"); if (att1 == null && att2 == null) { _log.severe("[EnhanceYourWeapon] Missing maxlevel/levelList in NPC List npcId: "+npcId+", skipping"); continue; } LevelingInfo info = new LevelingInfo(absorbType, isSkillNeeded, chance); if (att1 != null) { int maxLevel = Integer.parseInt(att1.getNodeValue()); for(int i = 0; i <= maxLevel; i++) temp.put(i, info); } else { StringTokenizer st = new StringTokenizer(att2.getNodeValue(), ","); int tokenCount = st.countTokens(); for (int i=0; i < tokenCount; i++) { Integer value = Integer.decode(st.nextToken().trim()); if (value == null) { _log.severe("[EnhanceYourWeapon] Bad Level value!! npcId: "+npcId+ " token: "+ i); value = 0; } temp.put(value, info); } } } } if (temp.isEmpty()) { _log.severe("[EnhanceYourWeapon] No leveling info for npcId: "+npcId+", skipping"); continue; } _npcLevelingInfos.put(npcId, temp); } } } } } } catch(Exception e) { _log.log(Level.WARNING, "[EnhanceYourWeapon] Could not parse data.xml file: " + e.getMessage(), e); } _log.info("[EnhanceYourWeapon] Loaded " + _soulCrystals.size() + " Soul Crystal data."); _log.info("[EnhanceYourWeapon] Loaded " + _npcLevelingInfos.size() + " npc Leveling info data."); } public Q350_EnhanceYourWeapon(int questId, String name, String descr) { super(questId, name, descr); for(int npcId : STARTING_NPCS) { addStartNpc(npcId); addTalkId(npcId); } load(); for(int npcId : _npcLevelingInfos.keySet()) { addSkillSeeId(npcId); addKillId(npcId); } } @Override public String onSkillSee (L2Npc npc, L2PcInstance caster, L2Skill skill, L2Object[] targets, boolean isPet) { super.onSkillSee(npc, caster, skill, targets, isPet); if (skill == null || skill.getId() != 2096) return null; else if (caster == null || caster.isDead()) return null; if (!(npc instanceof L2Attackable) || npc.isDead() || !_npcLevelingInfos.containsKey(npc.getNpcId())) return null; try { ((L2Attackable)npc).addAbsorber(caster); } catch (Exception e) { _log.log(Level.SEVERE, "", e); } return null; } @Override public String onKill (L2Npc npc, L2PcInstance killer, boolean isPet) { if (npc instanceof L2Attackable && _npcLevelingInfos.containsKey(npc.getNpcId())) levelSoulCrystals((L2Attackable)npc, killer); return null; } @Override public String onAdvEvent (String event, L2Npc npc, L2PcInstance player) { String htmltext = event; QuestState st = player.getQuestState(qn); if (event.endsWith("-04.htm")) { st.set("cond","1"); st.setState(State.STARTED); st.playSound("ItemSound.quest_accept"); } else if (event.endsWith("-09.htm")) st.giveItems(RED_SOUL_CRYSTAL0_ID,1); else if (event.endsWith("-10.htm")) st.giveItems(GREEN_SOUL_CRYSTAL0_ID,1); else if (event.endsWith("-11.htm")) st.giveItems(BLUE_SOUL_CRYSTAL0_ID,1); else if (event.equalsIgnoreCase("exit.htm")) st.exitQuest(true); return htmltext; } @Override public String onTalk(L2Npc npc,L2PcInstance player) { String htmltext = getNoQuestMsg(player); QuestState st = player.getQuestState(qn); if (st == null) return htmltext; if (st.getState() == State.CREATED) st.set("cond","0"); if (st.getInt("cond") == 0) htmltext = npc.getNpcId() + "-01.htm"; else if (check(st)) htmltext = npc.getNpcId() + "-03.htm"; else if (st.getQuestItemsCount(RED_SOUL_CRYSTAL0_ID) == 0 && st.getQuestItemsCount(GREEN_SOUL_CRYSTAL0_ID) == 0 && st.getQuestItemsCount(BLUE_SOUL_CRYSTAL0_ID) == 0) htmltext = npc.getNpcId() + "-21.htm"; return htmltext; } public static void main(String[] args) { new Q350_EnhanceYourWeapon(350, qn, "Enhance Your Weapon"); } /** * Calculate the leveling chance of Soul Crystals based on the attacker that killed this L2Attackable * * @param attacker The player that last killed this L2Attackable * $ Rewrite 06.12.06 - Yesod * $ Rewrite 08.01.10 - Gigiikun */ public void levelSoulCrystals(L2Attackable mob, L2PcInstance killer) { // Only L2PcInstance can absorb a soul if (killer == null) { mob.resetAbsorbList(); return; } FastMap players = FastMap.newInstance(); int maxSCLevel = 0; //TODO: what if mob support last_hit + party? if (isPartyLevelingMonster(mob.getNpcId()) && killer.getParty() != null) { // firts get the list of players who has one Soul Cry and the quest for (L2PcInstance pl : killer.getParty().getPartyMembers()) { if (pl == null) continue; SoulCrystal sc = getSCForPlayer(pl); if (sc == null) continue; players.put(pl, sc); if (maxSCLevel < sc.getLevel() && _npcLevelingInfos.get(mob.getNpcId()).containsKey(sc.getLevel())) maxSCLevel = sc.getLevel(); } } else { SoulCrystal sc = getSCForPlayer(killer); if (sc != null) { players.put(killer, sc); if (maxSCLevel < sc.getLevel() && _npcLevelingInfos.get(mob.getNpcId()).containsKey(sc.getLevel())) maxSCLevel = sc.getLevel(); } } //Init some useful vars LevelingInfo mainlvlInfo = _npcLevelingInfos.get(mob.getNpcId()).get(maxSCLevel); if (mainlvlInfo == null) /*throw new NullPointerException("Target: "+mob+ " player: "+killer+" level: "+maxSCLevel);*/ return; // If this mob is not require skill, then skip some checkings if (mainlvlInfo.isSkillNeeded()) { // Fail if this L2Attackable isn't absorbed or there's no one in its _absorbersList if (!mob.isAbsorbed() /*|| _absorbersList == null*/) { mob.resetAbsorbList(); return; } // Fail if the killer isn't in the _absorbersList of this L2Attackable and mob is not boss AbsorberInfo ai = mob.getAbsorbersList().get(killer.getObjectId()); boolean isSuccess = true; if (ai == null || ai._objId != killer.getObjectId()) isSuccess = false; // Check if the soul crystal was used when HP of this L2Attackable wasn't higher than half of it if (ai != null && ai._absorbedHP > (mob.getMaxHp()/2.0)) isSuccess = false; if (!isSuccess) { mob.resetAbsorbList(); return; } } switch (mainlvlInfo.getAbsorbCrystalType()) { case PARTY_ONE_RANDOM: // This is a naive method for selecting a random member. It gets any random party member and // then checks if the member has a valid crystal. It does not select the random party member // among those who have crystals, only. However, this might actually be correct (same as retail). if (killer.getParty() != null) { L2PcInstance lucky = killer.getParty().getPartyMembers().get(Rnd.get(killer.getParty().getMemberCount())); tryToLevelCrystal(lucky, players.get(lucky), mob); } else tryToLevelCrystal(killer, players.get(killer), mob); break; case PARTY_RANDOM: if (killer.getParty() != null) { FastList luckyParty = FastList.newInstance(); luckyParty.addAll(killer.getParty().getPartyMembers()); while (Rnd.get(100) < 33 && !luckyParty.isEmpty()) { L2PcInstance lucky = luckyParty.remove(Rnd.get(luckyParty.size())); if (players.containsKey(lucky)) tryToLevelCrystal(lucky, players.get(lucky), mob); } FastList.recycle(luckyParty); } else if (Rnd.get(100) < 33) tryToLevelCrystal(killer, players.get(killer), mob); break; case FULL_PARTY: if (killer.getParty() != null) for(L2PcInstance pl : killer.getParty().getPartyMembers()) tryToLevelCrystal(pl, players.get(pl), mob); else tryToLevelCrystal(killer, players.get(killer), mob); break; case LAST_HIT: tryToLevelCrystal(killer, players.get(killer), mob); break; } FastMap.recycle(players); } private boolean isPartyLevelingMonster(int npcId) { for (LevelingInfo li : _npcLevelingInfos.get(npcId).values()) { if (li.getAbsorbCrystalType() != AbsorbCrystalType.LAST_HIT) return true; } return false; } private SoulCrystal getSCForPlayer(L2PcInstance player) { QuestState st = player.getQuestState(qn); if (st == null || st.getState() != State.STARTED) return null; L2ItemInstance[] inv = player.getInventory().getItems(); SoulCrystal ret = null; for (L2ItemInstance item : inv) { int itemId = item.getItemId(); if (!_soulCrystals.containsKey(itemId)) continue; if (ret != null) return null; else ret = _soulCrystals.get(itemId); } return ret; } private void tryToLevelCrystal(L2PcInstance player, SoulCrystal sc, L2Attackable mob) { if (sc == null || !_npcLevelingInfos.containsKey(mob.getNpcId())) return; // If the crystal level is way too high for this mob, say that we can't increase it if (!_npcLevelingInfos.get(mob.getNpcId()).containsKey(sc.getLevel())) { player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_REFUSED)); return; } if (Rnd.get(100) <= _npcLevelingInfos.get(mob.getNpcId()).get(sc.getLevel()).getChance()) exchangeCrystal(player, mob, sc.getItemId(), sc.getLeveledItemId(), false); else player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_FAILED)); } private void exchangeCrystal(L2PcInstance player, L2Attackable mob, int takeid, int giveid, boolean broke) { L2ItemInstance Item = player.getInventory().destroyItemByItemId("SoulCrystal", takeid, 1, player, mob); if (Item != null) { // Prepare inventory update packet InventoryUpdate playerIU = new InventoryUpdate(); playerIU.addRemovedItem(Item); // Add new crystal to the killer's inventory Item = player.getInventory().addItem("SoulCrystal", giveid, 1, player, mob); playerIU.addItem(Item); // Send a sound event and text message to the player if (broke) player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SOUL_CRYSTAL_BROKE)); else player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_SUCCEEDED)); // Send system message SystemMessage sms = SystemMessage.getSystemMessage(SystemMessageId.EARNED_ITEM_S1); sms.addItemName(giveid); player.sendPacket(sms); // Send inventory update packet player.sendPacket(playerIU); } } }