Q350_EnhanceYourWeapon.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. /*
  2. * This program is free software: you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation, either version 3 of the License, or (at your option) any later
  5. * version.
  6. *
  7. * This program is distributed in the hope that it will be useful, but WITHOUT
  8. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  9. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  10. * details.
  11. *
  12. * You should have received a copy of the GNU General Public License along with
  13. * this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. package quests.Q350_EnhanceYourWeapon;
  16. import java.io.File;
  17. import java.util.StringTokenizer;
  18. import java.util.logging.Level;
  19. import javax.xml.parsers.DocumentBuilderFactory;
  20. import javolution.util.FastList;
  21. import javolution.util.FastMap;
  22. import org.w3c.dom.Document;
  23. import org.w3c.dom.NamedNodeMap;
  24. import org.w3c.dom.Node;
  25. import com.l2jserver.Config;
  26. import com.l2jserver.gameserver.model.L2ItemInstance;
  27. import com.l2jserver.gameserver.model.L2Object;
  28. import com.l2jserver.gameserver.model.L2Skill;
  29. import com.l2jserver.gameserver.model.actor.L2Attackable;
  30. import com.l2jserver.gameserver.model.actor.L2Attackable.AbsorberInfo;
  31. import com.l2jserver.gameserver.model.actor.L2Npc;
  32. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  33. import com.l2jserver.gameserver.model.quest.Quest;
  34. import com.l2jserver.gameserver.model.quest.QuestState;
  35. import com.l2jserver.gameserver.model.quest.State;
  36. import com.l2jserver.gameserver.network.SystemMessageId;
  37. import com.l2jserver.gameserver.network.serverpackets.InventoryUpdate;
  38. import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
  39. import com.l2jserver.util.Rnd;
  40. public class Q350_EnhanceYourWeapon extends Quest
  41. {
  42. private static final String qn = "350_EnhanceYourWeapon";
  43. private static final int[] STARTING_NPCS = { 30115, 30856, 30194 };
  44. private static final int RED_SOUL_CRYSTAL0_ID = 4629;
  45. private static final int GREEN_SOUL_CRYSTAL0_ID = 4640;
  46. private static final int BLUE_SOUL_CRYSTAL0_ID = 4651;
  47. private final FastMap<Integer, SoulCrystal> _soulCrystals = new FastMap<Integer, SoulCrystal>();
  48. // <npcid, <level, LevelingInfo>>
  49. private final FastMap<Integer, FastMap<Integer, LevelingInfo>> _npcLevelingInfos = new FastMap<Integer, FastMap<Integer, LevelingInfo>>();
  50. private static enum AbsorbCrystalType
  51. {
  52. LAST_HIT,
  53. FULL_PARTY,
  54. PARTY_ONE_RANDOM,
  55. PARTY_RANDOM
  56. }
  57. private static final class SoulCrystal
  58. {
  59. private final int _level;
  60. private final int _itemId;
  61. private final int _leveledItemId;
  62. public SoulCrystal(int level, int itemId, int leveledItemId)
  63. {
  64. _level = level;
  65. _itemId = itemId;
  66. _leveledItemId = leveledItemId;
  67. }
  68. public final int getLevel()
  69. {
  70. return _level;
  71. }
  72. public final int getItemId()
  73. {
  74. return _itemId;
  75. }
  76. public final int getLeveledItemId()
  77. {
  78. return _leveledItemId;
  79. }
  80. }
  81. private static final class LevelingInfo
  82. {
  83. private final AbsorbCrystalType _absorbCrystalType;
  84. private final boolean _isSkillNeeded;
  85. private final int _chance;
  86. public LevelingInfo(AbsorbCrystalType absorbCrystalType, boolean isSkillNeeded, int chance)
  87. {
  88. _absorbCrystalType = absorbCrystalType;
  89. _isSkillNeeded = isSkillNeeded;
  90. _chance = chance;
  91. }
  92. public final AbsorbCrystalType getAbsorbCrystalType()
  93. {
  94. return _absorbCrystalType;
  95. }
  96. public final boolean isSkillNeeded()
  97. {
  98. return _isSkillNeeded;
  99. }
  100. public final int getChance()
  101. {
  102. return _chance;
  103. }
  104. }
  105. private boolean check(QuestState st)
  106. {
  107. for (int i = 4629; i < 4665; i++)
  108. if (st.getQuestItemsCount(i) > 0)
  109. return true;
  110. return false;
  111. }
  112. private void load()
  113. {
  114. try
  115. {
  116. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  117. factory.setValidating(false);
  118. factory.setIgnoringComments(true);
  119. File file = new File(Config.DATAPACK_ROOT + "/data/scripts/quests/Q350_EnhanceYourWeapon/data.xml");
  120. if (!file.exists())
  121. {
  122. _log.severe("[EnhanceYourWeapon] Missing data.xml. The quest wont work without it!");
  123. return;
  124. }
  125. Document doc = factory.newDocumentBuilder().parse(file);
  126. Node first = doc.getFirstChild();
  127. if (first != null && "list".equalsIgnoreCase(first.getNodeName()))
  128. {
  129. for (Node n = first.getFirstChild(); n != null; n = n.getNextSibling())
  130. {
  131. if ("crystal".equalsIgnoreCase(n.getNodeName()))
  132. {
  133. for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
  134. {
  135. if ("item".equalsIgnoreCase(d.getNodeName()))
  136. {
  137. NamedNodeMap attrs = d.getAttributes();
  138. Node att = attrs.getNamedItem("itemId");
  139. if (att == null)
  140. {
  141. _log.severe("[EnhanceYourWeapon] Missing itemId in Crystal List, skipping");
  142. continue;
  143. }
  144. int itemId = Integer.parseInt(attrs.getNamedItem("itemId").getNodeValue());
  145. att = attrs.getNamedItem("level");
  146. if (att == null)
  147. {
  148. _log.severe("[EnhanceYourWeapon] Missing level in Crystal List itemId: "+itemId+", skipping");
  149. continue;
  150. }
  151. int level = Integer.parseInt(attrs.getNamedItem("level").getNodeValue());
  152. att = attrs.getNamedItem("leveledItemId");
  153. if (att == null)
  154. {
  155. _log.severe("[EnhanceYourWeapon] Missing leveledItemId in Crystal List itemId: "+itemId+", skipping");
  156. continue;
  157. }
  158. int leveledItemId = Integer.parseInt(attrs.getNamedItem("leveledItemId").getNodeValue());
  159. _soulCrystals.put(itemId, new SoulCrystal(level, itemId, leveledItemId));
  160. }
  161. }
  162. }
  163. else if ("npc".equalsIgnoreCase(n.getNodeName()))
  164. {
  165. for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
  166. {
  167. if ("item".equalsIgnoreCase(d.getNodeName()))
  168. {
  169. NamedNodeMap attrs = d.getAttributes();
  170. Node att = attrs.getNamedItem("npcId");
  171. if (att == null)
  172. {
  173. _log.severe("[EnhanceYourWeapon] Missing npcId in NPC List, skipping");
  174. continue;
  175. }
  176. int npcId = Integer.parseInt(att.getNodeValue());
  177. FastMap<Integer, LevelingInfo> temp = new FastMap<Integer, LevelingInfo>();
  178. for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
  179. {
  180. boolean isSkillNeeded = false;
  181. int chance = 5;
  182. AbsorbCrystalType absorbType = AbsorbCrystalType.LAST_HIT;
  183. if ("detail".equalsIgnoreCase(cd.getNodeName()))
  184. {
  185. attrs = cd.getAttributes();
  186. att = attrs.getNamedItem("absorbType");
  187. if (att != null)
  188. absorbType = Enum.valueOf(AbsorbCrystalType.class, att.getNodeValue());
  189. att = attrs.getNamedItem("chance");
  190. if (att != null)
  191. chance = Integer.parseInt(att.getNodeValue());
  192. att = attrs.getNamedItem("skill");
  193. if (att != null)
  194. isSkillNeeded = Boolean.parseBoolean(att.getNodeValue());
  195. Node att1 = attrs.getNamedItem("maxLevel");
  196. Node att2 = attrs.getNamedItem("levelList");
  197. if (att1 == null && att2 == null)
  198. {
  199. _log.severe("[EnhanceYourWeapon] Missing maxlevel/levelList in NPC List npcId: "+npcId+", skipping");
  200. continue;
  201. }
  202. LevelingInfo info = new LevelingInfo(absorbType, isSkillNeeded, chance);
  203. if (att1 != null)
  204. {
  205. int maxLevel = Integer.parseInt(att1.getNodeValue());
  206. for(int i = 0; i <= maxLevel; i++)
  207. temp.put(i, info);
  208. }
  209. else
  210. {
  211. StringTokenizer st = new StringTokenizer(att2.getNodeValue(), ",");
  212. int tokenCount = st.countTokens();
  213. for (int i=0; i < tokenCount; i++)
  214. {
  215. Integer value = Integer.decode(st.nextToken().trim());
  216. if (value == null)
  217. {
  218. _log.severe("[EnhanceYourWeapon] Bad Level value!! npcId: "+npcId+ " token: "+ i);
  219. value = 0;
  220. }
  221. temp.put(value, info);
  222. }
  223. }
  224. }
  225. }
  226. if (temp.isEmpty())
  227. {
  228. _log.severe("[EnhanceYourWeapon] No leveling info for npcId: "+npcId+", skipping");
  229. continue;
  230. }
  231. _npcLevelingInfos.put(npcId, temp);
  232. }
  233. }
  234. }
  235. }
  236. }
  237. }
  238. catch(Exception e)
  239. {
  240. _log.log(Level.WARNING, "[EnhanceYourWeapon] Could not parse data.xml file: " + e.getMessage(), e);
  241. }
  242. _log.info("[EnhanceYourWeapon] Loaded " + _soulCrystals.size() + " Soul Crystal data.");
  243. _log.info("[EnhanceYourWeapon] Loaded " + _npcLevelingInfos.size() + " npc Leveling info data.");
  244. }
  245. public Q350_EnhanceYourWeapon(int questId, String name, String descr)
  246. {
  247. super(questId, name, descr);
  248. for(int npcId : STARTING_NPCS)
  249. {
  250. addStartNpc(npcId);
  251. addTalkId(npcId);
  252. }
  253. load();
  254. for(int npcId : _npcLevelingInfos.keySet())
  255. {
  256. addSkillSeeId(npcId);
  257. addKillId(npcId);
  258. }
  259. }
  260. @Override
  261. public String onSkillSee (L2Npc npc, L2PcInstance caster, L2Skill skill, L2Object[] targets, boolean isPet)
  262. {
  263. super.onSkillSee(npc, caster, skill, targets, isPet);
  264. if (skill == null || skill.getId() != 2096)
  265. return null;
  266. else if (caster == null || caster.isDead())
  267. return null;
  268. if (!(npc instanceof L2Attackable) || npc.isDead() || !_npcLevelingInfos.containsKey(npc.getNpcId()))
  269. return null;
  270. try
  271. {
  272. ((L2Attackable)npc).addAbsorber(caster);
  273. }
  274. catch (Exception e)
  275. {
  276. _log.log(Level.SEVERE, "", e);
  277. }
  278. return null;
  279. }
  280. @Override
  281. public String onKill (L2Npc npc, L2PcInstance killer, boolean isPet)
  282. {
  283. if (npc instanceof L2Attackable && _npcLevelingInfos.containsKey(npc.getNpcId()))
  284. levelSoulCrystals((L2Attackable)npc, killer);
  285. return null;
  286. }
  287. @Override
  288. public String onAdvEvent (String event, L2Npc npc, L2PcInstance player)
  289. {
  290. String htmltext = event;
  291. QuestState st = player.getQuestState(qn);
  292. if (event.endsWith("-04.htm"))
  293. {
  294. st.set("cond","1");
  295. st.setState(State.STARTED);
  296. st.playSound("ItemSound.quest_accept");
  297. }
  298. else if (event.endsWith("-09.htm"))
  299. st.giveItems(RED_SOUL_CRYSTAL0_ID,1);
  300. else if (event.endsWith("-10.htm"))
  301. st.giveItems(GREEN_SOUL_CRYSTAL0_ID,1);
  302. else if (event.endsWith("-11.htm"))
  303. st.giveItems(BLUE_SOUL_CRYSTAL0_ID,1);
  304. else if (event.equalsIgnoreCase("exit.htm"))
  305. st.exitQuest(true);
  306. return htmltext;
  307. }
  308. @Override
  309. public String onTalk(L2Npc npc,L2PcInstance player)
  310. {
  311. String htmltext = getNoQuestMsg(player);
  312. QuestState st = player.getQuestState(qn);
  313. if (st == null)
  314. return htmltext;
  315. if (st.getState() == State.CREATED)
  316. st.set("cond","0");
  317. if (st.getInt("cond") == 0)
  318. htmltext = npc.getNpcId() + "-01.htm";
  319. else if (check(st))
  320. htmltext = npc.getNpcId() + "-03.htm";
  321. else if (st.getQuestItemsCount(RED_SOUL_CRYSTAL0_ID) == 0
  322. && st.getQuestItemsCount(GREEN_SOUL_CRYSTAL0_ID) == 0
  323. && st.getQuestItemsCount(BLUE_SOUL_CRYSTAL0_ID) == 0)
  324. htmltext = npc.getNpcId() + "-21.htm";
  325. return htmltext;
  326. }
  327. public static void main(String[] args)
  328. {
  329. new Q350_EnhanceYourWeapon(350, qn, "Enhance Your Weapon");
  330. }
  331. /**
  332. * Calculate the leveling chance of Soul Crystals based on the attacker that killed this L2Attackable
  333. *
  334. * @param attacker The player that last killed this L2Attackable
  335. * $ Rewrite 06.12.06 - Yesod
  336. * $ Rewrite 08.01.10 - Gigiikun
  337. */
  338. public void levelSoulCrystals(L2Attackable mob, L2PcInstance killer)
  339. {
  340. // Only L2PcInstance can absorb a soul
  341. if (killer == null)
  342. {
  343. mob.resetAbsorbList();
  344. return;
  345. }
  346. FastMap<L2PcInstance, SoulCrystal> players = FastMap.newInstance();
  347. int maxSCLevel = 0;
  348. //TODO: what if mob support last_hit + party?
  349. if (isPartyLevelingMonster(mob.getNpcId()) && killer.getParty() != null)
  350. {
  351. // firts get the list of players who has one Soul Cry and the quest
  352. for (L2PcInstance pl : killer.getParty().getPartyMembers())
  353. {
  354. if (pl == null)
  355. continue;
  356. SoulCrystal sc = getSCForPlayer(pl);
  357. if (sc == null)
  358. continue;
  359. players.put(pl, sc);
  360. if (maxSCLevel < sc.getLevel() && _npcLevelingInfos.get(mob.getNpcId()).containsKey(sc.getLevel()))
  361. maxSCLevel = sc.getLevel();
  362. }
  363. }
  364. else
  365. {
  366. SoulCrystal sc = getSCForPlayer(killer);
  367. if (sc != null)
  368. {
  369. players.put(killer, sc);
  370. if (maxSCLevel < sc.getLevel()
  371. && _npcLevelingInfos.get(mob.getNpcId()).containsKey(sc.getLevel()))
  372. maxSCLevel = sc.getLevel();
  373. }
  374. }
  375. //Init some useful vars
  376. LevelingInfo mainlvlInfo = _npcLevelingInfos.get(mob.getNpcId()).get(maxSCLevel);
  377. if (mainlvlInfo == null)
  378. /*throw new NullPointerException("Target: "+mob+ " player: "+killer+" level: "+maxSCLevel);*/
  379. return;
  380. // If this mob is not require skill, then skip some checkings
  381. if (mainlvlInfo.isSkillNeeded())
  382. {
  383. // Fail if this L2Attackable isn't absorbed or there's no one in its _absorbersList
  384. if (!mob.isAbsorbed() /*|| _absorbersList == null*/)
  385. {
  386. mob.resetAbsorbList();
  387. return;
  388. }
  389. // Fail if the killer isn't in the _absorbersList of this L2Attackable and mob is not boss
  390. AbsorberInfo ai = mob.getAbsorbersList().get(killer.getObjectId());
  391. boolean isSuccess = true;
  392. if (ai == null || ai._objId != killer.getObjectId())
  393. isSuccess = false;
  394. // Check if the soul crystal was used when HP of this L2Attackable wasn't higher than half of it
  395. if (ai != null && ai._absorbedHP > (mob.getMaxHp()/2.0))
  396. isSuccess = false;
  397. if (!isSuccess)
  398. {
  399. mob.resetAbsorbList();
  400. return;
  401. }
  402. }
  403. switch (mainlvlInfo.getAbsorbCrystalType())
  404. {
  405. case PARTY_ONE_RANDOM:
  406. // This is a naive method for selecting a random member. It gets any random party member and
  407. // then checks if the member has a valid crystal. It does not select the random party member
  408. // among those who have crystals, only. However, this might actually be correct (same as retail).
  409. if (killer.getParty() != null)
  410. {
  411. L2PcInstance lucky = killer.getParty().getPartyMembers().get(Rnd.get(killer.getParty().getMemberCount()));
  412. tryToLevelCrystal(lucky, players.get(lucky), mob);
  413. }
  414. else
  415. tryToLevelCrystal(killer, players.get(killer), mob);
  416. break;
  417. case PARTY_RANDOM:
  418. if (killer.getParty() != null)
  419. {
  420. FastList<L2PcInstance> luckyParty = FastList.newInstance();
  421. luckyParty.addAll(killer.getParty().getPartyMembers());
  422. while (Rnd.get(100) < 33 && !luckyParty.isEmpty())
  423. {
  424. L2PcInstance lucky = luckyParty.remove(Rnd.get(luckyParty.size()));
  425. if (players.containsKey(lucky))
  426. tryToLevelCrystal(lucky, players.get(lucky), mob);
  427. }
  428. FastList.recycle(luckyParty);
  429. }
  430. else if (Rnd.get(100) < 33)
  431. tryToLevelCrystal(killer, players.get(killer), mob);
  432. break;
  433. case FULL_PARTY:
  434. if (killer.getParty() != null)
  435. for(L2PcInstance pl : killer.getParty().getPartyMembers())
  436. tryToLevelCrystal(pl, players.get(pl), mob);
  437. else
  438. tryToLevelCrystal(killer, players.get(killer), mob);
  439. break;
  440. case LAST_HIT:
  441. tryToLevelCrystal(killer, players.get(killer), mob);
  442. break;
  443. }
  444. FastMap.recycle(players);
  445. }
  446. private boolean isPartyLevelingMonster(int npcId)
  447. {
  448. for (LevelingInfo li : _npcLevelingInfos.get(npcId).values())
  449. {
  450. if (li.getAbsorbCrystalType() != AbsorbCrystalType.LAST_HIT)
  451. return true;
  452. }
  453. return false;
  454. }
  455. private SoulCrystal getSCForPlayer(L2PcInstance player)
  456. {
  457. QuestState st = player.getQuestState(qn);
  458. if (st == null || st.getState() != State.STARTED)
  459. return null;
  460. L2ItemInstance[] inv = player.getInventory().getItems();
  461. SoulCrystal ret = null;
  462. for (L2ItemInstance item : inv)
  463. {
  464. int itemId = item.getItemId();
  465. if (!_soulCrystals.containsKey(itemId))
  466. continue;
  467. if (ret != null)
  468. return null;
  469. else
  470. ret = _soulCrystals.get(itemId);
  471. }
  472. return ret;
  473. }
  474. private void tryToLevelCrystal(L2PcInstance player, SoulCrystal sc, L2Attackable mob)
  475. {
  476. if (sc == null || !_npcLevelingInfos.containsKey(mob.getNpcId()))
  477. return;
  478. // If the crystal level is way too high for this mob, say that we can't increase it
  479. if (!_npcLevelingInfos.get(mob.getNpcId()).containsKey(sc.getLevel()))
  480. {
  481. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_REFUSED));
  482. return;
  483. }
  484. if (Rnd.get(100) <= _npcLevelingInfos.get(mob.getNpcId()).get(sc.getLevel()).getChance())
  485. exchangeCrystal(player, mob, sc.getItemId(), sc.getLeveledItemId(), false);
  486. else
  487. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_FAILED));
  488. }
  489. private void exchangeCrystal(L2PcInstance player, L2Attackable mob, int takeid, int giveid, boolean broke)
  490. {
  491. L2ItemInstance Item = player.getInventory().destroyItemByItemId("SoulCrystal", takeid, 1, player, mob);
  492. if (Item != null)
  493. {
  494. // Prepare inventory update packet
  495. InventoryUpdate playerIU = new InventoryUpdate();
  496. playerIU.addRemovedItem(Item);
  497. // Add new crystal to the killer's inventory
  498. Item = player.getInventory().addItem("SoulCrystal", giveid, 1, player, mob);
  499. playerIU.addItem(Item);
  500. // Send a sound event and text message to the player
  501. if (broke)
  502. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SOUL_CRYSTAL_BROKE));
  503. else
  504. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_SUCCEEDED));
  505. // Send system message
  506. SystemMessage sms = SystemMessage.getSystemMessage(SystemMessageId.EARNED_ITEM_S1);
  507. sms.addItemName(giveid);
  508. player.sendPacket(sms);
  509. // Send inventory update packet
  510. player.sendPacket(playerIU);
  511. }
  512. }
  513. }