Browse Source

TVT Event improvements by Merwllyra

This patch fixes following issues:
 * '''Exploitable: Teams can be forged by exiting and re-entering the competition'''
Players would check the team count and exit/enter the event to be able to choose which side they fight for, which made unbalanced teams for the event. This is mostly bad for small servers, but also affects High Rate servers. 
 * '''Bug: The teleporter teleports players in a fixed spot.''' A random radius around the spot should be used instead. 
When 20 players are teleported in the same spot, some of them will be stuck and won't be able to move.
 * '''Bug: Members of the same clan must use forced attack when playing against each other in TvT'''
 * '''Feature: Instead of "time to pass" values, use "time of day" values in TvT configuration'''
Most of the times it's better to have the events announced on the website/forum and the players will know when to come online to start playing. However, if you restart the server (due to a bug or maintenance) before the event, the timer would reset. This addresses the issue, and instead of an interval, a time of day is specified for each event.
 * '''Feature: Move TvT to the next phase by admin command'''
If for some reason an event should start before the announced time or should move to the next phase (due to teams full or whatever) a GM may use the `tvt_advance` command to skip the TvT event to the next phase.
DrHouse 16 years ago
parent
commit
b36e2635f4

+ 1 - 1
L2_GameServer/java/config/l2jmods.properties

@@ -105,7 +105,7 @@ WeddingDivorceCosts=20
 # enable TvTEvent
 TvTEventEnabled = false
 # Time Between TvT events (in minutes, 300 = 5 hours)
-TvTEventInterval = 300
+TvTEventInterval = 9:00,15:00,21:00,3:00
 #  Registration timer (in minutes) from start of event.
 TvTEventParticipationTime = 60
 #  Event running time, in minutes

+ 3 - 3
L2_GameServer/java/net/sf/l2j/Config.java

@@ -499,7 +499,7 @@ public final class Config
     public static int		L2JMOD_CHAMPION_REWARD_ID;
     public static int		L2JMOD_CHAMPION_REWARD_QTY;
     public static boolean	TVT_EVENT_ENABLED;
-    public static int		TVT_EVENT_INTERVAL;
+    public static String[]	TVT_EVENT_INTERVAL;
     public static int		TVT_EVENT_PARTICIPATION_TIME;
     public static int		TVT_EVENT_RUNNING_TIME;
     public static int		TVT_EVENT_PARTICIPATION_NPC_ID;
@@ -1677,7 +1677,7 @@ public final class Config
 	                L2JMOD_CHAMPION_REWARD_QTY            	= Integer.parseInt(L2JModSettings.getProperty("ChampionRewardItemQty", "1"));
 	
 	                TVT_EVENT_ENABLED                        = Boolean.parseBoolean(L2JModSettings.getProperty("TvTEventEnabled", "false"));
-	                TVT_EVENT_INTERVAL                        = Integer.parseInt(L2JModSettings.getProperty("TvTEventInterval", "18000"));
+	                TVT_EVENT_INTERVAL                        = L2JModSettings.getProperty("TvTEventInterval", "20:00").split(",");
 	                TVT_EVENT_PARTICIPATION_TIME            = Integer.parseInt(L2JModSettings.getProperty("TvTEventParticipationTime", "3600"));
 	                TVT_EVENT_RUNNING_TIME                    = Integer.parseInt(L2JModSettings.getProperty("TvTEventRunningTime", "1800"));
 	                TVT_EVENT_PARTICIPATION_NPC_ID            = Integer.parseInt(L2JModSettings.getProperty("TvTEventParticipationNpcId", "0"));
@@ -2211,7 +2211,7 @@ public final class Config
         else if (pName.equalsIgnoreCase("WeddingFormalWear")) L2JMOD_WEDDING_FORMALWEAR = Boolean.parseBoolean(pValue);
         else if (pName.equalsIgnoreCase("WeddingDivorceCosts")) L2JMOD_WEDDING_DIVORCE_COSTS = Integer.parseInt(pValue);
         else if (pName.equalsIgnoreCase("TvTEventEnabled")) TVT_EVENT_ENABLED = Boolean.parseBoolean(pValue);
-        else if (pName.equalsIgnoreCase("TvTEventInterval")) TVT_EVENT_INTERVAL = Integer.parseInt(pValue);
+        else if (pName.equalsIgnoreCase("TvTEventInterval")) TVT_EVENT_INTERVAL = pValue.split(",");
         else if (pName.equalsIgnoreCase("TvTEventParticipationTime")) TVT_EVENT_PARTICIPATION_TIME = Integer.parseInt(pValue);
         else if (pName.equalsIgnoreCase("TvTEventRunningTime")) TVT_EVENT_RUNNING_TIME = Integer.parseInt(pValue);
         else if (pName.equalsIgnoreCase("TvTEventParticipationNpcId")) TVT_EVENT_PARTICIPATION_NPC_ID = Integer.parseInt(pValue);

+ 7 - 1
L2_GameServer/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminTvTEvent.java

@@ -20,6 +20,7 @@ import net.sf.l2j.gameserver.model.L2Object;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
 import net.sf.l2j.gameserver.model.entity.TvTEvent;
 import net.sf.l2j.gameserver.model.entity.TvTEventTeleporter;
+import net.sf.l2j.gameserver.model.entity.TvTManager;
 
 /**
  * @author FBIagent
@@ -29,7 +30,8 @@ public class AdminTvTEvent implements IAdminCommandHandler
 	private static final String[] ADMIN_COMMANDS =
 	{
 		"admin_tvt_add",
-		"admin_tvt_remove"
+		"admin_tvt_remove",
+		"admin_tvt_advance"
 	};
 	
 	public boolean useAdminCommand(String command, L2PcInstance activeChar)
@@ -58,6 +60,10 @@ public class AdminTvTEvent implements IAdminCommandHandler
 			
 			remove(activeChar, (L2PcInstance) target);
 		}
+		else if ( command.equals( "admin_tvt_advance" ) )
+		{
+			TvTManager.getInstance().skipDelay();
+		}
 		
 		return true;
 	}

+ 4 - 0
L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java

@@ -7765,6 +7765,10 @@ public final class L2PcInstance extends L2PlayableInstance
 				return false;
 		}
 
+		// Check if the attacker is in TvT and TvT is started
+		if (TvTEvent.isStarted() && TvTEvent.isPlayerParticipant(getObjectId()))
+			return true;
+
 		// Check if the attacker is not in the same clan
 		if (getClan() != null && attacker != null && getClan().isMember(attacker.getObjectId()))
 			return false;

+ 50 - 2
L2_GameServer/java/net/sf/l2j/gameserver/model/entity/TvTEvent.java

@@ -14,7 +14,9 @@
  */
 package net.sf.l2j.gameserver.model.entity;
 
+import java.util.Map;
 import java.util.logging.Logger;
+import javolution.util.FastMap;
 
 import net.sf.l2j.Config;
 import net.sf.l2j.gameserver.datatables.DoorTable;
@@ -128,7 +130,21 @@ public class TvTEvent
 		setState(EventState.PARTICIPATING);
 		return true;
 	}
-	
+
+	private static int highestLevelPcInstanceOf(Map< Integer, L2PcInstance > players)
+	{
+		int maxLevel = Integer.MIN_VALUE, maxLevelId = -1;
+		for (L2PcInstance player : players.values())
+		{
+			if (player.getLevel() >= maxLevel)
+			{
+				maxLevel = player.getLevel();
+				maxLevelId = player.getObjectId();
+			}
+		}
+		return maxLevelId;
+	}
+
 	/**
 	 * Starts the TvTEvent fight<br>
 	 * 1. Set state EventState.STARTING<br>
@@ -143,7 +159,39 @@ public class TvTEvent
 	{
 		// Set state to STARTING
 		setState(EventState.STARTING);
-		
+
+		// Randomize and balance team distribution
+		Map< Integer, L2PcInstance > allParticipants = new FastMap< Integer, L2PcInstance >();
+		allParticipants.putAll(_teams[0].getParticipatedPlayers());
+		allParticipants.putAll(_teams[1].getParticipatedPlayers());
+		System.out.println(allParticipants.size());
+		_teams[0].cleanMe();
+		_teams[1].cleanMe();
+		System.out.println(allParticipants.size());
+		int balance[] = { 0, 0 }, priority = 0, highestLevelPlayerId;
+		L2PcInstance highestLevelPlayer;
+		// XXX: allParticipants should be sorted by level instead of using highestLevelPcInstanceOf for every fetch
+		while (allParticipants.size() > 0)
+		{
+			// Priority team gets one player
+			highestLevelPlayerId = highestLevelPcInstanceOf(allParticipants);
+			highestLevelPlayer = allParticipants.get(highestLevelPlayerId);
+			allParticipants.remove(highestLevelPlayerId);
+			_teams[priority].addPlayer(highestLevelPlayer);
+			balance[priority] += highestLevelPlayer.getLevel();
+			// Exiting if no more players
+			if (allParticipants.size() == 0) break;
+			// The other team gets one player
+			// XXX: Code not dry
+			highestLevelPlayerId = highestLevelPcInstanceOf(allParticipants);
+			highestLevelPlayer = allParticipants.get(highestLevelPlayerId);
+			allParticipants.remove(highestLevelPlayerId);
+			_teams[priority].addPlayer(highestLevelPlayer);
+			balance[priority] += highestLevelPlayer.getLevel();
+			// Recalculating priority
+			priority = balance[0] > balance[1] ? 1 : 0;
+		}
+
 		// Check for enought participants
 		if (_teams[0].getParticipatedPlayerCount() < Config.TVT_EVENT_MIN_PLAYERS_IN_TEAMS || _teams[1].getParticipatedPlayerCount() < Config.TVT_EVENT_MIN_PLAYERS_IN_TEAMS)
 		{

+ 2 - 1
L2_GameServer/java/net/sf/l2j/gameserver/model/entity/TvTEventTeleporter.java

@@ -19,6 +19,7 @@ import net.sf.l2j.gameserver.ThreadPoolManager;
 import net.sf.l2j.gameserver.model.L2Effect;
 import net.sf.l2j.gameserver.model.L2Summon;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
+import net.sf.l2j.util.Rnd;
 
 public class TvTEventTeleporter implements Runnable
 {
@@ -84,7 +85,7 @@ public class TvTEventTeleporter implements Runnable
 		_playerInstance.setCurrentCp(_playerInstance.getMaxCp());
 		_playerInstance.setCurrentHp(_playerInstance.getMaxHp());
 		_playerInstance.setCurrentMp(_playerInstance.getMaxMp());
-		_playerInstance.teleToLocation(_coordinates[0], _coordinates[1], _coordinates[2], false);
+		_playerInstance.teleToLocation( _coordinates[ 0 ] + Rnd.get(101)-50, _coordinates[ 1 ] + Rnd.get(101)-50, _coordinates[ 2 ], false );
 		
 		if (TvTEvent.isStarted() && !_adminRemove)
 		{

+ 43 - 4
L2_GameServer/java/net/sf/l2j/gameserver/model/entity/TvTManager.java

@@ -14,6 +14,8 @@
  */
 package net.sf.l2j.gameserver.model.entity;
 
+import java.util.Calendar;
+import java.util.concurrent.ScheduledFuture;
 import java.util.logging.Logger;
 
 import net.sf.l2j.Config;
@@ -66,8 +68,35 @@ public class TvTManager
 	 */
 	public void scheduleEventStart()
 	{
-		_task = new TvTStartTask(System.currentTimeMillis() + 60000L * Config.TVT_EVENT_INTERVAL);
-		ThreadPoolManager.getInstance().executeTask(_task);
+		try {
+			Calendar currentTime = Calendar.getInstance();
+			Calendar nextStartTime = null;
+			Calendar testStartTime = null;
+			for (String timeOfDay : Config.TVT_EVENT_INTERVAL) {
+				// Creating a Calendar object from the specified interval value
+				testStartTime = Calendar.getInstance();
+				testStartTime.setLenient(true);
+				String[] splitTimeOfDay = timeOfDay.split(":");
+				testStartTime.set(Calendar.HOUR_OF_DAY, Integer.parseInt(splitTimeOfDay[0]));
+				testStartTime.set(Calendar.MINUTE, Integer.parseInt(splitTimeOfDay[1]));
+				// If the date is in the past, make it the next day (Example: Checking for "1:00", when the time is 23:57.)
+				if (testStartTime.getTimeInMillis() < currentTime.getTimeInMillis())
+				{
+					testStartTime.add(Calendar.DAY_OF_MONTH, 1);
+				}
+				// Check for the test date to be the minimum (smallest in the specified list)
+				if (nextStartTime == null || testStartTime.getTimeInMillis() < nextStartTime.getTimeInMillis())
+				{
+					nextStartTime = testStartTime;
+				}
+			}
+			_task = new TvTStartTask(nextStartTime.getTimeInMillis());
+			ThreadPoolManager.getInstance().executeTask(_task);
+		}
+		catch (Exception e)
+		{
+			_log.warning("TvTEventEngine[TvTManager.scheduleEventStart()]: Error figuring out a start time. Check TvTEventInterval in config file.");
+		}
 	}
 	
 	/**
@@ -123,13 +152,23 @@ public class TvTManager
 		
 		this.scheduleEventStart();
 	}
-	
+
+	public void skipDelay()
+	{
+		if (_task.nextRun.cancel(false))
+		{
+			_task.setStartTime(System.currentTimeMillis());
+			ThreadPoolManager.getInstance().executeTask(_task);
+		}
+	}
+
 	/**
 	 * Class for TvT cycles
 	 */
 	class TvTStartTask implements Runnable
 	{
 		private long _startTime;
+		public ScheduledFuture<?> nextRun;
 		
 		public TvTStartTask(long startTime)
 		{
@@ -205,7 +244,7 @@ public class TvTManager
 			
 			if (delay > 0)
 			{
-				ThreadPoolManager.getInstance().scheduleGeneral(this, nextMsg * 1000);
+				nextRun = ThreadPoolManager.getInstance().scheduleGeneral(this, nextMsg*1000);
 			}
 		}