Noticias:

No tienes permiso para ver los enlaces. Para poder verlos Registrate o Conectate.

Menú Principal

Monster Race - The retail event of Lineage II

Iniciado por Neron, Jun 16, 2026, 03:33 PM

Tema anterior - Siguiente tema

Neron

Cita de: Baggos¡Hola a todos!

Lineage II tiene algunos eventos agradables y entretenidos por sí solos, pero han sido olvidados ya que ningún servidor los ha utilizado. Modifiqué el evento minorista Monster Race para que pudiera ser útil para los servidores.

Así es como funciona ahora:

- El tiempo se ha reducido y comienza cada 15 minutos.
- En lugar de adena, puede optar por apostar cualquier artículo que desee. Los montos de las apuestas son 20/10/30.
- Los monstruos hablan entre sí para crear una "competencia" entre ellos. Hay un retraso de varios minutos entre los mensajes. El video ha sido cortado.
- Se ha añadido recompensa para el segundo ganador. Los ganadores primero y segundo darán unas probabilidades aleatorias entre 0.4 y 0.5 || calculaOddsForLanes() funciona de otra manera nueva.
- El primer ganador obtiene el 100% del monto de las apuestas, mientras que el segundo ganador obtiene solo el 60% del monto de las apuestas.
- Los jugadores pueden comprar tantos boletos como quieran sin que ocurran errores, ya que la cantidad devuelta es mucho menor si compran los 8 carriles.
- El nombre del ganador se ha añadido al SQL y HTML para mostrar los mejores corredores en los últimos juegos.
- No importa qué ID de artículo vaya a utilizar, el HTML reconocerá automáticamente el nombre del artículo para que aparezca en la compra del ticket.
- Se ha realizado una limpieza significativa en el código para evitar errores y para escribirlo en un estilo más moderno, de alguna manera.

En definitiva, se ha modificado para funcionar como un evento que puede entretener a los jugadores y ofrecer una muy buena recompensa, haciendo el evento necesario para los jugadores.

Todas las probabilidades anteriores son indicativas y se pueden cambiar como desee, solo tenga cuidado con cualquier cantidad mala que pueda causar grandes recompensas para los jugadores.

\ No newline at end of file
diff --git a/aCis_gameserver/java/net/sf/l2j/Config.java b/aCis_gameserver/java/net/sf/l2j/Config.java
index 65daaa6..3cd7841 100644
--- a/aCis_gameserver/java/net/sf/l2j/Config.java
+++ b/aCis_gameserver/java/net/sf/l2j/Config.java
@@ -201,6 +201,12 @@
 	public static int FISH_CHAMPIONSHIP_REWARD_4;
 	public static int FISH_CHAMPIONSHIP_REWARD_5;
 	
+	/** Derby Race system */
+	public static int _itemID;
+	public static int _price1;
+	public static int _price2;
+	public static int _price3;
+	
 	// --------------------------------------------------
 	// GeoEngine
 	// --------------------------------------------------
@@ -803,6 +809,11 @@
 		FISH_CHAMPIONSHIP_REWARD_3 = events.getProperty("FishChampionshipReward3", 300000);
 		FISH_CHAMPIONSHIP_REWARD_4 = events.getProperty("FishChampionshipReward4", 200000);
 		FISH_CHAMPIONSHIP_REWARD_5 = events.getProperty("FishChampionshipReward5", 100000);
+		
+		_itemID = events.getProperty("ItemID", 6673);
+		_price1 = events.getProperty("Price1", 10);
+		_price2 = events.getProperty("Price2", 20);
+		_price3 = events.getProperty("Price3", 30);
 	}
 	
 	/**
diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/data/manager/DerbyTrackManager.java b/aCis_gameserver/java/net/sf/l2j/gameserver/data/manager/DerbyTrackManager.java
index e8efc06..8122d4a 100644
--- a/aCis_gameserver/java/net/sf/l2j/gameserver/data/manager/DerbyTrackManager.java
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/data/manager/DerbyTrackManager.java
@@ -1,13 +1,14 @@
 package net.sf.l2j.gameserver.data.manager;
 
-import java.lang.reflect.Constructor;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
@@ -18,14 +19,17 @@
 import net.sf.l2j.commons.random.Rnd;
 
 import net.sf.l2j.gameserver.data.xml.NpcData;
+import net.sf.l2j.gameserver.enums.RaceState;
+import net.sf.l2j.gameserver.enums.SayType;
 import net.sf.l2j.gameserver.idfactory.IdFactory;
 import net.sf.l2j.gameserver.model.HistoryInfo;
+import net.sf.l2j.gameserver.model.World;
 import net.sf.l2j.gameserver.model.actor.Npc;
-import net.sf.l2j.gameserver.model.actor.template.NpcTemplate;
 import net.sf.l2j.gameserver.model.zone.type.DerbyTrackZone;
 import net.sf.l2j.gameserver.network.SystemMessageId;
 import net.sf.l2j.gameserver.network.serverpackets.DeleteObject;
 import net.sf.l2j.gameserver.network.serverpackets.MonRaceInfo;
+import net.sf.l2j.gameserver.network.serverpackets.NpcSay;
 import net.sf.l2j.gameserver.network.serverpackets.PlaySound;
 import net.sf.l2j.gameserver.network.serverpackets.SystemMessage;
 
@@ -33,54 +37,35 @@
 {
 	protected static final CLogger LOGGER = new CLogger(DerbyTrackManager.class.getName());
 	
-	private static final String SAVE_HISTORY = "INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)";
+	private static final String SAVE_HISTORY = "INSERT INTO mdt_history (race_id, first, second, odd_rate, winner) VALUES (?,?,?,?,?)";
 	private static final String LOAD_HISTORY = "SELECT * FROM mdt_history";
 	private static final String LOAD_BETS = "SELECT * FROM mdt_bets";
 	private static final String SAVE_BETS = "REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)";
 	private static final String CLEAR_BETS = "UPDATE mdt_bets SET bet = 0";
 	
-	public enum RaceState
-	{
-		ACCEPTING_BETS,
-		WAITING,
-		STARTING_RACE,
-		RACE_END
-	}
+	private static final PlaySound SOUND_1 = new PlaySound(1, "S_Race");
+	private static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start");
+	private static final List<List<Integer>> CODES = Arrays.asList(Arrays.asList(-1, 0), Arrays.asList(0, 15322), Arrays.asList(13765, -1));
+	private static final int RACE_MANAGER = 30995;
 	
-	protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race");
-	protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start");
+	private final List<Npc> _runners = new ArrayList<>(); // List holding initial npcs, shuffled on a new race.
+	private final TreeMap<Integer, HistoryInfo> _history = new TreeMap<>(); // List holding old race records.
+	public final Map<Integer, Long> _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values are set to 0 after every race.
+	private final List<Double> _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation.
+	private final List<Integer> _runnerIndices = new ArrayList<>(); // Create a list of indices for all the runners
 	
-	protected static final int[][] CODES =
-	{
-		{
-			-1,
-			0
-		},
-		{
-			0,
-			15322
-		},
-		{
-			13765,
-			-1
-		}
-	};
+	private int _raceNumber = 1;
+	private int _finalCountdown = 0;
+	private RaceState _state = RaceState.FINISHED;
 	
-	protected final List<Npc> _runners = new ArrayList<>(); // List holding initial npcs, shuffled on a new race.
-	protected final TreeMap<Integer, HistoryInfo> _history = new TreeMap<>(); // List holding old race records.
-	protected final Map<Integer, Long> _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values are set to 0 after every race.
-	protected final List<Double> _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation.
+	private MonRaceInfo _packet;
 	
-	protected int _raceNumber = 1;
-	protected int _finalCountdown = 0;
-	protected RaceState _state = RaceState.RACE_END;
-	
-	protected MonRaceInfo _packet;
-	
-	private List<Npc> _chosenRunners; // Holds the actual list of 8 runners.
+	public List<Npc> _chosenRunners; // Holds the actual list of 8 runners.
 	private int[][] _speeds;
-	private int _firstIndex; // Index going from 0-7.
-	private int _secondIndex; // Index going from 0-7.
+	private int _firstPlaceIndex; // Index going from 0-7.
+	private int _secondPlaceIndex; // Index going from 0-7.
+	private String _message;
+	private Timer _timer = new Timer();
 	
 	protected DerbyTrackManager()
 	{
@@ -90,58 +75,71 @@
 		// Feed _betsPerLane with stored informations on bets.
 		loadBets();
 		
-		// Feed _runners, we will only have to shuffle it when needed.
+		// Initialize _runners list with NPC templates and their constructors
 		try
 		{
-			for (int i = 31003; i < 31027; i++)
+			for (var i = 31003; i < 31027; i++)
 			{
-				final NpcTemplate template = NpcData.getInstance().getTemplate(i);
-				if (template == null)
-					continue;
-				
-				final Constructor<?> constructor = Class.forName("net.sf.l2j.gameserver.model.actor.instance." + template.getType()).getConstructors()[0];
-				
-				_runners.add((Npc) constructor.newInstance(IdFactory.getInstance().getNextId(), template));
+				var template = NpcData.getInstance().getTemplate(i);
+				if (template != null)
+				{
+					var constructor = Class.forName("net.sf.l2j.gameserver.model.actor.instance." + template.getType()).getConstructors()[0];
+					var npc = (Npc) constructor.newInstance(IdFactory.getInstance().getNextId(), template);
+					_runners.add(npc);
+				}
 			}
 		}
 		catch (Exception e)
 		{
-			LOGGER.error("Couldn't initialize runners.", e);
+			LOGGER.error("Failed to initialize runners", e);
 		}
 		
+		// Initialize _speeds array
 		_speeds = new int[8][20];
 		
+		// Schedule announcement task to run every second
 		ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000);
 	}
 	
-	public List<Npc> getRunners()
+	/**
+	 * @return the list of selected runners.
+	 */
+	public List<Npc> getChosenRunners()
 	{
 		return _chosenRunners;
 	}
 	
 	/**
-	 * @param index : The actual index of List to check on.
-	 * @return the name of the Npc.
+	 * Returns the name of the runner at the given index in the selected runners list.
+	 * @param index of the runner.
+	 * @return the name of the runner.
 	 */
 	public String getRunnerName(int index)
 	{
-		final Npc npc = _chosenRunners.get(index);
-		return (npc == null) ? "" : npc.getName();
+		if (index >= 0 && index < _chosenRunners.size())
+		{
+			Npc runner = _chosenRunners.get(index);
+			return runner.getName();
+		}
+		return "";
 	}
 	
+	/**
+	 * @return the speeds of the runners.
+	 */
 	public int[][] getSpeeds()
 	{
 		return _speeds;
 	}
 	
-	public int getFirst()
+	public int getFirstPlace()
 	{
-		return _firstIndex;
+		return _firstPlaceIndex;
 	}
 	
-	public int getSecond()
+	public int getSecondPlace()
 	{
-		return _secondIndex;
+		return _secondPlaceIndex;
 	}
 	
 	public MonRaceInfo getRacePacket()
@@ -161,187 +159,189 @@
 	
 	public List<HistoryInfo> getLastHistoryEntries()
 	{
-		return _history.descendingMap().values().stream().limit(8).collect(Collectors.toList());
+		return new ArrayList<>(_history.descendingMap().values()).subList(0, Math.min(8, _history.size()));
 	}
 	
 	public HistoryInfo getHistoryInfo(int raceNumber)
 	{
-		return _history.get(raceNumber);
+		return _history.getOrDefault(raceNumber, null);
 	}
 	
 	public List<Double> getOdds()
 	{
-		return _odds;
+		return new ArrayList<>(_odds);
 	}
 	
 	public void newRace()
 	{
-		// Edit _history.
-		_history.put(_raceNumber, new HistoryInfo(_raceNumber, 0, 0, 0));
+		// Add a new history entry.
+		_history.put(_raceNumber, new HistoryInfo(_raceNumber, 0, 0, 0, ""));
 		
-		// Randomize _runners.
+		// Randomize runners.
 		Collections.shuffle(_runners);
 		
-		// Setup 8 new creatures ; pickup the first 8 from _runners.
-		_chosenRunners = _runners.subList(0, 8);
+		// Choose 8 runners from the shuffled list.
+		_chosenRunners = new ArrayList<>(_runners.subList(0, 8));
 	}
 	
-	public void newSpeeds()
+	public void CalculateLaneSpeeds()
 	{
-		_speeds = new int[8][20];
-		
-		int total = 0;
-		int winnerDistance = 0;
-		int secondDistance = 0;
+		var speeds = new int[8][];
+		var firstPlaceIndex = -1;
+		var secondPlaceIndex = -1;
+		var currentWinnerTotal = 0;
+		var currentRunnerUpTotal = 0;
 		
 		// For each lane.
-		for (int i = 0; i < 8; i++)
+		for (var laneIndex = 0; laneIndex < 8; laneIndex++)
 		{
-			// Reset value upon new lane.
-			total = 0;
+			var totalSpeed = 0;
+			var segmentSpeeds = new int[20];
 			
-			// Test the 20 segments.
-			for (int j = 0; j < 20; j++)
+			// Calculate the speed for each of the 20 segments in the lane.
+			for (var segmentIndex = 0; segmentIndex < 20; segmentIndex++)
 			{
-				if (j == 19)
-					_speeds[i][j] = 100;
+				if (segmentIndex == 19)
+					// Set the final segment speed to 100.
+					segmentSpeeds[segmentIndex] = 100;
 				else
-					_speeds[i][j] = Rnd.get(60) + 65;
+					// Generate a random speed.
+					segmentSpeeds[segmentIndex] = Rnd.get(50) + 55;
 				
-				// feed actual total to current lane total.
-				total += _speeds[i][j];
+				// Calculate the total speed for this lane.
+				totalSpeed += segmentSpeeds[segmentIndex];
 			}
 			
-			// The current total for this line is superior or equals to previous winner ; it means we got a new winner, and the old winner becomes second.
-			if (total >= winnerDistance)
+			// Check if this lane is the new winner or runner-up.
+			if (totalSpeed >= currentWinnerTotal)
 			{
-				// Old winner becomes second.
-				_secondIndex = _firstIndex;
+				// The previous winner becomes the runner-up.
+				secondPlaceIndex = firstPlaceIndex;
+				currentRunnerUpTotal = currentWinnerTotal;
 				
-				// Old winner distance is the second.
-				secondDistance = winnerDistance;
-				
-				// Find the good index.
-				_firstIndex = i;
-				
-				// Set the new limit to bypass winner position.
-				winnerDistance = total;
+				// Set this lane as the new winner.
+				firstPlaceIndex = laneIndex;
+				currentWinnerTotal = totalSpeed;
 			}
-			// The total wasn't enough to
-			else if (total >= secondDistance)
+			else if (totalSpeed >= currentRunnerUpTotal)
 			{
-				// Find the good index.
-				_secondIndex = i;
-				
-				// Set the new limit to bypass second position.
-				secondDistance = total;
+				// Set this lane as the new runner-up.
+				secondPlaceIndex = laneIndex;
+				currentRunnerUpTotal = totalSpeed;
 			}
+			
+			// Store the speeds for this lane.
+			speeds[laneIndex] = segmentSpeeds;
 		}
+		
+		// Store the results.
+		_speeds = speeds;
+		_firstPlaceIndex = firstPlaceIndex;
+		_secondPlaceIndex = secondPlaceIndex;
 	}
 	
 	/**
-	 * Load past races informations, feeding _history arrayList.<br>
-	 * Also sets _raceNumber, based on latest HistoryInfo loaded.
+	 * Loads information on past races and adds it to the _history arrayList. Also sets _raceNumber based on the latest HistoryInfo loaded.
 	 */
 	protected void loadHistory()
 	{
-		try (Connection con = ConnectionPool.getConnection();
-			PreparedStatement ps = con.prepareStatement(LOAD_HISTORY);
-			ResultSet rs = ps.executeQuery())
+		try (var connection = ConnectionPool.getConnection();
+			var preparedStatement = connection.prepareStatement(LOAD_HISTORY);
+			var resultSet = preparedStatement.executeQuery())
 		{
-			while (rs.next())
+			while (resultSet.next())
 			{
-				final int savedRaceNumber = rs.getInt("race_id");
-				
-				_history.put(savedRaceNumber, new HistoryInfo(savedRaceNumber, rs.getInt("first"), rs.getInt("second"), rs.getDouble("odd_rate")));
-				
-				// Calculate the current race number.
+				var savedRaceNumber = resultSet.getInt("race_id");
+				var historyInfo = new HistoryInfo(savedRaceNumber, resultSet.getInt("first"), resultSet.getInt("second"), resultSet.getDouble("odd_rate"), resultSet.getString("winner"));
+				_history.put(savedRaceNumber, historyInfo);
 				if (_raceNumber <= savedRaceNumber)
 					_raceNumber = savedRaceNumber + 1;
 			}
 		}
-		catch (Exception e)
+		catch (SQLException e)
 		{
-			LOGGER.error("Can't load Derby Track history.", e);
+			LOGGER.error("Failed to load Derby Track history", e);
 		}
 		LOGGER.info("Loaded {} Derby Track records, currently on race #{}.", _history.size(), _raceNumber);
 	}
 	
 	/**
-	 * Save an {@link HistoryInfo} record into database.
-	 * @param history The HistoryInfo to store.
+	 * Saves a {@link HistoryInfo} record into the database.
+	 * @param history The HistoryInfo object to be stored.
 	 */
 	protected void saveHistory(HistoryInfo history)
 	{
-		try (Connection con = ConnectionPool.getConnection();
-			PreparedStatement ps = con.prepareStatement(SAVE_HISTORY))
+		try (var con = ConnectionPool.getConnection();
+			var ps = con.prepareStatement(SAVE_HISTORY))
 		{
 			ps.setInt(1, history.getRaceId());
-			ps.setInt(2, history.getFirst());
-			ps.setInt(3, history.getSecond());
+			ps.setInt(2, history.getFirstPlace());
+			ps.setInt(3, history.getSecondPlace());
 			ps.setDouble(4, history.getOddRate());
-			ps.execute();
+			ps.setString(5, history.getWinnerName());
+			ps.executeUpdate();
 		}
-		catch (Exception e)
+		catch (SQLException e)
 		{
-			LOGGER.error("Can't save Derby Track history.", e);
+			LOGGER.error("Failed to save Derby Track history.", e);
 		}
 	}
 	
 	/**
-	 * Load current bets per lane ; initialize the map keys.
+	 * Load current bets per lane and initialize the map keys.
 	 */
 	protected void loadBets()
 	{
-		try (Connection con = ConnectionPool.getConnection();
-			PreparedStatement ps = con.prepareStatement(LOAD_BETS);
-			ResultSet rs = ps.executeQuery())
+		try (var con = ConnectionPool.getConnection();
+			var ps = con.prepareStatement(LOAD_BETS);
+			var rs = ps.executeQuery())
 		{
 			while (rs.next())
 				setBetOnLane(rs.getInt("lane_id"), rs.getLong("bet"), false);
 		}
-		catch (Exception e)
+		catch (SQLException e)
 		{
-			LOGGER.error("Can't load Derby Track bets.", e);
+			LOGGER.error("Failed to load Derby Track bets.", e);
 		}
 	}
 	
 	/**
-	 * Save the current lane bet into database.
-	 * @param lane : The lane to affect.
-	 * @param sum : The sum to set.
+	 * Save the current lane bet into the database.
+	 * @param lane The lane to affect.
+	 * @param sum The sum to set.
 	 */
 	protected void saveBet(int lane, long sum)
 	{
-		try (Connection con = ConnectionPool.getConnection();
-			PreparedStatement ps = con.prepareStatement(SAVE_BETS))
+		try (var con = ConnectionPool.getConnection();
+			var ps = con.prepareStatement(SAVE_BETS))
 		{
 			ps.setInt(1, lane);
 			ps.setLong(2, sum);
 			ps.execute();
 		}
-		catch (Exception e)
+		catch (SQLException e)
 		{
-			LOGGER.error("Can't save Derby Track bet.", e);
+			LOGGER.error("Failed to save Derby Track bet.", e);
 		}
 	}
 	
 	/**
-	 * Clear all lanes bets, either on database or Map.
+	 * Clear all lane bets, either in the database or the Map.
 	 */
 	protected void clearBets()
 	{
-		for (int key : _betsPerLane.keySet())
+		for (var key : _betsPerLane.keySet())
+		{
 			_betsPerLane.put(key, 0L);
-		
-		try (Connection con = ConnectionPool.getConnection();
-			PreparedStatement ps = con.prepareStatement(CLEAR_BETS))
+		}
+		try (var con = ConnectionPool.getConnection();
+			var ps = con.prepareStatement(CLEAR_BETS))
 		{
 			ps.execute();
 		}
-		catch (Exception e)
+		catch (SQLException e)
 		{
-			LOGGER.error("Can't clear Derby Track bets.", e);
+			LOGGER.error("Failed to clear Derby Track bets.", e);
 		}
 	}
 	
@@ -353,7 +353,7 @@
 	 */
 	public void setBetOnLane(int lane, long amount, boolean saveOnDb)
 	{
-		final long sum = _betsPerLane.getOrDefault(lane, 0L) + amount;
+		var sum = _betsPerLane.getOrDefault(lane, 0L) + amount;
 		
 		_betsPerLane.put(lane, sum);
 		
@@ -362,161 +362,221 @@
 	}
 	
 	/**
-	 * Calculate odds for every lane, based on others lanes.
+	 * Calculates odds for each lane based on other lanes.
 	 */
-	protected void calculateOdds()
+	protected void calculateOddsForLanes()
 	{
-		// Clear previous List holding old odds.
+		// Clear the previous odds.
 		_odds.clear();
 		
-		// Sort bets lanes per lane.
-		final Map<Integer, Long> sortedLanes = new TreeMap<>(_betsPerLane);
+		// Sort the bets per lane.
+		Map<Integer, Long> sortedLanes = new TreeMap<>(_betsPerLane);
 		
-		// Pass a first loop in order to calculate total sum of all lanes.
-		long sumOfAllLanes = 0;
-		for (long amount : sortedLanes.values())
-			sumOfAllLanes += amount;
+		// Calculate the total sum of all lanes.
+		var sumOfAllLanes = sortedLanes.values().stream().mapToLong(Long::valueOf).sum();
 		
-		// As we get the sum, we can now calculate the odd rate of each lane.
-		for (long amount : sortedLanes.values())
-			_odds.add((amount == 0) ? 0D : Math.max(1.25, sumOfAllLanes * 0.7 / amount));
+		// Calculate the odd rate of each lane based on the sum of the lane amounts.
+		for (var amount : sortedLanes.values())
+		{
+			var randomValue = getRandomOddValue();
+			var odd = (amount == 0) ? 0D : Math.max(1.25, sumOfAllLanes * randomValue / amount);
+			_odds.add(odd);
+		}
+	}
+	
+	// Generate a random odd value.
+	private static final double[] oddValues =
+	{
+		0.4,
+		0.5
+	};
+	
+	private static double getRandomOddValue()
+	{
+		Random rand = new Random();
+		return oddValues[rand.nextInt(oddValues.length)];
+	}
+	
+	// New method to get runner odd by runner number
+	public double getRunnerOdd(int runnerNumber)
+	{
+		if (runnerNumber > 0 && runnerNumber <= _odds.size())
+			return _odds.get(runnerNumber - 1);
+		return 0.0;
 	}
 	
 	private class Announcement implements Runnable
 	{
-		public Announcement()
-		{
-		}
-		
 		@Override
 		public void run()
 		{
-			if (_finalCountdown > 1200)
+			if (_finalCountdown > 900)
 				_finalCountdown = 0;
 			
 			switch (_finalCountdown)
 			{
 				case 0:
 					newRace();
-					newSpeeds();
-					
-					_state = RaceState.ACCEPTING_BETS;
-					_packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getRunners(), getSpeeds());
-					
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_AVAILABLE_FOR_S1_RACE).addNumber(_raceNumber));
+					CalculateLaneSpeeds();
+					_state = RaceState.BETTING;
+					_packet = new MonRaceInfo(CODES.get(0).get(0), CODES.get(0).get(1), getChosenRunners(), getSpeeds());
+					World.toAllOnlinePlayers(new NpcSay(RACE_MANAGER, SayType.TRADE, "Monster Race: Tickets are now available. Next announcements inside of race zone."));
 					break;
-				
-				case 30: // 30 sec
-				case 60: // 1 min
-				case 90: // 1 min 30 sec
-				case 120: // 2 min
-				case 150: // 2 min 30
-				case 180: // 3 min
-				case 210: // 3 min 30
-				case 240: // 4 min
-				case 270: // 4 min 30 sec
-				case 330: // 5 min 30 sec
-				case 360: // 6 min
-				case 390: // 6 min 30 sec
-				case 420: // 7 min
-				case 450: // 7 min 30
-				case 480: // 8 min
-				case 510: // 8 min 30
-				case 540: // 9 min
-				case 570: // 9 min 30 sec
-				case 630: // 10 min 30 sec
-				case 660: // 11 min
-				case 690: // 11 min 30 sec
-				case 720: // 12 min
-				case 750: // 12 min 30
-				case 780: // 13 min
-				case 810: // 13 min 30
-				case 870: // 14 min 30 sec
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE).addNumber(_raceNumber));
+				case 180: // 3 minutes
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, new NpcSay(RACE_MANAGER, SayType.TRADE, "7 minutes left to begin the race."));
 					break;
-				
-				case 300: // 5 min
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE).addNumber(_raceNumber), SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES).addNumber(10));
+				case 240: // 4 minutes
+					talk();
 					break;
-				
-				case 600: // 10 min
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE).addNumber(_raceNumber), SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES).addNumber(5));
+				case 420: // 7 minutes
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, new NpcSay(RACE_MANAGER, SayType.TRADE, "Bets are about to close in 3 minutes. Good luck everyone!"));
 					break;
-				
-				case 840: // 14 min
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE).addNumber(_raceNumber), SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES).addNumber(1));
-					break;
-				
-				case 900: // 15 min
+				case 600: // 10 minutes
 					_state = RaceState.WAITING;
+					calculateOddsForLanes();
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, new NpcSay(RACE_MANAGER, SayType.TRADE, "The race is about to begin in 1 minute. Get ready!"));
+					break;
+				case 630: // 10 min 30 sec
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, SystemMessage.sendString("Monster Race: The race begins in 30 seconds."));
 					
-					calculateOdds();
-					
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE).addNumber(_raceNumber), SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_S1_TICKET_SALES_CLOSED));
 					break;
-				
-				case 960: // 16 min
-				case 1020: // 17 min
-					final int minutes = (_finalCountdown == 960) ? 2 : 1;
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_S2_BEGINS_IN_S1_MINUTES).addNumber(minutes));
+				case 650: // 10 min 50 sec
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, SystemMessage.sendString("Monster Race: Race is about to begin! Countdown in five seconds."));
 					break;
-				
-				case 1050: // 17 min 30 sec
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_S1_BEGINS_IN_30_SECONDS));
-					break;
-				
-				case 1070: // 17 min 50 sec
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_S1_COUNTDOWN_IN_FIVE_SECONDS));
-					break;
-				
-				case 1075: // 17 min 55 sec
-				case 1076: // 17 min 56 sec
-				case 1077: // 17 min 57 sec
-				case 1078: // 17 min 58 sec
-				case 1079: // 17 min 59 sec
-					final int seconds = 1080 - _finalCountdown;
+				case 655: // 10 min 55 sec
+				case 656: // 10 min 56 sec
+				case 657: // 10 min 57 sec
+				case 658: // 10 min 58 sec
+				case 659: // 10 min 59 sec
+					var seconds = 660 - _finalCountdown;
 					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_BEGINS_IN_S1_SECONDS).addNumber(seconds));
 					break;
-				
-				case 1080: // 18 min
-					_state = RaceState.STARTING_RACE;
-					_packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getRunners(), getSpeeds());
-					
+				case 660: // 11 min
+					_state = RaceState.STARTING;
+					_packet = new MonRaceInfo(CODES.get(1).get(0), CODES.get(1).get(1), getChosenRunners(), getSpeeds());
 					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_RACE_START), SOUND_1, SOUND_2, _packet);
 					break;
-				
-				case 1085: // 18 min 5 sec
-					_packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getRunners(), getSpeeds());
+				case 665: // 11 min 5 sec
+					// Shuffle the indices to get a random order of runners
+					Collections.shuffle(_runnerIndices);
 					
+					for (int i = 0; i < 4; i++)
+					{
+						var runnerIndex = _runnerIndices.get(i);
+						var delay = 0;
+						switch (i)
+						{
+							case 0:
+								_message = "I will win!";
+								delay = 4000;
+								break;
+							case 1:
+								_message = "No! I will be the winner!";
+								delay = 7000;
+								break;
+							case 2:
+								_message = "Oh my legs! But I will try more..";
+								delay = 10000;
+								break;
+							case 3:
+								_message = "I'll need an ice cream after this.";
+								delay = 12000;
+								break;
+						}
+						_timer.schedule(new SayTask(new NpcSay(getChosenRunners().get(runnerIndex), SayType.ALL, _message)), delay);
+					}
+					_packet = new MonRaceInfo(CODES.get(2).get(0), CODES.get(2).get(1), getChosenRunners(), getSpeeds());
 					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet);
 					break;
-				
-				case 1115: // 18 min 35 sec
-					_state = RaceState.RACE_END;
-					
-					// Retrieve current HistoryInfo and populate it with data, then stores it in database.
-					final HistoryInfo info = getHistoryInfo(_raceNumber);
+				case 690: // 11 min 30 sec
+					_state = RaceState.FINISHED;
+					var info = getHistoryInfo(_raceNumber);
 					if (info != null)
 					{
-						info.setFirst(getFirst());
-						info.setSecond(getSecond());
-						info.setOddRate(_odds.get(getFirst()));
-						
+						info.setFirst(getFirstPlace());
+						info.setSecond(getSecondPlace());
+						info.setOddRate(_odds.get(getFirstPlace()));
+						info.setWinnerName(getRunnerName(getFirstPlace()));
 						saveHistory(info);
 					}
-					
-					// Clear bets.
 					clearBets();
+					var firstPlace = getFirstPlace() + 1;
+					var secondPlace = getSecondPlace() + 1;
+					var firstPlaceOdd = _odds.get(getFirstPlace());
+					var secondPlaceOdd = _odds.get(getSecondPlace());
+					var first = _chosenRunners.get(getFirstPlace());
+					var second = _chosenRunners.get(getSecondPlace());
 					
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_FIRST_PLACE_S1_SECOND_S2).addNumber(getFirst() + 1).addNumber(getSecond() + 1), SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_S1_RACE_END).addNumber(_raceNumber));
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, new NpcSay(first, SayType.SHOUT, "I win! I win.. Yea!!!"));
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, new NpcSay(second, SayType.SHOUT, "I came second! I'm so happy!"));
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, SystemMessage.sendString("First place goes to lane " + firstPlace + ". Prize Odds: " + firstPlaceOdd));
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, SystemMessage.sendString("Second place goes to lane " + secondPlace + ". Prize Odds: " + secondPlaceOdd * 0.70));
 					_raceNumber++;
 					break;
-				
-				case 1140: // 19 min
-					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getRunners().get(0)), new DeleteObject(getRunners().get(1)), new DeleteObject(getRunners().get(2)), new DeleteObject(getRunners().get(3)), new DeleteObject(getRunners().get(4)), new DeleteObject(getRunners().get(5)), new DeleteObject(getRunners().get(6)), new DeleteObject(getRunners().get(7)));
+				case 720: // 12 minutes. End of event!
+					var deleteObjects = getChosenRunners().stream().map(DeleteObject::new).collect(Collectors.toList());
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, deleteObjects.toArray(new DeleteObject[0]));
+					ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, SystemMessage.sendString("Next Race will take place in 3 minutes."));
+
 					break;
 			}
-			_finalCountdown += 1;
+			_finalCountdown++;
+		}
+	}
+	
+	/**
+	 * Talks to runners to convince players to bet on them for a race. Displays five messages at different intervals from different runners.
+	 */
+	private void talk()
+	{
+		for (int i = 0; i < getChosenRunners().size(); i++)
+			_runnerIndices.add(i);
+		
+		// Shuffle the indices to get a random order of runners
+		Collections.shuffle(_runnerIndices);
+		
+		for (int i = 0; i < 4; i++)
+		{
+			// Create a timer to schedule messages
+			Timer timer = new Timer();
+			int runnerIndex = _runnerIndices.get(i);
+			String message = "";
+			int delay = 0;
+			switch (i)
+			{
+				case 0:
+					message = "I am runner " + (runnerIndex + 1) + ". Choose me! I will win.";
+					break;
+				case 1:
+					message = "No! Runner " + (_runnerIndices.get(0) + 1) + " is not as fast as me! Choose me instead! I'm runner " + (runnerIndex + 1);
+					delay = 30000;
+					break;
+				case 2:
+					message = "I am runner " + (runnerIndex + 1) + ". I'm not the fastest, but I'll try my best!";
+					delay = 240000;
+					break;
+				case 3:
+					message = "Good luck to all of us! May the fastest one win!";
+					delay = 420000;
+					break;
+			}
+			timer.schedule(new SayTask(new NpcSay(getChosenRunners().get(runnerIndex), SayType.SHOUT, message)), delay);
+		}
+	}
+	
+	private class SayTask extends TimerTask
+	{
+		private NpcSay say;
+		
+		public SayTask(NpcSay say)
+		{
+			this.say = say;
+		}
+		
+		@Override
+		public void run()
+		{
+			ZoneManager.toAllPlayersInZoneType(DerbyTrackZone.class, say);
 		}
 	}
 	
diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/enums/RaceState.java b/aCis_gameserver/java/net/sf/l2j/gameserver/enums/RaceState.java
new file mode 100644
index 0000000..b2fcd09
--- /dev/null
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/enums/RaceState.java
@@ -0,0 +1,12 @@
+package net.sf.l2j.gameserver.enums;
+
+/**
+ * Enumeration representing the state of the race.
+ */
+public enum RaceState
+{
+	BETTING, // Currently in progress for betting
+	WAITING, // Currently waiting to begin
+	STARTING, // About to start
+	FINISHED // Just finished
+}
diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/model/HistoryInfo.java b/aCis_gameserver/java/net/sf/l2j/gameserver/model/HistoryInfo.java
index f3822d2..9911f34 100644
--- a/aCis_gameserver/java/net/sf/l2j/gameserver/model/HistoryInfo.java
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/model/HistoryInfo.java
@@ -3,16 +3,18 @@
 public class HistoryInfo
 {
 	private final int _raceId;
-	private int _first;
-	private int _second;
+	private int _firstPlace;
+	private int _secondPlace;
 	private double _oddRate;
+	private String _winner;
 	
-	public HistoryInfo(int raceId, int first, int second, double oddRate)
+	public HistoryInfo(int raceId, int first, int second, double oddRate, String winner)
 	{
 		_raceId = raceId;
-		_first = first;
-		_second = second;
+		_firstPlace = first;
+		_secondPlace = second;
 		_oddRate = oddRate;
+		_winner = winner;
 	}
 	
 	public int getRaceId()
@@ -20,14 +22,14 @@
 		return _raceId;
 	}
 	
-	public int getFirst()
+	public int getFirstPlace()
 	{
-		return _first;
+		return _firstPlace;
 	}
 	
-	public int getSecond()
+	public int getSecondPlace()
 	{
-		return _second;
+		return _secondPlace;
 	}
 	
 	public double getOddRate()
@@ -35,18 +37,28 @@
 		return _oddRate;
 	}
 	
+	public String getWinnerName()
+	{
+		return _winner;
+	}
+	
 	public void setFirst(int first)
 	{
-		_first = first;
+		_firstPlace = first;
 	}
 	
 	public void setSecond(int second)
 	{
-		_second = second;
+		_secondPlace = second;
 	}
 	
 	public void setOddRate(double oddRate)
 	{
 		_oddRate = oddRate;
 	}
+	
+	public void setWinnerName(String winner)
+	{
+		_winner = winner;
+	}
 }
\ No newline at end of file
diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/model/actor/instance/DerbyTrackManagerNpc.java b/aCis_gameserver/java/net/sf/l2j/gameserver/model/actor/instance/DerbyTrackManagerNpc.java
index 46c5d68..61143f2 100644
--- a/aCis_gameserver/java/net/sf/l2j/gameserver/model/actor/instance/DerbyTrackManagerNpc.java
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/model/actor/instance/DerbyTrackManagerNpc.java
@@ -4,15 +4,16 @@
 
 import net.sf.l2j.commons.lang.StringUtil;
 
+import net.sf.l2j.Config;
 import net.sf.l2j.gameserver.data.manager.DerbyTrackManager;
-import net.sf.l2j.gameserver.data.manager.DerbyTrackManager.RaceState;
+import net.sf.l2j.gameserver.data.xml.ItemData;
+import net.sf.l2j.gameserver.enums.RaceState;
 import net.sf.l2j.gameserver.idfactory.IdFactory;
-import net.sf.l2j.gameserver.model.HistoryInfo;
 import net.sf.l2j.gameserver.model.WorldObject;
-import net.sf.l2j.gameserver.model.actor.Npc;
 import net.sf.l2j.gameserver.model.actor.Player;
 import net.sf.l2j.gameserver.model.actor.template.NpcTemplate;
 import net.sf.l2j.gameserver.model.item.instance.ItemInstance;
+import net.sf.l2j.gameserver.model.item.kind.Item;
 import net.sf.l2j.gameserver.network.SystemMessageId;
 import net.sf.l2j.gameserver.network.serverpackets.ActionFailed;
 import net.sf.l2j.gameserver.network.serverpackets.DeleteObject;
@@ -21,16 +22,11 @@
 
 public class DerbyTrackManagerNpc extends Folk
 {
-	protected static final int[] TICKET_PRICES =
+	protected static final int TICKET_PRICES[] =
 	{
-		100,
-		500,
-		1000,
-		5000,
-		10000,
-		20000,
-		50000,
-		100000
+		Config._price1,
+		Config._price2,
+		Config._price3
 	};
 	
 	public DerbyTrackManagerNpc(int objectId, NpcTemplate template)
@@ -41,49 +37,47 @@
 	@Override
 	public void onBypassFeedback(Player player, String command)
 	{
+		Item ItemName = ItemData.getInstance().getTemplate(Config._itemID);
+		String itemName = ItemName.getName();
+		
 		if (command.startsWith("BuyTicket"))
 		{
-			if (DerbyTrackManager.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)
+			var currentRaceState = DerbyTrackManager.getInstance().getCurrentRaceState();
+			
+			if (currentRaceState != RaceState.BETTING)
 			{
 				player.sendPacket(SystemMessageId.MONSRACE_TICKETS_NOT_AVAILABLE);
 				super.onBypassFeedback(player, "Chat 0");
 				return;
 			}
-			
-			int val = Integer.parseInt(command.substring(10));
+			var val = Integer.parseInt(command.substring(10));
 			if (val == 0)
 			{
 				player.setRace(0, 0);
 				player.setRace(1, 0);
 			}
-			
-			if ((val == 10 && player.getRace(0) == 0) || (val == 20 && player.getRace(0) == 0 && player.getRace(1) == 0))
+			else if ((val == 10 && player.getRace(0) == 0) || (val == 20 && player.getRace(0) == 0 && player.getRace(1) == 0))
 				val = 0;
 			
-			int npcId = getTemplate().getNpcId();
-			
-			String search;
-			String replace;
-			
-			final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-			
+			var npcId = getTemplate().getNpcId();
+			var html = new NpcHtmlMessage(getObjectId());
 			if (val < 10)
 			{
 				html.setFile(getHtmlPath(npcId, 2));
-				for (int i = 0; i < 8; i++)
+				for (var i = 0; i < 8; i++)
 				{
-					int n = i + 1;
-					search = "Mob" + n;
+					var n = i + 1;
+					var search = "Mob" + n;
 					html.replace(search, DerbyTrackManager.getInstance().getRunnerName(i));
 				}
-				search = "No1";
-				if (val == 0)
-					html.replace(search, "");
-				else
+				var search = "No1";
+				if (val != 0)
 				{
 					html.replace(search, val);
 					player.setRace(0, val);
 				}
+				else
+					html.replace(search, "");
 			}
 			else if (val < 20)
 			{
@@ -92,226 +86,244 @@
 				
 				html.setFile(getHtmlPath(npcId, 3));
 				html.replace("0place", player.getRace(0));
-				search = "Mob1";
-				replace = DerbyTrackManager.getInstance().getRunnerName(player.getRace(0) - 1);
+				var search = "Mob1";
+				var replace = DerbyTrackManager.getInstance().getRunnerName(player.getRace(0) - 1);
 				html.replace(search, replace);
+				search = "itemname";
+				html.replace(search, itemName);
+				for (var i = 0; i < TICKET_PRICES.length; i++)
+				{
+					search = "price" + (i + 1);
+					html.replace(search, String.valueOf(TICKET_PRICES[i]));
+				}
 				search = "0adena";
-				
-				if (val == 10)
-					html.replace(search, "");
-				else
+				if (val != 10)
 				{
 					html.replace(search, TICKET_PRICES[val - 11]);
 					player.setRace(1, val - 10);
 				}
+				else
+					html.replace(search, "");
 			}
 			else if (val == 20)
 			{
 				if (player.getRace(0) == 0 || player.getRace(1) == 0)
 					return;
 				
+				var raceId = player.getRace(0);
+				var priceId = player.getRace(1);
+				var ticketPrice = TICKET_PRICES[priceId - 1];
+				
 				html.setFile(getHtmlPath(npcId, 4));
-				html.replace("0place", player.getRace(0));
-				search = "Mob1";
-				replace = DerbyTrackManager.getInstance().getRunnerName(player.getRace(0) - 1);
-				html.replace(search, replace);
-				search = "0adena";
-				int price = TICKET_PRICES[player.getRace(1) - 1];
-				html.replace(search, price);
-				search = "0tax";
-				int tax = 0;
-				html.replace(search, tax);
-				search = "0total";
-				int total = price + tax;
-				html.replace(search, total);
+				html.replace("0place", raceId);
+				html.replace("Mob1", DerbyTrackManager.getInstance().getRunnerName(raceId - 1));
+				html.replace("0adena", ticketPrice);
+				html.replace("0tax", 0);
+				html.replace("0total", ticketPrice);
+				html.replace("itemname", itemName);
+				
 			}
 			else
 			{
 				if (player.getRace(0) == 0 || player.getRace(1) == 0)
 					return;
 				
-				int ticket = player.getRace(0);
+				int raceId = player.getRace(0);
 				int priceId = player.getRace(1);
-				
-				if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true))
-					return;
+				int ticketPrice = TICKET_PRICES[priceId - 1];
+
+				if (!player.destroyItemByItemId("Race", Config._itemID, ticketPrice, this, true))
+				{
+				    player.sendMessage("Not enough " + itemName + " to purchase ticket.");
+				    return;
+				}
 				
 				player.setRace(0, 0);
 				player.setRace(1, 0);
 				
-				ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443);
+				var item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443);
 				item.setCount(1);
 				item.setEnchantLevel(DerbyTrackManager.getInstance().getRaceNumber());
-				item.setCustomType1(ticket);
-				item.setCustomType2(TICKET_PRICES[priceId - 1] / 100);
+				item.setCustomType1(raceId);
+				item.setCustomType2(ticketPrice / 10);
 				
 				player.addItem("Race", item, player, false);
 				player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.ACQUIRED_S1_S2).addNumber(DerbyTrackManager.getInstance().getRaceNumber()).addItemName(4443));
 				
-				// Refresh lane bet.
-				DerbyTrackManager.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true);
+				DerbyTrackManager.getInstance().setBetOnLane(raceId, ticketPrice, true);
 				super.onBypassFeedback(player, "Chat 0");
 				return;
 			}
+			
 			html.replace("1race", DerbyTrackManager.getInstance().getRaceNumber());
 			html.replace("%objectId%", getObjectId());
 			player.sendPacket(html);
 			player.sendPacket(ActionFailed.STATIC_PACKET);
 		}
-		else if (command.equals("ShowOdds"))
+		else if (command.equalsIgnoreCase("ShowOdds"))
 		{
-			if (DerbyTrackManager.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)
+			var currentRaceState = DerbyTrackManager.getInstance().getCurrentRaceState();
+			if (currentRaceState == RaceState.BETTING)
 			{
 				player.sendPacket(SystemMessageId.MONSRACE_NO_PAYOUT_INFO);
 				super.onBypassFeedback(player, "Chat 0");
 				return;
 			}
 			
-			final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
+			var html = new NpcHtmlMessage(getObjectId());
 			html.setFile(getHtmlPath(getTemplate().getNpcId(), 5));
-			for (int i = 0; i < 8; i++)
+			
+			for (var i = 0; i < 8; i++)
 			{
-				final int n = i + 1;
+				var n = i + 1;
+				var mobName = DerbyTrackManager.getInstance().getRunnerName(i);
+				html.replace("Mob" + n, mobName);
 				
-				html.replace("Mob" + n, DerbyTrackManager.getInstance().getRunnerName(i));
-				
-				// Odd
-				final double odd = DerbyTrackManager.getInstance().getOdds().get(i);
+				var odd = DerbyTrackManager.getInstance().getOdds().get(i);
 				html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;");
 			}
+			
 			html.replace("1race", DerbyTrackManager.getInstance().getRaceNumber());
 			html.replace("%objectId%", getObjectId());
+			
 			player.sendPacket(html);
 			player.sendPacket(ActionFailed.STATIC_PACKET);
 		}
-		else if (command.equals("ShowInfo"))
+		else if (command.equalsIgnoreCase("ShowInfo"))
 		{
-			final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
+			var html = new NpcHtmlMessage(getObjectId());
 			html.setFile(getHtmlPath(getTemplate().getNpcId(), 6));
 			
-			for (int i = 0; i < 8; i++)
+			for (var i = 1; i <= 8; i++)
 			{
-				int n = i + 1;
-				String search = "Mob" + n;
-				html.replace(search, DerbyTrackManager.getInstance().getRunnerName(i));
+				var search = "Mob" + i;
+				html.replace(search, DerbyTrackManager.getInstance().getRunnerName(i - 1));
 			}
-			html.replace("%objectId%", getObjectId());
+			
+			html.replace("%objectId%", String.valueOf(getObjectId()));
 			player.sendPacket(html);
 			player.sendPacket(ActionFailed.STATIC_PACKET);
 		}
 		else if (command.equals("ShowTickets"))
 		{
-			// Generate data.
-			final StringBuilder sb = new StringBuilder();
+			var sb = new StringBuilder();
 			
-			// Retrieve player's tickets.
-			for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443))
+			for (var ticket : player.getInventory().getAllItemsByItemId(4443))
 			{
-				// Don't list current race tickets.
+				// Skip tickets for the current race.
 				if (ticket.getEnchantLevel() == DerbyTrackManager.getInstance().getRaceNumber())
 					continue;
 				
-				StringUtil.append(sb, "<tr><td><a action=\"bypass -h npc_%objectId%_ShowTicket ", ticket.getObjectId(), "\">", ticket.getEnchantLevel(), " Race Number</a></td><td align=right><font color=\"LEVEL\">", ticket.getCustomType1(), "</font> Number</td><td align=right><font color=\"LEVEL\">", ticket.getCustomType2() * 100, "</font> Adena</td></tr>");
+				StringUtil.append(sb, "<tr><td><a action=\"bypass -h npc_%objectId%_ShowTicket ", ticket.getObjectId(), "\">", ticket.getEnchantLevel(), " Race Number</a></td><td align=right><font color=\"LEVEL\">", ticket.getCustomType1(), "</font> Number</td><td align=right><font color=\"LEVEL\">Your Bet: ", ticket.getCustomType2() * 10, "</font></td></tr>");
 			}
-			
-			final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
+			var html = new NpcHtmlMessage(getObjectId());
 			html.setFile(getHtmlPath(getTemplate().getNpcId(), 7));
 			html.replace("%tickets%", sb.toString());
 			html.replace("%objectId%", getObjectId());
 			player.sendPacket(html);
 			player.sendPacket(ActionFailed.STATIC_PACKET);
 		}
+		
 		else if (command.startsWith("ShowTicket"))
 		{
-			// Retrieve ticket objectId.
-			final int val = Integer.parseInt(command.substring(11));
+			var val = Integer.parseInt(command.substring(11));
 			if (val == 0)
 			{
 				super.onBypassFeedback(player, "Chat 0");
 				return;
 			}
 			
-			// Retrieve ticket on player's inventory.
-			final ItemInstance ticket = player.getInventory().getItemByObjectId(val);
+			// Find ticket in player's inventory.
+			var ticket = player.getInventory().getItemByObjectId(val);
 			if (ticket == null)
 			{
 				super.onBypassFeedback(player, "Chat 0");
 				return;
 			}
 			
-			final int raceId = ticket.getEnchantLevel();
-			final int lane = ticket.getCustomType1();
-			final int bet = ticket.getCustomType2() * 100;
+			var raceId = ticket.getEnchantLevel();
+			var lane = ticket.getCustomType1();
+			var bet = ticket.getCustomType2() * 10;
 			
 			// Retrieve HistoryInfo for that race.
-			final HistoryInfo info = DerbyTrackManager.getInstance().getHistoryInfo(raceId);
+			var info = DerbyTrackManager.getInstance().getHistoryInfo(raceId);
 			if (info == null)
 			{
 				super.onBypassFeedback(player, "Chat 0");
 				return;
 			}
 			
-			final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
+			var html = new NpcHtmlMessage(getObjectId());
 			html.setFile(getHtmlPath(getTemplate().getNpcId(), 8));
 			html.replace("%raceId%", raceId);
 			html.replace("%lane%", lane);
 			html.replace("%bet%", bet);
-			html.replace("%firstLane%", info.getFirst() + 1);
-			html.replace("%odd%", (lane == info.getFirst() + 1) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "0.01");
+			html.replace("%firstLane%", info.getFirstPlace() + 1);
+			html.replace("%secondLane%", info.getSecondPlace() + 1);
+			html.replace("%odd%", lane == info.getFirstPlace() + 1 ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : lane == info.getSecondPlace() + 1 ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate() * 0.6) : "0.01");
 			html.replace("%objectId%", getObjectId());
 			html.replace("%ticketObjectId%", val);
+			
 			player.sendPacket(html);
 			player.sendPacket(ActionFailed.STATIC_PACKET);
 		}
 		else if (command.startsWith("CalculateWin"))
 		{
-			// Retrieve ticket objectId.
-			final int val = Integer.parseInt(command.substring(13));
-			if (val == 0)
+			// Extract ticket objectId.
+			var ticketId = Integer.parseInt(command.substring(13));
+			if (ticketId == 0)
 			{
 				super.onBypassFeedback(player, "Chat 0");
 				return;
 			}
 			
-			// Delete ticket on player's inventory.
-			final ItemInstance ticket = player.getInventory().getItemByObjectId(val);
+			// Check if player has the ticket.
+			var ticket = player.getInventory().getItemByObjectId(ticketId);
 			if (ticket == null)
 			{
 				super.onBypassFeedback(player, "Chat 0");
 				return;
 			}
 			
-			final int raceId = ticket.getEnchantLevel();
-			final int lane = ticket.getCustomType1();
-			final int bet = ticket.getCustomType2() * 100;
+			// Extract ticket info.
+			var raceId = ticket.getEnchantLevel();
+			var lane = ticket.getCustomType1();
+			var bet = ticket.getCustomType2() * 10;
 			
-			// Retrieve HistoryInfo for that race.
-			final HistoryInfo info = DerbyTrackManager.getInstance().getHistoryInfo(raceId);
-			if (info == null)
+			// Get race history.
+			var raceHistory = DerbyTrackManager.getInstance().getHistoryInfo(raceId);
+			if (raceHistory == null)
 			{
 				super.onBypassFeedback(player, "Chat 0");
 				return;
 			}
 			
-			// Destroy the ticket.
-			if (player.destroyItem("MonsterTrack", ticket, this, true))
-				player.addAdena("MonsterTrack", (int) (bet * ((lane == info.getFirst() + 1) ? info.getOddRate() : 0.01)), this, true);
+			// Calculate and reward winnings.
+			var firstPlace = raceHistory.getFirstPlace() + 1;
+			var secondPlace = raceHistory.getSecondPlace() + 1;
+			var oddRate = (lane == firstPlace) ? raceHistory.getOddRate() : (lane == secondPlace) ? raceHistory.getOddRate() * 0.6 : 0.10;
+			var winnings = (int) (bet * oddRate);
 			
+			if (player.destroyItem("MonsterTrack", ticket, this, true))
+				player.addItem("MonsterTrack", Config._itemID, winnings, this, true);
+			
+			// Notify player and exit bypass.
 			super.onBypassFeedback(player, "Chat 0");
+			return;
 		}
-		else if (command.equals("ViewHistory"))
+		else if (command.equalsIgnoreCase("ViewHistory"))
 		{
 			// Generate data.
-			final StringBuilder sb = new StringBuilder();
+			var sb = new StringBuilder();
 			
 			// Retrieve current race number.
-			final int raceNumber = DerbyTrackManager.getInstance().getRaceNumber();
+			var raceNumber = DerbyTrackManager.getInstance().getRaceNumber();
 			
 			// Retrieve the few latest entries.
-			for (HistoryInfo info : DerbyTrackManager.getInstance().getLastHistoryEntries())
-				StringUtil.append(sb, "<tr><td><font color=\"LEVEL\">", info.getRaceId(), "</font> th</td><td><font color=\"LEVEL\">", (raceNumber == info.getRaceId()) ? 0 : info.getFirst() + 1, "</font> Lane </td><td><font color=\"LEVEL\">", (raceNumber == info.getRaceId()) ? 0 : info.getSecond() + 1, "</font> Lane</td><td align=right><font color=00ffff>", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), "</font> Times</td></tr>");
+			for (var info : DerbyTrackManager.getInstance().getLastHistoryEntries())
+				StringUtil.append(sb, "<tr><td><font color=\"LEVEL\">", info.getRaceId(), "</font> th</td><td><font color=\"LEVEL\"><div style=\"text-align:center;\">", (raceNumber == info.getRaceId()) ? "Race in progress.." : info.getWinnerName(), "</div></font> </td><td align=right><font color=00ffff>", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), "</font></td></tr>");
 			
-			final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
+			var html = new NpcHtmlMessage(getObjectId());
 			html.setFile(getHtmlPath(getTemplate().getNpcId(), 9));
 			html.replace("%infos%", sb.toString());
 			html.replace("%objectId%", getObjectId());
@@ -326,19 +338,21 @@
 	public void addKnownObject(WorldObject object)
 	{
 		if (object instanceof Player)
-			((Player) object).sendPacket(DerbyTrackManager.getInstance().getRacePacket());
+		{
+			var player = (Player) object;
+			player.sendPacket(DerbyTrackManager.getInstance().getRacePacket());
+		}
 	}
 	
 	@Override
 	public void removeKnownObject(WorldObject object)
 	{
 		super.removeKnownObject(object);
-		
 		if (object instanceof Player)
 		{
-			final Player player = ((Player) object);
-			
-			for (Npc npc : DerbyTrackManager.getInstance().getRunners())
+			var player = (Player) object;
+			var derbyTrackManager = DerbyTrackManager.getInstance();
+			for (var npc : derbyTrackManager.getChosenRunners())
 				player.sendPacket(new DeleteObject(npc));
 		}
 	}

Link Descarga: No tienes permiso para ver los enlaces. Para poder verlos Registrate o Conectate.