Jump to content

Universal Drop System


Recommended Posts

Hey MxC I'd like to share a simple, but util java modification. It's written for aCis, but you can simple adapt to any chronicle and project by changing the XML parser.

 

This mod will allow you to set specific drops to specific monsters with determined level range, or determined class (RB, GB, Normal Mobs...) with chances.

Why i made this mode? Normally L2J set monster drops like > Monster "X" have Items "Y" as drops. Then, to facilitate servers configuration i did the opposite.

Like: Item "X" will be dropped by a specific range of mobs.

 

There are 2 types of Universal Drops:

1. UniversalDropData.xml


<?xml version="1.0" encoding="utf-8"?>
<list>
	<UniversalDrop itemId="8762" minAmount="1" maxAmount="1" chance="1" premiumApplied="False" monstersIDs="200;201;202"/>
</list>

This mean: Top-Grade Life Stone (8762) will be dropped by monsters with ids (200, 201 and 202) regardless of their levels and class with 1% of chance.

 

2. CategorizedUniversalDropData.xml

 

<?xml version="1.0" encoding="utf-8"?>
<list>
	<CategorizedUniversalDrop itemId="8742" minAmount="1" maxAmount="1" minLevel="40" maxLevel="99" chance="5" premiumApplied="False" dropType="MONSTER"/>
	<CategorizedUniversalDrop itemId="8752" minAmount="1" maxAmount="1" minLevel="40" maxLevel="99" chance="10" premiumApplied="False" dropType="RAIDBOSS"/>
	<CategorizedUniversalDrop itemId="8762" minAmount="1" maxAmount="1" minLevel="40" maxLevel="99" chance="15" premiumApplied="False" dropType="GRANDBOSS"/>
  <CategorizedUniversalDrop itemId="3470" minAmount="1" maxAmount="10" minLevel="40" maxLevel="99" chance="40" premiumApplied="False" dropType="ALL"/>
	
</list>

This mean almost same of uncatecorized universal drops, except by the fact you decide class and level of monsters will drops the specified item.

I think this mod will cover all sorts of monsters in lineage 2 if you know how to use.

 

About

Adaptation:

 

First create a class UniversalDrop.java

 

package dev.universalDrop;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Zaun
 */
public class UniversalDrop
{
	private List<Integer> monsters = new ArrayList<>();
	private int itemId;
	private int chance;
	private int[] amount = new int[2];
	private int[] level = new int[2];
	private boolean premiumApplied;
	private UniversalDropType dropType = UniversalDropType.ALL;
	
	/**
	 * @return the monsters
	 */
	public List<Integer> getMonsters()
	{
		return monsters;
	}
	
	/**
	 * @param monsters the monsters to set
	 */
	public void setMonsters(List<Integer> monsters)
	{
		this.monsters = monsters;
	}
	
	/**
	 * @return the id
	 */
	public int getItemId()
	{
		return itemId;
	}
	
	/**
	 * @param id the id to set
	 */
	public void setItemId(int id)
	{
		this.itemId = id;
	}
	
	/**
	 * @return the amount
	 */
	public int[] getAmount()
	{
		return amount;
	}
	
	/**
	 * @param amount the amount to set
	 */
	public void setAmount(int[] amount)
	{
		this.amount = amount;
	}
	
	/**
	 * @return the chance
	 */
	public int getChance()
	{
		return chance;
	}
	
	/**
	 * @param chance the chance to set
	 */
	public void setChance(int chance)
	{
		this.chance = chance;
	}
	
	/**
	 * @return the premiumApplied
	 */
	public boolean isPremiumApplied()
	{
		return premiumApplied;
	}
	
	/**
	 * @param premiumApplied the premiumApplied to set
	 */
	public void setPremiumApplied(boolean premiumApplied)
	{
		this.premiumApplied = premiumApplied;
	}
	
	/**
	 * @return the dropType
	 */
	public UniversalDropType getDropType()
	{
		return dropType;
	}
	
	/**
	 * @param dropType the dropType to set
	 */
	public void setDropType(UniversalDropType dropType)
	{
		this.dropType = dropType;
	}
	
	/**
	 * @return the level
	 */
	public int[] getLevel()
	{
		return level;
	}
	
	/**
	 * @param level the level to set
	 */
	public void setLevel(int[] level)
	{
		this.level = level;
	}
}

 

 

Then create UniversalDropData.java

 

package dev.universalDrop.data.xml;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.l2j.commons.data.xml.XMLDocument;

import net.sf.l2j.gameserver.data.ItemTable;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import dev.universalDrop.UniversalDrop;
import dev.universalDrop.UniversalDropType;

/**
 * @author Zaun
 */
public class UniversalDropData extends XMLDocument
{
	private Map<Integer, UniversalDrop> universalDrops = new HashMap<>();
	private Map<Integer, UniversalDrop> categorizedUniversalDrops = new HashMap<>();
	
	public UniversalDropData()
	{
		load();
	}
	
	public static UniversalDropData getInstance()
	{
		return SingleTonHolder._instance;
	}
	
	private static class SingleTonHolder
	{
		protected static final UniversalDropData _instance = new UniversalDropData();
	}
	
	public void reload()
	{
		universalDrops.clear();
		categorizedUniversalDrops.clear();
		load();
	}
	
	@Override
	protected void load()
	{
		loadDocument("./data/xml/UniversalDrop/UniversalDropData.xml");
		LOG.info("UniversalDropData: Loaded " + universalDrops.size() + " Universal drops.");
		
		loadDocument("./data/xml/UniversalDrop/CategorizedUniversalDropData.xml");
		LOG.info("UniversalDropData: Loaded " + categorizedUniversalDrops.size() + " Universal drops.");
	}
	
	@Override
	protected void parseDocument(Document doc, File f)
	{
		try
		{
			
			// First element is never read.
			final Node n = doc.getFirstChild();
			
			for (Node o = n.getFirstChild(); o != null; o = o.getNextSibling())
			{
				if (!"UniversalDrop".equalsIgnoreCase(o.getNodeName()))
					continue;
				
				NamedNodeMap attrs = o.getAttributes();
				UniversalDrop universalDrop = null;
				
				int itemId = Integer.parseInt(attrs.getNamedItem("itemId").getNodeValue());
				int minAmount = Integer.parseInt(attrs.getNamedItem("minAmount").getNodeValue());
				int maxAmount = Integer.parseInt(attrs.getNamedItem("maxAmount").getNodeValue());
				int chance = Integer.parseInt(attrs.getNamedItem("chance").getNodeValue());
				String monstersIDs = attrs.getNamedItem("monstersIDs").getNodeValue();
				boolean premiumApplied = Boolean.parseBoolean(attrs.getNamedItem("premiumApplied").getNodeValue());
				if (ItemTable.getInstance().getTemplate(itemId) != null)
				{
					universalDrop = new UniversalDrop();
					
					universalDrop.setItemId(itemId);
					universalDrop.setChance(chance);
					
					int[] amount = new int[2];
					amount[0] = minAmount;
					amount[1] = maxAmount;
					
					universalDrop.setAmount(amount);
					int[] level = new int[2];
					level[0] = -1;
					level[1] = 0;
					
					universalDrop.setLevel(level);
					universalDrop.setDropType(UniversalDropType.ALL);
					List<Integer> monsters = new ArrayList<>();
					for (String monsterId : monstersIDs.split(";"))
					{
						monsters.add(Integer.parseInt(monsterId));
					}
					universalDrop.setMonsters(monsters);
					universalDrop.setPremiumApplied(premiumApplied);
					universalDrops.put(itemId, universalDrop);
				}
				else
				{
					LOG.warning("Item Id: " + itemId + " is an invalid item for Universal drop ID: " + itemId + "(uncategorized).");
				}
				
			}
			
			for (Node o = n.getFirstChild(); o != null; o = o.getNextSibling())
			{
				if (!"CategorizedUniversalDrop".equalsIgnoreCase(o.getNodeName()))
					continue;
				
				NamedNodeMap attrs = o.getAttributes();
				UniversalDrop universalDrop = null;
				
				int itemId = Integer.parseInt(attrs.getNamedItem("itemId").getNodeValue());
				int minAmount = Integer.parseInt(attrs.getNamedItem("minAmount").getNodeValue());
				int maxAmount = Integer.parseInt(attrs.getNamedItem("maxAmount").getNodeValue());
				int minLevel = Integer.parseInt(attrs.getNamedItem("minLevel").getNodeValue());
				int maxLevel = Integer.parseInt(attrs.getNamedItem("maxLevel").getNodeValue());
				int chance = Integer.parseInt(attrs.getNamedItem("chance").getNodeValue());
				UniversalDropType dropType = UniversalDropType.valueOf(attrs.getNamedItem("dropType").getNodeValue());
				boolean premiumApplied = Boolean.parseBoolean(attrs.getNamedItem("premiumApplied").getNodeValue());
				if (ItemTable.getInstance().getTemplate(itemId) != null)
				{
					universalDrop = new UniversalDrop();
					
					universalDrop.setItemId(itemId);
					universalDrop.setChance(chance);
					int[] amount = new int[2];
					amount[0] = minAmount;
					amount[1] = maxAmount;
					
					universalDrop.setAmount(amount);
					
					int[] level = new int[2];
					level[0] = minLevel;
					level[1] = maxLevel;
					universalDrop.setDropType(dropType);
					universalDrop.setLevel(level);
					List<Integer> monsters = new ArrayList<>();
					universalDrop.setMonsters(monsters);
					universalDrop.setPremiumApplied(premiumApplied);
					categorizedUniversalDrops.put(itemId, universalDrop);
					
				}
				else
				{
					LOG.warning("Item Id: " + itemId + " is an invalid item for Universal drop ID: " + itemId + "(categorized).");
				}
				
			}
		}
		catch (Exception e)
		{
			LOG.warning("Universal Drop Data: Error while creating table: " + e);
			e.printStackTrace();
		}
	}
	
	public List<Integer> getUncategorizedUniversalDropItemsIds()
	{
		List<Integer> items = new ArrayList<>();
		
		for (Map.Entry<Integer, UniversalDrop> entry : universalDrops.entrySet())
		{
			items.add(entry.getKey());
		}
		return items;
	}
	
	public List<Integer> monstersWithDropId(int itemId)
	{
		
		for (Map.Entry<Integer, UniversalDrop> entry : universalDrops.entrySet())
		{
			if (entry.getValue().getItemId() == itemId)
			{
				return entry.getValue().getMonsters();
			}
		}
		return new ArrayList<>();
	}
	
	public UniversalDrop getUncategorizedUniversalDropById(int itemId)
	{
		return universalDrops.get(itemId);
	}
	
	public UniversalDrop getCategorizedUniversalDropById(int itemId)
	{
		return categorizedUniversalDrops.get(itemId);
	}
	
	public Collection<UniversalDrop> getAllCategorizedUniversalDrops()
	{
		return categorizedUniversalDrops.values();
	}
}

 

 

Then create this enum

 

package dev.universalDrop;

/**
 * @author Zaun
 */
public enum UniversalDropType
{
	ALL,
	RAIDBOSS,
	GRANDBOSS,
	MONSTER
}

 

 

Then you need to modify your core. If you using aCis find class "Attackable.java" in "...model.actor" package.

 

add this method:

public void universalDropItem(UniversalDrop universalDrop, Player player)
	{
		int chance = universalDrop.getChance();
		int amount = 0;
		IntIntHolder item = null;
		int premiumBonus = 0;
		Premium premium = player.getPremium();
		if (Rnd.get(100) <= chance)
		{
			amount = Rnd.get(universalDrop.getAmount()[0], universalDrop.getAmount()[1]);
			item = new IntIntHolder(universalDrop.getItemId(), amount);
			if (universalDrop.isPremiumApplied() && player.getPremium().getLevel() > 0)
			{
				premiumBonus = (int) (amount * premium.getItemDropRate()) - amount;
				amount *= premium.getItemDropRate();
				item.setPremiumBonus(premiumBonus);
			}
			
			// Check if the autoLoot mode is active
			if ((isRaid() && Config.AUTO_LOOT_RAID) || (!isRaid() && Config.AUTO_LOOT))
				player.doAutoLoot(this, item); // Give this or these Item(s) to the Player that has killed the L2Attackable
			else
				dropItem(player, item); // drop the item on the ground
				
		}
	}

Finde method:

public void doItemDrop(NpcTemplate npcTemplate, Creature mainDamageDealer)

add this code:

// Custom universal drop (uncategorized)
		for (int dropId : UniversalDropData.getInstance().getUncategorizedUniversalDropItemsIds())
		{
			if (UniversalDropData.getInstance().monstersWithDropId(dropId).contains(getNpcId()))
			{
				UniversalDrop universalDrop = UniversalDropData.getInstance().getUncategorizedUniversalDropById(dropId);
				universalDropItem(universalDrop, player);
			}
		}
		
		// Custom universal drop (categorized)
		for (UniversalDrop universalDrop : UniversalDropData.getInstance().getAllCategorizedUniversalDrops())
		{
			UniversalDropType dropType = universalDrop.getDropType();
			// if monster level doesn't correspond to drop level limit, stop execution
			if (!(getLevel() >= universalDrop.getLevel()[0] && getLevel() <= universalDrop.getLevel()[1]))
			{
				continue;
			}
			if (dropType.equals(UniversalDropType.ALL))
			{
				universalDropItem(universalDrop, player);
			}
			else if (dropType.equals(UniversalDropType.RAIDBOSS))
			{
				if (!(this instanceof RaidBoss))
				{
					continue;
				}
				universalDropItem(universalDrop, player);
			}
			else if (dropType.equals(UniversalDropType.GRANDBOSS))
			{
				if (!(this instanceof GrandBoss))
				{
					continue;
				}
				universalDropItem(universalDrop, player);
			}
			else if (dropType.equals(UniversalDropType.MONSTER))
			{
				if (!(this instanceof Monster))
				{
					continue;
				}
				universalDropItem(universalDrop, player);
			}
			
		}

 

Then add this line into your GameServer.java

UniversalDropData.getInstance();

This will call the parser of drops.

 

Now you need to go into your data pack and create a folder inside ./data/ and then create these XML files:

 

"./data/xml/UniversalDrop/UniversalDropData.xml"
./data/xml/UniversalDrop/CategorizedUniversalDropData.xml

 

You can use the post start xml code to create XML files.

 

If you need any help to adapt this code in your source just send me a PM. Enjoy

 

 

  • Like 1
  • Upvote 1
Link to comment
Share on other sites

Thank you for sharing good job!!!

Edit: the only "flaw" I can note is that your chance is integer but drops use normally 0.001224% for example so it should be a float value for more accurate drops? maybe there is a way for improvement what do you think?

Edited by Nightw0lf
Link to comment
Share on other sites

  • You can use stream for getUncategorizedUniversalDropItemsIds / monstersWithDropId for oneliners.
  • monstersWithDropId should be properly named, and shouldn't create an empty List (but return Collections.emptyList() instead).

  • IXmlReader use would cut your parser class by alot.

  • equals can be replaced by == for enum comparison. Added to that, you can use NpcTemplate#isType for easy instance comparison (if you don't mind the enum.toString()).

  • getUncategorizedUniversalDropById use should be null checked.

  • You can/should use {} wildcard for LOGGER(s) parameters, cf. :
     

    LOGGER.info("Loaded {} crests.", _crests.size());

     

Edited by Tryskell
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...