/*
* Copyright (C) 2004-2015 L2J DataPack
*
* This file is part of L2J DataPack.
*
* L2J DataPack 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.
*
* L2J DataPack 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.Q00350_EnhanceYourWeapon;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Level;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jserver.Config;
import com.l2jserver.gameserver.model.AbsorberInfo;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.actor.L2Attackable;
import com.l2jserver.gameserver.model.actor.L2Npc;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
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.model.skills.Skill;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.InventoryUpdate;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
/**
* Enhance Your Weapon (350)
* @author Gigiikun
*/
public class Q00350_EnhanceYourWeapon extends Quest
{
private static enum AbsorbCrystalType
{
LAST_HIT,
FULL_PARTY,
PARTY_ONE_RANDOM,
PARTY_RANDOM
}
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 int getChance()
{
return _chance;
}
public final boolean isSkillNeeded()
{
return _isSkillNeeded;
}
}
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 getItemId()
{
return _itemId;
}
public final int getLevel()
{
return _level;
}
public final int getLeveledItemId()
{
return _leveledItemId;
}
}
// NPCs
private static final int[] STARTING_NPCS =
{
30115,
30856,
30194
};
// Items
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 static final Map SOUL_CRYSTALS = new HashMap<>();
// >
private static final Map> NPC_LEVELING_INFO = new HashMap<>();
public Q00350_EnhanceYourWeapon()
{
super(350, Q00350_EnhanceYourWeapon.class.getSimpleName(), "Enhance Your Weapon");
addStartNpc(STARTING_NPCS);
addTalkId(STARTING_NPCS);
load();
for (int npcId : NPC_LEVELING_INFO.keySet())
{
addSkillSeeId(npcId);
addKillId(npcId);
}
}
@Override
public String onAdvEvent(String event, L2Npc npc, L2PcInstance player)
{
String htmltext = event;
QuestState st = getQuestState(player, false);
if (event.endsWith("-04.htm"))
{
st.startQuest();
}
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 onKill(L2Npc npc, L2PcInstance killer, boolean isSummon)
{
if (npc.isAttackable() && NPC_LEVELING_INFO.containsKey(npc.getId()))
{
levelSoulCrystals((L2Attackable) npc, killer);
}
return null;
}
@Override
public String onSkillSee(L2Npc npc, L2PcInstance caster, Skill skill, L2Object[] targets, boolean isSummon)
{
super.onSkillSee(npc, caster, skill, targets, isSummon);
if ((skill == null) || (skill.getId() != 2096))
{
return null;
}
else if ((caster == null) || caster.isDead())
{
return null;
}
if (!npc.isAttackable() || npc.isDead() || !NPC_LEVELING_INFO.containsKey(npc.getId()))
{
return null;
}
try
{
((L2Attackable) npc).addAbsorber(caster);
}
catch (Exception e)
{
_log.log(Level.SEVERE, "", e);
}
return null;
}
@Override
public String onTalk(L2Npc npc, L2PcInstance player)
{
String htmltext = getNoQuestMsg(player);
final QuestState st = getQuestState(player, true);
if (st == null)
{
return htmltext;
}
if (st.getState() == State.CREATED)
{
st.set("cond", "0");
}
if (st.getInt("cond") == 0)
{
htmltext = npc.getId() + "-01.htm";
}
else if (check(st))
{
htmltext = npc.getId() + "-03.htm";
}
else if (!st.hasQuestItems(RED_SOUL_CRYSTAL0_ID) && !st.hasQuestItems(GREEN_SOUL_CRYSTAL0_ID) && !st.hasQuestItems(BLUE_SOUL_CRYSTAL0_ID))
{
htmltext = npc.getId() + "-21.htm";
}
return htmltext;
}
private static boolean check(QuestState st)
{
for (int i = 4629; i < 4665; i++)
{
if (st.hasQuestItems(i))
{
return true;
}
}
return false;
}
private static 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(SystemMessageId.SOUL_CRYSTAL_BROKE);
}
else
{
player.sendPacket(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);
}
}
private static SoulCrystal getSCForPlayer(L2PcInstance player)
{
final QuestState st = player.getQuestState(Q00350_EnhanceYourWeapon.class.getSimpleName());
if ((st == null) || !st.isStarted())
{
return null;
}
L2ItemInstance[] inv = player.getInventory().getItems();
SoulCrystal ret = null;
for (L2ItemInstance item : inv)
{
int itemId = item.getId();
if (!SOUL_CRYSTALS.containsKey(itemId))
{
continue;
}
if (ret != null)
{
return null;
}
ret = SOUL_CRYSTALS.get(itemId);
}
return ret;
}
private static boolean isPartyLevelingMonster(int npcId)
{
for (LevelingInfo li : NPC_LEVELING_INFO.get(npcId).values())
{
if (li.getAbsorbCrystalType() != AbsorbCrystalType.LAST_HIT)
{
return true;
}
}
return false;
}
private static void levelCrystal(L2PcInstance player, SoulCrystal sc, L2Attackable mob)
{
if ((sc == null) || !NPC_LEVELING_INFO.containsKey(mob.getId()))
{
return;
}
// If the crystal level is way too high for this mob, say that we can't increase it
if (!NPC_LEVELING_INFO.get(mob.getId()).containsKey(sc.getLevel()))
{
player.sendPacket(SystemMessageId.SOUL_CRYSTAL_ABSORBING_REFUSED);
return;
}
if (getRandom(100) <= NPC_LEVELING_INFO.get(mob.getId()).get(sc.getLevel()).getChance())
{
exchangeCrystal(player, mob, sc.getItemId(), sc.getLeveledItemId(), false);
}
else
{
player.sendPacket(SystemMessageId.SOUL_CRYSTAL_ABSORBING_FAILED);
}
}
/**
* Calculate the leveling chance of Soul Crystals based on the attacker that killed this L2Attackable
* @param mob
* @param killer The player that last killed this L2Attackable $ Rewrite 06.12.06 - Yesod $ Rewrite 08.01.10 - Gigiikun
*/
public static void levelSoulCrystals(L2Attackable mob, L2PcInstance killer)
{
// Only L2PcInstance can absorb a soul
if (killer == null)
{
mob.resetAbsorbList();
return;
}
Map players = new HashMap<>();
int maxSCLevel = 0;
// TODO: what if mob support last_hit + party?
if (isPartyLevelingMonster(mob.getId()) && (killer.getParty() != null))
{
// firts get the list of players who has one Soul Cry and the quest
for (L2PcInstance pl : killer.getParty().getMembers())
{
if (pl == null)
{
continue;
}
SoulCrystal sc = getSCForPlayer(pl);
if (sc == null)
{
continue;
}
players.put(pl, sc);
if ((maxSCLevel < sc.getLevel()) && NPC_LEVELING_INFO.get(mob.getId()).containsKey(sc.getLevel()))
{
maxSCLevel = sc.getLevel();
}
}
}
else
{
SoulCrystal sc = getSCForPlayer(killer);
if (sc != null)
{
players.put(killer, sc);
if ((maxSCLevel < sc.getLevel()) && NPC_LEVELING_INFO.get(mob.getId()).containsKey(sc.getLevel()))
{
maxSCLevel = sc.getLevel();
}
}
}
// Init some useful vars
LevelingInfo mainlvlInfo = NPC_LEVELING_INFO.get(mob.getId()).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.getObjectId() != 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.getAbsorbedHp() > (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().getMembers().get(getRandom(killer.getParty().getMemberCount()));
levelCrystal(lucky, players.get(lucky), mob);
}
else
{
levelCrystal(killer, players.get(killer), mob);
}
break;
case PARTY_RANDOM:
if (killer.getParty() != null)
{
List luckyParty = new ArrayList<>();
luckyParty.addAll(killer.getParty().getMembers());
while ((getRandom(100) < 33) && !luckyParty.isEmpty())
{
L2PcInstance lucky = luckyParty.remove(getRandom(luckyParty.size()));
if (players.containsKey(lucky))
{
levelCrystal(lucky, players.get(lucky), mob);
}
}
}
else if (getRandom(100) < 33)
{
levelCrystal(killer, players.get(killer), mob);
}
break;
case FULL_PARTY:
if (killer.getParty() != null)
{
for (L2PcInstance pl : killer.getParty().getMembers())
{
levelCrystal(pl, players.get(pl), mob);
}
}
else
{
levelCrystal(killer, players.get(killer), mob);
}
break;
case LAST_HIT:
levelCrystal(killer, players.get(killer), mob);
break;
}
}
/**
* TODO: Implement using DocumentParser.
*/
private static void load()
{
try
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setIgnoringComments(true);
File file = new File(Config.DATAPACK_ROOT, "data/levelUpCrystalData.xml");
if (!file.exists())
{
_log.severe("[EnhanceYourWeapon] Missing levelUpCrystalData.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());
SOUL_CRYSTALS.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());
Map temp = new HashMap<>();
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 if (att2 != null)
{
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;
}
NPC_LEVELING_INFO.put(npcId, temp);
}
}
}
}
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "[EnhanceYourWeapon] Could not parse levelUpCrystalData.xml file: " + e.getMessage(), e);
}
_log.info("[EnhanceYourWeapon] Loaded " + SOUL_CRYSTALS.size() + " Soul Crystal data.");
_log.info("[EnhanceYourWeapon] Loaded " + NPC_LEVELING_INFO.size() + " npc Leveling info data.");
}
}