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.

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.



  • Posts

    • Te esperamos este viernes en  L2 Seal Proyecto Hunter . Open : 1/12/23 a las 21:00hs GMT-3 Para cerrar el año,de la mejor manera te esperamos en el servidor mid más grande de habla hispana.   !!!Te lo vas a perder!!! Estamos de regreso, con mucho contenido nuevo. Cliente : H5. Rates: EXP/SP: x20 Adena: x10 Seal Stones: x4 Raid Boss EXP/SP: x5 Spoil: x10 Keymats Drop/Spoil: x5 Raid Boss Drop: x5 Drop: x8 Manor: x2 Eppaulletes: x5  Features : NPC Buffer com buffs de 2 hora GM-Shop up to Dynasty Mana Potions (1000 MP, 10 seg. Cooldown) Skills autolearn inclusive los 81/83 Buff Slots: 24 (+4 com Divine Inspiration LVL 4) Auto pickup configurable Offline Shop (Colocar el char en modo store y cerrar el cliente.) Cambio de 1ª classe (10.000 adena) Cambio de 2ª classe (500.000 adena) Cambio de 3ª classe (2.000.000 adena) Sub class No requiere quest items ni quest. Nobless Via quest (H5 Quest) Community con status de Raid Boss Shift + Click para ver a lista de drops (Rate Server) Global Shout / Delay 20 segundos. Max. Clientes/PC: 4 Max. Ventanas de Autofarm 1 por HWID Max. Miembros de clan en Zonas Épicas: Sin Restriccion Allys permitidas (Max. 2 Clanes) Cancellation: 2-4 buff con chance y random. Puedes contactarnos en : Discord: https://discord.com/invite/hmVaZFrFEy 🦸 Website:  www.l2seal.com
    • Good day everybody.   A credible team that SGuard represents is looking for java developers.    These positions is full time only i.e. your time is mostly dedicated within the team and team tasks.  All necessary tools are provided.  The team is consistent with > 6 team members and the team is looking to expand to meet various goals. A decent Lineage 2 Essense OR Main (GOD) knowledge is needed.  Experience with l2r/l2p/l2s (not l2j or mobius) type of servers. The team is friendly and goal oriented, very active and resourceful.   Requirements are standard however strong java codding skill is necessary for the position.  Multi language is a plus but not necessary (English/Russian), any is accepted.   Terms and conditions including compensation are reasonable and considered to be up for the industry standards. Details are negotiable with suitable candidates.   You have to have either a public name that can be checked out or a strong reference from credible sources. You will need to have a headset and a mic just for the initial processing, there're no team meetings or conferences after that.   The team has decided it is in their best interest to not publish their information at this time. Details will be available only for suitable candidates.   If you think you're a good fit. Please contact me at:   skype: live:sguard.soft telegram: https://t.me/tech_support_s2    
    • SCAMMER Please do not get scammed! His Sellix Feedback is fake, his store opening date is manipulated by scripts. Daniele, the developer of Sellix confirmed this before suspending his account. You will receive a rar file with a doc that contains some random email and password and it won’t work. He won’t respond to your tickets either. I lost $120
    • Lineage II Interlude: Ember x3 The server offers a retail atmosphere with NPC buffs, low rates, and a bug-free environment. The auto-hunt feature transforms your game adventure by allowing you to easily accomplish challenges while managing the demands of a busy life.   Server Status: Online [Closed Beta Test] Official Opening: TBD Website: l2ember.online Community: Discord Channel   Key Features Our network infrastructure can support up to 5,000 simultaneous connections without compromising the quality of gameplay. The augmentation system follows the guidelines of the official server. Geodata operates smoothly and without issues. The game's data, which includes monster spawns, loot availability, and player skills, is working correctly. We are actively working on official-like quests and refining the game. Our AI is adequate and corresponds to the PTS server. Sieges are similar to the official server, without any lag or glitches. The Olympiad is fully functional and may be customized to the preferences of the players.   Rates EXP/SP (Regular Monsters): x3 EXP/SP (Raid Bosses): x2 EXP/SP (Quest): x1 Items Drop Rate: x2 Items Drop Rate (Raid Bosses): x2 Items Drop Rate (Quest): x2 Quest Items Reward Rate: x1 Quest Adena Reward Rate: x1 Adena Drop Rate: x2 Seal Stones Drop Rate: x2 Spoil Drop Rate: x2
    • I come here to show some of my work for any L2j version.   Base project for the work: L2jAcis 401   I will constantly update when I create more demonstration videos.   SellBuff System ( Online And Offline Mode ) : 50€  Payments: PayPal / Binance Videos:      NOTE: If I have broken any rules, please send me a PM.  
  • Topics

×
×
  • Create New...