Browse Source

BETA: XML spawnlist related:
* Support for optional static parameters and overriding some template parameters (random animation, random walk and aggro range at this moment) for certain NPC instances via XML spawnlist.
* Support for enable/disable certain spawnlist in XML
Scripts related:
* Support for NPC variables (allows to store multiple dynamic values for NPC (useful for scripts)).

Reviewed by: !UnAfraid, xban1x

VlLight 11 years ago
parent
commit
2b76da115b

+ 133 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/datatables/NpcPersonalAIData.java

@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2004-2013 L2J Server
+ * 
+ * This file is part of L2J Server.
+ * 
+ * L2J Server 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 Server 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 <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.gameserver.datatables;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.l2jserver.gameserver.model.L2Spawn;
+import com.l2jserver.gameserver.model.actor.L2Npc;
+import com.l2jserver.util.Rnd;
+
+/**
+ * This class holds parameter, specific to certain NPC's.
+ * It can be either general parameters overrided for certain NPC instance instead of template parameters(aggro range, for example), or some optional parameters, handled by datapack scripts.<br>
+ * @author GKR
+ */
+public class NpcPersonalAIData
+{
+	private final Map<String, Map<String, Integer>> _AIData = new HashMap<>();
+	
+	/**
+	 * Instantiates a new table.
+	 */
+	protected NpcPersonalAIData()
+	{
+	}
+	
+	/**
+	 * Stores data for given spawn.
+	 * @param spawnDat spawn to process
+	 * @param data Map of AI values
+	 */
+	public void storeData(L2Spawn spawnDat, Map<String, Integer> data)
+	{
+		if ((data != null) && !data.isEmpty())
+		{
+			// check for spawn name. Since spawn name is key for AI Data, generate random name, if spawn name isn't specified
+			if (spawnDat.getName() == null)
+			{
+				spawnDat.setName(Long.toString(Rnd.nextLong()));
+			}
+			
+			_AIData.put(spawnDat.getName(), data);
+		}
+	}
+	
+	/**
+	 * Gets AI value with given spawnName and paramName
+	 * @param spawnName spawn name to check
+	 * @param paramName parameter to check
+	 * @return value of given parameter for given spawn name
+	 */
+	public int getAIValue(String spawnName, String paramName)
+	{
+		return hasAIValue(spawnName, paramName) ? _AIData.get(spawnName).get(paramName) : -1;
+	}
+	
+	/**
+	 * Verifies if there is AI value with given spawnName and paramName
+	 * @param spawnName spawn name to check
+	 * @param paramName parameter name to check
+	 * @return {@code true} if parameter paramName is set for spawn spawnName, {@code false} otherwise
+	 */
+	public boolean hasAIValue(String spawnName, String paramName)
+	{
+		return (spawnName != null) && _AIData.containsKey(spawnName) && _AIData.get(spawnName).containsKey(paramName);
+	}
+	
+	/**
+	 * Initializes npc parameters by specified values.
+	 * @param npc NPC to process
+	 * @param spawn link to NPC's spawn
+	 * @param spawnName name of spawn
+	 */
+	public void initializeNpcParameters(L2Npc npc, L2Spawn spawn, String spawnName)
+	{
+		if (_AIData.containsKey(spawnName))
+		{
+			Map<String, Integer> map = _AIData.get(spawnName);
+			
+			try
+			{
+				for (String key : map.keySet())
+				{
+					switch (key)
+					{
+						case "disableRandomAnimation":
+							npc.setRandomAnimationEnabled((map.get(key) == 0));
+							break;
+						case "disableRandomWalk":
+							npc.setIsNoRndWalk((map.get(key) == 1));
+							spawn.setIsNoRndWalk((map.get(key) == 1));
+							break;
+					}
+				}
+			}
+			catch (Exception e)
+			{
+				// Do nothing
+			}
+		}
+	}
+	
+	/**
+	 * Gets the single instance of NpcTable.
+	 * @return single instance of NpcTable
+	 */
+	public static NpcPersonalAIData getInstance()
+	{
+		return SingletonHolder._instance;
+	}
+	
+	private static class SingletonHolder
+	{
+		protected static final NpcPersonalAIData _instance = new NpcPersonalAIData();
+	}
+}

+ 70 - 3
L2J_Server_BETA/java/com/l2jserver/gameserver/datatables/SpawnTable.java

@@ -23,6 +23,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.Statement;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.logging.Level;
@@ -113,13 +114,26 @@ public final class SpawnTable extends DocumentParser
 		{
 			if (list.getNodeName().equalsIgnoreCase("list"))
 			{
+				attrs = list.getAttributes();
+				// skip disabled spawnlists
+				if (!Boolean.parseBoolean(attrs.getNamedItem("enabled").getNodeValue()))
+				{
+					continue;
+				}
 				for (Node param = list.getFirstChild(); param != null; param = param.getNextSibling())
 				{
 					attrs = param.getAttributes();
 					if (param.getNodeName().equalsIgnoreCase("spawn"))
 					{
 						String territoryName = null;
+						String spawnName = null;
+						Map<String, Integer> map = null;
 						
+						// Check, if spawn name specified
+						if (attrs.getNamedItem("name") != null)
+						{
+							spawnName = parseString(attrs, "name");
+						}
 						// Check, if spawn territory specified and exists
 						if ((attrs.getNamedItem("zone") != null) && (ZoneManager.getInstance().getSpawnTerritory(attrs.getNamedItem("zone").getNodeValue()) != null))
 						{
@@ -129,7 +143,36 @@ public final class SpawnTable extends DocumentParser
 						for (Node npctag = param.getFirstChild(); npctag != null; npctag = npctag.getNextSibling())
 						{
 							attrs = npctag.getAttributes();
-							if (npctag.getNodeName().equalsIgnoreCase("npc"))
+							// Check if there are any AI parameters
+							if (npctag.getNodeName().equalsIgnoreCase("AIData"))
+							{
+								attrs = npctag.getAttributes();
+								if (map == null)
+								{
+									map = new HashMap<>();
+								}
+								for (Node c = npctag.getFirstChild(); c != null; c = c.getNextSibling())
+								{
+									// Skip odd nodes
+									if (c.getNodeName().equals("#text"))
+									{
+										continue;
+									}
+									int val;
+									switch (c.getNodeName())
+									{
+										case "disableRandomAnimation":
+										case "disableRandomWalk":
+											val = Boolean.parseBoolean(c.getTextContent()) ? 1 : 0;
+											break;
+										default:
+											val = Integer.parseInt(c.getTextContent());
+									}
+									map.put(c.getNodeName(), val);
+								}
+							}
+							// Check for NPC spawns
+							else if (npctag.getNodeName().equalsIgnoreCase("npc"))
 							{
 								// mandatory
 								final int templateId = parseInt(attrs, "id");
@@ -161,6 +204,7 @@ public final class SpawnTable extends DocumentParser
 								spawnInfo.set("y", y);
 								spawnInfo.set("z", z);
 								spawnInfo.set("territoryName", territoryName);
+								spawnInfo.set("spawnName", spawnName);
 								
 								// trying to read optional parameters
 								if (attrs.getNamedItem("heading") != null)
@@ -192,7 +236,7 @@ public final class SpawnTable extends DocumentParser
 									}
 								}
 								
-								_xmlSpawnCount += addSpawn(spawnInfo);
+								_xmlSpawnCount += addSpawn(spawnInfo, map);
 							}
 						}
 					}
@@ -246,7 +290,13 @@ public final class SpawnTable extends DocumentParser
 		return npcSpawnCount;
 	}
 	
-	private int addSpawn(StatsSet spawnInfo)
+	/**
+	 * Creates NPC spawn
+	 * @param spawnInfo StatsSet of spawn parameters
+	 * @param AIData Map of specific AI parameters for this spawn
+	 * @return count NPC instances, spawned by this spawn
+	 */
+	private int addSpawn(StatsSet spawnInfo, Map<String, Integer> AIData)
 	{
 		L2Spawn spawnDat;
 		int ret = 0;
@@ -261,11 +311,18 @@ public final class SpawnTable extends DocumentParser
 			spawnDat.setRespawnDelay(spawnInfo.getInt("respawnDelay", 0), spawnInfo.getInt("respawnRandom", 0));
 			spawnDat.setLocationId(spawnInfo.getInt("locId", 0));
 			String territoryName = spawnInfo.getString("territoryName", "");
+			String spawnName = spawnInfo.getString("spawnName", "");
 			spawnDat.setCustom(spawnInfo.getBoolean("isCustomSpawn", false));
+			if (!spawnName.isEmpty())
+			{
+				spawnDat.setName(spawnName);
+			}
 			if (!territoryName.isEmpty())
 			{
 				spawnDat.setSpawnTerritory(ZoneManager.getInstance().getSpawnTerritory(territoryName));
 			}
+			// Register AI Data for this spawn
+			NpcPersonalAIData.getInstance().storeData(spawnDat, AIData);
 			switch (spawnInfo.getInt("periodOfDay", 0))
 			{
 				case 0: // default
@@ -292,6 +349,16 @@ public final class SpawnTable extends DocumentParser
 		return ret;
 	}
 	
+	/**
+	 * Wrapper for {@link #addSpawn(StatsSet, Map)}.
+	 * @param spawnInfo StatsSet of spawn parameters
+	 * @return count NPC instances, spawned by this spawn
+	 */
+	private int addSpawn(StatsSet spawnInfo)
+	{
+		return addSpawn(spawnInfo, null);
+	}
+	
 	public Map<Integer, Set<L2Spawn>> getSpawnTable()
 	{
 		return _spawnTable;

+ 34 - 3
L2J_Server_BETA/java/com/l2jserver/gameserver/model/L2Spawn.java

@@ -30,6 +30,7 @@ import javolution.util.FastList;
 import com.l2jserver.Config;
 import com.l2jserver.gameserver.GeoData;
 import com.l2jserver.gameserver.ThreadPoolManager;
+import com.l2jserver.gameserver.datatables.NpcPersonalAIData;
 import com.l2jserver.gameserver.datatables.TerritoryTable;
 import com.l2jserver.gameserver.idfactory.IdFactory;
 import com.l2jserver.gameserver.model.actor.L2Attackable;
@@ -38,6 +39,7 @@ import com.l2jserver.gameserver.model.actor.L2Npc;
 import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
 import com.l2jserver.gameserver.model.interfaces.IIdentifiable;
 import com.l2jserver.gameserver.model.interfaces.ILocational;
+import com.l2jserver.gameserver.model.interfaces.INamable;
 import com.l2jserver.gameserver.model.interfaces.IPositionable;
 import com.l2jserver.gameserver.model.zone.type.NpcSpawnTerritory;
 import com.l2jserver.util.Rnd;
@@ -49,10 +51,12 @@ import com.l2jserver.util.Rnd;
  * The heading of the L2NpcInstance can be a random heading if not defined (value= -1) or an exact heading (ex : merchant...).
  * @author Nightmare
  */
-public class L2Spawn implements IPositionable, IIdentifiable
+public class L2Spawn implements IPositionable, IIdentifiable, INamable
 {
 	protected static final Logger _log = Logger.getLogger(L2Spawn.class.getName());
 	
+	/** String identifier of this spawn */
+	private String _name;
 	/** The link on the L2NpcTemplate object containing generic and static properties of this spawn (ex : RewardExp, RewardSP, AggroRange...) */
 	private L2NpcTemplate _template;
 	/** The maximum number of L2NpcInstance that can manage this L2Spawn */
@@ -156,6 +160,24 @@ public class L2Spawn implements IPositionable, IIdentifiable
 		return _maximumCount;
 	}
 	
+ 	/**
+	 * @return the String Identifier of this spawn.
+ 	 */
+	@Override
+	public String getName()
+	{
+		return _name;
+	}
+	
+ 	/**
+	 * Set the String Identifier of this spawn.
+	 * @param name
+	 */
+	public void setName(String name)
+	{
+		_name = name;
+	}
+	
 	/**
 	 * @return the Identifier of the location area where L2NpcInstance can be spwaned.
 	 */
@@ -531,6 +553,12 @@ public class L2Spawn implements IPositionable, IIdentifiable
 				return mob;
 			}
 			mob = (L2Npc) tmp;
+			// Check for certain AI data, overriden in spawnlist 
+			if (_name != null)
+			{
+				NpcPersonalAIData.getInstance().initializeNpcParameters(mob, this, _name);
+			}
+
 			return initializeNpcInstance(mob);
 		}
 		catch (Exception e)
@@ -595,8 +623,11 @@ public class L2Spawn implements IPositionable, IIdentifiable
 		mob.setDecayed(false);
 		// Set the HP and MP of the L2NpcInstance to the max
 		mob.setCurrentHpMp(mob.getMaxHp(), mob.getMaxMp());
-		// Set default value
-		mob.setScriptValue(0);
+		// Clear script variables
+		if (mob.hasVariables())
+		{
+			mob.getVariables().getSet().clear();
+		}
 		// Set is not random walk default value
 		mob.setIsNoRndWalk(isNoRndWalk());
 		

+ 72 - 6
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/L2Npc.java

@@ -32,6 +32,7 @@ import com.l2jserver.gameserver.ThreadPoolManager;
 import com.l2jserver.gameserver.cache.HtmCache;
 import com.l2jserver.gameserver.datatables.CategoryData;
 import com.l2jserver.gameserver.datatables.ItemTable;
+import com.l2jserver.gameserver.datatables.NpcPersonalAIData;
 import com.l2jserver.gameserver.enums.AIType;
 import com.l2jserver.gameserver.enums.CategoryType;
 import com.l2jserver.gameserver.enums.InstanceType;
@@ -73,6 +74,7 @@ import com.l2jserver.gameserver.model.olympiad.Olympiad;
 import com.l2jserver.gameserver.model.quest.Quest;
 import com.l2jserver.gameserver.model.skills.L2Skill;
 import com.l2jserver.gameserver.model.skills.targets.L2TargetType;
+import com.l2jserver.gameserver.model.variables.NpcVariables;
 import com.l2jserver.gameserver.model.zone.type.L2TownZone;
 import com.l2jserver.gameserver.network.SystemMessageId;
 import com.l2jserver.gameserver.network.serverpackets.AbstractNpcInfo;
@@ -128,7 +130,6 @@ public class L2Npc extends L2Character
 	private int _soulshotamount = 0;
 	private int _spiritshotamount = 0;
 	private int _displayEffect = 0;
-	private int _scriptVal = 0;
 	
 	private final L2NpcAIData _staticAIData = getTemplate().getAIDataStatic();
 	
@@ -573,11 +574,11 @@ public class L2Npc extends L2Character
 	}
 	
 	/**
-	 * @return the Aggro Range of this L2NpcInstance contained in the L2NpcTemplate.
+	 * @return the Aggro Range of this L2NpcInstance either contained in the L2NpcTemplate, or overriden by spawnlist AI value.
 	 */
 	public int getAggroRange()
 	{
-		return _staticAIData.getAggroRange();
+		return hasAIValue("aggroRange") ? getAIValue("aggroRange") : _staticAIData.getAggroRange();
 	}
 	
 	/**
@@ -1843,19 +1844,84 @@ public class L2Npc extends L2Character
 		}
 	}
 	
+	/**
+	 * Short wrapper for backward compatibility
+	 * @return stored script value
+	 */
 	public int getScriptValue()
 	{
-		return _scriptVal;
+		return getVariables().getInt("SCRIPT_VAL");
 	}
 	
+	/**
+	 * Short wrapper for backward compatibility. Stores script value
+	 * @param val value to store
+	 */
 	public void setScriptValue(int val)
 	{
-		_scriptVal = val;
+		getVariables().set("SCRIPT_VAL", val);
 	}
 	
+	/**
+	 * Short wrapper for backward compatibility.
+	 * @param val value to store
+	 * @return {@code true} if stored script value equals given value, {@code false} otherwise 	 
+	 */
 	public boolean isScriptValue(int val)
 	{
-		return _scriptVal == val;
+		return getVariables().getInt("SCRIPT_VAL") == val;
+	}
+	
+	/**
+	 * @param paramName the parameter name to check
+	 * @return given AI parameter value
+	 */
+	public int getAIValue(final String paramName)
+	{
+		return hasAIValue(paramName) ? NpcPersonalAIData.getInstance().getAIValue(getSpawn().getName(), paramName) : -1;
+	}
+	
+	/**
+	 * @param paramName the parameter name to check
+	 * @return {@code true} if given parameter is set for NPC, {@code false} otherwise
+	 */
+	public boolean hasAIValue(final String paramName)
+	{
+		return (getSpawn() != null) && (getSpawn().getName() != null) && NpcPersonalAIData.getInstance().hasAIValue(getSpawn().getName(), paramName);
+	}
+	
+	/**
+	 * @param npc NPC to check
+	 * @return {@code true} if both given NPC and this NPC is in the same spawn group, {@code false} otherwise
+	 */
+	public boolean isInMySpawnGroup(L2Npc npc)
+	{
+		return ((getSpawn() != null) && (npc.getSpawn() != null) && (getSpawn().getName() != null) && (getSpawn().getName().equals(npc.getSpawn().getName())));
+	}
+	
+	/**
+	 * @return {@code true} if NPC currently located in own spawn point, {@code false} otherwise
+	 */
+	public boolean staysInSpawnLoc()
+	{
+		return ((getSpawn() != null) && (getSpawn().getX(this) == getX()) && (getSpawn().getY(this) == getY()));
+	}
+	
+ 	/**
+	 * @return {@code true} if {@link NpcVariables} instance is attached to current player's scripts, {@code false} otherwise.
+	 */
+	public boolean hasVariables()
+	{
+		return getScript(NpcVariables.class) != null;
+	}
+	
+	/**
+	 * @return {@link NpcVariables} instance containing parameters regarding NPC.
+	 */
+	public NpcVariables getVariables()
+	{
+		final NpcVariables vars = getScript(NpcVariables.class);
+		return vars != null ? vars : addScript(new NpcVariables());
 	}
 	
 	/**

+ 47 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/model/variables/NpcVariables.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2004-2013 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 <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.gameserver.model.variables;
+
+/**
+ * @author GKR
+ */
+public class NpcVariables extends AbstractVariables
+{
+	public NpcVariables()
+	{
+	}
+	
+	@Override
+	public int getInt(String key)
+	{
+		return super.getInt(key, 0);
+	}
+	
+	@Override
+	public boolean restoreMe()
+	{
+		return true;
+	}
+	
+	@Override
+	public boolean storeMe()
+	{
+		return true;
+	}
+}